new-s-block 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 (2) hide show
  1. package/bin/cli.js +206 -0
  2. package/package.json +20 -0
package/bin/cli.js ADDED
@@ -0,0 +1,206 @@
1
+ #!/usr/bin/env node
2
+
3
+ 'use strict';
4
+
5
+ const readline = require('readline');
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ const cwd = process.cwd();
10
+
11
+ // ── Helpers ────────────────────────────────────────────────────────────────
12
+
13
+ function toKebabCase(str) {
14
+ return str
15
+ .trim()
16
+ .toLowerCase()
17
+ .replace(/[\s_]+/g, '-')
18
+ .replace(/[^a-z0-9-]/g, '');
19
+ }
20
+
21
+ function toTitleCase(str) {
22
+ return str
23
+ .replace(/-/g, ' ')
24
+ .replace(/\b\w/g, function (c) { return c.toUpperCase(); });
25
+ }
26
+
27
+ // Split "hero-banner" into ["hero", "banner"]
28
+ function splitKeywords(blockName) {
29
+ return blockName.split('-').filter(Boolean);
30
+ }
31
+
32
+ function ensureDir(dirPath) {
33
+ if (!fs.existsSync(dirPath)) {
34
+ fs.mkdirSync(dirPath, { recursive: true });
35
+ }
36
+ }
37
+
38
+ function writeFile(filePath, content) {
39
+ ensureDir(path.dirname(filePath));
40
+ fs.writeFileSync(filePath, content, 'utf8');
41
+ console.log(' \u2714 Created: ' + path.relative(cwd, filePath));
42
+ }
43
+
44
+ // ── Prompt helper ──────────────────────────────────────────────────────────
45
+
46
+ function prompt(rl, question) {
47
+ return new Promise(function (resolve) {
48
+ rl.question(question, function (answer) {
49
+ resolve(answer);
50
+ });
51
+ });
52
+ }
53
+
54
+ // ── Main ───────────────────────────────────────────────────────────────────
55
+
56
+ async function main() {
57
+ console.log('\n\uD83E\uDDF1 ACF Block Generator\n');
58
+
59
+ const rl = readline.createInterface({
60
+ input: process.stdin,
61
+ output: process.stdout,
62
+ });
63
+
64
+ // 1. Block name
65
+ let blockName = '';
66
+ while (!blockName) {
67
+ const raw = await prompt(rl, 'Block name (kebab-case, e.g. hero-banner): ');
68
+ blockName = toKebabCase(raw);
69
+ if (!blockName) {
70
+ console.log(' \u26A0 Block name is required. Please try again.');
71
+ }
72
+ }
73
+
74
+ // 2. Block label
75
+ const defaultLabel = toTitleCase(blockName);
76
+ const labelRaw = await prompt(rl, 'Block label [' + defaultLabel + ']: ');
77
+ const blockLabel = labelRaw.trim() || defaultLabel;
78
+
79
+ // 3. Description
80
+ const blockDescription = await prompt(rl, 'Block description (optional): ');
81
+
82
+ // 4. Extra keywords
83
+ const extraKeywordsRaw = await prompt(rl, 'Additional keywords (comma-separated, optional): ');
84
+ const extraKeywords = extraKeywordsRaw
85
+ .split(',')
86
+ .map(function (k) { return k.trim().toLowerCase(); })
87
+ .filter(Boolean);
88
+
89
+ // 5. Blocks folder
90
+ const folderRaw = await prompt(rl, 'Blocks folder [blocks]: ');
91
+ const blocksDir = 'blocks/' + (folderRaw.trim() || '');
92
+
93
+ rl.close();
94
+
95
+ // ── Resolve paths ────────────────────────────────────────────────────────
96
+
97
+ // Normalise: strip any trailing slash and duplicate 'blocks/' the user may have typed
98
+ const subFolder = folderRaw.trim().replace(/^blocks[\/\\]?/i, '').replace(/[\/\\]+$/, '');
99
+ const blockDir = subFolder
100
+ ? path.join(cwd, 'blocks', subFolder, blockName)
101
+ : path.join(cwd, 'blocks', blockName);
102
+
103
+ const renderFile = path.join(blockDir, blockName + '.php');
104
+ const blockJsonFile = path.join(blockDir, 'block.json');
105
+ const scssFile = path.join(cwd, 'assets', 'sass', 'blocks', blockName + '.scss');
106
+
107
+ if (fs.existsSync(blockDir)) {
108
+ console.error('\n\u274C Block folder already exists: ' + path.relative(cwd, blockDir) + '\n');
109
+ process.exit(1);
110
+ }
111
+
112
+ console.log('\n\uD83D\uDCC2 Generating files...\n');
113
+
114
+ // ── block.json ───────────────────────────────────────────────────────────
115
+
116
+ const svgIcon = '<svg version="1.0" xmlns="http://www.w3.org/2000/svg"\n width="607.000000pt" height="607.000000pt" viewBox="0 0 607.000000 607.000000"\n preserveAspectRatio="xMidYMid meet">\n\n<g transform="translate(0.000000,607.000000) scale(0.100000,-0.100000)"\nfill="#000000" stroke="none">\n<path d="M480 4194 l0 -1395 43 3 42 3 3 1348 2 1347 1355 0 1355 0 0 45 0 45\n-1400 0 -1400 0 0 -1396z"/>\n<path d="M2880 4669 c-377 -36 -651 -181 -809 -428 -148 -232 -148 -626 0\n-878 47 -81 179 -210 273 -268 114 -71 283 -140 596 -244 436 -146 563 -217\n632 -359 32 -64 33 -70 33 -192 0 -147 -15 -208 -72 -289 -75 -106 -208 -170\n-403 -191 -59 -7 -143 -9 -190 -5 -231 18 -518 118 -712 248 l-67 44 -110\n-183 -110 -183 37 -32 c140 -124 400 -237 674 -295 168 -35 451 -44 616 -20\n307 45 556 166 699 337 57 70 128 214 153 311 37 150 35 366 -5 522 -69 266\n-226 426 -564 577 -62 28 -246 95 -408 150 -400 134 -526 199 -593 306 -58 91\n-67 300 -17 398 109 216 498 304 902 204 99 -25 283 -114 349 -168 26 -23 52\n-41 57 -41 4 0 61 69 125 153 64 83 121 158 127 165 23 29 -209 200 -343 251\n-238 92 -592 137 -870 110z"/>\n<path d="M5510 1910 l0 -1350 -1355 0 -1355 0 0 -45 0 -45 1400 0 1400 0 0\n1395 0 1395 -45 0 -45 0 0 -1350z"/>\n</g>\n</svg>\n';
117
+
118
+ // Build keywords: individual words from block name + "stier" + any extras
119
+ var autoKeywords = splitKeywords(blockName);
120
+ autoKeywords.push('stier');
121
+ var allKeywords = autoKeywords.concat(extraKeywords);
122
+ // Deduplicate
123
+ allKeywords = allKeywords.filter(function (v, i, a) { return a.indexOf(v) === i; });
124
+
125
+ var keywordsJson = '["' + allKeywords.join('", "') + '"]';
126
+
127
+ // Build block.json as a string to keep keywords on one line
128
+ const blockJsonStr = '{\n' +
129
+ ' "name": "acf/' + blockName + '",\n' +
130
+ ' "title": "' + blockLabel + '",\n' +
131
+ ' "description": "' + (blockDescription.trim() || blockLabel) + ' Block",\n' +
132
+ ' "category": "s-tier",\n' +
133
+ ' "viewScript": "file:./' + blockName + '.js",\n' +
134
+ ' "icon": ' + JSON.stringify(svgIcon) + ',\n' +
135
+ ' "keywords": ' + keywordsJson + ',\n' +
136
+ ' "acf": {\n' +
137
+ ' "blockVersion": 3,\n' +
138
+ ' "renderTemplate": "' + blockName + '.php"\n' +
139
+ ' },\n' +
140
+ ' "supports": {\n' +
141
+ ' "align": false,\n' +
142
+ ' "anchor": true\n' +
143
+ ' }\n' +
144
+ '}';
145
+
146
+ writeFile(blockJsonFile, blockJsonStr);
147
+
148
+ // ── render.php ───────────────────────────────────────────────────────────
149
+
150
+ const innerClass = 'st' + blockName.replace(/-/g, '') + '_inner';
151
+
152
+ const renderPhp = '<?php\n' +
153
+ "$padding = get_field('padding');\n" +
154
+ '\n' +
155
+ "$class = 'st_block st_" + blockName + "';\n" +
156
+ "if ( ! empty( $block['className'] ) ) {\n" +
157
+ " $class .= ' ' . $block['className'];\n" +
158
+ '}\n' +
159
+ '\n' +
160
+ "$anchor = '';\n" +
161
+ "if ( ! empty( $block['anchor'] ) ) {\n" +
162
+ " $anchor = 'id=\"' . esc_attr( $block['anchor'] ) . '\" ';\n" +
163
+ '}\n' +
164
+ '\n' +
165
+ 'if ( ! empty( $padding) ) {\n' +
166
+ " $class .= ' ' . $padding;\n" +
167
+ '}\n' +
168
+ '\n' +
169
+ '?>\n' +
170
+ '\n' +
171
+ '<section <?php echo $anchor; ?> class="<?php echo esc_attr( $class ); ?>">\n' +
172
+ "\t<?php get_template_part('components/background'); ?>\n" +
173
+ '\t<div class="container ' + innerClass + '">\n' +
174
+ "\t\t<?php get_template_part('components/intro'); ?>\n" +
175
+ '\t</div>\n' +
176
+ '</section>\n';
177
+
178
+ writeFile(renderFile, renderPhp);
179
+
180
+ // ── block-name.scss ──────────────────────────────────────────────────────
181
+
182
+ const scssContent = '.st_' + blockName + ' {\n\n}\n';
183
+
184
+ writeFile(scssFile, scssContent);
185
+
186
+ // ── Summary ──────────────────────────────────────────────────────────────
187
+
188
+ console.log('\n\u2705 Block "' + blockLabel + '" created successfully!\n');
189
+ console.log(' \uD83D\uDCC1 ' + path.relative(cwd, blockDir) + '/');
190
+ console.log(' \u251C\u2500\u2500 block.json');
191
+ console.log(' \u2514\u2500\u2500 ' + blockName + '.php');
192
+ console.log(' \uD83D\uDCC1 assets/sass/blocks/');
193
+ console.log(' \u2514\u2500\u2500 ' + blockName + '.scss');
194
+ console.log('');
195
+ var cyan = '\x1b[36m';
196
+ var reset = '\x1b[0m';
197
+ console.log('');
198
+ console.log(" Don't forget to add " + cyan + "@import 'blocks/" + blockName + "';" + reset + " in 'assets/sass/theme.scss'");
199
+ console.log(" Don't forget to add " + cyan + "register_block_type( __DIR__ . '/../blocks/" + blockName + "' );" + reset + " in 'includes/template-functions.php'");
200
+ console.log('');
201
+ }
202
+
203
+ main().catch(function (err) {
204
+ console.error('\n\u274C Error: ' + err.message);
205
+ process.exit(1);
206
+ });
package/package.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "new-s-block",
3
+ "version": "1.0.0",
4
+ "description": "CLI tool to scaffold ACF Gutenberg blocks",
5
+ "bin": {
6
+ "new-s-block": "./bin/cli.js"
7
+ },
8
+ "keywords": [
9
+ "acf",
10
+ "gutenberg",
11
+ "wordpress",
12
+ "block",
13
+ "scaffold"
14
+ ],
15
+ "author": "",
16
+ "license": "MIT",
17
+ "engines": {
18
+ "node": ">=14.19.3"
19
+ }
20
+ }