@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.
- package/LICENSE +21 -0
- package/README.md +326 -0
- package/bin/index.js +19 -0
- package/lib/generator.js +435 -0
- package/package.json +30 -0
- package/template/_editorconfig +19 -0
- package/template/_github/ISSUE_TEMPLATE/bug_report.yml +29 -0
- package/template/_github/ISSUE_TEMPLATE/config.yml +1 -0
- package/template/_github/ISSUE_TEMPLATE/feature_request.yml +18 -0
- package/template/_github/PULL_REQUEST_TEMPLATE.md +16 -0
- package/template/_github/workflows/lint.yml +31 -0
- package/template/_github/workflows/phpcs.yml +29 -0
- package/template/_github/workflows/phpunit.yml +34 -0
- package/template/_gitignore +34 -0
- package/template/_nvmrc +1 -0
- package/template/_stylelintignore +11 -0
- package/template/composer.json +35 -0
- package/template/eslint.config.js +8 -0
- package/template/includes/Admin/Menu.php +67 -0
- package/template/includes/Assets.php +140 -0
- package/template/includes/Blocks.php +53 -0
- package/template/includes/Plugin.php +59 -0
- package/template/includes/Traits/Singleton.php +46 -0
- package/template/languages/.gitkeep +0 -0
- package/template/lefthook.yml +13 -0
- package/template/phpcs.xml.dist +42 -0
- package/template/phpunit.xml.dist +19 -0
- package/template/plugin-skeleton.php +64 -0
- package/template/readme.txt +25 -0
- package/template/src/block/block.json +17 -0
- package/template/src/block/edit.js +17 -0
- package/template/src/block/index.js +13 -0
- package/template/src/block/render.php +13 -0
- package/template/src/block/style.scss +8 -0
- package/template/src/block/view.js +5 -0
- package/template/src/global/css/admin.scss +8 -0
- package/template/src/global/js/admin.js +5 -0
- package/template/tests/Unit/AbstractTestCase.php +25 -0
- package/template/tests/Unit/AssetsTest.php +58 -0
- package/template/tests/Unit/MenuTest.php +38 -0
- package/template/tests/bootstrap.php +56 -0
package/lib/generator.js
ADDED
|
@@ -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
|
package/template/_nvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
20
|