Merge pull request #32121 from woocommerce/merge/woocommerce-admin

Merge WooCommerce Admin

This merges WooCommerce Admin, along with its commit history, into `plugins/woocommerce-admin`.
This commit is contained in:
Christopher Allford 2022-03-18 14:19:18 -07:00 committed by GitHub
commit 9479568e3d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2110 changed files with 252093 additions and 4182 deletions

View File

@ -0,0 +1 @@
extends @wordpress/browserslist-config

View File

@ -0,0 +1,34 @@
# A set of files you probably don't want in your WordPress.org distribution
.distignore
.editorconfig
.git
.gitignore
.gitlab-ci.yml
.travis.yml
.DS_Store
Thumbs.db
behat.yml
bin
circle.yml
composer.json
composer.lock
Gruntfile.js
package.json
package-lock.json
phpunit.xml
phpunit.xml.dist
multisite.xml
multisite.xml.dist
phpcs.xml
phpcs.xml.dist
README.md
wp-cli.local.yml
yarn.lock
tests
packages/admin-e2e-tests
vendor
config
node_modules
*.sql
*.tar.gz
*.zip

View File

@ -0,0 +1,24 @@
# This file is for unifying the coding style for different editors and IDEs
# editorconfig.org
# WordPress Coding Standards
# https://make.wordpress.org/core/handbook/coding-standards/
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 4
tab_width = 4
indent_style = tab
insert_final_newline = true
trim_trailing_whitespace = true
[*.{md,json,yml}]
trim_trailing_whitespace = false
indent_style = space
indent_size = 2
[*.json]
indent_style = tab

View File

@ -0,0 +1,12 @@
bin/*
!bin/generate-docs
!.eslintrc.js
build
build-module
coverage
languages
node_modules
vendor
legacy
tests/e2e
build-types

View File

@ -0,0 +1,80 @@
module.exports = {
env: {
'jest/globals': true,
},
extends: [ 'plugin:@woocommerce/eslint-plugin/recommended' ],
settings: {
'import/resolver': 'typescript',
// List of modules that are externals in our webpack config.
'import/core-modules': [ '@woocommerce/settings', 'lodash', 'react' ],
react: {
pragma: 'createElement',
},
},
root: true,
rules: {
// temporary conversion to warnings until the below are all handled.
'@wordpress/i18n-translator-comments': 'warn',
'@wordpress/valid-sprintf': 'warn',
'jsdoc/check-tag-names': [
'error',
{
definedTags: [
'jest-environment',
'filter',
'action',
'slotFill',
'scope',
],
},
],
'import/no-extraneous-dependencies': 'warn',
'import/no-unresolved': 'warn',
'jest/no-deprecated-functions': 'warn',
'@wordpress/no-unsafe-wp-apis': 'warn',
'jest/valid-title': 'warn',
'@wordpress/no-global-active-element': 'warn',
'no-unused-vars': [
'error',
{
varsIgnorePattern: 'createElement',
},
],
'react/react-in-jsx-scope': 'error',
},
overrides: [
{
files: [ '*.ts', '*.tsx' ],
parser: '@typescript-eslint/parser',
extends: [
'plugin:@woocommerce/eslint-plugin/recommended',
'plugin:@typescript-eslint/recommended',
],
rules: {
camelcase: 'off',
'import/no-unresolved': 'warn',
'import/no-extraneous-dependencies': 'warn',
'@typescript-eslint/no-explicit-any': 'error',
'no-use-before-define': 'off',
'@typescript-eslint/no-use-before-define': [ 'error' ],
'jsdoc/require-param': 'off',
// Making use of typescript no-shadow instead, fixes issues with enum.
'no-shadow': 'off',
'@typescript-eslint/no-shadow': [ 'error' ],
'@typescript-eslint/no-empty-function': 'off',
},
},
{
files: [
'client/**/*.js',
'client/**/*.jsx',
'**/stories/*.js',
'**/stories/*.jsx',
'**/docs/example.js',
],
rules: {
'react/react-in-jsx-scope': 'off',
},
},
],
};

View File

