@wordpress/create-block 3.6.0 → 4.0.1-next.d6164808d3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 4.0.0-next.0 (2022-08-23)
6
+
7
+ ### Breaking Change
8
+
9
+ - Increase the minimum Node.js version to 14 and minimum npm version to 6.14.4 ([#43141](https://github.com/WordPress/gutenberg/pull/43141)).
10
+
11
+ ### New Feature
12
+
13
+ - Add `--no-plugin` flag to allow scaffolding of a block in an existing plugin ([#41642](https://github.com/WordPress/gutenberg/pull/41642))
14
+ - Introduce the `--variant` flag to allow selection of a variant as defined in the template ([#41289](https://github.com/WordPress/gutenberg/pull/41289)).
15
+
5
16
  ## 3.6.0 (2022-07-13)
6
17
 
7
18
  ### Enhancement
package/README.md CHANGED
@@ -20,7 +20,7 @@ $ cd todo-list
20
20
  $ npm start
21
21
  ```
22
22
 
23
- _(requires `node` version `12.0.0` or above, and `npm` version `6.9.0` or above)_
23
+ _(requires `node` version `14.0.0` or above, and `npm` version `6.14.4` or above)_
24
24
 
25
25
  It creates a WordPress plugin that you need to [install manually](https://wordpress.org/support/article/managing-plugins/#manual-plugin-installation).
26
26
 
@@ -41,6 +41,7 @@ Options:
41
41
  ```bash
42
42
  -V, --version output the version number
43
43
  -t, --template <name> project template type name; allowed values: "static" (default), "es5", the name of an external npm package, or the path to a local directory
44
+ --no-plugin scaffold block files only
44
45
  --namespace <value> internal namespace for the block name
45
46
  --title <value> display title for the block and the WordPress plugin
46
47
  --short-description <value> short description for the block and the WordPress plugin
@@ -49,6 +50,7 @@ Options:
49
50
  --no-wp-scripts disable integration with `@wordpress/scripts` package
50
51
  --wp-env enable integration with `@wordpress/env` package
51
52
  -h, --help output usage information
53
+ --variant choose a block variant as defined by the template
52
54
  ```
53
55
 
54
56
  More examples:
@@ -71,12 +73,25 @@ $ npx @wordpress/create-block --template my-template-package
71
73
  $ npx @wordpress/create-block --template ./path/to/template-directory
72
74
  ```
73
75
 
74
- 4. Help you need to use `npx` to output usage information.
76
+ 4. Generating a dynamic block based on the built-in template.
77
+
78
+ ```bash
79
+ $ npx @wordpress/create-block --variant dynamic
80
+ ```
81
+
82
+ 5. Help – you need to use `npx` to output usage information.
75
83
 
76
84
  ```bash
77
85
  $ npx @wordpress/create-block --help
78
86
  ```
79
87
 
88
+ 5. No plugin mode – it is also possible to scaffold only block files into the current directory.
89
+
90
+
91
+ ```bash
92
+ $ npx @wordpress/create-block --no-plugin
93
+ ```
94
+
80
95
  When you scaffold a block, you must provide at least a `slug` name, the `namespace` which usually corresponds to either the `theme` or `plugin` name. In most cases, we recommended pairing blocks with WordPress plugins rather than themes, because only using plugin ensures that all blocks still work when your theme changes.
81
96
 
82
97
  ## Available Commands
@@ -4,7 +4,6 @@
4
4
  const inquirer = require( 'inquirer' );
5
5
  const checkSync = require( 'check-node-version' );
6
6
  const tools = require( 'check-node-version/tools' );
7
- const { forEach } = require( 'lodash' );
8
7
  const { promisify } = require( 'util' );
9
8
 
10
9
  /**
@@ -21,13 +20,15 @@ async function checkSystemRequirements( engines ) {
21
20
  log.error( 'Minimum system requirements not met!' );
22
21
  log.info( '' );
23
22
 
24
- forEach( result.versions, ( { isSatisfied, wanted }, name ) => {
25
- if ( ! isSatisfied ) {
26
- log.error(
27
- `Error: Wanted ${ name } version ${ wanted.raw } (${ wanted.range })`
28
- );
23
+ Object.entries( result.versions ).forEach(
24
+ ( [ name, { isSatisfied, wanted } ] ) => {
25
+ if ( ! isSatisfied ) {
26
+ log.error(
27
+ `Error: Wanted ${ name } version ${ wanted.raw } (${ wanted.range })`
28
+ );
29
+ }
29
30
  }
30
- } );
31
+ );
31
32
 
32
33
  log.info( '' );
33
34
  log.error( 'The program may not complete correctly if you continue.' );
@@ -46,13 +47,16 @@ async function checkSystemRequirements( engines ) {
46
47
  log.error( 'Cancelled.' );
47
48
  log.info( '' );
48
49
 
49
- forEach( result.versions, ( { isSatisfied, wanted }, name ) => {
50
- if ( ! isSatisfied ) {
51
- log.info(
52
- tools[ name ].getInstallInstructions( wanted.raw )
53
- );
50
+ Object.entries( result.versions ).forEach(
51
+ ( [ name, { isSatisfied, wanted } ] ) => {
52
+ if ( ! isSatisfied ) {
53
+ log.info(
54
+ tools[ name ].getInstallInstructions( wanted.raw )
55
+ );
56
+ }
54
57
  }
55
- } );
58
+ );
59
+
56
60
  process.exit( 1 );
57
61
  }
58
62
  }
package/lib/index.js CHANGED
@@ -2,8 +2,8 @@
2
2
  * External dependencies
3
3
  */
4
4
  const inquirer = require( 'inquirer' );
5
+ const { capitalCase } = require( 'change-case' );
5
6
  const program = require( 'commander' );
6
- const { pickBy, startCase } = require( 'lodash' );
7
7
 
8
8
  /**
9
9
  * Internal dependencies
@@ -34,8 +34,8 @@ program
34
34
  .arguments( '[slug]' )
35
35
  .option(
36
36
  '-t, --template <name>',
37
- 'project template type name; allowed values: "static", "es5", the name of an external npm package, or the path to a local directory',
38
- 'static'
37
+ 'project template type name; allowed values: "standard", "es5", the name of an external npm package, or the path to a local directory',
38
+ 'standard'
39
39
  )
40
40
  .option( '--namespace <value>', 'internal namespace for the block name' )
41
41
  .option(
@@ -57,10 +57,13 @@ program
57
57
  'disable integration with `@wordpress/scripts` package'
58
58
  )
59
59
  .option( '--wp-env', 'enable integration with `@wordpress/env` package' )
60
+ .option( '--no-plugin', 'scaffold only block files' )
61
+ .option( '--variant <variant>', 'the variant of the template to use' )
60
62
  .action(
61
63
  async (
62
64
  slug,
63
65
  {
66
+ plugin,
64
67
  category,
65
68
  namespace,
66
69
  shortDescription: description,
@@ -68,22 +71,27 @@ program
68
71
  title,
69
72
  wpScripts,
70
73
  wpEnv,
74
+ variant,
71
75
  }
72
76
  ) => {
73
77
  await checkSystemRequirements( engines );
74
78
  try {
75
79
  const pluginTemplate = await getPluginTemplate( templateName );
76
- const defaultValues = getDefaultValues( pluginTemplate );
77
- const optionsValues = pickBy(
78
- {
80
+ const defaultValues = getDefaultValues(
81
+ pluginTemplate,
82
+ variant
83
+ );
84
+ const optionsValues = Object.fromEntries(
85
+ Object.entries( {
86
+ plugin,
79
87
  category,
80
88
  description,
81
89
  namespace,
82
90
  title,
83
91
  wpScripts,
84
92
  wpEnv,
85
- },
86
- ( value ) => value !== undefined
93
+ variant,
94
+ } ).filter( ( [ , value ] ) => value !== undefined )
87
95
  );
88
96
 
89
97
  if ( slug ) {
@@ -91,59 +99,84 @@ program
91
99
  ...defaultValues,
92
100
  slug,
93
101
  // Transforms slug to title as a fallback.
94
- title: startCase( slug ),
102
+ title: capitalCase( slug ),
95
103
  ...optionsValues,
96
104
  };
97
105
  await scaffold( pluginTemplate, answers );
98
106
  } else {
99
107
  log.info( '' );
100
108
  log.info(
101
- "Let's customize your WordPress plugin with blocks:"
109
+ plugin
110
+ ? "Let's customize your WordPress plugin with blocks:"
111
+ : "Let's add a new block to your existing WordPress plugin:"
102
112
  );
103
113
 
104
114
  const filterOptionsProvided = ( { name } ) =>
105
115
  ! Object.keys( optionsValues ).includes( name );
106
- const blockPrompts = getPrompts( pluginTemplate, [
107
- 'slug',
108
- 'namespace',
109
- 'title',
110
- 'description',
111
- 'dashicon',
112
- 'category',
113
- ] ).filter( filterOptionsProvided );
116
+
117
+ // Get the variant prompt first. This will help get the default values
118
+ const variantPrompt =
119
+ Object.keys( pluginTemplate.variants )?.length > 1
120
+ ? getPrompts( pluginTemplate, [ 'variant' ] )
121
+ : false;
122
+
123
+ const variantSelection = variantPrompt
124
+ ? await inquirer.prompt( variantPrompt )
125
+ : false;
126
+
127
+ const blockPrompts = getPrompts(
128
+ pluginTemplate,
129
+ [
130
+ 'slug',
131
+ 'namespace',
132
+ 'title',
133
+ 'description',
134
+ 'dashicon',
135
+ 'category',
136
+ ],
137
+ variantSelection.variant
138
+ ).filter( filterOptionsProvided );
139
+
114
140
  const blockAnswers = await inquirer.prompt( blockPrompts );
115
141
 
116
- const pluginAnswers = await inquirer
117
- .prompt( {
118
- type: 'confirm',
119
- name: 'configurePlugin',
120
- message:
121
- 'Do you want to customize the WordPress plugin?',
122
- default: false,
123
- } )
124
- .then( async ( { configurePlugin } ) => {
125
- if ( ! configurePlugin ) {
126
- return {};
127
- }
142
+ const pluginAnswers = plugin
143
+ ? await inquirer
144
+ .prompt( {
145
+ type: 'confirm',
146
+ name: 'configurePlugin',
147
+ message:
148
+ 'Do you want to customize the WordPress plugin?',
149
+ default: false,
150
+ } )
151
+ .then( async ( { configurePlugin } ) => {
152
+ if ( ! configurePlugin ) {
153
+ return {};
154
+ }
155
+
156
+ const pluginPrompts = getPrompts(
157
+ pluginTemplate,
158
+ [
159
+ 'pluginURI',
160
+ 'version',
161
+ 'author',
162
+ 'license',
163
+ 'licenseURI',
164
+ 'domainPath',
165
+ 'updateURI',
166
+ ]
167
+ ).filter( filterOptionsProvided );
168
+ const result = await inquirer.prompt(
169
+ pluginPrompts
170
+ );
171
+ return result;
172
+ } )
173
+ : {};
128
174
 
129
- const pluginPrompts = getPrompts( pluginTemplate, [
130
- 'pluginURI',
131
- 'version',
132
- 'author',
133
- 'license',
134
- 'licenseURI',
135
- 'domainPath',
136
- 'updateURI',
137
- ] ).filter( filterOptionsProvided );
138
- const result = await inquirer.prompt(
139
- pluginPrompts
140
- );
141
- return result;
142
- } );
143
175
  await scaffold( pluginTemplate, {
144
176
  ...defaultValues,
145
177
  ...optionsValues,
146
178
  ...blockAnswers,
179
+ ...variantSelection,
147
180
  ...pluginAnswers,
148
181
  } );
149
182
  }
package/lib/init-block.js CHANGED
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- const { omitBy } = require( 'lodash' );
5
4
  const { dirname, join } = require( 'path' );
6
5
  const makeDir = require( 'make-dir' );
7
6
  const { writeFile } = require( 'fs' ).promises;
@@ -15,6 +14,7 @@ const { writeOutputTemplate } = require( './output' );
15
14
  async function initBlockJSON( {
16
15
  $schema,
17
16
  apiVersion,
17
+ plugin,
18
18
  slug,
19
19
  namespace,
20
20
  title,
@@ -33,13 +33,15 @@ async function initBlockJSON( {
33
33
  info( '' );
34
34
  info( 'Creating a "block.json" file.' );
35
35
 
36
- const outputFile = join( process.cwd(), slug, folderName, 'block.json' );
36
+ const outputFile = plugin
37
+ ? join( process.cwd(), slug, folderName, 'block.json' )
38
+ : join( process.cwd(), slug, 'block.json' );
37
39
  await makeDir( dirname( outputFile ) );
38
40
  await writeFile(
39
41
  outputFile,
40
42
  JSON.stringify(
41
- omitBy(
42
- {
43
+ Object.fromEntries(
44
+ Object.entries( {
43
45
  $schema,
44
46
  apiVersion,
45
47
  name: namespace + '/' + slug,
@@ -54,8 +56,7 @@ async function initBlockJSON( {
54
56
  editorScript,
55
57
  editorStyle,
56
58
  style,
57
- },
58
- ( value ) => ! value
59
+ } ).filter( ( [ , value ] ) => !! value )
59
60
  ),
60
61
  null,
61
62
  '\t'
@@ -65,15 +66,17 @@ async function initBlockJSON( {
65
66
 
66
67
  module.exports = async function ( outputTemplates, view ) {
67
68
  await Promise.all(
68
- Object.keys( outputTemplates ).map(
69
- async ( outputFile ) =>
70
- await writeOutputTemplate(
71
- outputTemplates[ outputFile ],
72
- join( view.folderName, outputFile ),
73
- view
74
- )
75
- )
76
- );
69
+ Object.keys( outputTemplates ).map( async ( outputFile ) => {
70
+ const pathName = view.plugin
71
+ ? join( view.folderName, outputFile )
72
+ : join( process.cwd(), view.slug, outputFile );
77
73
 
74
+ await writeOutputTemplate(
75
+ outputTemplates[ outputFile ],
76
+ pathName,
77
+ view
78
+ );
79
+ } )
80
+ );
78
81
  await initBlockJSON( view );
79
82
  };
@@ -2,7 +2,6 @@
2
2
  * External dependencies
3
3
  */
4
4
  const { command } = require( 'execa' );
5
- const { isEmpty, omitBy } = require( 'lodash' );
6
5
  const npmPackageArg = require( 'npm-package-arg' );
7
6
  const { join } = require( 'path' );
8
7
  const writePkg = require( 'write-pkg' );
@@ -24,6 +23,7 @@ module.exports = async ( {
24
23
  npmDependencies,
25
24
  npmDevDependencies,
26
25
  customScripts,
26
+ isDynamicVariant,
27
27
  } ) => {
28
28
  const cwd = join( process.cwd(), slug );
29
29
 
@@ -32,8 +32,8 @@ module.exports = async ( {
32
32
 
33
33
  await writePkg(
34
34
  cwd,
35
- omitBy(
36
- {
35
+ Object.fromEntries(
36
+ Object.entries( {
37
37
  name: slug,
38
38
  version,
39
39
  description,
@@ -43,19 +43,22 @@ module.exports = async ( {
43
43
  main: wpScripts && 'build/index.js',
44
44
  scripts: {
45
45
  ...( wpScripts && {
46
- build: 'wp-scripts build',
46
+ build: isDynamicVariant
47
+ ? 'wp-scripts build --webpack-copy-php'
48
+ : 'wp-scripts build',
47
49
  format: 'wp-scripts format',
48
50
  'lint:css': 'wp-scripts lint-style',
49
51
  'lint:js': 'wp-scripts lint-js',
50
52
  'packages-update': 'wp-scripts packages-update',
51
53
  'plugin-zip': 'wp-scripts plugin-zip',
52
- start: 'wp-scripts start',
54
+ start: isDynamicVariant
55
+ ? 'wp-scripts start --webpack-copy-php'
56
+ : 'wp-scripts start',
53
57
  } ),
54
58
  ...( wpEnv && { env: 'wp-env' } ),
55
59
  ...customScripts,
56
60
  },
57
- },
58
- isEmpty
61
+ } ).filter( ( [ , value ] ) => !! value )
59
62
  )
60
63
  );
61
64
 
package/lib/output.js CHANGED
@@ -13,13 +13,15 @@ const writeOutputAsset = async ( inputFile, outputFile, view ) => {
13
13
  };
14
14
 
15
15
  const writeOutputTemplate = async ( inputFile, outputFile, view ) => {
16
- // Output files can have names that depend on the slug provided.
17
- const outputFilePath = join(
18
- view.slug,
19
- outputFile.replace( /\$slug/g, view.slug )
20
- );
16
+ const outputFilePath = view.plugin
17
+ ? join( view.slug, outputFile.replace( /\$slug/g, view.slug ) )
18
+ : outputFile;
21
19
  await makeDir( dirname( outputFilePath ) );
22
- writeFile( outputFilePath, render( inputFile, view ) );
20
+ // If the rendered template is empty, don't write it. This is how we can conditionally add template files.
21
+ const renderedFile = render( inputFile, view );
22
+ if ( renderedFile.trim().length ) {
23
+ writeFile( outputFilePath, renderedFile );
24
+ }
23
25
  };
24
26
 
25
27
  module.exports = {
package/lib/prompts.js CHANGED
@@ -1,14 +1,19 @@
1
1
  /**
2
- * External dependencies
2
+ * Capitalizes the first letter in a string.
3
+ *
4
+ * @param {string} str The string whose first letter the function will capitalize.
5
+ *
6
+ * @return {string} Capitalized string.
3
7
  */
4
- const { isEmpty, upperFirst } = require( 'lodash' );
8
+ const upperFirst = ( [ firstLetter, ...rest ] ) =>
9
+ firstLetter.toUpperCase() + rest.join( '' );
5
10
 
6
11
  // Block metadata.
7
12
  const slug = {
8
13
  type: 'input',
9
14
  name: 'slug',
10
15
  message:
11
- 'The block slug used for identification (also the plugin and output folder name):',
16
+ 'The block slug used for identification (also the output folder name):',
12
17
  validate( input ) {
13
18
  if ( ! /^[a-z][a-z0-9\-]*$/.test( input ) ) {
14
19
  return 'Invalid block slug specified. Block slug can contain only lowercase alphanumeric characters or dashes, and start with a letter.';
@@ -56,7 +61,7 @@ const dashicon = {
56
61
  message:
57
62
  'The dashicon to make it easier to identify your block (optional):',
58
63
  validate( input ) {
59
- if ( ! isEmpty( input ) && ! /^[a-z][a-z0-9\-]*$/.test( input ) ) {
64
+ if ( input.length && ! /^[a-z][a-z0-9\-]*$/.test( input ) ) {
60
65
  return 'Invalid dashicon name specified. Visit https://developer.wordpress.org/resource/dashicons/ to discover available names.';
61
66
  }
62
67
 
@@ -129,8 +134,16 @@ const updateURI = {
129
134
  message: 'A custom update URI for the plugin (optional):',
130
135
  };
131
136
 
137
+ const variant = {
138
+ type: 'list',
139
+ name: 'variant',
140
+ message: 'The template variant to use for this block:',
141
+ choices: [],
142
+ };
143
+
132
144
  module.exports = {
133
145
  slug,
146
+ variant,
134
147
  namespace,
135
148
  title,
136
149
  description,
package/lib/scaffold.js CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * External dependencies
3
3
  */
4
- const { snakeCase, camelCase, upperFirst } = require( 'lodash' );
4
+ const { pascalCase, snakeCase } = require( 'change-case' );
5
5
 
6
6
  /**
7
7
  * Internal dependencies
@@ -10,14 +10,16 @@ const initBlock = require( './init-block' );
10
10
  const initPackageJSON = require( './init-package-json' );
11
11
  const initWPScripts = require( './init-wp-scripts' );
12
12
  const initWPEnv = require( './init-wp-env' );
13
- const { code, info, success } = require( './log' );
13
+ const { code, info, success, error } = require( './log' );
14
14
  const { writeOutputAsset, writeOutputTemplate } = require( './output' );
15
+ const { getTemplateVariantVars } = require( './templates' );
15
16
 
16
17
  module.exports = async (
17
- { blockOutputTemplates, pluginOutputTemplates, outputAssets },
18
+ { blockOutputTemplates, pluginOutputTemplates, outputAssets, variants },
18
19
  {
19
20
  $schema,
20
21
  apiVersion,
22
+ plugin,
21
23
  namespace,
22
24
  slug,
23
25
  title,
@@ -42,22 +44,39 @@ module.exports = async (
42
44
  editorScript,
43
45
  editorStyle,
44
46
  style,
47
+ variant,
45
48
  }
46
49
  ) => {
47
50
  slug = slug.toLowerCase();
48
51
  namespace = namespace.toLowerCase();
52
+ /**
53
+ * --no-plugin relies on the used template supporting the [blockTemplatesPath property](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-create-block/#blocktemplatespath).
54
+ * If the blockOutputTemplates object has no properties, we can assume that there was a custom --template passed that
55
+ * doesn't support it.
56
+ */
57
+ if ( ! plugin && Object.keys( blockOutputTemplates ) < 1 ) {
58
+ error(
59
+ 'No block files found in the template. Please ensure that the template supports the blockTemplatesPath property.'
60
+ );
61
+ return;
62
+ }
49
63
 
50
64
  info( '' );
51
- info( `Creating a new WordPress plugin in the "${ slug }" directory.` );
65
+ info(
66
+ plugin
67
+ ? `Creating a new WordPress plugin in the ${ slug } directory.`
68
+ : `Creating a new block in the ${ slug } directory.`
69
+ );
52
70
 
53
71
  const view = {
54
72
  $schema,
55
73
  apiVersion,
74
+ plugin,
56
75
  namespace,
57
76
  namespaceSnakeCase: snakeCase( namespace ),
58
77
  slug,
59
78
  slugSnakeCase: snakeCase( slug ),
60
- slugPascalCase: upperFirst( camelCase( slug ) ),
79
+ slugPascalCase: pascalCase( slug ),
61
80
  title,
62
81
  description,
63
82
  dashicon,
@@ -81,18 +100,22 @@ module.exports = async (
81
100
  editorScript,
82
101
  editorStyle,
83
102
  style,
103
+ ...getTemplateVariantVars( variants, variant ),
104
+ ...variants[ variant ],
84
105
  };
85
106
 
86
- await Promise.all(
87
- Object.keys( pluginOutputTemplates ).map(
88
- async ( outputFile ) =>
89
- await writeOutputTemplate(
90
- pluginOutputTemplates[ outputFile ],
91
- outputFile,
92
- view
93
- )
94
- )
95
- );
107
+ if ( plugin ) {
108
+ await Promise.all(
109
+ Object.keys( pluginOutputTemplates ).map(
110
+ async ( outputFile ) =>
111
+ await writeOutputTemplate(
112
+ pluginOutputTemplates[ outputFile ],
113
+ outputFile,
114
+ view
115
+ )
116
+ )
117
+ );
118
+ }
96
119
 
97
120
  await Promise.all(
98
121
  Object.keys( outputAssets ).map(
@@ -107,21 +130,26 @@ module.exports = async (
107
130
 
108
131
  await initBlock( blockOutputTemplates, view );
109
132
 
110
- await initPackageJSON( view );
111
-
112
- if ( wpScripts ) {
113
- await initWPScripts( view );
114
- }
133
+ if ( plugin ) {
134
+ await initPackageJSON( view );
135
+ if ( wpScripts ) {
136
+ await initWPScripts( view );
137
+ }
115
138
 
116
- if ( wpEnv ) {
117
- await initWPEnv( view );
139
+ if ( wpEnv ) {
140
+ await initWPEnv( view );
141
+ }
118
142
  }
119
143
 
120
144
  info( '' );
145
+
121
146
  success(
122
- `Done: WordPress plugin "${ title }" bootstrapped in the "${ slug }" directory.`
147
+ plugin
148
+ ? `Done: WordPress plugin ${ title } bootstrapped in the ${ slug } directory.`
149
+ : `Done: Block "${ title }" bootstrapped in the ${ slug }directory.`
123
150
  );
124
- if ( wpScripts ) {
151
+
152
+ if ( plugin && wpScripts ) {
125
153
  info( '' );
126
154
  info( 'You can run several commands inside:' );
127
155
  info( '' );
@@ -145,18 +173,18 @@ module.exports = async (
145
173
  info( '' );
146
174
  code( ' $ npm run packages-update' );
147
175
  info( ' Updates WordPress packages to the latest version.' );
176
+ info( '' );
177
+ info( 'To enter the directory type:' );
178
+ info( '' );
179
+ code( ` $ cd ${ slug }` );
148
180
  }
149
- info( '' );
150
- info( 'To enter the directory type:' );
151
- info( '' );
152
- code( ` $ cd ${ slug }` );
153
- if ( wpScripts ) {
181
+ if ( plugin && wpScripts ) {
154
182
  info( '' );
155
183
  info( 'You can start development with:' );
156
184
  info( '' );
157
185
  code( ' $ npm start' );
158
186
  }
159
- if ( wpEnv ) {
187
+ if ( plugin && wpEnv ) {
160
188
  info( '' );
161
189
  info( 'You can start WordPress with:' );
162
190
  info( '' );
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Retrieves the translation of text.
3
3
  *
4
- * @see https://developer.wordpress.org/block-editor/packages/packages-i18n/
4
+ * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-i18n/
5
5
  */
6
6
  import { __ } from '@wordpress/i18n';
7
7
 
@@ -9,7 +9,7 @@ import { __ } from '@wordpress/i18n';
9
9
  * React hook that is used to mark the block wrapper element.
10
10
  * It provides all the necessary props like the class name.
11
11
  *
12
- * @see https://developer.wordpress.org/block-editor/packages/packages-block-editor/#useBlockProps
12
+ * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/#useblockprops
13
13
  */
14
14
  import { useBlockProps } from '@wordpress/block-editor';
15
15
 
@@ -25,7 +25,7 @@ import './editor.scss';
25
25
  * The edit function describes the structure of your block in the context of the
26
26
  * editor. This represents what the editor will render when the block is used.
27
27
  *
28
- * @see https://developer.wordpress.org/block-editor/developers/block-api/block-edit-save/#edit
28
+ * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#edit
29
29
  *
30
30
  * @return {WPElement} Element to render.
31
31
  */
@@ -18,7 +18,9 @@ import './style.scss';
18
18
  * Internal dependencies
19
19
  */
20
20
  import Edit from './edit';
21
+ {{#isStaticVariant}}
21
22
  import save from './save';
23
+ {{/isStaticVariant}}
22
24
  import metadata from './block.json';
23
25
 
24
26
  /**
@@ -31,8 +33,10 @@ registerBlockType( metadata.name, {
31
33
  * @see ./edit.js
32
34
  */
33
35
  edit: Edit,
36
+ {{#isStaticVariant}}
34
37
  /**
35
38
  * @see ./save.js
36
39
  */
37
40
  save,
41
+ {{/isStaticVariant}}
38
42
  } );
@@ -1,15 +1,9 @@
1
- /**
2
- * Retrieves the translation of text.
3
- *
4
- * @see https://developer.wordpress.org/block-editor/packages/packages-i18n/
5
- */
6
- import { __ } from '@wordpress/i18n';
7
-
1
+ {{#isStaticVariant}}
8
2
  /**
9
3
  * React hook that is used to mark the block wrapper element.
10
4
  * It provides all the necessary props like the class name.
11
5
  *
12
- * @see https://developer.wordpress.org/block-editor/packages/packages-block-editor/#useBlockProps
6
+ * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/#useblockprops
13
7
  */
14
8
  import { useBlockProps } from '@wordpress/block-editor';
15
9
 
@@ -18,17 +12,15 @@ import { useBlockProps } from '@wordpress/block-editor';
18
12
  * be combined into the final markup, which is then serialized by the block
19
13
  * editor into `post_content`.
20
14
  *
21
- * @see https://developer.wordpress.org/block-editor/developers/block-api/block-edit-save/#save
15
+ * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#save
22
16
  *
23
17
  * @return {WPElement} Element to render.
24
18
  */
25
19
  export default function save() {
26
20
  return (
27
21
  <p { ...useBlockProps.save() }>
28
- { __(
29
- '{{title}} – hello from the saved content!',
30
- '{{textdomain}}'
31
- ) }
22
+ { '{{title}} – hello from the saved content!' }
32
23
  </p>
33
24
  );
34
25
  }
26
+ {{/isStaticVariant}}
@@ -0,0 +1,5 @@
1
+ {{#isDynamicVariant}}
2
+ <p <?php echo get_block_wrapper_attributes(); ?>>
3
+ <?php esc_html_e( '{{title}} – hello from a dynamic block!', '{{textdomain}}' ); ?>
4
+ </p>
5
+ {{/isDynamicVariant}}
@@ -68,7 +68,7 @@ function {{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init() {
68
68
  array(),
69
69
  filemtime( "$dir/$style_css" )
70
70
  );
71
-
71
+ {{^isDynamicVariant}}
72
72
  register_block_type(
73
73
  '{{namespace}}/{{slug}}',
74
74
  array(
@@ -77,5 +77,33 @@ function {{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init() {
77
77
  'style' => '{{namespace}}-{{slug}}-block',
78
78
  )
79
79
  );
80
+ {{/isDynamicVariant}}
81
+ {{#isDynamicVariant}}
82
+ register_block_type(
83
+ '{{namespace}}/{{slug}}',
84
+ array(
85
+ 'editor_script' => '{{namespace}}-{{slug}}-block-editor',
86
+ 'editor_style' => '{{namespace}}-{{slug}}-block-editor',
87
+ 'style' => '{{namespace}}-{{slug}}-block',
88
+ 'render_callback' => '{{namespaceSnakeCase}}_{{slugSnakeCase}}_render_callback',
89
+ )
90
+ );
91
+ {{/isDynamicVariant}}
80
92
  }
81
93
  add_action( 'init', '{{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init' );
94
+ {{#isDynamicVariant}}
95
+ /**
96
+ * Render callback function
97
+ *
98
+ * @param array $attributes The block attributes.
99
+ * @param string $content The block content.
100
+ * @param WP_Block $block Block instance.
101
+ *
102
+ * @return string The rendered output.
103
+ */
104
+ function {{namespaceSnakeCase}}_{{slugSnakeCase}}_render_callback( $atts, $content, $block) {
105
+ ob_start();
106
+ require plugin_dir_path( __FILE__ ) . '/template.php';
107
+ return ob_get_clean();
108
+ }
109
+ {{/isDynamicVariant}}
@@ -16,14 +16,14 @@
16
16
  /**
17
17
  * Retrieves the translation of text.
18
18
  *
19
- * @see https://developer.wordpress.org/block-editor/packages/packages-i18n/
19
+ * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-i18n/
20
20
  */
21
21
  var __ = wp.i18n.__;
22
22
 
23
23
  /**
24
24
  * This hook is used to mark the block wrapper element.
25
25
  *
26
- * @see https://developer.wordpress.org/block-editor/packages/packages-block-editor/#useBlockProps
26
+ * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/#useblockprops
27
27
  */
28
28
  var useBlockProps = wp.blockEditor.useBlockProps;
29
29
 
@@ -84,7 +84,7 @@
84
84
  * The edit function describes the structure of your block in the context of the editor.
85
85
  * This represents what the editor will render when the block is used.
86
86
  *
87
- * @see https://developer.wordpress.org/block-editor/developers/block-api/block-edit-save/#edit
87
+ * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#edit
88
88
  *
89
89
  * @return {WPElement} Element to render.
90
90
  */
@@ -94,13 +94,12 @@
94
94
  useBlockProps(),
95
95
  __( '{{title}} – hello from the editor!', '{{textdomain}}' )
96
96
  );
97
- },
98
-
97
+ }{{#isStaticVariant}},
99
98
  /**
100
99
  * The save function defines the way in which the different attributes should be combined
101
100
  * into the final markup, which is then serialized by the block editor into `post_content`.
102
101
  *
103
- * @see https://developer.wordpress.org/block-editor/developers/block-api/block-edit-save/#save
102
+ * @see https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#save
104
103
  *
105
104
  * @return {WPElement} Element to render.
106
105
  */
@@ -108,9 +107,10 @@
108
107
  return el(
109
108
  'p',
110
109
  useBlockProps.save(),
111
- __( '{{title}} – hello from the saved content!', '{{textdomain}}' )
110
+ '{{title}} – hello from the saved content!',
112
111
  );
113
112
  },
113
+ {{/isStaticVariant}}
114
114
  } );
115
115
  }(
116
116
  window.wp
@@ -0,0 +1,5 @@
1
+ {{#isDynamicVariant}}
2
+ <p <?php echo get_block_wrapper_attributes(); ?>>
3
+ <?php esc_html_e( '{{title}} – hello from a dynamic block!', '{{textdomain}}' ); ?>
4
+ </p>
5
+ {{/isDynamicVariant}}
@@ -37,7 +37,35 @@
37
37
  *
38
38
  * @see https://developer.wordpress.org/reference/functions/register_block_type/
39
39
  */
40
+ {{#isStaticVariant}}
40
41
  function {{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init() {
41
42
  register_block_type( __DIR__ . '/build' );
42
43
  }
43
44
  add_action( 'init', '{{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init' );
45
+ {{/isStaticVariant}}
46
+ {{#isDynamicVariant}}
47
+ function {{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init() {
48
+ register_block_type(
49
+ __DIR__ . '/build',
50
+ array(
51
+ 'render_callback' => '{{namespaceSnakeCase}}_{{slugSnakeCase}}_render_callback',
52
+ )
53
+ );
54
+ }
55
+ add_action( 'init', '{{namespaceSnakeCase}}_{{slugSnakeCase}}_block_init' );
56
+
57
+ /**
58
+ * Render callback function
59
+ *
60
+ * @param array $attributes The block attributes.
61
+ * @param string $content The block content.
62
+ * @param WP_Block $block Block instance.
63
+ *
64
+ * @return string The rendered output.
65
+ */
66
+ function {{namespaceSnakeCase}}_{{slugSnakeCase}}_render_callback( $atts, $content, $block) {
67
+ ob_start();
68
+ require plugin_dir_path( __FILE__ ) . 'build/template.php';
69
+ return ob_get_clean();
70
+ }
71
+ {{/isDynamicVariant}}
package/lib/templates.js CHANGED
@@ -6,7 +6,6 @@ const glob = require( 'fast-glob' );
6
6
  const { resolve } = require( 'path' );
7
7
  const { existsSync } = require( 'fs' );
8
8
  const { mkdtemp, readFile } = require( 'fs' ).promises;
9
- const { fromPairs } = require( 'lodash' );
10
9
  const npmPackageArg = require( 'npm-package-arg' );
11
10
  const { tmpdir } = require( 'os' );
12
11
  const { join } = require( 'path' );
@@ -25,7 +24,7 @@ const predefinedPluginTemplates = {
25
24
  slug: 'example-static-es5',
26
25
  title: 'Example Static (ES5)',
27
26
  description:
28
- 'Example static block scaffolded with Create Block tool – no build step required.',
27
+ 'Example block scaffolded with Create Block tool – no build step required.',
29
28
  dashicon: 'smiley',
30
29
  wpScripts: false,
31
30
  editorScript: 'file:./index.js',
@@ -33,18 +32,31 @@ const predefinedPluginTemplates = {
33
32
  style: 'file:./style.css',
34
33
  },
35
34
  templatesPath: join( __dirname, 'templates', 'es5' ),
35
+ variants: {
36
+ static: {},
37
+ dynamic: {
38
+ slug: 'example-dynamic-es5',
39
+ title: 'Example Dynamic (ES5)',
40
+ },
41
+ },
36
42
  },
37
- static: {
43
+ standard: {
38
44
  defaultValues: {
39
45
  slug: 'example-static',
40
46
  title: 'Example Static',
41
- description:
42
- 'Example static block scaffolded with Create Block tool.',
47
+ description: 'Example block scaffolded with Create Block tool.',
43
48
  dashicon: 'smiley',
44
49
  supports: {
45
50
  html: false,
46
51
  },
47
52
  },
53
+ variants: {
54
+ static: {},
55
+ dynamic: {
56
+ slug: 'example-dynamic',
57
+ title: 'Example Dynamic',
58
+ },
59
+ },
48
60
  },
49
61
  };
50
62
 
@@ -53,7 +65,7 @@ const getOutputTemplates = async ( outputTemplatesPath ) => {
53
65
  cwd: outputTemplatesPath,
54
66
  dot: true,
55
67
  } );
56
- return fromPairs(
68
+ return Object.fromEntries(
57
69
  await Promise.all(
58
70
  outputTemplatesFiles.map( async ( outputTemplateFile ) => {
59
71
  const outputFile = outputTemplateFile.replace(
@@ -75,7 +87,7 @@ const getOutputAssets = async ( outputAssetsPath ) => {
75
87
  cwd: outputAssetsPath,
76
88
  dot: true,
77
89
  } );
78
- return fromPairs(
90
+ return Object.fromEntries(
79
91
  await Promise.all(
80
92
  outputAssetFiles.map( async ( outputAssetFile ) => {
81
93
  const outputAsset = await readFile(
@@ -101,6 +113,7 @@ const configToTemplate = async ( {
101
113
  blockTemplatesPath,
102
114
  defaultValues = {},
103
115
  assetsPath,
116
+ variants,
104
117
  ...deprecated
105
118
  } ) => {
106
119
  if ( defaultValues === null || typeof defaultValues !== 'object' ) {
@@ -130,6 +143,7 @@ const configToTemplate = async ( {
130
143
  defaultValues,
131
144
  outputAssets: assetsPath ? await getOutputAssets( assetsPath ) : {},
132
145
  pluginOutputTemplates: await getOutputTemplates( pluginTemplatesPath ),
146
+ variants,
133
147
  };
134
148
  };
135
149
 
@@ -198,7 +212,7 @@ const getPluginTemplate = async ( templateName ) => {
198
212
  }
199
213
  };
200
214
 
201
- const getDefaultValues = ( pluginTemplate ) => {
215
+ const getDefaultValues = ( pluginTemplate, variant ) => {
202
216
  return {
203
217
  $schema: 'https://schemas.wp.org/trunk/block.json',
204
218
  apiVersion: 2,
@@ -217,12 +231,25 @@ const getDefaultValues = ( pluginTemplate ) => {
217
231
  editorStyle: 'file:./index.css',
218
232
  style: 'file:./style-index.css',
219
233
  ...pluginTemplate.defaultValues,
234
+ ...pluginTemplate.variants[ variant ],
220
235
  };
221
236
  };
222
237
 
223
- const getPrompts = ( pluginTemplate, keys ) => {
238
+ const getPrompts = ( pluginTemplate, keys, variant ) => {
224
239
  const defaultValues = getDefaultValues( pluginTemplate );
240
+ const variantData = pluginTemplate.variants[ variant ] ?? false;
225
241
  return keys.map( ( promptName ) => {
242
+ if ( promptName === 'variant' ) {
243
+ prompts[ promptName ].choices = Object.keys(
244
+ pluginTemplate.variants
245
+ );
246
+ }
247
+ if ( variantData && variantData[ promptName ] ) {
248
+ return {
249
+ ...prompts[ promptName ],
250
+ default: variantData[ promptName ],
251
+ };
252
+ }
226
253
  return {
227
254
  ...prompts[ promptName ],
228
255
  default: defaultValues[ promptName ],
@@ -230,8 +257,34 @@ const getPrompts = ( pluginTemplate, keys ) => {
230
257
  } );
231
258
  };
232
259
 
260
+ const getTemplateVariantVars = ( variants, variant ) => {
261
+ const variantVars = {};
262
+ if ( variants ) {
263
+ const variantNames = Object.keys( variants );
264
+ const chosenVariant = variant ?? variantNames[ 0 ]; // If no variant is passed, use the first in the array as the default
265
+
266
+ if ( variantNames.includes( chosenVariant ) ) {
267
+ for ( const variantName of variantNames ) {
268
+ const key =
269
+ variantName.charAt( 0 ).toUpperCase() +
270
+ variantName.slice( 1 );
271
+ variantVars[ `is${ key }Variant` ] =
272
+ chosenVariant === variantName ?? false;
273
+ }
274
+ } else {
275
+ throw new CLIError(
276
+ `"${ chosenVariant }" is not a valid variant for this template. Available variants are: ${ variantNames.join(
277
+ ', '
278
+ ) }`
279
+ );
280
+ }
281
+ }
282
+ return variantVars;
283
+ };
284
+
233
285
  module.exports = {
234
286
  getPluginTemplate,
235
287
  getDefaultValues,
236
288
  getPrompts,
289
+ getTemplateVariantVars,
237
290
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wordpress/create-block",
3
- "version": "3.6.0",
3
+ "version": "4.0.1-next.d6164808d3.0",
4
4
  "description": "Generates PHP, JS and CSS code for registering a block for a WordPress plugin.",
5
5
  "author": "The WordPress Contributors",
6
6
  "license": "GPL-2.0-or-later",
@@ -20,8 +20,8 @@
20
20
  "url": "https://github.com/WordPress/gutenberg/issues"
21
21
  },
22
22
  "engines": {
23
- "node": ">=12",
24
- "npm": ">=6.9"
23
+ "node": ">=14",
24
+ "npm": ">=6.14.4"
25
25
  },
26
26
  "files": [
27
27
  "lib"
@@ -31,14 +31,14 @@
31
31
  "wp-create-block": "./index.js"
32
32
  },
33
33
  "dependencies": {
34
- "@wordpress/lazy-import": "^1.4.2",
34
+ "@wordpress/lazy-import": "^1.4.3-next.d6164808d3.0",
35
35
  "chalk": "^4.0.0",
36
+ "change-case": "^4.1.2",
36
37
  "check-node-version": "^4.1.0",
37
38
  "commander": "^9.2.0",
38
39
  "execa": "^4.0.2",
39
40
  "fast-glob": "^3.2.7",
40
41
  "inquirer": "^7.1.0",
41
- "lodash": "^4.17.21",
42
42
  "make-dir": "^3.0.0",
43
43
  "mustache": "^4.0.0",
44
44
  "npm-package-arg": "^8.1.5",
@@ -48,5 +48,5 @@
48
48
  "publishConfig": {
49
49
  "access": "public"
50
50
  },
51
- "gitHead": "9d9d33bbdf317a4381b8ca1713e43bb50df653b3"
51
+ "gitHead": "ba8a396d2f418e53a6c4c50575582f3f3eb11ff7"
52
52
  }