@sabbir1991/wpscaffold 1.0.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.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +326 -0
  3. package/bin/index.js +19 -0
  4. package/lib/generator.js +435 -0
  5. package/package.json +30 -0
  6. package/template/_editorconfig +19 -0
  7. package/template/_github/ISSUE_TEMPLATE/bug_report.yml +29 -0
  8. package/template/_github/ISSUE_TEMPLATE/config.yml +1 -0
  9. package/template/_github/ISSUE_TEMPLATE/feature_request.yml +18 -0
  10. package/template/_github/PULL_REQUEST_TEMPLATE.md +16 -0
  11. package/template/_github/workflows/lint.yml +31 -0
  12. package/template/_github/workflows/phpcs.yml +29 -0
  13. package/template/_github/workflows/phpunit.yml +34 -0
  14. package/template/_gitignore +34 -0
  15. package/template/_nvmrc +1 -0
  16. package/template/_stylelintignore +11 -0
  17. package/template/composer.json +35 -0
  18. package/template/eslint.config.js +8 -0
  19. package/template/includes/Admin/Menu.php +67 -0
  20. package/template/includes/Assets.php +140 -0
  21. package/template/includes/Blocks.php +53 -0
  22. package/template/includes/Plugin.php +59 -0
  23. package/template/includes/Traits/Singleton.php +46 -0
  24. package/template/languages/.gitkeep +0 -0
  25. package/template/lefthook.yml +13 -0
  26. package/template/phpcs.xml.dist +42 -0
  27. package/template/phpunit.xml.dist +19 -0
  28. package/template/plugin-skeleton.php +64 -0
  29. package/template/readme.txt +25 -0
  30. package/template/src/block/block.json +17 -0
  31. package/template/src/block/edit.js +17 -0
  32. package/template/src/block/index.js +13 -0
  33. package/template/src/block/render.php +13 -0
  34. package/template/src/block/style.scss +8 -0
  35. package/template/src/block/view.js +5 -0
  36. package/template/src/global/css/admin.scss +8 -0
  37. package/template/src/global/js/admin.js +5 -0
  38. package/template/tests/Unit/AbstractTestCase.php +25 -0
  39. package/template/tests/Unit/AssetsTest.php +58 -0
  40. package/template/tests/Unit/MenuTest.php +38 -0
  41. package/template/tests/bootstrap.php +56 -0