@ -0,0 +1,25 @@
# Line-ending normalization
* text=auto
/.* export-ignore
/*.md export-ignore
/node_modules* export-ignore
/tests export-ignore
/bin export-ignore
/phpcs.* export-ignore
/phpunit.* export-ignore
/composer.lock export-ignore
/composer.json export-ignore
/renovate.json export-ignore
/webpack.config.js export-ignore
/postcss.config.js export-ignore
/package.json export-ignore
/package-lock.json export-ignore
/babel.config.js export-ignore
/Gruntfile.js export-ignore
/packages* export-ignore
/docs* export-ignore
/config* export-ignore
/client* export-ignore
/docker* export-ignore
/storybook* export-ignore

View File

@ -0,0 +1,43 @@
---
name: Bug report
about: Create a report to help us improve
---
### Describe the bug
<!-- A clear and concise description of what the bug is. Please be as descriptive as possible. -->
### To Reproduce
<!-- Describe the steps to reproduce the behavior.-->
1. Go to '…'
2. Click on '…'
3. Scroll down to …'
4. See error
### Actual behavior:
<!-- A clear and concise description of what actually happens. -->
### Screenshots
<!-- If applicable, add screenshots to help explain your problem. -->
### Expected behavior
<!-- A clear and concise description of what you expected to happen. -->
### Desktop (please complete the following information):
* OS: [e.g. iOS]
* Browser [e.g. chrome, safari]
* Version [e.g. 22]
### Smartphone (please complete the following information):
* Device: [e.g. iPhone6]
* OS: [e.g. iOS8.1]
* Browser [e.g. stock browser, safari]
* Version [e.g. 22]
### Additional context
<!--Any additional context or details you think might be helpful.-->
<!--Ticket numbers/links, plugin versions, system statuses etc.-->

View File

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
---
### Is your feature request related to a problem? Please describe.
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when […] -->
### Describe the solution you'd like
<!-- A clear and concise description of what you want to happen. -->
### Describe alternatives you've considered
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
### Should this be prioritized? Why?
### Additional context
<!--Any additional context, screenshots, or details you think might be helpful.-->

View File

@ -0,0 +1,27 @@
Fixes #
_Replace this with a good description of your changes & reasoning._
### Accessibility
<!-- If you've changed or added any interactions, check off the appropriate items below. You can delete any that don't apply. Use this space to elaborate on anything if needed. -->
- [ ] I've tested using only a keyboard (no mouse)
- [ ] I've tested using a screen reader
- [ ] All animations respect [`prefers-reduced-motion`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-reduced-motion)
- [ ] All text has [at least a 4.5 color contrast with its background](https://webaim.org/resources/contrastchecker/)
### Screenshots
### Detailed test instructions:
- Ex: Open page `url`
- Click XYZ…
<!--
Please add your test instructions to `/TESTING-INSTRUCTIONS.md`.
-->
<!--- Please add a Changelog note
Enter a changelog note using the following CLI command `pnpm run changelogger -- add` and commit changes. --->

View File

@ -0,0 +1,18 @@
# PR Labeler Action
This is a standalone github action hosted within wc-admin that supports
auto adding or removing a PR label, which could be useful in various
automation scenarios.
To keep it simple and avoid checking in `node_modules` when you make
changes to `index.js` you'll need to compile it into the `dist` directory
via [@vercel/ncc](https://github.com/vercel/ncc).
Run these commands from the root of the action before pushing an update to `dist/index.js`:
```
pnpm i -g @vercel/ncc
ncc build index.js
```
This will compile the `node_modules` alongside the js into one single file.

View File

@ -0,0 +1,17 @@
name: 'PR labeler'
description: 'Add or remove a label from a PR'
inputs:
access_token:
description: 'Github token used to access the API'
required: true
label:
description: 'Label to add or remove'
required: true
default: ''
action:
description: 'Add or remove the label'
required: true
default: 'add'
runs:
using: 'node12'
main: 'dist/index.js'

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,60 @@
const core = require( '@actions/core' );
const github = require( '@actions/github' );
const getPRNumber = () => {
const pr = github.context.payload.pull_request;
return pr && pr.number ? pr.number : null;
};
const addLabel = async ( client, label, prNumber ) => {
await client.issues.addLabels( {
owner: github.context.repo.owner,
repo: github.context.repo.repo,
issue_number: prNumber,
labels: [ label ],
} );
};
const removeLabel = async ( client, label, prNumber ) => {
await client.issues.removeLabel( {
owner: github.context.repo.owner,
repo: github.context.repo.repo,
issue_number: prNumber,
name: label,
} );
};
async function run() {
try {
const prNumber = getPRNumber();
if ( ! prNumber ) {
console.log( 'This action only supports pull requests.' );
return;
}
const token = core.getInput( 'access_token', { required: true } );
const client = github.getOctokit( token );
const label = core.getInput( 'label', { required: true } );
const action = core.getInput( 'action', { required: true } );
const { data: pullRequest } = await client.pulls.get( {
owner: github.context.repo.owner,
repo: github.context.repo.repo,
pull_number: prNumber,
} );
const prHasLabel = pullRequest.labels.some( ( l ) => l.name === label );
if ( action === 'add' && ! prHasLabel ) {
await addLabel( client, label, prNumber );
} else if ( action === 'remove' && prHasLabel ) {
await removeLabel( client, label, prNumber );
}
} catch ( e ) {
core.error( e );
core.setFailed( e.message );
}
}
run();

View File

@ -0,0 +1,192 @@
{
"name": "pr-labeler",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@actions/core": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.5.0.tgz",
"integrity": "sha512-eDOLH1Nq9zh+PJlYLqEMkS/jLQxhksPNmUGNBHfa4G+tQmnIhzpctxmchETtVGyBOvXgOVVpYuE40+eS4cUnwQ=="
},
"@actions/github": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@actions/github/-/github-4.0.0.tgz",
"integrity": "sha512-Ej/Y2E+VV6sR9X7pWL5F3VgEWrABaT292DRqRU6R4hnQjPtC/zD3nagxVdXWiRQvYDh8kHXo7IDmG42eJ/dOMA==",
"requires": {
"@actions/http-client": "^1.0.8",
"@octokit/core": "^3.0.0",
"@octokit/plugin-paginate-rest": "^2.2.3",
"@octokit/plugin-rest-endpoint-methods": "^4.0.0"
}
},
"@actions/http-client": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
"requires": {
"tunnel": "0.0.6"
}
},
"@octokit/auth-token": {
"version": "2.4.5",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.4.5.tgz",
"integrity": "sha512-BpGYsPgJt05M7/L/5FoE1PiAbdxXFZkX/3kDYcsvd1v6UhlnE5e96dTDr0ezX/EFwciQxf3cNV0loipsURU+WA==",
"requires": {
"@octokit/types": "^6.0.3"
}
},
"@octokit/core": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.5.1.tgz",
"integrity": "sha512-omncwpLVxMP+GLpLPgeGJBF6IWJFjXDS5flY5VbppePYX9XehevbDykRH9PdCdvqt9TS5AOTiDide7h0qrkHjw==",
"requires": {
"@octokit/auth-token": "^2.4.4",
"@octokit/graphql": "^4.5.8",
"@octokit/request": "^5.6.0",
"@octokit/request-error": "^2.0.5",
"@octokit/types": "^6.0.3",
"before-after-hook": "^2.2.0",
"universal-user-agent": "^6.0.0"
}
},
"@octokit/endpoint": {
"version": "6.0.12",
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz",
"integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==",
"requires": {
"@octokit/types": "^6.0.3",
"is-plain-object": "^5.0.0",
"universal-user-agent": "^6.0.0"
}
},
"@octokit/graphql": {
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz",
"integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==",
"requires": {
"@octokit/request": "^5.6.0",
"@octokit/types": "^6.0.3",
"universal-user-agent": "^6.0.0"
}
},
"@octokit/openapi-types": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-10.0.0.tgz",
"integrity": "sha512-k1iO2zKuEjjRS1EJb4FwSLk+iF6EGp+ZV0OMRViQoWhQ1fZTk9hg1xccZII5uyYoiqcbC73MRBmT45y1vp2PPg=="
},
"@octokit/plugin-paginate-rest": {
"version": "2.16.0",
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.16.0.tgz",
"integrity": "sha512-8YYzALPMvEZ35kgy5pdYvQ22Roz+BIuEaedO575GwE2vb/ACDqQn0xQrTJR4tnZCJn7pi8+AWPVjrFDaERIyXQ==",
"requires": {
"@octokit/types": "^6.26.0"
}
},
"@octokit/plugin-rest-endpoint-methods": {
"version": "4.15.1",
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-4.15.1.tgz",
"integrity": "sha512-4gQg4ySoW7ktKB0Mf38fHzcSffVZd6mT5deJQtpqkuPuAqzlED5AJTeW8Uk7dPRn7KaOlWcXB0MedTFJU1j4qA==",
"requires": {
"@octokit/types": "^6.13.0",
"deprecation": "^2.3.1"
}
},
"@octokit/request": {
"version": "5.6.1",
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.1.tgz",
"integrity": "sha512-Ls2cfs1OfXaOKzkcxnqw5MR6drMA/zWX/LIS/p8Yjdz7QKTPQLMsB3R+OvoxE6XnXeXEE2X7xe4G4l4X0gRiKQ==",
"requires": {
"@octokit/endpoint": "^6.0.1",
"@octokit/request-error": "^2.1.0",
"@octokit/types": "^6.16.1",
"is-plain-object": "^5.0.0",
"node-fetch": "^2.6.1",
"universal-user-agent": "^6.0.0"
}
},
"@octokit/request-error": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz",
"integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==",
"requires": {
"@octokit/types": "^6.0.3",
"deprecation": "^2.0.0",
"once": "^1.4.0"
}
},
"@octokit/types": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.26.0.tgz",
"integrity": "sha512-RDxZBAFMtqs1ZPnbUu1e7ohPNfoNhTiep4fErY7tZs995BeHu369Vsh5woMIaFbllRWEZBfvTCS4hvDnMPiHrA==",
"requires": {
"@octokit/openapi-types": "^10.0.0"
}
},
"before-after-hook": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz",
"integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ=="
},
"deprecation": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
"integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ=="
},
"is-plain-object": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="
},
"node-fetch": {
"version": "2.6.7",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
"requires": {
"whatwg-url": "^5.0.0"
}
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"requires": {
"wrappy": "1"
}
},
"tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o="
},
"tunnel": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="
},
"universal-user-agent": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
},
"webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE="
},
"whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
"requires": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
}
}
}

View File

@ -0,0 +1,15 @@
{
"name": "pr-labeler",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "GPL-3.0-or-later",
"dependencies": {
"@actions/core": "^1.2.6",
"@actions/github": "^4.0.0"
}
}

View File

@ -0,0 +1,3 @@
Packages: [ "/packages/*" ]
Components: /packages/components
Build: [ "/.*", "*.config.js", "bin/*", "composer.json", "Gruntfile.js", "package.json" ]

View File

@ -0,0 +1,140 @@
[
{
"name": "category: accessibility",
"color": "5804ea",
"aliases": [ "Accessibility" ],
"description": "The issue/PR is related to accessibility."
},
{
"name": "category: duplicate",
"color": "5804ea",
"aliases": [],
"description": "The issue/PR is a duplicate of another issue."
},
{
"name": "category: i18n",
"color": "5804ea",
"aliases": [],
"description": "The issue/PR is related to internationalization."
},
{
"name": "category: performance",
"color": "5804ea",
"aliases": [ "[Type] Performance" ],
"description": "The issue/PR is related to performance."
},
{
"name": "category: refactor",
"color": "5804ea",
"aliases": [],
"description": "The issue/PR is related to refactoring."
},
{
"name": "category: won't fix",
"color": "5804ea",
"aliases": [],
"description": "The issue wont be fixed."
},
{
"name": "good first issue",
"color": "1eff05",
"aliases": [],
"description": "The issue is a good candidate for the first community contribution/for a newcomer to the team."
},
{
"name": "impact: high",
"color": "d73a4a",
"aliases": [],
"description": "This issue impacts a lot of users as reported by our Happiness Engineers."
},
{
"name": "needs design",
"color": "ed95d2",
"aliases": [ "Design", "[Status] Needs Design Review" ],
"description": "The issue requires design input/work from a designer."
},
{
"name": "needs docs",
"color": "ed95d2",
"aliases": [],
"description": "The issue/PR requires documentation to be added."
},
{
"name": "needs feedback",
"color": "ed95d2",
"aliases": [ "[Status] Needs feedback" ],
"description": "The issue/PR needs a response from any of the parties involved in the issue."
},
{
"name": "needs tests",
"color": "ed95d2",
"aliases": [],
"description": "The issue/PR needs tests before it can move forward."
},
{
"name": "priority: critical",
"color": "d73a4a",
"aliases": [ "[Priority] Critical" ],
"description": "The issue is critical—e.g. a fatal error, security problem affecting many customers."
},
{
"name": "priority: high",
"color": "d93f0b",
"aliases": [ "[Priority] High" ],
"description": "The issue/PR is high priority—it affects lots of customers substantially, but not critically."
},
{
"name": "priority: low",
"color": "e2f78c",
"aliases": [ "[Priority] Low" ],
"description": "The issue/PR is low priority—not many people are affected or theres a workaround, etc."
},
{
"name": "status: blocked",
"color": "d73a4a",
"aliases": [ "[Status] Blocked" ],
"description": "The issue is blocked from progressing, waiting for another piece of work to be done."
},
{
"name": "status: on hold",
"color": "d93f0b",
"aliases": [ "[Status] On Hold" ],
"description": "The issue is currently not prioritized."
},
{
"name": "type: bug",
"color": "d73a4a",
"aliases": [ "[Type] Bug" ],
"description": "The issue is a confirmed bug."
},
{
"name": "type: documentation",
"color": "0075ca",
"aliases": [ "Documentation" ],
"description": "This issue is a request for better documentation."
},
{
"name": "type: enhancement",
"color": "0075ca",
"aliases": [ "[Type] Enhancement" ],
"description": "The issue is a request for an enhancement."
},
{
"name": "type: question",
"color": "0075ca",
"aliases": [],
"description": "The issue is a question about how code works."
},
{
"name": "type: task",
"color": "0075ca",
"aliases": [ "[Type] Task" ],
"description": "The issue is an internally driven task (e.g. from another A8c team)."
},
{
"name": "type: technical debt",
"color": "0075ca",
"aliases": [ "Tech debt" ],
"description": "This issue/PR represents/solves the technical debt of the project."
}
]

View File

@ -0,0 +1,10 @@
name: 'Block Merge To main'
on: [ pull_request ]
jobs:
block_merge:
runs-on: ubuntu-latest
steps:
- name: Merge Blocked
if: github.event.pull_request.base.ref == 'main'
run: exit 1

View File

@ -0,0 +1,57 @@
name: 'Daily E2E Tests'
on:
schedule:
- cron: '0 0 * * *'
jobs:
e2e-tests:
runs-on: ubuntu-18.04
strategy:
fail-fast: false
matrix:
wordpress: ['https://wordpress.org/latest.zip', 'https://wordpress.org/nightly-builds/wordpress-latest.zip']
woocommerce: ['https://downloads.wordpress.org/plugin/woocommerce.zip', 'https://github.com/woocommerce/woocommerce/releases/download/nightly/woocommerce-trunk-nightly.zip']
exclude:
- {'wordpress': 'https://wordpress.org/nightly-builds/wordpress-latest.zip', 'woocommerce': 'https://github.com/woocommerce/woocommerce/releases/download/nightly/woocommerce-trunk-nightly.zip'}
steps:
- name: Check out repository code
uses: actions/checkout@v2
- name: Install PHP dependencies
run: |
composer install --no-dev
- name: Setup Node.js
uses: actions/setup-node@v2-beta
with:
node-version: '14'
- name: Install PNPM and install dependencies
uses: pnpm/action-setup@v2.2.1
with:
version: ^6.24.2
run_install: true
- name: Build
run: |
composer require wp-cli/i18n-command
pnpm run build:feature-config
pnpm run build
- name: Setup wp-env
env:
WP_ENV_CONFIG: '{ core: "${{ matrix.wordpress }}", plugins: [ ".", "${{ matrix.woocommerce }}" ] }'
run: |
pnpm -g i @wordpress/env
printf '%s\n' "$WP_ENV_CONFIG" > .wp-env-override.json
WP_ENV_TESTS_PORT=8084 wp-env start
wp-env run tests-cli "wp post create --post_type=page --post_status=publish --post_title='Ready' --post_content='E2E-tests.'"
- name: Test
env:
WC_E2E_SCREENSHOTS: 1
run: |
pnpm exec wc-e2e test:e2e
- name: Archive e2e test screenshots
if: ${{ always() }}
uses: actions/upload-artifact@v2
with:
name: e2e-screenshots
path: tests/e2e/screenshots
if-no-files-found: ignore
retention-days: 5

View File

@ -0,0 +1,40 @@
name: 'Daily PHP Tests'
on:
schedule:
- cron: '0 0 * * *'
jobs:
daily-test-php:
name: "Test PHP"
runs-on: ubuntu-18.04
strategy:
fail-fast: false
matrix:
wordpress: ['latest', 'nightly']
woocommerce: ['latest', 'nightly']
exclude:
- {'wordpress': 'nightly', 'woocommerce': 'nightly'}
steps:
- name: Check out repository code
uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2-beta
with:
node-version: '14'
- name: Install PNPM and install dependencies
uses: pnpm/action-setup@v2.2.1
with:
version: ^6.24.2
run_install: true
- name: Build
run: |
pnpm run build:feature-config
composer install --no-dev
shell: bash
- name: Run the PHP unit tests
env:
WP_VERSION: ${{ matrix.wordpress }}
WC_VERSION: ${{ matrix.woocommerce }}
run: pnpm run test:php
shell: bash

View File

@ -0,0 +1,50 @@
name: E2E tests
on: [pull_request]
jobs:
e2e-tests:
runs-on: ubuntu-18.04
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.7.0
with:
access_token: ${{ github.token }}
- name: Check out repository code
uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@2.9.0
with:
php-version: '7.3'
- name: Install PHP dependencies
run: |
composer self-update 2.0.6
composer i
- name: Setup Node.js
uses: actions/setup-node@v2-beta
with:
node-version: '14'
- name: Install PNPM and install dependencies
uses: pnpm/action-setup@v2.2.1
with:
version: ^6.24.2
run_install: true
- name: Build and run E2E Tests
env:
WC_E2E_SCREENSHOTS: 1
E2E_SLACK_CHANNEL: ${{ secrets.E2E_SLACK_CHANNEL }}
E2E_SLACK_TOKEN: ${{ secrets.E2E_SLACK_TOKEN }}
WP_VERSION: '5.8.0'
run: |
composer require wp-cli/i18n-command
pnpm run build
pnpm run e2e:docker-up
sleep 10
pnpm exec wc-e2e test:e2e
- name: Archive e2e test screenshots
if: ${{ always() }}
uses: actions/upload-artifact@v2
with:
name: e2e-screenshots
path: tests/e2e/screenshots
if-no-files-found: ignore
retention-days: 5

View File

@ -0,0 +1,33 @@
name: Publish docs
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2-beta
with:
node-version: '14'
- name: Install PNPM and install dependencies
uses: pnpm/action-setup@v2.2.1
with:
version: ^6.24.2
run_install: true
- name: Build
run: |
pnpm run build
pnpm run docs
- name: Deploy docs
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_branch: gh-pages
publish_dir: ./docs

View File

@ -0,0 +1,37 @@
name: Lint the changelog
on:
pull_request:
types: [opened, synchronize, reopened, edited]
jobs:
lint-changelog:
runs-on: ubuntu-latest
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.7.0
with:
access_token: ${{ github.token }}
- name: Check out repository code
uses: actions/checkout@v2
if: github.event.pull_request.user.login != 'renovate[bot]'
- name: skip-workflow
id: skip-workflow
uses: saulmaldonado/skip-workflow@v1.1.1
with:
phrase: /no changelog/i
github-token: ${{ secrets.GITHUB_TOKEN }}
pr-message: 'body'
search: '["pull_request"]'
- name: Check for changelog entry
if: github.event.pull_request.user.login != 'renovate[bot]' && !steps.skip-workflow.outputs.skip
env:
PR_NUMBER: ${{github.event.number}}
run: bin/ci/lint-changelog.sh
shell: bash
- name: Add a reminder label to the PR
uses: ./.github/actions/pr-labeler
if: github.event.pull_request.user.login != 'renovate[bot]' && always()
with:
access_token: ${{ github.token }}
label: ${{ env.label || 'needs changelog entry' }}
action: ${{ env.label_action || 'remove' }}

View File

@ -0,0 +1,25 @@
name: Lint the PHP
on: [pull_request]
jobs:
lint-php:
runs-on: ubuntu-latest
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.7.0
with:
access_token: ${{ github.token }}
- name: Check out repository code
uses: actions/checkout@v2
- name: Determine changed files
id: changed-files
uses: wyrihaximus/github-action-files-in-commit@v1.0
- name: Setup PHP
uses: shivammathur/setup-php@2.9.0
with:
php-version: 7.3
- name: Lint the PHP
env:
CHANGED_FILES: ${{ steps.changed-files.outputs.files }}
run: bin/phpcs.sh
shell: bash

View File

@ -0,0 +1,25 @@
name: Lint and test JS
on: [pull_request]
jobs:
test-lint-js:
runs-on: ubuntu-latest
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.7.0
with:
access_token: ${{ github.token }}
- name: Check out repository code
uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2.5.1
with:
node-version: '14'
- name: Install PNPM and install dependencies
uses: pnpm/action-setup@v2.2.1
with:
version: ^6.24.2
run_install: true
- name: Lint and test the JS
run: bin/js_lint_test.sh
shell: bash

View File

@ -0,0 +1,23 @@
name: 'Mark or close stale issues and PRs'
on:
schedule:
- cron: '00 * * * *'
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v3
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 60
days-before-close: 5
stale-issue-message: 'This issue has been automatically marked as stale because it has not had any recent activity. It will be closed if no further activity occurs. Thank you for your contributions.'
stale-pr-message: 'This PR has been automatically marked as stale because it has not had any recent activity. It will be closed if no further activity occurs. Thank you for your contributions.'
close-issue-message: 'This issue was automatically closed due to being stale. Please feel free to re-open it if you still experience this problem.'
close-pr-message: 'This PR was automatically closed due to being stale.'
stale-pr-label: 'status: stale'
stale-issue-label: 'status: stale'
exempt-issue-labels: 'cooldown period,priority: high,priority: critical,feature: inbox,feature: components,feature: analytics,feature: marketing,feature: onboarding,feature: navigation,feature: wcpay,feature: settings,feature: home screen,feature: customer effort score,feature: setup checklist,feature: activity panel,feature: rest api'
ascending: true

View File

@ -0,0 +1,87 @@
name: Run PHP unit tests
on: [pull_request]
jobs:
test-php:
env:
WP_CORE_DIR: '/tmp/wordpress'
COMPOSER_DEV: 1
runs-on: ubuntu-18.04
strategy:
fail-fast: false
matrix:
php: ['7.1', '7.2', '7.3']
wordpress: ['5.4', '5.6']
woocommerce: ['4.8.0', '4.9.1']
phpunit: ['7.5.20']
composer: ['2.0.6']
include:
- php: '7.0'
wordpress: '5.6'
woocommerce: 'latest'
phpunit: '6.5.9'
composer: '1.10.19'
- php: '7.0'
wordpress: '5.6'
woocommerce: '4.9.1'
phpunit: '6.5.9'
composer: '2.0.6'
- php: '8.0'
wordpress: '5.6'
woocommerce: '5.1.0'
phpunit: '7.5.20'
composer: '2.0.6'
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.7.0
with:
access_token: ${{ github.token }}
- name: Check out repository code
uses: actions/checkout@v2
- name: Setup PHP
uses: shivammathur/setup-php@2.9.0
with:
php-version: ${{matrix.php}}
tools: phpunit:${{matrix.phpunit}}
extensions: mysqli
- name: Setup Node.js
uses: actions/setup-node@v2-beta
with:
node-version: '14'
- name: Install PNPM and install dependencies
uses: pnpm/action-setup@v2.2.1
with:
version: ^6.24.2
run_install: true
- name: Set up the tests
env:
WP_VERSION: ${{matrix.wordpress}}
WC_VERSION: ${{matrix.woocommerce}}
PHP_UNIT: ${{matrix.phpunit}}
COMPOSER_VERSION: ${{matrix.composer}}
run: |
sudo /etc/init.d/mysql start
bash bin/ci/gh-install-wp-tests.sh wc_admin_test root 'root' localhost
cd "$WP_CORE_DIR/wp-content/plugins/woocommerce-admin/"
pnpm run build:feature-config
composer install
node --version
pnpm --version
timedatectl
- name: Add PHP8 Compatibility.
run: |
if [ "$(php -r "echo version_compare(PHP_VERSION,'8.0','>=');")" ]; then
cd "$WP_CORE_DIR/wp-content/plugins/woocommerce-admin/"
composer install
curl -L https://github.com/woocommerce/phpunit/archive/add-compatibility-with-php8-to-phpunit-7.zip -o /tmp/phpunit-7.5-fork.zip
unzip -d /tmp/phpunit-7.5-fork /tmp/phpunit-7.5-fork.zip
composer bin phpunit config --unset platform
composer bin phpunit config repositories.0 '{"type": "path", "url": "/tmp/phpunit-7.5-fork/phpunit-add-compatibility-with-php8-to-phpunit-7", "options": {"symlink": false}}'
composer bin phpunit require --dev -W phpunit/phpunit:@dev --ignore-platform-reqs
rm -rf ./vendor/phpunit/
composer dump-autoload
fi
- name: Run the PHP unit tests
run: bin/phpunit.sh
shell: bash

41
plugins/woocommerce-admin/.gitignore vendored Executable file
View File

@ -0,0 +1,41 @@
# Directories/files that may be generated by this project
node_modules/
/dist
docs/**/dist
build
build-module
build-style
build-types
languages/*
!languages/README.md
woocommerce-admin.zip
includes/feature-config.php
/storybook/wordpress
tests/e2e/screenshots/*
docs/components/storybook/*
# Directories/files that may appear in your environment
.DS_Store
Thumbs.db
wp-cli.local.yml
*.sql
*.swp
*.tar.gz
*.tgz
*.zip
.idea
.vscode/*
!.vscode/tasks.json
!.vscode/settings.json
# Composer
/vendor/
/vendor-bin/
/bin/composer/**/vendor/
# wp-env config
.wp-env.override.json
# Typescript
*.tsbuildinfo

View File

@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
pnpm exec lint-staged

View File

@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
node bin/pre-push-hook.js

View File

@ -0,0 +1 @@
enable-pre-post-scripts=true

View File

@ -0,0 +1 @@
14

View File

@ -0,0 +1,3 @@
// Import the default config file and expose it in the project root.
// Useful for editor integrations.
module.exports = require("@wordpress/prettier-config");

View File

@ -0,0 +1 @@
storybook

View File

@ -0,0 +1,3 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
}

View File

@ -0,0 +1,29 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "npm",
"script": "ts:check",
"problemMatcher": [ "$tsc" ],
"label": "npm: ts:check",
"detail": "Type checking",
"runOptions": {
"runOn": "default"
}
},
{
"type": "npm",
"script": "ts:check:watch",
"problemMatcher": {
"base": "$tsc-watch",
"applyTo": "allDocuments"
},
"isBackground": true,
"label": "npm: ts:check:watch",
"detail": "Incremental background type checks",
"runOptions": {
"runOn": "folderOpen"
}
}
]
}

View File

@ -0,0 +1,14 @@
{
"phpVersion": "7.4",
"core": null,
"plugins": [
".",
"https://downloads.wordpress.org/plugin/woocommerce.zip"
],
"config": {
"JETPACK_AUTOLOAD_DEV": true,
"WP_DEBUG_LOG": true,
"WP_DEBUG_DISPLAY": true,
"ALTERNATE_WP_CRON": true
}
}

View File

@ -0,0 +1,172 @@
# Contributing to WooCommerce Admin
Hi! Thank you for your interest in contributing to WooCommerce Admin. We appreciate it.
There are many ways to contribute reporting bugs, adding translations, feature suggestions, and fixing bugs.
## Reporting Bugs, Asking Questions, Sending Suggestions
Open [a GitHub issue](https://github.com/woocommerce/woocommerce-admin/issues/new/choose), that's all. If you have write access, add any appropriate labels.
If you're filing a bug, specific steps to reproduce are always helpful. Please include what you expected to see and what happened instead.
## Localizing WooCommerce Admin
To translate WooCommerce Admin in your locale or language, [select your locale here](https://translate.wordpress.org/projects/wp-plugins/woocommerce-admin) and translate *Development* (which contains the plugin's string) and/or *Development Readme* (please translate what you see in the Details tab of the [plugin page](https://wordpress.org/plugins/woocommerce-admin/)).
A Global Translation Editor (GTE) or Project Translation Editor (PTE) with suitable rights will process your translations in due time.
Language packs are automatically generated once 95% of the plugin's strings are translated and approved for a locale.
### Testing translations in development without language packs
1. Requires `WP-CLI` version 2.1.0 or greater.
1. Generate a translation file with `pnpm run i18n xx_YY` (Where xx_YY is your locale, like it_IT).
1. Generate needed JSON files for JavaScript-based strings: `pnpm run i18n:json`.
1. Generate needed `woocommerce-admin-xx_YY.mo` file using your translation tool.
1. Move `.mo` and `.json` files to `/wp-content/languages/plugins`.
## We're Here To Help
We encourage you to ask for help. We want your first experience with WooCommerce Admin to be a good one, so don't be shy. If you're wondering why something is the way it is, or how a decision was made, you can tag issues with [Type] Question or prefix them with “Question:”
## Contributing Code
If you're a first-time code contributor to the repository, here's a quick guide to get started:
1. Fork the repo to your own account.
2. Clone your fork into the `wp-content/plugins` directory of your preferred WordPress development environment.
3. Don't forget to create a branch to keep your changes. (`git checkout -b add/my-cool-thing`).
4. From the `woocommerce-admin` plugin directory, build with `pnpm install` and `pnpm start`.
5. Visit your dev environment in the browser to enable the `WooCommerce Admin` plugin and try it out.
Tips:
- Try to keep each PR small (around 200-250 lines or less, if you can), and having multiple very small commits in each PR is preferable to one larger commit (especially if the PR is larger).
- Don't combine code formatting changes with meaningful ones. If there's formatting work that needs to be done en masse, do it all in one PR, then open another one for meaningful code changes.
- Add unit tests to your PR for better code coverage and review.
After you've made your updates, you're ready to commit:
1. Run a complete build via `pnpm run build`.
2. Do a `composer install` to ensure PHP dependencies can run on the pre-commit hook.
3. Create your commit. Write a descriptive, but short first line (e.g. "Reports: Reticulate the splines"), and add more details below. If your commit addresses a github issue, reference it by number here (e.g. "This commit fixes issue #123 by reticulating all the splines.")
4. Push the branch up to your local fork, then create a PR via the GitHub web interface.
## Creating a Pull Request
The pull request template will remind you of some of the details you need to fill out in your pull request, but there are 2 critical pieces of information that may be needed, the changelog and the testing instructions.
### Changelog Entry
For many pull requests a changelog entry is required. We make use of the [Jetpack Changelogger tool](https://packagist.org/packages/automattic/jetpack-changelogger) to handle our changelogs.
To create a changelog entry run `pnpm run changelogger -- add` and answer the questions. This will create a changelog entry in the [./changelogs](./changelogs) directory with the data you provided. Upon our next release this will be added to our [changelog.txt](./changelog.txt).
In most cases you'll have to provide a changelog entry (the last question), be sure to add your PR number at the end, in the format below:
`<Description of change>. #<PR Number>`
For example:
`a cool new feature. #1234`
The types we use currently are: "Fix", "Add", "Update", "Dev", "Tweak", "Performance" and "Enhancement"
- `Fix`. For bugfixes minor and major. e.g. "Fix a crash when the user selected 0 for revenue."
- `Add`. This is reserved for new features and functionality. e.g. "A new page for payment settings."
- `Update`. This is used interchangeably with `Add` at the moment. Use your best discretion to choose.
- `Dev` is for a code change that doesn't have an obvious user facing benefit. e.g. "Refactor a class to be single responsibility."
- `Tweak`. For minor changes to user facing functionality. e.g. "Styling updates to the site footer."
- `Performance`. For changes that improve the performance of the application. e.g. "Optimized SQL query to run 5x faster.".
- `Enhancement`. This is used interchangeably with `Tweak` at the moment. Use your best discretion to choose.
### Testing Instructions
Every release we do some manual testing of new features, workflows and major bugfixes. For these kind of changes we need to include
testing instructions. If your pull request requires testing instructions you'll need to add them under the `## Unreleased` heading in
`TESTING-INSTRUCTIONS.md`. Add a detailed set of testing instructions to test your change.
### When to Add Testing Instructions
**DO** Add testing instructions for:
- Significant new features and workflows being added.
- Major bugs and regressions. (This does not include fatal crashes on main screens though, these are covered by general testing).
**DON'T** Add testing instructions for:
- Visual issues and changes.
- Minor bugs.
- Tweaks
- Analytics tracking
Please make testing instructions as comprehensive as possible as testers may not have context of how to test some aspects
of the system.
For example an instruction like: `Enable new navigation` should be `Toggle on the new navigation under WooCommerce->Settings->Advanced->Features`.
Assume the tester does not have context on how to test the feature except for a basic understanding of Wordpress.
## PHP Unit tests
### Setting up PHP unit tests using [VVV](https://github.com/Varying-Vagrant-Vagrants/VVV)
1. SSH into the Vagrant box:
1. `cd` down to the Vagrant root (where `www` lives)
2. `vagrant ssh`
2. `cd /srv/www/<name of wp install>/public_html/wp-content/plugins/woocommerce-admin`
3. Set up test environment: `bin/install-wp-tests.sh wc-admin-tests root root`
4. Generate feature config: `php bin/generate-feature-config.php`
*Note: A WooCommerce development environment is required to live within the same `plugins` folder. Follow these [steps](https://github.com/woocommerce/woocommerce/wiki/How-to-set-up-WooCommerce-development-environment) to do so.*
### Running tests
1. SSH into the Vagrant box (`vagrant ssh`)
2. `cd /srv/www/<name of wp install>/public_html/wp-content/plugins/woocommerce-admin`
3. `composer test` to actually run the test suite
#### Filtering tests
You can restrict the test cases run using `phpunit`'s filter command line argument.
For example, to just run Order Report Stats tests:
`composer test -- --filter="WC_Tests_Reports_Orders_Stats"`
## Helper Scripts
There are a number of helper scripts exposed via our `package.json` (below list is not exhaustive, you can view the [`package.json` file directly to see all](https://github.com/woocommerce/woocommerce-admin/blob/main/package.json)):
- `pnpm run lint` : Run eslint over the javascript files
- `pnpm run i18n` : A multi-step process, used to create a pot file from both the JS and PHP gettext calls. First it runs `i18n:js`, which creates a temporary `.pot` file from the JS files. Next it runs `i18n:php`, which converts that `.pot` file to a PHP file. Lastly, it runs `i18n:pot`, which creates the final `.pot` file from all the PHP files in the plugin (including the generated one with the JS strings).
- `pnpm test` : Run the JS test suite
- `pnpm run docs`: Runs the script for generating/updating docs.
## Debugging
### Debugging synced lookup information:
To debug synced lookup information in the database, you can bypass the action scheduler and immediately sync order and customer information by using the `woocommerce_analytics_disable_action_scheduling` hook.
```php
add_filter( 'woocommerce_analytics_disable_action_scheduling', '__return_true' );
```
### Using `debug` package.
Currently, the [debug package](https://github.com/visionmedia/debug) is utilized to provide additional debugging for various systems. This tool outputs additional debugging information in the browser console when it is activated.
To activate, open up your browser console and add this:
```js
localStorage.setItem( 'debug', 'wc-admin:*' );
```
## License
WooCommerce Admin is licensed under [GNU General Public License v3 (or later)](/license.txt).
All materials contributed should be compatible with the GPLv3. This means that if you own the material, you agree to license it under the GPLv3 license. If you are contributing code that is not your own, such as adding a component from another Open Source project, or adding an `pnpm` package, you need to make sure you follow these steps:
1. Check that the code has a license. If you can't find one, you can try to contact the original author and get permission to use, or ask them to release under a compatible Open Source license.
2. Check the license is compatible with [GPLv3](https://www.gnu.org/licenses/license-list.en.html#GPLCompatibleLicenses), note that the Apache 2.0 license is *not* compatible.

View File

@ -0,0 +1,62 @@
/* eslint-disable */
module.exports = function( grunt ) {
'use strict';
// Project configuration
grunt.initConfig( {
makepot: {
target: {
options: {
domainPath: '/languages',
exclude: [ '.git/*', 'bin/*', 'node_modules/*', 'tests/*' ],
mainFile: 'woocommerce-admin.php',
potFilename: 'woocommerce-admin.pot',
potHeaders: {
poedit: true,
'x-poedit-keywordslist': true,
},
type: 'wp-plugin',
updateTimestamp: true,
},
},
},
checktextdomain: {
options: {
text_domain: 'woocommerce-admin',
keywords: [
'__:1,2d',
'_e:1,2d',
'_x:1,2c,3d',
'esc_html__:1,2d',
'esc_html_e:1,2d',
'esc_html_x:1,2c,3d',
'esc_attr__:1,2d',
'esc_attr_e:1,2d',
'esc_attr_x:1,2c,3d',
'_ex:1,2c,3d',
'_n:1,2,4d',
'_nx:1,2,4c,5d',
'_n_noop:1,2,3d',
'_nx_noop:1,2,3c,4d',
],
},
files: {
src: [
'**/*.php', // Include all files/
'!node_modules/**', // Exclude node_modules/
'!tests/**', // Exclude tests/
'!vendor/**', // Exclude vendor/
'!tmp/**', // Exclude tmp/
],
expand: true,
},
},
} );
// Load NPM tasks to be used here.
grunt.loadNpmTasks( 'grunt-wp-i18n' );
grunt.loadNpmTasks( 'grunt-checktextdomain' );
grunt.util.linefeed = '\n';
};

View File

@ -0,0 +1,711 @@
### WooCommerce - eCommerce for WordPress
Copyright 2015 by the contributors
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
This program incorporates work covered by the following copyright and
permission notices:
Jigoshop is Copyright (c) 2011 Jigowatt Ltd.
http://jigowatt.com - http://jigoshop.com
Jigoshop is released under the GPL
and
WooCommerce - eCommerce for WordPress
Copyright 2015 by the contributors
WooCommerce is released under the GPL
---
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright © 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright © <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright © <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/philosophy/why-not-lgpl.html>.

View File

@ -0,0 +1,120 @@
# WooCommerce Admin
This is a feature plugin for a modern, javascript-driven WooCommerce Admin experience.
## Prerequisites
[WordPress 5.6 or greater](https://wordpress.org/download/) and [WooCommerce 5.7.0 or greater](https://wordpress.org/plugins/woocommerce/) should be installed prior to activating the WooCommerce Admin feature plugin.
For better debugging, it's also recommended you add `define( 'SCRIPT_DEBUG', true );` to your wp-config. This will load the unminified version of all libraries, and specifically the development build of React.
## Development
After cloning the repo, install dependencies:
- `pnpm install` to install JavaScript dependencies.
- `composer install` to gather PHP dependencies.
Now you can build the files using one of these commands:
- `pnpm run build` : Build a production version
- `pnpm run dev` : Build a development version
- `pnpm start` : Build a development version, watch files for changes
- `pnpm run build:release` : Build a WordPress plugin ZIP file (`woocommerce-admin.zip` will be created in the repository root)
- `DRY_RUN=1 pnpm run build:release` : Builds a Wordpress plugin ZIP **without** pushing it to Github and creating a release.
For more helper scripts [see here](./CONTRIBUTING.md#helper-scripts)
For some debugging tools/help [see here](./CONTRIBUTING.md#debugging)
For local development setup using Docker [see here](./docker/wc-admin-wp-env/README.md)
### Typescript
The `npm run ts:check` command will check your TypeScript files for errors, and has been added to `.vscode/tasks.json`.
Running this task in vscode will highlight the errors in your editor file navigator.
If you allow the `npm run ts:check:watch` command to run automatically as configured, it will run in the background and pick up any errors as you save the files.
Note: Even if you don't run this task, the IDE uses its language server to pick up type errors in files that are open. This is only necessary for picking up errors
across the entire repository even when they haven't been opened in the IDE.
### Testing
#### End-to-end tests
Tests live in `./tests/e2e`. An existing build is required prior running, please refer to the section above for steps. E2E tests use the `@woocommerce/e2e-environment` package which hosts a Docker container for testing, by default the container can be accessed at `http://localhost:8084`
All the commands from `@woocommerce/e2e-environment` can be run through `pnpm exec`.
```
# Set up the e2e environment
pnpm i
pnpm exec wc-e2e docker:up
```
Run tests using:
```
pnpm exec wc-e2e test:e2e-dev
```
or in headless mode:
```
pnpm exec wc-e2e test:e2e
```
Run a single test by adding the path to the file name:
```
pnpm exec wc-e2e test:e2e-dev tests/e2e/specs/activate-and-setup/complete-onboarding-wizard.test.ts
```
### Documentation
There is documentation in 2 forms available in the repo. A static set of documentation supported by docsify and also a Storybook containing component documentation for `@woocommerce/components`.
To view the docsify docs locally you can do:
```
pnpm install
cd docs
pnpm exec docsify serve
```
When deployed the docsify docs also host an embedded version of the storybook docs. To generate that and test it locally in docsify you'll need to run:
```
pnpm install
pnpm run docs
cd docs
pnpm exec docsify serve
```
Then navigate to `Components` from the left hand menu in the docs.
If you would like to view the storybook docs hosted standalone, then you can run:
```
pnpm install
pnpm run storybook
```
If you would like to view the storybook docs in right-to-left styling, you can run this instead:
```
pnpm install
pnpm run storybook-rtl
```
## Common Issues
If you're encountering any issue setting things up, chances are we have been there too. Please have a look at our [wiki](https://github.com/woocommerce/woocommerce-admin/wiki/Common-Issues) for a list of common problems.
## Privacy
If you have enabled WooCommerce usage tracking ( option `woocommerce_allow_tracking` ) then, in addition to the tracking described in https://woocommerce.com/usage-tracking/, this plugin also sends information about the actions that site administrators perform to Automattic - see https://automattic.com/privacy/#information-we-collect-automatically for more information.
## Contributing
There are many ways to contribute reporting bugs, adding translations, feature suggestions and fixing bugs. For full details, please see [CONTRIBUTING.md](./CONTRIBUTING.md)

View File

@ -0,0 +1,5 @@
# Reporting Security Issues
The WooCommerce team take security bugs seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions.
For security related issues and how to report them, please visit the [Automattic Security](https://automattic.com/security/) page.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,59 @@
const {
babelConfig: e2eBabelConfig,
} = require( '@woocommerce/e2e-environment' );
module.exports = function ( api ) {
api.cache( true );
return {
...e2eBabelConfig,
presets: [
...e2eBabelConfig.presets,
'@babel/preset-typescript',
'@wordpress/babel-preset-default',
],
sourceType: 'unambiguous',
plugins: [
/**
* This allows arrow functions as class methods so that binding
* methods to `this` in the constructor isn't required.
*/
'@babel/plugin-proposal-class-properties',
],
ignore: [ 'packages/**/node_modules' ],
env: {
production: {
plugins: [
[
'@wordpress/babel-plugin-makepot',
{
output: 'languages/woocommerce-admin.po',
},
],
],
},
storybook: {
plugins: [
/**
* We need to set loose mode here because the storybook's default babel config enables the loose mode.
* The 'loose' mode configuration must be the same for those babel plugins.
*
*/
[
'@babel/plugin-proposal-class-properties',
{ loose: true },
],
[
'@babel/plugin-proposal-private-methods',
{ loose: true },
],
[
'@babel/plugin-proposal-private-property-in-object',
{ loose: true },
],
],
},
},
};
};

View File

@ -0,0 +1,147 @@
#!/bin/bash
ZIP_FILE='woocommerce-admin.zip';
IS_CUSTOM_BUILD=false;
SLUG='';
while [ $# -gt 0 ]; do
if [[ $1 == '-s' || $1 == '--slug' ]]; then
IS_CUSTOM_BUILD=true
SLUG=$2
ZIP_FILE="woocommerce-admin-$2.zip";
fi
shift
done
# Exit if any command fails.
set -e
# Change to the expected directory.
cd "$(dirname "$0")"
cd ..
# Enable nicer messaging for build status.
BLUE_BOLD='\033[1;34m';
GREEN_BOLD='\033[1;32m';
RED_BOLD='\033[1;31m';
YELLOW_BOLD='\033[1;33m';
COLOR_RESET='\033[0m';
error () {
echo -e "\n${RED_BOLD}$1${COLOR_RESET}\n"
}
status () {
echo -e "\n${BLUE_BOLD}$1${COLOR_RESET}\n"
}
success () {
echo -e "\n${GREEN_BOLD}$1${COLOR_RESET}\n"
}
warning () {
echo -e "\n${YELLOW_BOLD}$1${COLOR_RESET}\n"
}
status "💃 Time to release WooCommerce Admin 🕺"
if [ $DRY_RUN ]; then
warning "This is a dry run, nothing will be pushed up to Github, it will only generate zip files."
fi
warning "Please enter the version number to tag, for example, 1.0.0: "
read -r VERSION
if [ $IS_CUSTOM_BUILD = true ]; then
PLUGIN_TAG="${VERSION}-${SLUG}"
warning "A release on Github will be made with the tag ${GREEN_BOLD}$PLUGIN_TAG${COLOR_RESET}"
warning "The resulting zip will be called ${GREEN_BOLD}$ZIP_FILE${COLOR_RESET}"
else
PLUGIN_TAG="${VERSION}-plugin"
CORE_TAG="${VERSION}"
warning "You are building a regular release of wc-admin."
warning "A plugin and Core release will be made to Github with the tags ${GREEN_BOLD}$PLUGIN_TAG${YELLOW_BOLD} and ${GREEN_BOLD}$CORE_TAG${COLOR_RESET}"
fi
warning "Ready to proceed? [y/N]: "
read -r PROCEED
if [ "$(echo "${PROCEED:-n}" | tr "[:upper:]" "[:lower:]")" != "y" ]; then
error "Release cancelled!"
exit 1
fi
# Make sure there are no changes in the working tree. Release builds should be
# traceable to a particular commit and reliably reproducible. (This is not
# totally true at the moment because we download nightly vendor scripts).
changed=
if ! git diff --exit-code > /dev/null; then
changed="file(s) modified"
elif ! git diff --cached --exit-code > /dev/null; then
changed="file(s) staged"
fi
if [ ! -z "$changed" ]; then
git status
error "ERROR: Cannot build plugin zip with dirty working tree. ☝️
Commit your changes and try again."
exit 1
fi
# Do a dry run of the repository reset. Prompting the user for a list of all
# files that will be removed should prevent them from losing important files!
status "Resetting the repository to pristine condition. ✨"
git clean -xdf --dry-run
warning "🚨 About to delete everything above! Is this okay? 🚨"
echo -n "[y]es/[N]o: "
read answer
if [ "$answer" != "${answer#[Yy]}" ]; then
# Remove ignored files to reset repository to pristine condition. Previous
# test ensures that changed files abort the plugin build.
status "Cleaning working directory... 🛀"
git clean -xdf
else
error "Fair enough; aborting. Tidy up your repo and try again. 🙂"
exit 1
fi
# Install PHP dependencies
status "Gathering PHP dependencies... 🐿️"
composer install --no-dev
# Build the plugin files.
status "Generating the plugin build... 👷‍♀️"
WC_ADMIN_PHASE=plugin pnpm run build
# Make a Github release.
status "Starting a Github release... 👷‍♀️"
if [ $DRY_RUN ]; then
PLUGIN_ZIP_FILE="woocommerce-admin-plugin.zip"
else
PLUGIN_ZIP_FILE=$ZIP_FILE
fi
./bin/github-deploy.sh ${PLUGIN_TAG} ${PLUGIN_ZIP_FILE}
if [ $IS_CUSTOM_BUILD = false ]; then
# Remove ignored files to reset repository to pristine condition. Previous
# test ensures that changed files abort the plugin build.
status "Cleaning working directory... 🛀"
git clean -xdf -e woocommerce-admin-plugin.zip
# Install PHP dependencies
status "Gathering PHP dependencies... 🐿️"
composer install --no-dev
# Build the Core files.
status "Generating a Core build... 👷‍♀️"
WC_ADMIN_PHASE=core pnpm run build
# Make a Github release.
status "Starting a Github release... 👷‍♀️"
./bin/github-deploy.sh ${CORE_TAG} ${ZIP_FILE}
fi
if [ $DRY_RUN ]; then
output 2 "Dry run successfully finished."
echo
echo "Generated $PLUGIN_ZIP_FILE for the woocommerce-admin plugin build"
echo "Generated $ZIP_FILE for the woocommerce-admin core build"
exit;
fi
success "Done. You've built WooCommerce Admin! 🎉 "

View File

@ -0,0 +1,10 @@
#!/bin/sh
#
# Build a test zip from current branch for uploading to test site
#
phase=${WC_ADMIN_PHASE:-plugin}
WC_ADMIN_PHASE=$phase pnpm run build
composer install --no-dev
rm woocommerce-admin.zip
./bin/make-zip.sh woocommerce-admin.zip

View File

@ -0,0 +1,168 @@
<?php
use Automattic\Jetpack\Changelog\Changelog;
use Automattic\Jetpack\Changelog\KeepAChangelogParser;
use Automattic\Jetpack\Changelogger\FormatterPlugin;
use Automattic\Jetpack\Changelogger\PluginTrait;
/**
* Jetpack Changelogger Formatter for WC Admin
*
* Class WCAdminFormatter
*/
class WCAdminFormatter extends KeepAChangelogParser implements FormatterPlugin {
use PluginTrait;
/**
* Bullet for changes.
*
* @var string
*/
private $bullet = '-';
/**
* String used as the date for an unreleased version.
*
* @var string
*/
private $unreleased = '== Unreleased ==';
/**
* Modified version of parse() from KeepAChangelogParser.
*
* @param string $changelog Changelog contents.
* @return Changelog
* @throws InvalidArgumentException If the changelog data cannot be parsed.
*/
public function parse( $changelog ) {
$ret = new Changelog();
// Fix newlines and expand tabs.
$changelog = strtr( $changelog, array( "\r\n" => "\n" ) );
$changelog = strtr( $changelog, array( "\r" => "\n" ) );
while ( strpos( $changelog, "\t" ) !== false ) {
$changelog = preg_replace_callback(
'/^([^\t\n]*)\t/m',
function ( $m ) {
return $m[1] . str_repeat( ' ', 4 - ( mb_strlen( $m[1] ) % 4 ) );
},
$changelog
);
}
// Entries make up the rest of the document.
$entries = array();
preg_match_all( '/^\=\=\s+([^\n=]+)\s+\=\=((?:(?!^\=\=).)+)/ms', $changelog, $matches );
foreach ( $matches[0] as $section ) {
$heading_pattern = '/^== +(\[?[^] ]+\]?) (.+?) ==/';
// Parse the heading and create a ChangelogEntry for it.
preg_match( $heading_pattern, $section, $heading );
if ( ! count( $heading ) ) {
throw new InvalidArgumentException( "Invalid heading: $heading" );
}
$version = $heading[1];
$timestamp = $heading[2];
if ( $timestamp === $this->unreleased ) {
$timestamp = null;
$entry_timestamp = new DateTime( 'now', new DateTimeZone( 'UTC' ) );
} else {
try {
$timestamp = new DateTime( $timestamp, new DateTimeZone( 'UTC' ) );
} catch ( \Exception $ex ) {
throw new InvalidArgumentException( "Heading has an invalid timestamp: $heading", 0, $ex );
}
if ( strtotime( $heading[2], 0 ) !== strtotime( $heading[2], 1000000000 ) ) {
throw new InvalidArgumentException( "Heading has a relative timestamp: $heading" );
}
$entry_timestamp = $timestamp;
}
$entry = $this->newChangelogEntry(
$version,
array(
'timestamp' => $timestamp,
)
);
$entries[] = $entry;
$content = trim( preg_replace( $heading_pattern, '', $section ) );
if ( '' === $content ) {
// Huh, no changes.
continue;
}
// Now parse all the subheadings and changes.
while ( '' !== $content ) {
$changes = array();
$rows = explode( "\n", $content );
foreach ( $rows as $row ) {
$row = trim( $row );
$row = preg_replace( '/' . $this->bullet . '/', '', $row, 1 );
$row_segments = explode( ':', $row );
$changes[] = array(
'subheading' => trim($row_segments[0]),
'content' => count( $row_segments ) > 1 ? trim($row_segments[1]) : '',
);
}
foreach ( $changes as $change ) {
$entry->appendChange(
$this->newChangeEntry(
array(
'subheading' => $change['subheading'],
'content' => $change['content'],
'timestamp' => $entry_timestamp,
)
)
);
}
$content = '';
}
}
$ret->setEntries( $entries );
return $ret;
}
/**
* Write a Changelog object to a string.
*
* @param Changelog $changelog Changelog object.
* @return string
*/
public function format( Changelog $changelog ) {
$ret = '';
$date_format = 'm/d/Y';
$bullet = '- ';
$indent = str_repeat( ' ', strlen( $bullet ) );
foreach ( $changelog->getEntries() as $entry ) {
$timestamp = $entry->getTimestamp();
$ret .= '== ' . $entry->getVersion() . ' ' . $timestamp->format( $date_format ) . " == \n\n";
$prologue = trim( $entry->getPrologue() );
if ( '' !== $prologue ) {
$ret .= "\n$prologue\n\n";
}
foreach ( $entry->getChangesBySubheading() as $heading => $changes ) {
foreach ( $changes as $change ) {
$text = trim( $change->getContent() );
if ( '' !== $text ) {
$ret .= $bullet . $heading . ': ' . str_replace( "\n", "\n$indent", $text ) . "\n";
}
}
}
$ret = trim( $ret ) . "\n\n";
}
$ret = trim( $ret ) . "\n";
return $ret;
}
}

View File

@ -0,0 +1,169 @@
#!/usr/bin/env bash
if [ $# -lt 3 ]; then
echo "usage: $0 <db-name> <db-user> <db-pass>"
exit 1
fi
DB_NAME=$1
DB_USER=$2
DB_PASS=$3
DB_HOST='localhost'
TMPDIR=${TMPDIR-/tmp}
TMPDIR=$(echo $TMPDIR | sed -e "s/\/$//")
WP_TESTS_DIR=${WP_TESTS_DIR-$TMPDIR/wordpress-tests-lib}
WP_CORE_DIR=${WP_CORE_DIR-$TMPDIR/wordpress/}
WP_TESTS_TAG="branches/$WP_VERSION"
download() {
if [ `which curl` ]; then
curl -s "$1" > "$2";
elif [ `which wget` ]; then
wget -nv -O "$2" "$1"
fi
}
set -ex
install_wp() {
echo "::group::{install_wp}"
mkdir -p $WP_CORE_DIR
download https://api.wordpress.org/core/version-check/1.7/ $TMPDIR/wp-latest.json
if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then
# version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x
LATEST_VERSION=${WP_VERSION%??}
else
# otherwise, scan the releases and get the most up to date minor version of the major release
local VERSION_ESCAPED=`echo $WP_VERSION | sed 's/\./\\\\./g'`
LATEST_VERSION=$(grep -o '"version":"'$VERSION_ESCAPED'[^"]*' $TMPDIR/wp-latest.json | sed 's/"version":"//' | head -1)
fi
if [[ -z "$LATEST_VERSION" ]]; then
local ARCHIVE_NAME="wordpress-$WP_VERSION"
else
local ARCHIVE_NAME="wordpress-$LATEST_VERSION"
fi
download https://wordpress.org/${ARCHIVE_NAME}.tar.gz $TMPDIR/wordpress.tar.gz
tar --strip-components=1 -zxmf $TMPDIR/wordpress.tar.gz -C $WP_CORE_DIR
download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php
echo "::endgroup::"
}
install_test_suite() {
echo "::group::{install_test_suite}"
# portable in-place argument for both GNU sed and Mac OSX sed
if [[ $(uname -s) == 'Darwin' ]]; then
local ioption='-i .bak'
else
local ioption='-i'
fi
# removes testing suite
rm -rf $WP_TESTS_DIR
# set up testing suite if it doesn't yet exist
if [ ! -d $WP_TESTS_DIR ]; then
# set up testing suite
mkdir -p $WP_TESTS_DIR
svn co https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes
fi
if [ ! -f wp-tests-config.php ]; then
download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php
# remove all forward slashes in the end
WP_CORE_DIR=$(echo $WP_CORE_DIR | sed "s:/\+$::")
sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php
sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php
sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php
sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php
sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php
fi
echo "::endgroup::"
}
install_db() {
echo "::group::{install_db}"
# drop existing database
echo "DROP DATABASE IF EXISTS $DB_NAME" | mysql --user="root" --password="$DB_PASS"
# create database
echo "CREATE DATABASE IF NOT EXISTS $DB_NAME" | mysql --user="root" --password="$DB_PASS"
echo "::endgroup::"
}
version() {
# convert version numbers to digits
echo "$@" | awk -F. '{ printf("%03d%03d%03d\n", $1,$2,$3); }';
}
install_deps() {
echo "::group::{install_deps}"
# Script Variables
WP_SITE_URL="http://local.wordpress.test"
WORKING_DIR="$PWD"
# Set up WordPress using wp-cli
mkdir -p "$WP_CORE_DIR"
cd "$WP_CORE_DIR"
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
# delete existing wp-config and woocommerce repository
rm -f wp-config.php
rm -rf wp-content/plugins/woocommerce
php wp-cli.phar core config --dbname=$DB_NAME --dbuser=root --dbpass=$DB_PASS --dbhost=localhost --dbprefix=wptests_ --allow-root
php wp-cli.phar core install --url="$WP_SITE_URL" --title="Example" --admin_user=admin --admin_password=password --admin_email=info@example.com --path=$WP_CORE_DIR --skip-email --allow-root
# Install WooCommerce (latest non-hyphenated (beta, RC) tag)
if [[ "$WC_VERSION" == "latest" ]]; then
INSTALL_WC_TAG="$(git ls-remote --tags https://github.com/woocommerce/woocommerce.git | awk '{print $2}' | sed 's/^refs\/tags\///' | grep -E '^[0-9]\.[0-9]\.[0-9]$' | sort -V | tail -n 1)"
else
INSTALL_WC_TAG="$WC_VERSION"
fi
# As zip file does not include tests, we have to get it from git repo.
git clone --depth 1 --branch $INSTALL_WC_TAG https://github.com/woocommerce/woocommerce.git
if [ "$(version "$INSTALL_WC_TAG")" -ge "$(version "6.0.0")" ]; then
# WooCommerce 6.0.0 introduced a breaking change to the repo structure.
# We need to clone and use the correct folder path.
mv woocommerce/plugins/woocommerce wp-content/plugins/
else
mv woocommerce wp-content/plugins/
fi
cd "wp-content/plugins/"
# Bring in WooCommerce Core dependencies
composer self-update $COMPOSER_VERSION
cd "woocommerce"
composer install --no-dev
composer self-update 2.0.6
cd "$WP_CORE_DIR"
php wp-cli.phar plugin activate woocommerce
# Install woocommerce-admin
cd "$WP_CORE_DIR/wp-content/plugins"
cp -R $GITHUB_WORKSPACE ./
# Activate woocommerce-admin
cd "$WP_CORE_DIR"
php wp-cli.phar plugin activate woocommerce-admin
# Back to original dir
cd "$WORKING_DIR"
echo "::endgroup::"
}
install_wp
install_test_suite
install_db
install_deps

View File

@ -0,0 +1,14 @@
#!/usr/bin/env bash
changelog_entry=$(sed -n '/^Significance/,/^==/p' changelogs/* | grep -w "#$PR_NUMBER")
if [ -z "$changelog_entry" ]
then
echo "Error: No changelog entry was provided for #$PR_NUMBER"
echo "label_action=add" >> $GITHUB_ENV
echo "label=needs changelog entry" >> $GITHUB_ENV
exit 1
fi
echo "label_action=remove" >> $GITHUB_ENV
echo "label=needs changelog entry" >> $GITHUB_ENV

View File

@ -0,0 +1,104 @@
<?php
/**
* Command line script for merging two .pot files.
*
* @package WooCommerce\Admin
*/
/**
* Get the two file names from the command line.
*/
if ( $argc < 2 ) {
echo "Usage: php -f {$argv[0]} source-file.pot destination-file.pot\n";
exit;
}
for ( $index = 1; $index <= 2; $index++ ) {
if ( ! is_file( $argv[ $index ] ) ) {
echo "File not found: {$argv[ $index ]}\n";
exit;
}
}
/**
* Check whether an output locale has been requested.
*/
if ( isset( $argv[3] ) && 0 === stripos( $argv[3], 'lang=' ) ) {
$locale = substr( $argv[3], 5 );
$target_file = preg_replace( '|\.pot?|', "-{$locale}.po", $argv[2] );
} else {
$target_file = $argv[2];
}
/**
* Parse a .pot file into an array.
*
* @param string $file_name Pot file name.
* @return array
*/
function woocommerce_admin_parse_pot( $file_name ) {
$fh = fopen( $file_name, 'r' );
$originals = array();
$references = array();
$messages = array();
$have_msgid = false;
while ( ! feof( $fh ) ) {
$line = trim( fgets( $fh ) );
if ( ! $line ) {
$message = implode( "\n", $messages );
$originals[ $message ] = $references;
$references = array();
$messages = array();
$have_msgid = false;
$message = '';
continue;
}
if ( 'msgid' == substr( $line, 0, 5 ) ) {
$have_msgid = true;
}
if ( $have_msgid ) {
$messages[] = $line;
} else {
$references[] = $line;
}
}
fclose( $fh );
$message = implode( "\n", $messages );
$originals[ $message ] = $references;
return $originals;
}
// Read the translation files.
$originals_1 = woocommerce_admin_parse_pot( $argv[1] );
$originals_2 = woocommerce_admin_parse_pot( $argv[2] );
// Delete the original sources.
unlink( $argv[1] );
unlink( $argv[2] );
// We don't want two .pot headers in the output.
array_shift( $originals_1 );
$fh = fopen( $target_file, 'w' );
foreach ( $originals_2 as $message => $original ) {
// Use the complete message section to match strings to be translated.
if ( isset( $originals_1[ $message ] ) ) {
$original = array_merge( $original, $originals_1[ $message ] );
unset( $originals_1[ $message ] );
}
fwrite( $fh, implode( "\n", $original ) );
fwrite( $fh, "\n" . $message ."\n\n" );
}
foreach ( $originals_1 as $message => $original ) {
fwrite( $fh, implode( "\n", $original ) );
fwrite( $fh, "\n" . $message ."\n\n" );
}
fclose( $fh );
echo "Created {$target_file}\n";

View File

@ -0,0 +1,12 @@
{
"minimum-stability": "dev",
"prefer-stable": true,
"require-dev": {
"phpunit/phpunit": "7.5.20"
},
"config": {
"platform": {
"php": "7.1"
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,39 @@
<?php
/**
* Generates an array of feature flags, based on the config used by the client application.
*
* @package WooCommerce\Admin
*/
/**
* Get phase for feature flags
* - development: All features should be enabled in development.
* - plugin: For the standalone feature plugin, for GitHub and WordPress.org.
* - core: Stable features for WooCommerce core merge.
*/
$phase = getenv( 'WC_ADMIN_PHASE' );
if ( ! in_array( $phase, array( 'development', 'plugin', 'core' ), true ) ) {
$phase = 'plugin'; // Default to plugin when running `pnpm run build`.
}
$config_json = file_get_contents( 'config/' . $phase . '.json' );
$config = json_decode( $config_json );
$write = "<?php\n";
$write .= "// WARNING: Do not directly edit this file.\n";
$write .= "// This file is auto-generated as part of the build process and things may break.\n";
$write .= "if ( ! function_exists( 'wc_admin_get_feature_config' ) ) {\n";
$write .= "\tfunction wc_admin_get_feature_config() {\n";
$write .= "\t\treturn array(\n";
foreach ( $config->features as $feature => $bool ) {
$write .= "\t\t\t'{$feature}' => " . ( $bool ? 'true' : 'false' ) . ",\n";
}
$write .= "\t\t);\n";
$write .= "\t}\n";
$write .= "}\n";
$config_file = fopen( 'includes/feature-config.php', 'w' );
fwrite( $config_file, $write );
fclose( $config_file );

View File

@ -0,0 +1,145 @@
#!/bin/sh
RELEASER_PATH=$(pwd)
PLUGIN_SLUG="woocommerce-admin"
GITHUB_ORG="woocommerce"
# wc-admin is always pre-release for now
IS_PRE_RELEASE=true
# Functions
# Check if string contains substring
is_substring() {
case "$2" in
*$1*)
return 0
;;
*)
return 1
;;
esac
}
# Output colorized strings
#
# Color codes:
# 0 - black
# 1 - red
# 2 - green
# 3 - yellow
# 4 - blue
# 5 - magenta
# 6 - cian
# 7 - white
output() {
echo "$(tput setaf "$1")$2$(tput sgr0)"
}
if ! [ -x "$(command -v hub)" ]; then
echo 'Error: hub is not installed. Install from https://github.com/github/hub' >&2
exit 1
fi
# Release script
echo
output 5 "wc-admin->GitHub RELEASE SCRIPT"
output 5 "============================="
echo
if [[ $1 == '' || $2 == '' ]]
then
output 1 "Please supply a tag and zip file name"
exit 1
fi
printf "This script will build files and create a tag on GitHub based on your local branch."
echo
echo
printf "Built files will reflect feature flag configs for Core."
echo
echo
printf "The /dist/ directory will also be pushed to the tagged release."
echo
if [ $DRY_RUN ]; then
output 2 "This is a dry run, only the zip will be generated."
fi
echo
echo "Before proceeding:"
echo " • Ensure you have checked out the branch you wish to release"
echo " • Ensure you have committed/pushed all local changes"
echo " • Did you remember to update changelogs, the readme and plugin files?"
echo " • Are there any changes needed to the readme file?"
echo " • If you are running this script directly instead of via '$ pnpm run build:release', ensure you have built assets and installed composer in --no-dev mode."
echo
output 3 "Do you want to continue? [y/N]: "
read -r PROCEED
echo
if [ "$(echo "${PROCEED:-n}" | tr "[:upper:]" "[:lower:]")" != "y" ]; then
output 1 "Release cancelled!"
exit 1
fi
VERSION=$1
ZIP_FILE=$2
CURRENTBRANCH="$(git rev-parse --abbrev-ref HEAD)"
if [ ! -d "dist" ]; then
output 3 "Dist directory not found. Aborting."
exit 1
fi
output 2 "Starting release to GitHub..."
echo
BRANCH="build/${VERSION}"
NOOP_ARG=""
DRY_RUN_ARG=""
if [ $DRY_RUN ]; then
NOOP_ARG="--noop"
DRY_RUN_ARG="--dry-run"
fi
# Create a release branch.
git checkout -b $BRANCH
# Force add feature-config.php
git add includes/feature-config.php --force $DRY_RUN_ARG
git add . $DRY_RUN_ARG
git commit -m "Adding feature-config.php directory to release" --no-verify $DRY_RUN_ARG
# Force add language files
git add languages/woocommerce-admin.pot --force $DRY_RUN_ARG
git add . $DRY_RUN_ARG
git commit -m "Adding translations to release" --no-verify $DRY_RUN_ARG
# Force add build directory and commit.
git add dist/. --force $DRY_RUN_ARG
git add . $DRY_RUN_ARG
git commit -m "Adding /dist directory to release" --no-verify $DRY_RUN_ARG
# Force add vendor directory and commit.
git add vendor/. --force $DRY_RUN_ARG
git add . $DRY_RUN_ARG
git commit -m "Adding /vendor directory to release" --no-verify $DRY_RUN_ARG
# Push branch upstream
git push origin $BRANCH $DRY_RUN_ARG
# Create the zip archive
./bin/make-zip.sh $ZIP_FILE
# Create the new release.
if [ $IS_PRE_RELEASE = true ]; then
hub $NOOP_ARG release create -m $VERSION -m "Release of version $VERSION. See readme.txt for details." -t $BRANCH --prerelease "v${VERSION}" --attach "${ZIP_FILE}"
else
hub $NOOP_ARG release create -m $VERSION -m "Release of version $VERSION. See readme.txt for details." -t $BRANCH "v${VERSION}" --attach "${ZIP_FILE}"
fi
git checkout $CURRENTBRANCH
git branch -D $BRANCH
git push origin --delete $BRANCH $DRY_RUN_ARG
output 2 "GitHub release complete."

View File

@ -0,0 +1,29 @@
# Hook Reference Generator
Compile a publishable JSON object of WooCommerce's JavaScript filters and slotFill entry points.
## Usage
Generate a new reference found at `bin/hook-reference/data.json` by running the following command.
```
pnpm run create-hook-reference
```
The data includes references to code in the Github repository by commit hash, so it is essential to commit the resulting data in a pull request to `main` so code references are publicly available.
## DocBlock Requirements
JavaScript documentation blocks require certain fields in order to be included in the reference.
### Filter
| Tag | Description |
| --------- | -------------------------------------------------- |
| `@filter` | Filter string used as `addFilter`'s first argument |
### SlotFill
| Tag | Description |
| ----------- | ------------------------- |
| `@slotFill` | The fill component's name |

View File

@ -0,0 +1,104 @@
const { readFile } = require( 'fs' ).promises;
const exec = require( 'await-exec' );
const { parse } = require( 'comment-parser/lib' );
const { relative, resolve } = require( 'path' );
const chalk = require( 'chalk' );
const dataTypes = [ 'action', 'filter', 'slotFill' ];
const getHooks = ( parsedData ) =>
parsedData.filter( ( docBlock ) =>
docBlock.tags.some( ( tag ) => dataTypes.includes( tag.tag ) )
);
const getSourceFile = ( file, commit, { source } ) => {
const first = source[ 0 ].number + 1;
const last = source[ source.length - 1 ].number + 1;
return `https://github.com/woocommerce/woocommerce-admin/blob/${ commit }/${ file }#L${ first }-L${ last }`;
};
const logProgress = ( fileName, { tags } ) => {
const hook = tags.find( ( tag ) => dataTypes.includes( tag.tag ) );
console.log(
chalk.green( `@${ hook.tag } ` ) +
chalk.cyan( `${ hook.name } ` ) +
chalk.yellow( 'generated in ' ) +
chalk.yellow.underline( fileName )
);
};
const addSourceFiles = async ( hooks, fileName ) => {
const { stdout } = await exec( 'git log --pretty="format:%H" -1' );
const commit = stdout.trim();
return hooks.map( ( hook ) => {
logProgress( fileName, hook );
hook.sourceFile = getSourceFile( fileName, commit, hook );
return hook;
} );
};
const prepareHooks = async ( path ) => {
const data = await readFile( path, 'utf-8' ).catch( ( err ) =>
console.error( 'Failed to read file', err )
);
const fileName = relative( resolve( __dirname, '../../' ), path );
const parsedData = parse( data );
const rawHooks = getHooks( parsedData );
return await addSourceFiles( rawHooks, fileName );
};
const makeDocObjects = async ( path ) => {
const hooks = await prepareHooks( path );
return hooks.map( ( { description, tags, sourceFile } ) => {
const tag = tags.find( ( tag ) => dataTypes.includes( tag.tag ) );
paramTags = tags.reduce(
( result, { tag, name, type, description } ) => {
if ( tag === 'param' ) {
result.push( {
name,
type,
description,
} );
}
return result;
},
[]
);
const docObject = {
description,
sourceFile,
name: tag ? tag.name : '',
type: tag.tag,
params: paramTags,
};
if ( tag.tag === 'slotFill' ) {
const scopeTab = tags.find( ( tag ) => tag.tag === 'scope' );
if ( scopeTab ) {
docObject.scope = scopeTab.name;
} else {
console.warn(
`Failed to find "scope" tag for slotFill "${ tag.name }" doc.`
);
}
}
return docObject;
} );
};
const createData = async ( paths ) => {
const data = await Promise.all(
paths.map( async ( path ) => {
return await makeDocObjects( path );
} )
);
return data.flat();
};
module.exports = createData;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,58 @@
const { stat, readdir, writeFile } = require( 'fs' ).promises;
const { resolve } = require( 'path' );
const createData = require( './data' );
const chalk = require( 'chalk' );
async function getFilePaths( dir ) {
const subdirs = await readdir( dir );
const files = await Promise.all(
subdirs.map( async ( subdir ) => {
const res = resolve( dir, subdir );
const _stat = await stat( res );
const isDir = _stat.isDirectory();
const isNotSourceDir =
isDir &&
/\/(build|build-module|build-style|node_modules)$/g.test( res );
if ( isNotSourceDir ) {
return;
}
return isDir ? getFilePaths( res ) : res;
} )
);
return files
.filter( ( f ) => !! f )
.reduce( ( a, f ) => a.concat( f ), [] );
}
async function getAllFilePaths( paths ) {
const allFiles = await Promise.all(
paths.map( async ( path ) => {
return await getFilePaths( path );
} )
);
return allFiles.reduce( ( a, f ) => a.concat( f ), [] );
}
const writeJSONFile = async ( data ) => {
const fileName = 'bin/hook-reference/data.json';
const stringifiedData = JSON.stringify( data, null, 4 );
await writeFile( fileName, stringifiedData + '\n' );
console.log( '\n' );
console.log(
chalk.greenBright(
'A new Hook Reference data source has been created. See `/bin/hook-reference/data.json` and be sure to commit changes.'
)
);
};
console.log( chalk.green( 'Preparing Hook Reference data file' ) );
console.log( '\n' );
getAllFilePaths( [ 'client', 'packages' ] )
.then( ( paths ) => createData( paths ) )
.then( ( data ) => writeJSONFile( data ) )
.catch( ( e ) => console.error( e ) );

View File

@ -0,0 +1,26 @@
#!/usr/bin/env bash
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
STORYBOOK_WORDPRESS_DIR="$DIR/../storybook/wordpress";
STORY_BOOK_CSS_PATH="$DIR/../storybook/wordpress/css";
TMP_DIR="$DIR/../storybook/wordpress/tmp";
ARCHIVE_CSS_PATH="wordpress/wp-admin/css";
ARCHIVE_IMG_PATH="wordpress/wp-admin/images";
mkdir -p "$STORY_BOOK_CSS_PATH";
mkdir -p "$TMP_DIR";
function download_and_extract_css {
curl -o "$STORYBOOK_WORDPRESS_DIR/wordpress-latest.zip" https://wordpress.org/nightly-builds/wordpress-latest.zip;
unzip -qq "$STORYBOOK_WORDPRESS_DIR/wordpress-latest.zip" "$ARCHIVE_CSS_PATH/*" "$ARCHIVE_IMG_PATH/*" -d "$TMP_DIR";
rsync -a "$TMP_DIR/$ARCHIVE_CSS_PATH" "$STORYBOOK_WORDPRESS_DIR";
rsync -a "$TMP_DIR/$ARCHIVE_IMG_PATH" "$STORYBOOK_WORDPRESS_DIR";
rm -r "$TMP_DIR";
}
if [ -z "$(find "$STORY_BOOK_CSS_PATH" -iname '*.css')" ] || [ "$1" == "-f" ]
then
# The directory is not empty, import css
download_and_extract_css;
else
echo "Wordpress CSS already imported, pass -f to force an update";
fi

View File

@ -0,0 +1,37 @@
#!/usr/bin/env node
/**
* Performs an `pnpm install`. Since that's a costly operation,
* it will only perform it if needed, that is, if the packages
* installed at `node_modules` aren't in sync over what
* `package-lock.json` has. For that, modification times of both
* files will be compared. If the package-lock is newer, it means that
* the packages at node_modules may be outdated. That will happen,
* for example, when switching branches.
*/
const fs = require( 'fs' );
const spawnSync = require( 'child_process' ).spawnSync;
const needsInstall = () => {
try {
const shrinkwrapTime = fs.statSync( 'pnpm-lock.yaml' ).mtime;
const nodeModulesTime = fs.statSync( 'node_modules' ).mtime;
return shrinkwrapTime - nodeModulesTime > 1000; // In Windows, directory mtime has less precision than file mtime
} catch ( e ) {
return true;
}
};
if ( needsInstall() ) {
const installResult = spawnSync( 'pnpm', [ 'install' ], {
shell: true,
stdio: 'inherit',
} ).status;
if ( installResult ) {
process.exit( installResult );
}
fs.utimesSync( 'node_modules', new Date(), new Date() );
}

View File

@ -0,0 +1,13 @@
const spawnSync = require( 'child_process' ).spawnSync;
const fs = require( 'fs' );
if ( ! fs.existsSync( 'node_modules' ) ) {
console.log( 'No "node_modules" present, installing dependencies...' );
const installResult = spawnSync( 'pnpm', [ 'install' ], {
shell: true,
stdio: 'inherit',
} ).status;
if ( installResult ) {
process.exit( installResult );
}
}

View File

@ -0,0 +1,231 @@
#!/usr/bin/env bash
if [ $# -lt 3 ]; then
echo "usage: $0 <db-name> <db-user> <db-pass> [db-host] [wp-version] [skip-database-creation]"
exit 1
fi
DB_NAME=$1
DB_USER=$2
DB_PASS=$3
DB_HOST=${4-localhost}
WP_VERSION=${5-latest}
SKIP_DB_CREATE=${6-false}
TMPDIR=${TMPDIR-/tmp}
TMPDIR=$(echo $TMPDIR | sed -e "s/\/$//")
WP_TESTS_DIR=${WP_TESTS_DIR-$TMPDIR/wordpress-tests-lib}
WP_CORE_DIR=${WP_CORE_DIR-$TMPDIR/wordpress/}
download() {
if [ `which curl` ]; then
curl -s "$1" > "$2";
elif [ `which wget` ]; then
wget -nv -O "$2" "$1"
fi
}
if [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+$ ]]; then
WP_TESTS_TAG="branches/$WP_VERSION"
elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; then
if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then
# version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x
WP_TESTS_TAG="tags/${WP_VERSION%??}"
else
WP_TESTS_TAG="tags/$WP_VERSION"
fi
elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then
WP_TESTS_TAG="trunk"
else
# http serves a single offer, whereas https serves multiple. we only want one
download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json
grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json
LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//')
if [[ -z "$LATEST_VERSION" ]]; then
echo "Latest WordPress version could not be found"
exit 1
fi
WP_TESTS_TAG="tags/$LATEST_VERSION"
fi
set -ex
install_wp() {
if [ -d $WP_CORE_DIR ]; then
return;
fi
mkdir -p $WP_CORE_DIR
if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then
mkdir -p $TMPDIR/wordpress-nightly
download https://wordpress.org/nightly-builds/wordpress-latest.zip $TMPDIR/wordpress-nightly/wordpress-nightly.zip
unzip -q $TMPDIR/wordpress-nightly/wordpress-nightly.zip -d $TMPDIR/wordpress-nightly/
mv $TMPDIR/wordpress-nightly/wordpress/* $WP_CORE_DIR
else
if [ $WP_VERSION == 'latest' ]; then
local ARCHIVE_NAME='latest'
elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+ ]]; then
# https serves multiple offers, whereas http serves single.
download https://api.wordpress.org/core/version-check/1.7/ $TMPDIR/wp-latest.json
if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then
# version x.x.0 means the first release of the major version, so strip off the .0 and download version x.x
LATEST_VERSION=${WP_VERSION%??}
else
# otherwise, scan the releases and get the most up to date minor version of the major release
local VERSION_ESCAPED=`echo $WP_VERSION | sed 's/\./\\\\./g'`
LATEST_VERSION=$(grep -o '"version":"'$VERSION_ESCAPED'[^"]*' $TMPDIR/wp-latest.json | sed 's/"version":"//' | head -1)
fi
if [[ -z "$LATEST_VERSION" ]]; then
local ARCHIVE_NAME="wordpress-$WP_VERSION"
else
local ARCHIVE_NAME="wordpress-$LATEST_VERSION"
fi
else
local ARCHIVE_NAME="wordpress-$WP_VERSION"
fi
download https://wordpress.org/${ARCHIVE_NAME}.tar.gz $TMPDIR/wordpress.tar.gz
tar --strip-components=1 -zxmf $TMPDIR/wordpress.tar.gz -C $WP_CORE_DIR
fi
download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php
}
install_test_suite() {
# portable in-place argument for both GNU sed and Mac OSX sed
if [[ $(uname -s) == 'Darwin' ]]; then
local ioption='-i .bak'
else
local ioption='-i'
fi
# removes testing suite
rm -rf $WP_TESTS_DIR
# set up testing suite
mkdir -p $WP_TESTS_DIR
svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes
svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data
if [ ! -f wp-tests-config.php ]; then
download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php
# remove all forward slashes in the end
WP_CORE_DIR=$(echo $WP_CORE_DIR | sed "s:/\+$::")
sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php
sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php
sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php
sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php
sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php
fi
}
install_db() {
if [ ${SKIP_DB_CREATE} = "true" ]; then
return 0
fi
# parse DB_HOST for port or socket references
local PARTS=(${DB_HOST//\:/ })
local DB_HOSTNAME=${PARTS[0]};
local DB_SOCK_OR_PORT=${PARTS[1]};
local EXTRA=""
if ! [ -z $DB_HOSTNAME ] ; then
if [ $(echo $DB_SOCK_OR_PORT | grep -e '^[0-9]\{1,\}$') ]; then
EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp"
elif ! [ -z $DB_SOCK_OR_PORT ] ; then
EXTRA=" --socket=$DB_SOCK_OR_PORT"
elif ! [ -z $DB_HOSTNAME ] ; then
EXTRA=" --host=$DB_HOSTNAME --protocol=tcp"
fi
fi
# drop existing database
mysqladmin drop -f $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA 2>/dev/null || true
# create database
mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA
}
version() {
# convert version numbers to digits
echo "$@" | awk -F. '{ printf("%03d%03d%03d\n", $1,$2,$3); }';
}
install_deps() {
# Script Variables
WP_SITE_URL="http://local.wordpress.test"
WORKING_DIR="$PWD"
# Set up WordPress using wp-cli
mkdir -p "$WP_CORE_DIR"
cd "$WP_CORE_DIR"
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
# delete existing wp-config and woocommerce repository
rm -f wp-config.php
rm -rf wp-content/plugins/woocommerce
php wp-cli.phar core config --dbname=$DB_NAME --dbuser=$DB_USER --dbpass=$DB_PASS --dbhost=$DB_HOST --dbprefix=wptests_
php wp-cli.phar core install --url="$WP_SITE_URL" --title="Example" --admin_user=admin --admin_password=password --admin_email=info@example.com --path=$WP_CORE_DIR --skip-email
# Install WooCommerce (latest non-hyphenated (beta, RC) tag)
if [[ "$WC_VERSION" == "latest" ]]; then
INSTALL_WC_TAG="$(git ls-remote --tags https://github.com/woocommerce/woocommerce.git | awk '{print $2}' | sed 's/^refs\/tags\///' | grep -E '^[0-9]\.[0-9]\.[0-9]$' | sort -V | tail -n 1)"
else
INSTALL_WC_TAG="$WC_VERSION"
fi
# As zip file does not include tests, we have to get it from git repo.
git clone --depth 1 --branch $INSTALL_WC_TAG https://github.com/woocommerce/woocommerce.git
if [ "$(version "$INSTALL_WC_TAG")" -ge "$(version "6.0.0")" ]; then
# WooCommerce 6.0.0 introduced a breaking change to the repo structure.
# We need to clone and use the correct folder path.
mv woocommerce/plugins/woocommerce wp-content/plugins/
else
mv woocommerce wp-content/plugins/
fi
cd "wp-content/plugins/"
# Bring in WooCommerce Core dependencies
cd "woocommerce"
composer install --no-dev
cd "$WP_CORE_DIR"
php wp-cli.phar plugin activate woocommerce
# Install woocommerce-admin, the correct branch, if running from Travis CI.
if [ "${TRAVIS}" = "true" ]; then
# Default to the pull request branch if this is a PR, otherwise pushed branch.
BRANCH=${TRAVIS_PULL_REQUEST_BRANCH:-$TRAVIS_BRANCH}
# Default to the pull request repo if this is a PR, otherwise default.
REPO=${TRAVIS_PULL_REQUEST_SLUG:-$TRAVIS_REPO_SLUG}
BRANCH="$(sed 's/#/%23/' <<<$BRANCH)"
# Checkout plugin via Git so all files are gathered.
cd "$WP_CORE_DIR/wp-content/plugins"
git clone https://github.com/$REPO.git
cd woocommerce-admin
git fetch origin $BRANCH
git checkout -B $BRANCH origin/$BRANCH
# Activate the plugin
cd "$WP_CORE_DIR"
php wp-cli.phar plugin activate woocommerce-admin
fi
# Back to original dir
cd "$WORKING_DIR"
}
install_wp
install_test_suite
install_db
install_deps

View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -o errexit
pnpm run -s install-if-deps-outdated
pnpm run lint
pnpm run build
pnpm test

View File

@ -0,0 +1,23 @@
#!/usr/bin/env bash
# Check for required version
WPCLI_VERSION=`wp cli version | cut -f2 -d' '`
if [ ${WPCLI_VERSION:0:1} -lt "2" -o ${WPCLI_VERSION:0:1} -eq "2" -a ${WPCLI_VERSION:2:1} -lt "1" ]; then
echo WP-CLI version 2.1.0 or greater is required to make JSON translation files
exit
fi
# Substitute JS source references with build references
for T in `find languages -name "*.po"`
do
sed \
-e 's/ client\/[^:]*:/ dist\/app\/index.js:/gp' \
-e 's/ packages\/components[^:]*:/ dist\/components\/index.js:/gp' \
-e 's/ packages\/date[^:]*:/ dist\/date\/index.js:/gp' \
$T | uniq > $T-build
rm $T
mv $T-build $T
done
# Make the JSON files
wp i18n make-json languages --no-purge

View File

@ -0,0 +1,44 @@
#!/bin/sh
#
# Build a installable plugin zip
# Output colorized strings
#
# Color codes:
# 0 - black
# 1 - red
# 2 - green
# 3 - yellow
# 4 - blue
# 5 - magenta
# 6 - cian
# 7 - white
output() {
echo "$(tput setaf "$1")$2$(tput sgr0)"
}
output 2 "Creating archive... 🎁"
ZIP_FILE=$1
build_files=$(find dist \( -name '*.js' -o -name '*.css' \))
asset_files=$(find dist \( -name '*.asset.php' \))
if [[ $(find dist/app \( -name '*.asset.php' \) | wc -l) -gt 1 ]]; then
output 3 "$asset_files"
output 1 "Multiple asset.php files exists per directory, have you removed the old build?"
exit;
fi
zip -r "${ZIP_FILE}" \
woocommerce-admin.php \
uninstall.php \
includes/ \
images/ \
$build_files \
$asset_files \
languages/woocommerce-admin.pot \
readme.txt \
changelog.txt \
src/ \
vendor/ \
src-internal/

View File

@ -0,0 +1,17 @@
#!/usr/bin/env bash
PHP_FILES_CHANGED=""
for FILE in $(echo $CHANGED_FILES | tr ',' '\n')
do
if [[ $FILE =~ ".php" && -e $FILE ]]; then
PHP_FILES_CHANGED+="$FILE "
fi
done
if [ "$PHP_FILES_CHANGED" != "" ]; then
composer install
echo "Running Code Sniffer."
./vendor/bin/phpcs --encoding=utf-8 -n -p $PHP_FILES_CHANGED
else
echo "No changed files detected, sniffer not run."
fi

View File

@ -0,0 +1,17 @@
#!/usr/bin/env bash
WORKING_DIR="$PWD"
cd "$WP_CORE_DIR/wp-content/plugins/woocommerce-admin/"
if [[ {$COMPOSER_DEV} == 1 || "$(php -r "echo version_compare(PHP_VERSION,'8.0','>=');")" ]]; then
./vendor/bin/phpunit --version
if [[ {$RUN_RANDOM} == 1 ]]; then
./vendor/bin/phpunit -c phpunit.xml.dist --order-by=random
else
./vendor/bin/phpunit -c phpunit.xml.dist
fi
else
phpunit --version
phpunit -c phpunit.xml.dist
fi
TEST_RESULT=$?
cd "$WORKING_DIR"
exit $TEST_RESULT

View File

@ -0,0 +1,16 @@
#!/usr/bin/env node
const execSync = require( 'child_process' ).execSync;
const readline = require( 'readline-sync' );
console.log( '\nBy contributing to this project, you license the materials you contribute ' +
'under the GNU General Public License v2 (or later). All materials must have ' +
'GPLv2 compatible licenses — see .github/CONTRIBUTING.md for details.\n\n' );
const currentBranch = execSync( 'git rev-parse --abbrev-ref HEAD' ).toString().trim();
if ( 'main' === currentBranch ) {
if ( ! readline.keyInYN( "You're about to push !!![ main ]!!!, is that what you intended?" ) ) {
process.exit( 1 );
}
}

View File

@ -0,0 +1,96 @@
#!/bin/bash
# Enable nicer messaging for build status.
BLUE_BOLD='\033[1;34m';
GREEN_BOLD='\033[1;32m';
RED_BOLD='\033[1;31m';
YELLOW_BOLD='\033[1;33m';
COLOR_RESET='\033[0m';
error () {
echo -e "\n🤯 ${RED_BOLD}$1${COLOR_RESET}\n"
}
status () {
echo -e "\n👩💻 ${BLUE_BOLD}$1${COLOR_RESET}\n"
}
success () {
echo -e "\n✅ ${GREEN_BOLD}$1${COLOR_RESET}\n"
}
warning () {
echo -e "\n${YELLOW_BOLD}$1${COLOR_RESET}\n"
}
# Make sure there are no changes in the working tree.
changed=
if ! git diff --exit-code > /dev/null; then
changed="file(s) modified"
elif ! git diff --cached --exit-code > /dev/null; then
changed="file(s) staged"
fi
if [ ! -z "$changed" ]; then
git status
error "ERROR: Cannot start pre-release with dirty working tree. ☝️ Commit your changes and try again."
exit 1
fi
status "Lets release WooCommerce Admin 🎉"
status "What branch/commit would you like to base this release on?"
echo -n "Branch/commit: "
read refspec
git checkout $refspec || { error "ERROR: Unable to checkout ${refspec}." ; exit 1; }
success "Checked out ${refspec}"
git pull origin ${refspec}
success "Pulled latest commits"
status "What version would you like to release?"
echo -n "Version: "
read release
branch="release/${release}"
exists=`git show-ref refs/heads/${branch}`
if [ -n "$exists" ]; then
error "ERROR: release branch already exists."
exit 1
fi
status "creating a release branch"
git checkout -b $branch || { error "ERROR: Unable to create release branch." ; exit 1; }
success "Release branch created: ${branch}"
status "Bumping version to ${release}"
pnpm --no-git-tag-version version $release || { error "ERROR: Invalid version number." ; exit 1; }
success "Version bumped successfully"
status "Run scripts to propagate version numbers and update dependencies."
pnpm run bump-version
status "Here are the changes so far. Make sure the following changes are reflected."
echo "- docs/: folder will have changes to documentation, if any."
echo "- package.json: new version number."
echo "- woocommerce-admin.php: new version number."
echo "- composer.json: new version number."
echo "- readme.txt: new version number."
echo "- src-internal/Admin/FeaturePlugin.php: new version number."
echo "- src/Composer/Package.php: new version number."
echo "- package-lock.json: dependencies updated."
echo -e "\n"
echo -e "\n"
git status

View File

@ -0,0 +1,46 @@
#!/bin/sh
CURRENT_PATH=`pwd`
# Pull down the SVN repository.
echo "Pulling down the SVN repository for woocommerce-admin"
SVN_WOOCOMMERCE_ADMIN_PATH=/tmp/woocommerce/svn-woocommerce-admin
svn co https://plugins.svn.wordpress.org/woocommerce-admin/ $SVN_WOOCOMMERCE_ADMIN_PATH
cd $SVN_WOOCOMMERCE_ADMIN_PATH
# Get the tagged version to release.
echo "Please enter the version number to release to wordpress.org, for example, 1.0.0: "
read -r VERSION
# Empty trunk/.
rm -rf trunk
mkdir trunk
# Download and unzip the plugin into trunk/.
echo "Downloading and unzipping the plugin"
PLUGIN_URL=https://github.com/woocommerce/woocommerce-admin/releases/download/v${VERSION}-plugin/woocommerce-admin.zip
curl -Lo woocommerce-admin.zip $PLUGIN_URL
unzip woocommerce-admin.zip -d trunk
rm woocommerce-admin.zip
# Add files in trunk/ to SVN.
cd trunk
svn add --force .
cd ..
# Commit the changes, which will automatically release the plugin to wordpress.org.
echo "Checking in the new version"
svn ci -m "Release v${VERSION}"
# Tag the release
echo "Tagging the release"
svn cp trunk tags/$VERSION
svn ci -m "Tagging v${VERSION}"
# Clean up.
cd ..
rm -rf svn-woocommerce-admin
cd $CURRENT_PATH

View File

@ -0,0 +1,5 @@
#!/bin/bash
PR_PATTERNS=$(echo $1 | sed 's/[^,]*/\(#&)/g' | sed 's/,/\\|/g')
git log --pretty=format:'%ad,%s' --all --date=iso-local --author-date-order --reverse --grep "$PR_PATTERNS" | sort -u | grep -o '\(#[0-9]\+\)'

View File

@ -0,0 +1,12 @@
module.exports = {
extends: [ 'plugin:@woocommerce/eslint-plugin/recommended' ],
rules: {
// temporary conversion to warnings until the below are all handled.
'@wordpress/i18n-translator-comments': 'warn',
'@wordpress/valid-sprintf': 'warn',
'jsdoc/check-tag-names': [
'error',
{ definedTags: [ 'jest-environment' ] },
],
},
};

View File

@ -0,0 +1,23 @@
# Directories/files that may be generated by this project
node_modules/
dist
build
build-module
build-style
languages/*
!languages/README.md
# Directories/files that may appear in your environment
.DS_Store
Thumbs.db
wp-cli.local.yml
*.sql
*.tar.gz
*.tgz
*.zip
.idea
.vscode/
# Composer
/vendor/

View File

@ -0,0 +1 @@
"@wordpress/prettier-config"

View File

@ -0,0 +1,24 @@
# Create Extension
Scaffold a modern JavaScript WordPress plugin with WooCommerce tooling.
## Includes
- [wp-scripts](https://github.com/WordPress/gutenberg/tree/master/packages/scripts)
- [WooCommerce Dependency Extraction Webpack Plugin](https://github.com/woocommerce/woocommerce-admin/tree/main/packages/dependency-extraction-webpack-plugin)
- [WooCommerce ESLint Plugin with WordPress Prettier](https://github.com/woocommerce/woocommerce-admin/tree/main/packages/eslint-plugin)
### Usage
At the root of a [WooCommerce Admin](https://github.com/woocommerce/woocommerce-admin) installation, run the create extension command.
```
pnpm run create-wc-extension
```
The script will create a sibling directory by a name of your choosing. Once you change directories into the new folder, install dependencies and start a development build.
```
pnpm install
pnpm start
```

View File

@ -0,0 +1,15 @@
# {{extension_name}}
A WooCommerce Admin Extension
## Development
To get started, run the following commands:
```text
pnpm install
pnpm start
```
See [wp-scripts](https://github.com/WordPress/gutenberg/tree/master/packages/scripts) for more usage information.

View File

@ -0,0 +1,43 @@
<?php
/**
* Plugin Name: {{extension_name}}
*
* @package WooCommerce\Admin
*/
/**
* Register the JS.
*/
function add_extension_register_script() {
if ( ! class_exists( 'Automattic\WooCommerce\Admin\PageController' ) || ! \Automattic\WooCommerce\Admin\PageController::is_admin_or_embed_page() ) {
return;
}
$script_path = '/build/index.js';
$script_asset_path = dirname( __FILE__ ) . '/build/index.asset.php';
$script_asset = file_exists( $script_asset_path )
? require( $script_asset_path )
: array( 'dependencies' => array(), 'version' => filemtime( $script_path ) );
$script_url = plugins_url( $script_path, __FILE__ );
wp_register_script(
'{{extension_slug}}',
$script_url,
$script_asset['dependencies'],
$script_asset['version'],
true
);
wp_register_style(
'{{extension_slug}}',
plugins_url( '/build/index.css', __FILE__ ),
// Add any dependencies styles may have, such as wp-components.
array(),
filemtime( dirname( __FILE__ ) . '/build/index.css' )
);
wp_enqueue_script( '{{extension_slug}}' );
wp_enqueue_style( '{{extension_slug}}' );
}
add_action( 'admin_enqueue_scripts', 'add_extension_register_script' );

View File

@ -0,0 +1,27 @@
{
"name": "{{extension_slug}}",
"title": "{{extension_name}}",
"license": "GPL-3.0-or-later",
"version": "0.1.0",
"description": "{{extension_name}}",
"scripts": {
"build": "wp-scripts build",
"check-engines": "wp-scripts check-engines",
"check-licenses": "wp-scripts check-licenses",
"format:js": "wp-scripts format-js",
"lint:css": "wp-scripts lint-style",
"lint:js": "wp-scripts lint-js",
"lint:md:docs": "wp-scripts lint-md-docs",
"lint:md:js": "wp-scripts lint-md-js",
"lint:pkg-json": "wp-scripts lint-pkg-json",
"packages-update": "wp-scripts packages-update",
"start": "wp-scripts start",
"test:e2e": "wp-scripts test-e2e",
"test:unit": "wp-scripts test-unit-js"
},
"devDependencies": {
"@wordpress/scripts": "^12.2.1",
"@woocommerce/eslint-plugin": "1.1.0",
"@woocommerce/dependency-extraction-webpack-plugin": "1.6.0"
}
}

View File

@ -0,0 +1,13 @@
const defaultConfig = require( '@wordpress/scripts/config/webpack.config' );
const WooCommerceDependencyExtractionWebpackPlugin = require( '@woocommerce/dependency-extraction-webpack-plugin' );
module.exports = {
...defaultConfig,
plugins: [
...defaultConfig.plugins.filter(
( plugin ) =>
plugin.constructor.name !== 'DependencyExtractionWebpackPlugin'
),
new WooCommerceDependencyExtractionWebpackPlugin(),
],
};

View File

@ -0,0 +1,4 @@
// Import SCSS entry file so that webpack picks up changes
import './index.scss';
console.log( 'hello world' );

View File

@ -0,0 +1 @@
// Add styles here.

View File

@ -0,0 +1,89 @@
const fs = require( 'fs-extra' );
const path = require( 'path' );
const promptly = require( 'promptly' );
const chalk = require( 'chalk' );
const files = [
'._gitignore',
'_README.md',
'_webpack.config.js',
'_main.php',
'_package.json',
'._eslintrc.js',
'._prettierrc.json',
];
const maybeThrowError = ( error ) => {
if ( error ) throw error;
};
( async () => {
console.log( '\n' );
console.log(
chalk.yellow(
'🎉 Welcome to WooCommerce Admin Extension Starter Pack 🎉'
)
);
console.log( '\n' );
const extensionName = await promptly.prompt(
chalk.yellow( 'What is the name of your extension?' )
);
const extensionSlug = extensionName.replace( / /g, '-' ).toLowerCase();
const folder = path.join( __dirname, extensionSlug );
fs.mkdir( folder, maybeThrowError );
files.forEach( ( file ) => {
const from = path.join( __dirname, file );
const to = path.join(
folder,
'_main.php' === file
? `${ extensionSlug }.php`
: file.replace( '_', '' )
);
fs.readFile( from, 'utf8', ( error, data ) => {
maybeThrowError( error );
const addSlugs = data.replace(
/{{extension_slug}}/g,
extensionSlug
);
const result = addSlugs.replace(
/{{extension_name}}/g,
extensionName
);
fs.writeFile( to, result, 'utf8', maybeThrowError );
} );
} );
fs.copy(
path.join( __dirname, 'src' ),
path.join( folder, 'src' ),
maybeThrowError
);
fs.copy( folder, path.join( '../', extensionSlug ), ( error ) => {
maybeThrowError( error );
fs.remove( folder, maybeThrowError );
} );
process.stdout.write( '\n' );
console.log(
chalk.green(
'Wonderful, your extension has been scaffolded and placed as a sibling directory to this one.'
)
);
process.stdout.write( '\n' );
console.log(
chalk.green(
'Run the following commands from the root of the extension and activate the plugin.'
)
);
process.stdout.write( '\n' );
console.log( 'pnpm install' );
console.log( 'pnpm start' );
process.stdout.write( '\n' );
} )();

View File

@ -0,0 +1,34 @@
<?php // phpcs:ignore WordPress.Files.FileName.NotHyphenatedLowercase
use Symfony\Component\Console\Application as SymfonyApplication;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* User interface for the changelogger tool.
*/
class Application extends SymfonyApplication {
const VERSION = '1.1.2';
/**
* Constructor.
*/
public function __construct() {
parent::__construct( 'Test Instruction Logger', self::VERSION );
}
/**
* Called when the application is run.
*
* @param InputInterface $input InputInterface.
* @param OutputInterface $output OutputInterface.
*
* @return int
* @throws Throwable
*/
public function doRun( InputInterface $input, OutputInterface $output ) {
return parent::doRun( $input, $output );
}
}

View File

@ -0,0 +1,60 @@
<?php
class Config {
const CONFIG_FILENAME = '.wca-test-instruction-logger.json';
const CHANGELOG_FILENAME = 'changelog.txt';
const REPOSITORY_PATH = 'woocommerce/woocommerce-admin';
const OUTPUT_FILENAME = 'TESTING-INSTRUCTIONS.md';
protected $wcRoot = __DIR__ . '/../../';
public function getOutputFilePath() {
return $this->wcRoot . '/' .self::OUTPUT_FILENAME;
}
public function getGithubToken() {
$config = $this->getConfig();
if (isset($config->token)) {
return $config->token;
}
return null;
}
public function getGithubCredentials() {
$config = $this->getConfig();
return array(
'username' => $config->username,
'token' => $config->token
);
}
public function getConfig() {
$filepath = getenv('HOME') . '/' . self::CONFIG_FILENAME;
if ( ! file_exists( $filepath ) ) {
return new stdClass();
}
return json_decode( file_get_contents( $filepath ) );
}
public function saveGithubToken($username, $token) {
$config = $this->getConfig();
$config->token = $token;
$config->username = $username;
return file_put_contents(
getenv('HOME').'/'.self::CONFIG_FILENAME,
json_encode($config, JSON_PRETTY_PRINT)
);
}
public function getChangelogFilepath() {
return $this->wcRoot . '/' .self::CHANGELOG_FILENAME;
}
public function getRepositoryPath() {
return self::REPOSITORY_PATH;
}
}

View File

@ -0,0 +1,30 @@
# Test Instruction Logger
Test Instruction Logger retrieves test instructions from the PRs in the `changelog.txt` and writes them into TESTING-INSTRUCTION.md.
## Prerequisites
Test Instruction Logger requires Github username and a personal access token to use the Github REST API.
1. Follow this [guide](https://docs.github.com/en/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token) to create a new personal access token.
2. Run `pnpm run test-instruction-logger github-credentials`. Enter your Github username and the perosnal access token. The data will be saved in `$HOME/.wca-test-instruction-logger.json`
## Writing to TESTING-INSTRUCTION.md
1. Update the `changelog.txt`
2. Run `pnpm run test-instruction-logger -- write :version`.
3. Verify `TESTING-INSTRUCTION.md`.
### Options
#### types
A comma seperated list of changelog types to retrieve the testing instructions from.
`pnpm run test-instruction-logger -- write :version --types=enhancement,add`
#### save-to
Allows you to save the testing instructions to a different file. Default: TESTING-INSTRUCTIONS.md
`pnpm run test-instruction-logger -- write :version --save-to=instructions.md`

View File

@ -0,0 +1,63 @@
<?php // phpcs:ignore WordPress.Files.FileName.NotHyphenatedLowercase
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;
/**
* "Add" command for the changelogger tool CLI.
*/
class SetGithubCredentialsCommand extends Command {
/**
* The default command name
*
* @var string|null
*/
protected static $defaultName = 'github-credentials';
private $config;
public function __construct(Config $config) {
$this->config = $config;
parent::__construct();
}
/**
* Configures the command.
*/
protected function configure() {
$this->setDescription( 'Save Github username and personal token.' );
}
/**
* Executes the command.
*
* @param InputInterface $input InputInterface.
* @param OutputInterface $output OutputInterface.
*
* @return bool
* @throws Exception
*/
protected function execute( InputInterface $input, OutputInterface $output ) {
$question = new Question( "Github username: ");
$username = $this->getHelper( 'question' )->ask( $input, $output, $question );
if ( null === $username ) { // non-interactive.
$output->writeln( 'Got EOF when attempting to query user, aborting.', OutputInterface::VERBOSITY_VERBOSE ); // @codeCoverageIgnore
return 1;
}
$question = new Question( "Github personal token: ");
$token = $this->getHelper( 'question' )->ask( $input, $output, $question );
if ( null === $token ) { // non-interactive.
$output->writeln( 'Got EOF when attempting to query user, aborting.', OutputInterface::VERBOSITY_VERBOSE ); // @codeCoverageIgnore
return 1;
}
$this->config->saveGithubToken( $username, $token );
$output->writeln( "Successfully updated!" );
return 0;
}
}

View File

@ -0,0 +1,374 @@
<?php // phpcs:ignore WordPress.Files.FileName.NotHyphenatedLowercase
use Automattic\Jetpack\Changelog\ChangeEntry;
use Automattic\Jetpack\Changelog\Changelog;
use Automattic\Jetpack\Changelog\ChangelogEntry;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* "Add" command for the changelogger tool CLI.
* @property Config config
*/
class WriteCommand extends Command {
/**
* The default command name
*
* @var string|null
*/
protected static $defaultName = 'write';
/**
* Bullet for changes.
*
* @var string
*/
protected $bullet = '-';
/**
* Github username and personal token.
* @var array
*/
protected $githubCredentials;
/**
* Jetpack Changelog formatter for WCA changelog.
*
* @var WCAdminFormatter
*/
protected $changeloggerFormatter;
/**
* WriteCommand constructor.
*
* @param Config $config
*/
public function __construct(Config $config) {
$this->config = $config;
$this->changeloggerFormatter = new WCAdminFormatter();
parent::__construct();
}
/**
* Configures the command.
*/
protected function configure() {
$this->setDescription( 'Generate test instructions from a given version.' )
->addArgument( 'version', InputArgument::REQUIRED, 'Release version from changelog.txt.' )
->addOption( 'save-to', null, InputOption::VALUE_REQUIRED, 'Specificity a file path to save the output.' )
->addOption( 'types', null, InputOption::VALUE_REQUIRED, 'List of changelog types to use for testing instructions.' );
}
/**
* Executes the command.
*
* @param InputInterface $input InputInterface.
* @param OutputInterface $output OutputInterface.
*
* @return bool
* @throws Exception
*/
protected function execute( InputInterface $input, OutputInterface $output ) {
$version = $input->getArgument( 'version' );
$saveTo = $input->getOption( 'save-to' );
if ( null === $saveTo ) {
$saveTo = $this->config->getOutputFilePath();
}
$types = $input->getOption( 'types');
$changelog_types = array();
if ( null !== $types ) {
$changelog_types = explode( ',', strtolower( $types ) );
}
$changelog = $this->changeloggerFormatter->parse( file_get_contents( $this->config->getChangelogFilepath() ) );
$this->githubCredentials = $this->config->getGithubCredentials();
if ( null === $this->githubCredentials['token'] ) {
$output->writeln("
<error>Github token is missing. Please refer to the README.md to set Github credentials.</>
");
return 1;
}
if ( null === $this->githubCredentials['username'] ) {
$output->writeln("
<error>Github username is missing. Please refer to the README.md to set Github credentials.</>
");
return 1;
}
$entry = $this->getChangelogEntryByVersion( $changelog, $version );
if ( null === $entry ) {
$output->writeln( "<error>{$version} does not exist.</>" );
return 1;
}
$prs = $this->extractPrNumbers( $entry->getChanges(), $changelog_types );
$prContents = $this->getPrContents( $prs );
if ( count($prContents) === 0 ) {
$output->writeln("<error>PRs in version {$version} doesn't have any test instructions</>");
return 0;
}
$testingInstructionsPath = $this->config->getOutputFilePath();
$testingInstructions = $this->buildTestInstructions( $testingInstructionsPath, $prContents, $version );
file_put_contents( $saveTo, $testingInstructions );
$output->writeln( "Successfully saved to {$saveTo}" );
return 0;
}
/**
* Build new TESTING-INSTRUCTIONS.md content by inserting $prContents.
*
* @param string $testingInstructionsPath Path of TESTING-INSTRUCTIONS.md
* @param array $prContents Array of PR content
* @param string $version Target version
*
* @return string
*/
protected function buildTestInstructions( $testingInstructionsPath, $prContents, $version ) {
// Get the content of the existing TESTING-INSTRUCTIONS.md.
$testingInstructions = file( $testingInstructionsPath );
$output = array();
// Get the first line (heading) from the TESTING-INSTRUCTIONS.md.
$output[] = array_shift( $testingInstructions );
// Put the version.
$output[] = '## '.$version;
foreach ( $prContents as $pr => $prContent ) {
if ( empty( $prContent['testInstructions'] ) ) {
continue;
}
$output[] = "\n### ".$prContent[ 'title' ].' #'.$pr."\n";
$output[] = rtrim( $prContent[ 'testInstructions' ] );
}
// Put the remaining contents from TESTING-INSTRUCTIONS.md.
$output[] = implode( '', $testingInstructions );
return implode( "\n", $output);
}
/**
* Get ChangelogEntry by the given version.
*
* @param Changelog $changelog
* @param $version
*
* @return ChangelogEntry|null
*/
protected function getChangelogEntryByVersion( Changelog $changelog, $version ) {
foreach ( $changelog->getEntries() as $entry ) {
if ( $entry->getVersion() === $version ) {
return $entry;
}
}
return null;
}
/**
* Extract testing instructions from the PR description.
*
* @param string $body PR description body.
*
* @return string Testing instructions.
*/
protected function getTestInstructions( $body ) {
// add additinal ### to make the regex working.
$body .= "\n###";
$pattern = '/### (Detailed test instructions:)\R+(.+?)(?=\R+###)/s';
preg_match( $pattern, $body, $matches );
if ( 3 === count( $matches ) ) {
$matches[2] = strtr($matches[2], array(
'No changelog required.' => '',
'No changelog required' => '',
'- [x] Include test instructions in the release' => '',
'- [ ] Include test instructions in the release' => ''
));
//Remove <!-- --> or <!--- ---> comments.
return preg_replace('/(?=<!--)([\s\S]*?)-->/', '', $matches[2]);
}
return '';
}
/**
* Extract PR #s from the change entries.
*
* @param ChangeEntry[] $changeEntires
*
* @return array
* @throws Exception
*/
protected function extractPrNumbers( $changeEntires = [], $changelogSubheadings = [] ) {
$prs = [];
foreach ( $changeEntires as $changeEntry ) {
$subheading = strtolower( $changeEntry->getSubheading() );
if ( 0 !== count( $changelogSubheadings ) && false === in_array( $subheading, $changelogSubheadings ) ) {
continue;
}
preg_match( "/#[0-9]+/", $changeEntry->getContent(), $matches );
if ( count( $matches ) ) {
array_push( $prs, str_replace( "#", "", $matches[0] ) );
} else {
throw new Exception( "Unable to find a PR # in '{$changeEntry->getContent()}'" );
}
}
return $prs;
}
/**
* Get the PR contents.
*
* @param array $prs PR numbers.
*
* @return array
* @throws Exception
*/
protected function getPrContents( array $prs ) {
$authorization = 'Basic '. base64_encode(
$this->githubCredentials['username'].':'.$this->githubCredentials['token']
);
$prs = array_map( function( $pr ) {
return "https://api.github.com/repos/" . $this->config->getRepositoryPath() . "/pulls/{$pr}";
}, $prs );
$header = array(
"Authorization: {$authorization}"
);
$responses = array();
foreach ( array_chunk( $prs, 5 ) as $chunk ) {
$chunk_responses = $this->multiRequest($chunk, array(
CURLOPT_HTTPHEADER => $header,
CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 6.2; WOW64; rv:17.0) Gecko/20100101 Firefox/17.0'
));
foreach ( $chunk_responses as $chunk_response ) {
array_push( $responses, json_decode( $chunk_response, false ) );
}
}
$contents = array();
foreach ( $responses as $response ) {
if ( false === isset( $response->url ) ) {
throw new Exception("Unable to retrieve content for the PR from {$response->url}");
}
if ( false === $this->shouldIncludeTestInstructions( $response->labels ) ) {
continue;
}
$testInstruction = $this->getTestInstructions( $response->body );
if ( '' == $testInstruction ) {
continue;
}
$contents[ $response->number ] = array(
'title' => $response->title,
'number' => $response->number,
'testInstructions' => $testInstruction
);
}
return $contents;
}
/**
* Determine if the given PR's test instructions should be included.
*
* @param Array $labels Labels from a PR.
*
* @return bool
*/
protected function shouldIncludeTestInstructions( $labels ) {
foreach ( $labels as $label ) {
if ( 'no release testing instructions' === $label->name ) {
return false;
}
}
return true;
}
/**
* Make multiple curl requests.
*
* @param array $data
* @param array $options
*
* @return array
*/
protected function multiRequest( array $data, $options = array() ) {
// array of curl handles
$curly = array();
// data to be returned
$result = array();
// multi handle
$mh = curl_multi_init();
// loop through $data and create curl handles
// then add them to the multi-handle
foreach ( $data as $id => $d ) {
$curly[$id] = curl_init();
$url = ( is_array( $d ) && ! empty( $d['url'] ) ) ? $d['url'] : $d;
curl_setopt( $curly[$id], CURLOPT_URL, $url );
curl_setopt( $curly[$id], CURLOPT_HEADER, 0 );
curl_setopt( $curly[$id], CURLOPT_RETURNTRANSFER, 1 );
// post?
if ( is_array( $d ) ) {
if ( ! empty( $d['post'] ) ) {
curl_setopt( $curly[$id], CURLOPT_POST, 1 );
curl_setopt( $curly[$id], CURLOPT_POSTFIELDS, $d['post'] );
}
}
// extra options?
if ( ! empty( $options ) ) {
curl_setopt_array( $curly[$id], $options );
}
curl_multi_add_handle( $mh, $curly[$id] );
}
// execute the handles
$running = null;
do {
curl_multi_exec( $mh, $running );
} while( $running > 0 );
// get content and remove handles
foreach( $curly as $id => $c ) {
$result[$id] = curl_multi_getcontent( $c );
curl_multi_remove_handle( $mh, $c );
}
// all done
curl_multi_close( $mh );
return $result;
}
}

View File

@ -0,0 +1,23 @@
#!/usr/bin/env php
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
// Make sure this script is being run over the PHP CLI.
if ( 'cli' !== php_sapi_name() ) {
return;
}
require_once __DIR__ . '/../../../vendor/autoload.php';
require_once __DIR__ . '/../Config.php';
require_once __DIR__ . '/../Application.php';
require_once __DIR__ . '/../WriteCommand.php';
require_once __DIR__ . '/../SetGithubCredentialsCommand.php';
require_once __DIR__ . '/../../changelogger/WCAdminFormatter.php';
$config = new Config;
$app = new Application();
$app->add(new WriteCommand($config));
$app->add(new SetGithubCredentialsCommand($config));
$app->run();

View File

@ -0,0 +1,21 @@
#!/usr/bin/env bash
# usage: travis.sh before|after
if [ "$1" == 'before' ]; then
cd "$WP_CORE_DIR/wp-content/plugins/woocommerce-admin/"
pnpm run build:feature-config
# This is a temporary solution for a breaking change in composer 2.0.7
# to the Jetpack autoloader. This can be removed when
# https://github.com/Automattic/jetpack/pull/17813 is merged.
composer self-update 2.0.6
if [[ "$COMPOSER_DEV" == "1" ]]; then
composer install
else
composer install --no-dev
fi
# phpunit ^7 is not supported in php 7.0
if [[ "$PHPUNIT" == "6" ]]; then
curl -fsSL -o vendor/bin/phpunit https://phar.phpunit.de/phpunit-6.5.9.phar --create-dirs && chmod +x vendor/bin/phpunit
fi
fi

View File

@ -0,0 +1,40 @@
<?php
/**
* Updates PHP versions to match those in package.json before start or build.
*
* @package WooCommerce\Admin
*/
$package_json = file_get_contents( 'package.json' );
$package = json_decode( $package_json );
function replace_version( $filename, $package_json ) {
$lines = array();
$file = file( $filename );
foreach ( $file as $line ) {
if ( stripos( $line, ' * Version: ' ) !== false ) {
$line = " * Version: {$package_json->version}\n";
}
if ( stripos( $line, ">define( 'WC_ADMIN_VERSION_NUMBER'," ) !== false ) {
$line = "\t\t\$this->define( 'WC_ADMIN_VERSION_NUMBER', '{$package_json->version}' );\n";
}
if ( stripos( $line, "const VERSION =" ) !== false ) {
$line = "\tconst VERSION = '{$package_json->version}';\n";
}
if ( stripos( $line, 'Stable tag: ' ) !== false ) {
$line = "Stable tag: {$package_json->version}\n";
}
if ( stripos( $line, '"version":' ) !== false ) {
$line = "\t\"version\": \"{$package_json->version}\",\n";
}
$lines[] = $line;
}
file_put_contents( $filename, $lines );
}
replace_version( 'woocommerce-admin.php', $package );
replace_version( 'src-internal/Admin/FeaturePlugin.php', $package );
replace_version( 'src/Composer/Package.php', $package );
replace_version( 'readme.txt', $package );
replace_version( 'composer.json', $package );

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,4 @@
Significance: minor
Type: Add
Add capability for ExPlat integration to authenticate WPCOM users. #8428

View File

@ -0,0 +1,4 @@
Significance: minor
Type: Dev
Internalize our API controllers by using the @internal jsdoc. #8429

View File

@ -0,0 +1,4 @@
Significance: minor
Type: Enhancement
Add internal methods to Notes DataStore to retrieve notes without paging or application of woocommerce_note_where_clauses filter. #8387

View File

@ -0,0 +1,4 @@
Significance: minor
Type: Enhancement
Add chart color filter for overriding default chart colors. #8258

View File

@ -0,0 +1,4 @@
Significance: patch
Type: Dev
Add README to hook reference generation script #8004

View File

@ -0,0 +1,4 @@
Significance: patch
Type: Add
#7911 - Added @woocommerce/block-data to Webpack Dependency Extraction plugin

View File

@ -0,0 +1,4 @@
Significance: patch
Type: Dev
Enabled optional typescript checking on ./client subfolder #8372

View File

@ -0,0 +1,4 @@
Significance: minor
Type: Update
Remove is_primary column from the wp_wc_admin_notes table. #8474

Some files were not shown because too many files have changed in this diff Show More