@@ -0,0 +1,435 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Core generator for create-scaffold.
5
+ *
6
+ * Copies the bundled template/, applies type markers and text replacements,
7
+ * then writes a ready-to-develop WordPress plugin directory.
8
+ */
9
+
10
+ const readline = require( 'readline' );
11
+ const fs = require( 'fs' );
12
+ const path = require( 'path' );
13
+ const { execSync } = require( 'child_process' );
14
+
15
+ const TEMPLATE_DIR = path.join( __dirname, '..', 'template' );
16
+
17
+ // ── Helpers ──────────────────────────────────────────────────────────────────
18
+
19
+ function ask( rl, question, defaultVal = '' ) {
20
+ return new Promise( ( resolve ) => {
21
+ const prompt = defaultVal ? ` ${ question } [${ defaultVal }]: ` : ` ${ question }: `;
22
+ rl.question( prompt, ( answer ) => resolve( answer.trim() || defaultVal ) );
23
+ } );
24
+ }
25
+
26
+ function getGitConfig( key ) {
27
+ try {
28
+ return execSync( `git config --global ${ key }`, { encoding: 'utf8' } ).trim();
29
+ } catch {
30
+ return '';
31
+ }
32
+ }
33
+
34
+ function ensureHttps( uri ) {
35
+ if ( ! uri ) return uri;
36
+ return /^https?:\/\//i.test( uri ) ? uri : 'https://' + uri;
37
+ }
38
+
39
+ function sanitizeSlug( val ) {
40
+ return toSlug( val );
41
+ }
42
+
43
+ function sanitizeNamespace( val ) {
44
+ return val.replace( /[^A-Za-z0-9_]/g, '' ).replace( /^\d+/, '' ) || 'Plugin';
45
+ }
46
+
47
+ function sanitizeConstant( val ) {
48
+ const upper = val.toUpperCase().replace( /[^A-Z0-9_]/g, '_' );
49
+ return upper.replace( /^[^A-Z]+/, '' ) || 'PLUGIN';
50
+ }
51
+
52
+ function warnIfChanged( label, original, sanitized ) {
53
+ if ( original !== sanitized ) {
54
+ console.log( ` ⚠ ${ label } sanitized: "${ original }" → "${ sanitized }"` );
55
+ }
56
+ }
57
+
58
+ function toSlug( str ) {
59
+ return str
60
+ .toLowerCase()
61
+ .replace( /[^a-z0-9]+/g, '-' )
62
+ .replace( /^-+|-+$/g, '' );
63
+ }
64
+
65
+ function toPascalCase( str ) {
66
+ return str
67
+ .split( /[-_\s]+/ )
68
+ .map( ( w ) => w.charAt( 0 ).toUpperCase() + w.slice( 1 ) )
69
+ .join( '' );
70
+ }
71
+
72
+ function toSnake( str ) {
73
+ return str.replace( /-/g, '_' );
74
+ }
75
+
76
+ function toConstant( str ) {
77
+ return toSnake( str ).toUpperCase();
78
+ }
79
+
80
+ // ── Marker processing ─────────────────────────────────────────────────────────
81
+
82
+ /**
83
+ * Remove or strip a marker section.
84
+ *
85
+ * @param {string} content Raw file text.
86
+ * @param {string} marker Marker name without @/comment syntax.
87
+ * @param {boolean} removeContent true = delete enclosed content; false = keep content, remove markers only.
88
+ */
89
+ function removeSection( content, marker, removeContent ) {
90
+ const esc = marker.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' );
91
+
92
+ if ( removeContent ) {
93
+ const re = new RegExp(
94
+ `[ \\t]*/\\* @${ esc } \\*/[\\s\\S]*?/\\* @${ esc }-end \\*/[ \\t]*\\n?`,
95
+ 'g'
96
+ );
97
+ return content.replace( re, '' );
98
+ }
99
+
100
+ const reO = new RegExp( `[ \\t]*/\\* @${ esc } \\*/[ \\t]*\\n?`, 'g' );
101
+ const reC = new RegExp( `[ \\t]*/\\* @${ esc }-end \\*/[ \\t]*\\n?`, 'g' );
102
+ return content.replace( reO, '' ).replace( reC, '' );
103
+ }
104
+
105
+ function applyTypeMarkers( content, pluginType ) {
106
+ if ( pluginType === 'admin' ) {
107
+ content = removeSection( content, 'skeleton-block', true );
108
+ content = removeSection( content, 'skeleton-block-only', true );
109
+ content = removeSection( content, 'skeleton-admin', false );
110
+ } else if ( pluginType === 'block' ) {
111
+ content = removeSection( content, 'skeleton-admin', true );
112
+ content = removeSection( content, 'skeleton-block-only', false );
113
+ content = removeSection( content, 'skeleton-block', false );
114
+ } else {
115
+ content = removeSection( content, 'skeleton-block-only', true );
116
+ content = removeSection( content, 'skeleton-admin', false );
117
+ content = removeSection( content, 'skeleton-block', false );
118
+ }
119
+ return content;
120
+ }
121
+
122
+ // ── Replacements ──────────────────────────────────────────────────────────────
123
+
124
+ function buildReplacements( { pluginName, slug, description, author, authorUri, pluginUri, nsVendor, nsPackage, constPrefix } ) {
125
+ const fnPrefix = toSnake( slug );
126
+ const composerVendor = nsVendor.toLowerCase();
127
+ const composerName = `${ composerVendor }/${ slug }`;
128
+ const fullNs = `${ nsVendor }\\${ nsPackage }`;
129
+
130
+ return [
131
+ [ 'PluginVendor\\\\PluginSkeleton', `${ nsVendor }\\\\${ nsPackage }` ],
132
+ [ 'PluginVendor\\PluginSkeleton', fullNs ],
133
+ [ 'sabbir1991/plugin-skeleton', composerName ],
134
+ [ 'https://github.com/sabbir1991/plugin-skeleton', pluginUri ],
135
+ [ 'https://yoursite.com', authorUri ],
136
+ [ 'Plugin Skeleton', pluginName ],
137
+ [ 'plugin-skeleton', slug ],
138
+ [ 'PLUGIN_SKELETON', constPrefix ],
139
+ [ 'plugin_skeleton', fnPrefix ],
140
+ [ 'PluginSkeleton', nsPackage ],
141
+ [ 'Your Name', author ],
142
+ [ 'yourname', toSlug( author ) ],
143
+ [ 'A WordPress plugin skeleton for rapid development.', description ],
144
+ ];
145
+ }
146
+
147
+ function applyReplacements( content, replacements ) {
148
+ let result = content;
149
+ for ( const [ from, to ] of replacements ) {
150
+ result = result.split( from ).join( to );
151
+ }
152
+ return result;
153
+ }
154
+
155
+ // ── Template copying ──────────────────────────────────────────────────────────
156
+
157
+ const TEXT_EXTS = new Set( [
158
+ '.php', '.js', '.ts', '.json', '.xml', '.yml', '.yaml',
159
+ '.css', '.scss', '.txt', '.md', '.pot', '.sh', '.dist',
160
+ ] );
161
+
162
+ const TEXT_DOTFILES = new Set( [
163
+ '_editorconfig', '_gitignore', '_nvmrc', '_stylelintignore',
164
+ '.editorconfig', '.gitignore', '.nvmrc', '.stylelintignore',
165
+ '.gitkeep',
166
+ ] );
167
+
168
+ function isTextFile( filePath ) {
169
+ const ext = path.extname( filePath ).toLowerCase();
170
+ const base = path.basename( filePath );
171
+ return TEXT_EXTS.has( ext ) || TEXT_DOTFILES.has( base );
172
+ }
173
+
174
+ /**
175
+ * Map template filename → output filename.
176
+ * Files/dirs starting with _ become dotfiles/dirs starting with .
177
+ */
178
+ function mapName( name ) {
179
+ return name.startsWith( '_' ) ? '.' + name.slice( 1 ) : name;
180
+ }
181
+
182
+ /**
183
+ * Recursively copy template → target, applying transformations.
184
+ */
185
+ function copyTemplate( srcDir, destDir, replacements, pluginType, slug ) {
186
+ fs.mkdirSync( destDir, { recursive: true } );
187
+
188
+ const entries = fs.readdirSync( srcDir, { withFileTypes: true } );
189
+
190
+ const SKIP = new Set( [ '.DS_Store', 'Thumbs.db', '.DS_Store?' ] );
191
+
192
+ for ( const entry of entries ) {
193
+ if ( SKIP.has( entry.name ) ) continue;
194
+
195
+ const srcPath = path.join( srcDir, entry.name );
196
+ const destName = mapName( entry.name );
197
+ const destPath = path.join( destDir, destName );
198
+
199
+ if ( entry.isDirectory() ) {
200
+ copyTemplate( srcPath, destPath, replacements, pluginType, slug );
201
+ continue;
202
+ }
203
+
204
+ if ( ! entry.isFile() ) continue;
205
+
206
+ if ( isTextFile( srcPath ) ) {
207
+ let content = fs.readFileSync( srcPath, 'utf8' );
208
+ content = applyTypeMarkers( content, pluginType );
209
+ content = applyReplacements( content, replacements );
210
+ fs.writeFileSync( destPath, content, 'utf8' );
211
+ } else {
212
+ fs.copyFileSync( srcPath, destPath );
213
+ }
214
+ }
215
+ }
216
+
217
+ // ── package.json builder ──────────────────────────────────────────────────────
218
+
219
+ function buildPackageJson( { slug, pluginName, description, author, pluginType } ) {
220
+ const makepot = `wp i18n make-pot . languages/${ slug }.pot --exclude=vendor,node_modules,build,assets/build --ignore-domain`;
221
+ const zip = `npm run build && composer install --no-dev --prefer-dist --no-progress --no-interaction --optimize-autoloader && rm -rf ${ slug }.zip && wp-scripts plugin-zip`;
222
+
223
+ const base = {
224
+ name: slug,
225
+ version: '1.0.0',
226
+ description,
227
+ author,
228
+ license: 'GPL-2.0-or-later',
229
+ };
230
+
231
+ let scripts = {
232
+ format: 'wp-scripts format',
233
+ 'lint:css': 'wp-scripts lint-style src/',
234
+ 'lint:js': 'wp-scripts lint-js src/',
235
+ 'fix:js': 'wp-scripts lint-js --fix src/',
236
+ 'fix:css': 'wp-scripts lint-style --fix src/',
237
+ 'lint:php': 'composer run cs',
238
+ 'fix:php': 'composer run cs:fix',
239
+ 'packages-update':'wp-scripts packages-update',
240
+ makepot,
241
+ 'test:php': 'composer run test',
242
+ prepare: 'lefthook install || true',
243
+ };
244
+
245
+ let files = [ 'includes', 'languages', 'vendor', `${ slug }.php`, 'readme.txt', 'package.json', 'composer.json' ];
246
+
247
+ let devDependencies = {
248
+ '@wordpress/scripts': '^32.1.0',
249
+ 'lefthook': '^2.1.6',
250
+ };
251
+
252
+ if ( pluginType === 'admin' ) {
253
+ scripts = {
254
+ build: `wp-scripts build src/global/js/admin.js --output-path=assets/build && npm run makepot`,
255
+ start: `wp-scripts start src/global/js/admin.js --output-path=assets/build`,
256
+ 'build:custom': `wp-scripts build src/global/js/admin.js --output-path=assets/build`,
257
+ zip,
258
+ ...scripts,
259
+ };
260
+ files = [ 'assets/build', ...files ];
261
+ } else if ( pluginType === 'block' ) {
262
+ scripts = {
263
+ build: `wp-scripts build && npm run makepot`,
264
+ start: `wp-scripts start`,
265
+ zip,
266
+ ...scripts,
267
+ };
268
+ files = [ 'build', ...files ];
269
+ devDependencies = {
270
+ '@wordpress/block-editor': '^12.5.0',
271
+ '@wordpress/blocks': '^15.18.0',
272
+ '@wordpress/element': '^5.14.0',
273
+ '@wordpress/i18n': '^6.18.0',
274
+ ...devDependencies,
275
+ };
276
+ } else {
277
+ scripts = {
278
+ build: `wp-scripts build && wp-scripts build js/admin.js --output-path=assets/build --webpack-src-dir=src/global && npm run makepot`,
279
+ start: `wp-scripts start`,
280
+ 'start:custom': `wp-scripts start js/admin.js --output-path=assets/build --webpack-src-dir=src/global`,
281
+ 'build:custom': `wp-scripts build js/admin.js --output-path=assets/build --webpack-src-dir=src/global`,
282
+ zip,
283
+ ...scripts,
284
+ };
285
+ files = [ 'assets/build', 'build', ...files ];
286
+ devDependencies = {
287
+ '@wordpress/block-editor': '^12.5.0',
288
+ '@wordpress/blocks': '^15.18.0',
289
+ '@wordpress/element': '^5.14.0',
290
+ '@wordpress/i18n': '^6.18.0',
291
+ ...devDependencies,
292
+ };
293
+ }
294
+
295
+ return { ...base, scripts, files, devDependencies };
296
+ }
297
+
298
+ // ── Type-based cleanup ────────────────────────────────────────────────────────
299
+
300
+ function cleanupByType( targetDir, pluginType ) {
301
+ function rm( rel ) {
302
+ const full = path.join( targetDir, rel );
303
+ if ( fs.existsSync( full ) ) {
304
+ fs.rmSync( full, { recursive: true, force: true } );
305
+ }
306
+ }
307
+
308
+ if ( pluginType === 'admin' ) {
309
+ rm( 'src/block' );
310
+ rm( 'includes/Blocks.php' );
311
+ } else if ( pluginType === 'block' ) {
312
+ rm( 'src/global' );
313
+ rm( 'includes/Admin/Menu.php' );
314
+ rm( 'tests/Unit/MenuTest.php' );
315
+
316
+ const adminDir = path.join( targetDir, 'includes', 'Admin' );
317
+ if ( fs.existsSync( adminDir ) && fs.readdirSync( adminDir ).length === 0 ) {
318
+ fs.rmdirSync( adminDir );
319
+ }
320
+ }
321
+ }
322
+
323
+ // ── Main ──────────────────────────────────────────────────────────────────────
324
+
325
+ async function generate( initialName = '' ) {
326
+ const rl = readline.createInterface( { input: process.stdin, output: process.stdout } );
327
+
328
+ const gitAuthor = getGitConfig( 'user.name' );
329
+ const gitEmail = getGitConfig( 'user.email' );
330
+
331
+ console.log( '\n ╔══════════════════════════════╗' );
332
+ console.log( ' ║ Create WordPress Scaffold ║' );
333
+ console.log( ' ╚══════════════════════════════╝\n' );
334
+ console.log( ' Press Enter to accept default values in brackets.\n' );
335
+
336
+ try {
337
+ const pluginName = await ask( rl, 'Plugin Name', initialName || 'My Plugin' );
338
+ const defaultSlug = toSlug( pluginName );
339
+ const rawSlug = await ask( rl, 'Slug / text-domain', defaultSlug );
340
+ const slug = sanitizeSlug( rawSlug ) || defaultSlug;
341
+ warnIfChanged( 'Slug', rawSlug, slug );
342
+
343
+ const description = await ask( rl, 'Description', `A WordPress plugin.` );
344
+ const author = await ask( rl, 'Author Name', gitAuthor || 'yourname' );
345
+ const authorEmail = await ask( rl, 'Author Email', gitEmail || 'your-email' );
346
+ const authorUri = ensureHttps( await ask( rl, 'Author URI', 'https://yoursite.com' ) );
347
+ const pluginUri = ensureHttps( await ask( rl, 'Plugin URI', `${ authorUri }/${ slug }` ) );
348
+
349
+ const rawNsVendor = await ask( rl, 'Namespace Vendor', 'CompanyName' );
350
+ const nsVendor = sanitizeNamespace( rawNsVendor );
351
+ warnIfChanged( 'Namespace Vendor', rawNsVendor, nsVendor );
352
+
353
+ const rawNsPackage = await ask( rl, 'Namespace Package', toPascalCase( slug ) );
354
+ const nsPackage = sanitizeNamespace( rawNsPackage );
355
+ warnIfChanged( 'Namespace Package', rawNsPackage, nsPackage );
356
+
357
+ const rawConst = await ask( rl, 'Constant Prefix', toConstant( slug ) );
358
+ const constPrefix = sanitizeConstant( rawConst );
359
+ warnIfChanged( 'Constant Prefix', rawConst, constPrefix );
360
+
361
+ const typeRaw = await ask( rl, 'Plugin Type [admin / block / both]', 'both' );
362
+ const pluginType = [ 'admin', 'block', 'both' ].includes( typeRaw.toLowerCase() )
363
+ ? typeRaw.toLowerCase()
364
+ : 'both';
365
+
366
+ const fnPrefix = toSnake( slug );
367
+ const composerVendor = nsVendor.toLowerCase();
368
+ const composerName = `${ composerVendor }/${ slug }`;
369
+ const fullNs = `${ nsVendor }\\${ nsPackage }`;
370
+
371
+ console.log( '\n ─── Summary ───────────────────────────────────' );
372
+ console.log( ` Plugin Name : ${ pluginName }` );
373
+ console.log( ` Slug : ${ slug }` );
374
+ console.log( ` Namespace : ${ fullNs }` );
375
+ console.log( ` Composer : ${ composerName }` );
376
+ console.log( ` Constant : ${ constPrefix }` );
377
+ console.log( ` Function : ${ fnPrefix }` );
378
+ console.log( ` Author : ${ author }${ authorEmail ? ` <${ authorEmail }>` : '' }` );
379
+ console.log( ` Plugin URI : ${ pluginUri }` );
380
+ console.log( ` Type : ${ pluginType }` );
381
+ console.log( ' ────────────────────────────────────────────────\n' );
382
+
383
+ const targetDir = path.join( process.cwd(), slug );
384
+
385
+ if ( fs.existsSync( targetDir ) ) {
386
+ console.error( ` Error: Directory '${ slug }' already exists in the current folder.` );
387
+ rl.close();
388
+ return;
389
+ }
390
+
391
+ const confirm = await ask( rl, `Create plugin in ./${ slug }? (yes/no)`, 'yes' );
392
+ rl.close();
393
+
394
+ if ( ! [ 'yes', 'y' ].includes( confirm.toLowerCase() ) ) {
395
+ console.log( '\n Aborted.\n' );
396
+ return;
397
+ }
398
+
399
+ // Build context for replacements
400
+ const context = { pluginName, slug, description, author, authorUri, pluginUri, nsVendor, nsPackage, constPrefix };
401
+ const replacements = buildReplacements( context );
402
+
403
+ console.log( `\n Creating ${ slug }/ ...\n` );
404
+
405
+ // Copy template
406
+ copyTemplate( TEMPLATE_DIR, targetDir, replacements, pluginType, slug );
407
+
408
+ // Rename main PHP file: plugin-skeleton.php → {slug}.php
409
+ const oldPhp = path.join( targetDir, 'plugin-skeleton.php' );
410
+ const newPhp = path.join( targetDir, `${ slug }.php` );
411
+ if ( fs.existsSync( oldPhp ) ) {
412
+ fs.renameSync( oldPhp, newPhp );
413
+ }
414
+
415
+ // Type-based cleanup
416
+ cleanupByType( targetDir, pluginType );
417
+
418
+ // Write package.json
419
+ const pkg = buildPackageJson( { slug, pluginName, description, author: authorEmail ? `${ author } <${ authorEmail }>` : author, pluginType } );
420
+ fs.writeFileSync( path.join( targetDir, 'package.json' ), JSON.stringify( pkg, null, '\t' ) + '\n', 'utf8' );
421
+
422
+ // Done
423
+ console.log( ` ✅ Created: ${ path.resolve( targetDir ) }\n` );
424
+ console.log( ' Next steps:\n' );
425
+ console.log( ` cd ${ slug }` );
426
+ console.log( ' composer install' );
427
+ console.log( ' npm install' );
428
+ console.log( ' npm run build\n' );
429
+ } catch ( err ) {
430
+ rl.close();
431
+ throw err;
432
+ }
433
+ }
434
+
435
+ module.exports = { generate };
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@sabbir1991/wpscaffold",
3
+ "version": "1.0.0",
4
+ "description": "Create a production-ready WordPress plugin scaffold instantly",
5
+ "bin": {
6
+ "wpscaffold": "bin/index.js"
7
+ },
8
+ "files": [
9
+ "bin",
10
+ "lib",
11
+ "template"
12
+ ],
13
+ "engines": {
14
+ "node": ">=20.0.0"
15
+ },
16
+ "keywords": [
17
+ "wordpress",
18
+ "plugin",
19
+ "scaffold",
20
+ "create",
21
+ "gutenberg",
22
+ "block"
23
+ ],
24
+ "author": "sabbir1991",
25
+ "license": "MIT",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "https://github.com/sabbir1991/wpscaffold.git"
29
+ }
30
+ }
@@ -0,0 +1,19 @@
1
+ root = true
2
+
3
+ [*]
4
+ indent_style = tab
5
+ indent_size = 4
6
+ end_of_line = lf
7
+ charset = utf-8
8
+ trim_trailing_whitespace = true
9
+ insert_final_newline = true
10
+
11
+ [*.md]
12
+ trim_trailing_whitespace = false
13
+
14
+ [{.jshintrc,*.json,*.yml}]
15
+ indent_style = tab
16
+ indent_size = 4
17
+
18
+ [{*.txt,wp-config-sample.php}]
19
+ end_of_line = crlf
@@ -0,0 +1,29 @@
1
+ name: Bug report
2
+ description: Report a problem with Plugin Skeleton
3
+ labels: [bug]
4
+ body:
5
+ - type: textarea
6
+ id: description
7
+ attributes:
8
+ label: Description
9
+ description: What happened, and what did you expect to happen?
10
+ validations:
11
+ required: true
12
+ - type: textarea
13
+ id: steps
14
+ attributes:
15
+ label: Steps to reproduce
16
+ placeholder: |
17
+ 1. Go to ...
18
+ 2. Click ...
19
+ 3. See error
20
+ validations:
21
+ required: true
22
+ - type: input
23
+ id: versions
24
+ attributes:
25
+ label: Environment
26
+ description: Plugin / WordPress / PHP versions
27
+ placeholder: "Plugin 1.0.0, WP 7.0, PHP 8.2"
28
+ validations:
29
+ required: false
@@ -0,0 +1 @@
1
+ blank_issues_enabled: false
@@ -0,0 +1,18 @@
1
+ name: Feature request
2
+ description: Suggest an idea for Plugin Skeleton
3
+ labels: [enhancement]
4
+ body:
5
+ - type: textarea
6
+ id: problem
7
+ attributes:
8
+ label: Problem
9
+ description: What problem would this feature solve?
10
+ validations:
11
+ required: true
12
+ - type: textarea
13
+ id: solution
14
+ attributes:
15
+ label: Proposed solution
16
+ description: What would you like to happen?
17
+ validations:
18
+ required: true
@@ -0,0 +1,16 @@
1
+ ## Summary
2
+
3
+ <!-- Describe what this PR does and why -->
4
+
5
+ ## Type of change
6
+
7
+ - [ ] Bug fix
8
+ - [ ] New feature
9
+ - [ ] Refactor
10
+ - [ ] Documentation
11
+
12
+ ## Checklist
13
+
14
+ - [ ] Code follows the project coding standards (`npm run lint:php`, `npm run lint:js`)
15
+ - [ ] Tests pass (`composer run test`)
16
+ - [ ] Relevant documentation updated
@@ -0,0 +1,31 @@
1
+ name: JS & CSS Lint
2
+
3
+ on:
4
+ push:
5
+ branches: [main, develop]
6
+ pull_request:
7
+ branches: [main, develop]
8
+
9
+ jobs:
10
+ lint:
11
+ name: Lint JS & CSS
12
+ runs-on: ubuntu-latest
13
+
14
+ steps:
15
+ - name: Checkout
16
+ uses: actions/checkout@v4
17
+
18
+ - name: Setup Node
19
+ uses: actions/setup-node@v4
20
+ with:
21
+ node-version: '20'
22
+ cache: 'npm'
23
+
24
+ - name: Install dependencies
25
+ run: npm install --no-audit --no-fund
26
+
27
+ - name: Lint JavaScript
28
+ run: npm run lint:js
29
+
30
+ - name: Lint CSS/SCSS
31
+ run: npm run lint:css
@@ -0,0 +1,29 @@
1
+ name: PHP CodeSniffer
2
+
3
+ on:
4
+ push:
5
+ branches: [main, develop]
6
+ pull_request:
7
+ branches: [main, develop]
8
+
9
+ jobs:
10
+ phpcs:
11
+ name: PHP CodeSniffer
12
+ runs-on: ubuntu-latest
13
+
14
+ steps:
15
+ - name: Checkout
16
+ uses: actions/checkout@v4
17
+
18
+ - name: Setup PHP
19
+ uses: shivammathur/setup-php@v2
20
+ with:
21
+ php-version: '8.2'
22
+ tools: composer
23
+ coverage: none
24
+
25
+ - name: Install dependencies
26
+ run: composer install --prefer-dist --no-progress --no-interaction
27
+
28
+ - name: Run PHPCS
29
+ run: composer run cs
@@ -0,0 +1,34 @@
1
+ name: PHPUnit Tests
2
+
3
+ on:
4
+ push:
5
+ branches: [main, develop]
6
+ pull_request:
7
+ branches: [main, develop]
8
+
9
+ jobs:
10
+ phpunit:
11
+ name: PHPUnit (PHP ${{ matrix.php }})
12
+ runs-on: ubuntu-latest
13
+
14
+ strategy:
15
+ fail-fast: false
16
+ matrix:
17
+ php: ['8.1', '8.2', '8.3', '8.4']
18
+
19
+ steps:
20
+ - name: Checkout
21
+ uses: actions/checkout@v4
22
+
23
+ - name: Setup PHP ${{ matrix.php }}
24
+ uses: shivammathur/setup-php@v2
25
+ with:
26
+ php-version: ${{ matrix.php }}
27
+ tools: composer
28
+ coverage: none
29
+
30
+ - name: Install dependencies
31
+ run: composer update --prefer-dist --no-progress --no-interaction
32
+
33
+ - name: Run PHPUnit
34
+ run: composer run test
@@ -0,0 +1,34 @@
1
+ node_modules
2
+ vendor
3
+ .idea
4
+
5
+ # Compiled output
6
+ /build
7
+ /assets/build
8
+
9
+ # Packages
10
+ *.7z
11
+ *.dmg
12
+ *.gz
13
+ *.iso
14
+ *.jar
15
+ *.rar
16
+ *.tar
17
+ *.zip
18
+
19
+ # OS generated files
20
+ .DS_Store
21
+ .DS_Store?
22
+ ._*
23
+ .Spotlight-V100
24
+ .Trashes
25
+ ehthumbs.db
26
+ Thumbs.db
27
+
28
+ # Logs
29
+ *.log
30
+ *.sql
31
+ *.sqlite
32
+
33
+ # Test cache
34
+ .phpunit.result.cache
@@ -0,0 +1 @@
1
+ 20