create-wp-typia 0.1.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 (90) hide show
  1. package/README.md +33 -0
  2. package/dist/cli.js +87837 -0
  3. package/dist/highlights-eq9cgrbb.scm +604 -0
  4. package/dist/highlights-ghv9g403.scm +205 -0
  5. package/dist/highlights-hk7bwhj4.scm +284 -0
  6. package/dist/highlights-r812a2qc.scm +150 -0
  7. package/dist/highlights-x6tmsnaa.scm +115 -0
  8. package/dist/injections-73j83es3.scm +27 -0
  9. package/dist/tree-sitter-javascript-nd0q4pe9.wasm +0 -0
  10. package/dist/tree-sitter-markdown-411r6y9b.wasm +0 -0
  11. package/dist/tree-sitter-markdown_inline-j5349f42.wasm +0 -0
  12. package/dist/tree-sitter-typescript-zxjzwt75.wasm +0 -0
  13. package/dist/tree-sitter-zig-e78zbjpm.wasm +0 -0
  14. package/lib/entry.js +29 -0
  15. package/lib/node-cli.js +326 -0
  16. package/lib/package-managers.d.ts +29 -0
  17. package/lib/package-managers.js +170 -0
  18. package/lib/scaffold.d.ts +64 -0
  19. package/lib/scaffold.js +565 -0
  20. package/lib/template-registry.d.ts +18 -0
  21. package/lib/template-registry.js +58 -0
  22. package/package.json +64 -0
  23. package/src/cli.ts +329 -0
  24. package/templates/advanced/README.md.mustache +70 -0
  25. package/templates/advanced/block.json.mustache +42 -0
  26. package/templates/advanced/index.js +21 -0
  27. package/templates/advanced/package.json.mustache +48 -0
  28. package/templates/advanced/scripts/generate-migrations.ts.mustache +267 -0
  29. package/templates/advanced/scripts/lib/typia-metadata-core.ts +806 -0
  30. package/templates/advanced/scripts/migration-cli.ts.mustache +260 -0
  31. package/templates/advanced/scripts/sync-types-to-block-json.ts.mustache +25 -0
  32. package/templates/advanced/src/admin/migration-dashboard.tsx.mustache +450 -0
  33. package/templates/advanced/src/components/ErrorBoundary.tsx.mustache +47 -0
  34. package/templates/advanced/src/deprecated.ts.mustache +184 -0
  35. package/templates/advanced/src/edit.tsx.mustache +93 -0
  36. package/templates/advanced/src/hooks/useDebounce.ts.mustache +20 -0
  37. package/templates/advanced/src/hooks/useLocalStorage.ts.mustache +31 -0
  38. package/templates/advanced/src/hooks.ts.mustache +56 -0
  39. package/templates/advanced/src/index.tsx.mustache +16 -0
  40. package/templates/advanced/src/migration-detector.ts.mustache +417 -0
  41. package/templates/advanced/src/migrations/index.ts.mustache +361 -0
  42. package/templates/advanced/src/save.tsx.mustache +40 -0
  43. package/templates/advanced/src/style.scss.mustache +84 -0
  44. package/templates/advanced/src/types/versions.ts.mustache +108 -0
  45. package/templates/advanced/src/types.ts.mustache +45 -0
  46. package/templates/advanced/src/utils/classnames.ts.mustache +51 -0
  47. package/templates/advanced/src/utils/debounce.ts.mustache +37 -0
  48. package/templates/advanced/src/utils/index.ts.mustache +7 -0
  49. package/templates/advanced/src/utils/uuid.ts.mustache +17 -0
  50. package/templates/advanced/src/validators.ts.mustache +39 -0
  51. package/templates/advanced/src/view.ts.mustache +59 -0
  52. package/templates/advanced/tsconfig.json.mustache +9 -0
  53. package/templates/advanced/webpack.config.js.mustache +85 -0
  54. package/templates/basic/package.json.mustache +39 -0
  55. package/templates/basic/scripts/lib/typia-metadata-core.ts +806 -0
  56. package/templates/basic/scripts/sync-types-to-block-json.ts +25 -0
  57. package/templates/basic/src/block.json +51 -0
  58. package/templates/basic/src/edit.tsx +85 -0
  59. package/templates/basic/src/hooks.ts +75 -0
  60. package/templates/basic/src/index.tsx +37 -0
  61. package/templates/basic/src/save.tsx +27 -0
  62. package/templates/basic/src/style.scss +42 -0
  63. package/templates/basic/src/types.ts +47 -0
  64. package/templates/basic/src/validators.ts +39 -0
  65. package/templates/basic/tsconfig.json +20 -0
  66. package/templates/basic/webpack.config.js +85 -0
  67. package/templates/full/package.json.mustache +40 -0
  68. package/templates/full/scripts/lib/typia-metadata-core.ts +806 -0
  69. package/templates/full/scripts/sync-types-to-block-json.ts.mustache +25 -0
  70. package/templates/full/src/block.json.mustache +121 -0
  71. package/templates/full/src/edit.tsx.mustache +300 -0
  72. package/templates/full/src/editor.scss.mustache +251 -0
  73. package/templates/full/src/hooks.ts.mustache +140 -0
  74. package/templates/full/src/index.tsx.mustache +27 -0
  75. package/templates/full/src/save.tsx.mustache +39 -0
  76. package/templates/full/src/style.scss.mustache +224 -0
  77. package/templates/full/src/types.ts.mustache +34 -0
  78. package/templates/full/src/validators.ts.mustache +84 -0
  79. package/templates/full/tsconfig.json.mustache +9 -0
  80. package/templates/full/webpack.config.js.mustache +85 -0
  81. package/templates/interactivity/package.json.mustache +41 -0
  82. package/templates/interactivity/scripts/lib/typia-metadata-core.ts +806 -0
  83. package/templates/interactivity/scripts/sync-types-to-block-json.ts.mustache +25 -0
  84. package/templates/interactivity/src/block.json.mustache +75 -0
  85. package/templates/interactivity/src/edit.tsx.mustache +206 -0
  86. package/templates/interactivity/src/interactivity.ts.mustache +183 -0
  87. package/templates/interactivity/src/save.tsx.mustache +87 -0
  88. package/templates/interactivity/src/types.ts.mustache +29 -0
  89. package/templates/interactivity/tsconfig.json.mustache +9 -0
  90. package/templates/interactivity/webpack.config.js.mustache +85 -0
@@ -0,0 +1,64 @@
1
+ export interface ScaffoldProjectOptions {
2
+ projectDir: string;
3
+ templateId: string;
4
+ answers: {
5
+ author: string;
6
+ description: string;
7
+ namespace: string;
8
+ slug: string;
9
+ title: string;
10
+ };
11
+ packageManager: "bun" | "npm" | "pnpm" | "yarn";
12
+ allowExistingDir?: boolean;
13
+ noInstall?: boolean;
14
+ installDependencies?: (context: {
15
+ projectDir: string;
16
+ packageManager: "bun" | "npm" | "pnpm" | "yarn";
17
+ }) => Promise<void> | void;
18
+ }
19
+
20
+ export interface ScaffoldProjectResult {
21
+ projectDir: string;
22
+ templateId: string;
23
+ packageManager: "bun" | "npm" | "pnpm" | "yarn";
24
+ variables: Record<string, string>;
25
+ }
26
+
27
+ export function detectAuthor(): string;
28
+ export function collectScaffoldAnswers(options: {
29
+ projectName: string;
30
+ templateId: string;
31
+ yes?: boolean;
32
+ promptText?: (
33
+ message: string,
34
+ defaultValue: string,
35
+ validate?: (input: string) => boolean | string,
36
+ ) => Promise<string>;
37
+ }): Promise<{
38
+ author: string;
39
+ description: string;
40
+ namespace: string;
41
+ slug: string;
42
+ title: string;
43
+ }>;
44
+ export function getDefaultAnswers(projectName: string, templateId: string): {
45
+ author: string;
46
+ description: string;
47
+ namespace: string;
48
+ slug: string;
49
+ title: string;
50
+ };
51
+ export function resolveTemplateId(options: {
52
+ templateId?: string;
53
+ yes?: boolean;
54
+ isInteractive?: boolean;
55
+ selectTemplate?: () => Promise<string> | string;
56
+ }): Promise<string>;
57
+ export function resolvePackageManagerId(options: {
58
+ packageManager?: "bun" | "npm" | "pnpm" | "yarn";
59
+ yes?: boolean;
60
+ isInteractive?: boolean;
61
+ selectPackageManager?: () => Promise<"bun" | "npm" | "pnpm" | "yarn"> | "bun" | "npm" | "pnpm" | "yarn";
62
+ }): Promise<"bun" | "npm" | "pnpm" | "yarn">;
63
+ export function scaffoldProject(options: ScaffoldProjectOptions): Promise<ScaffoldProjectResult>;
64
+ export function runLegacyCli(templateId: string, argv?: string[]): Promise<void>;
@@ -0,0 +1,565 @@
1
+ import fs from "node:fs";
2
+ import { promises as fsp } from "node:fs";
3
+ import path from "node:path";
4
+ import readline from "node:readline";
5
+ import { execSync } from "node:child_process";
6
+
7
+ import {
8
+ PACKAGE_MANAGER_IDS,
9
+ formatInstallCommand,
10
+ formatRunScript,
11
+ getPackageManager,
12
+ getPackageManagerSelectOptions,
13
+ transformPackageManagerText,
14
+ } from "./package-managers.js";
15
+ import { TEMPLATE_IDS, getTemplateById } from "./template-registry.js";
16
+
17
+ const BLOCK_SLUG_PATTERN = /^[a-z][a-z0-9-]*$/;
18
+ const LOCKFILES = {
19
+ bun: ["bun.lock", "bun.lockb"],
20
+ npm: ["package-lock.json"],
21
+ pnpm: ["pnpm-lock.yaml"],
22
+ yarn: ["yarn.lock"],
23
+ };
24
+
25
+ function toKebabCase(input) {
26
+ return input
27
+ .trim()
28
+ .replace(/([a-z0-9])([A-Z])/g, "$1-$2")
29
+ .replace(/[^A-Za-z0-9]+/g, "-")
30
+ .replace(/^-+|-+$/g, "")
31
+ .replace(/-{2,}/g, "-")
32
+ .toLowerCase();
33
+ }
34
+
35
+ function toSnakeCase(input) {
36
+ return toKebabCase(input).replace(/-/g, "_");
37
+ }
38
+
39
+ function toPascalCase(input) {
40
+ return toKebabCase(input)
41
+ .split("-")
42
+ .filter(Boolean)
43
+ .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
44
+ .join("");
45
+ }
46
+
47
+ function toTitle(input) {
48
+ return toKebabCase(input)
49
+ .split("-")
50
+ .filter(Boolean)
51
+ .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
52
+ .join(" ");
53
+ }
54
+
55
+ function validateBlockSlug(input) {
56
+ return BLOCK_SLUG_PATTERN.test(input) || "Use lowercase letters, numbers, and hyphens only";
57
+ }
58
+
59
+ export function detectAuthor() {
60
+ try {
61
+ return (
62
+ execSync("git config user.name", {
63
+ encoding: "utf8",
64
+ stdio: ["ignore", "pipe", "ignore"],
65
+ }).trim() || "Your Name"
66
+ );
67
+ } catch {
68
+ return "Your Name";
69
+ }
70
+ }
71
+
72
+ function createReadlinePrompt() {
73
+ const rl = readline.createInterface({
74
+ input: process.stdin,
75
+ output: process.stdout,
76
+ });
77
+
78
+ return {
79
+ async text(message, defaultValue, validate) {
80
+ const suffix = defaultValue ? ` (${defaultValue})` : "";
81
+ const answer = await new Promise((resolve) => {
82
+ rl.question(`${message}${suffix}: `, resolve);
83
+ });
84
+
85
+ const value = String(answer).trim() || defaultValue;
86
+ if (validate) {
87
+ const result = validate(value);
88
+ if (result !== true) {
89
+ console.error(`❌ ${result}`);
90
+ return this.text(message, defaultValue, validate);
91
+ }
92
+ }
93
+
94
+ return value;
95
+ },
96
+ async select(message, options, defaultValue) {
97
+ console.log(message);
98
+ options.forEach((option, index) => {
99
+ const hint = option.hint ? ` - ${option.hint}` : "";
100
+ console.log(` ${index + 1}. ${option.label}${hint}`);
101
+ });
102
+
103
+ const answer = await this.text("Choice", String(defaultValue ?? 1));
104
+ const numericChoice = Number(answer);
105
+ if (!Number.isNaN(numericChoice) && options[numericChoice - 1]) {
106
+ return options[numericChoice - 1].value;
107
+ }
108
+
109
+ const directChoice = options.find((option) => option.value === answer);
110
+ if (directChoice) {
111
+ return directChoice.value;
112
+ }
113
+
114
+ console.error(`❌ Invalid selection: ${answer}`);
115
+ return this.select(message, options, defaultValue);
116
+ },
117
+ close() {
118
+ rl.close();
119
+ },
120
+ };
121
+ }
122
+
123
+ export function getDefaultAnswers(projectName, templateId) {
124
+ const template = getTemplateById(templateId);
125
+ const slugDefault = toKebabCase(projectName || "my-wp-typia-block");
126
+ return {
127
+ author: detectAuthor(),
128
+ description: template.description,
129
+ namespace: "create-block",
130
+ slug: slugDefault,
131
+ title: toTitle(slugDefault),
132
+ };
133
+ }
134
+
135
+ export async function resolveTemplateId({
136
+ templateId,
137
+ yes = false,
138
+ isInteractive = false,
139
+ selectTemplate,
140
+ }) {
141
+ if (templateId) {
142
+ return getTemplateById(templateId).id;
143
+ }
144
+
145
+ if (yes) {
146
+ return "basic";
147
+ }
148
+
149
+ if (!isInteractive || !selectTemplate) {
150
+ throw new Error(`Template is required in non-interactive mode. Use --template <${TEMPLATE_IDS.join("|")}>.`);
151
+ }
152
+
153
+ return selectTemplate();
154
+ }
155
+
156
+ export async function resolvePackageManagerId({
157
+ packageManager,
158
+ yes = false,
159
+ isInteractive = false,
160
+ selectPackageManager,
161
+ }) {
162
+ if (packageManager) {
163
+ return getPackageManager(packageManager).id;
164
+ }
165
+
166
+ if (yes) {
167
+ throw new Error(
168
+ `Package manager is required when using --yes. Use --package-manager <${PACKAGE_MANAGER_IDS.join("|")}>.`,
169
+ );
170
+ }
171
+
172
+ if (!isInteractive || !selectPackageManager) {
173
+ throw new Error(
174
+ `Package manager is required in non-interactive mode. Use --package-manager <${PACKAGE_MANAGER_IDS.join("|")}>.`,
175
+ );
176
+ }
177
+
178
+ return selectPackageManager();
179
+ }
180
+
181
+ export async function collectScaffoldAnswers({
182
+ projectName,
183
+ templateId,
184
+ yes = false,
185
+ promptText,
186
+ }) {
187
+ const defaults = getDefaultAnswers(projectName, templateId);
188
+
189
+ if (yes) {
190
+ return defaults;
191
+ }
192
+
193
+ if (!promptText) {
194
+ throw new Error("Interactive answers require a promptText callback.");
195
+ }
196
+
197
+ const slug = toKebabCase(
198
+ await promptText("Block slug", defaults.slug, validateBlockSlug),
199
+ );
200
+
201
+ return {
202
+ author: await promptText("Author", defaults.author),
203
+ description: await promptText("Description", defaults.description),
204
+ namespace: await promptText("Namespace", defaults.namespace),
205
+ slug,
206
+ title: await promptText("Block title", toTitle(slug)),
207
+ };
208
+ }
209
+
210
+ export function getTemplateVariables(templateId, answers) {
211
+ const template = getTemplateById(templateId);
212
+ const slug = toKebabCase(answers.slug);
213
+ const slugSnakeCase = toSnakeCase(slug);
214
+ const pascalCase = toPascalCase(slug);
215
+ const title = answers.title.trim();
216
+ const namespace = answers.namespace.trim();
217
+ const description = answers.description.trim();
218
+
219
+ return {
220
+ author: answers.author.trim(),
221
+ category: template.defaultCategory,
222
+ cssClassName: `wp-block-${slug}`,
223
+ dashCase: slug,
224
+ dashicon: "smiley",
225
+ description,
226
+ keyword: slug.replace(/-/g, " "),
227
+ namespace,
228
+ needsMigration: "{{needsMigration}}",
229
+ pascalCase,
230
+ slug,
231
+ slugCamelCase: pascalCase.charAt(0).toLowerCase() + pascalCase.slice(1),
232
+ slugKebabCase: slug,
233
+ slugSnakeCase,
234
+ textDomain: slugSnakeCase,
235
+ textdomain: slugSnakeCase,
236
+ title,
237
+ titleCase: pascalCase,
238
+ };
239
+ }
240
+
241
+ function replaceVariables(content, variables) {
242
+ return content.replace(/\{\{([^}]+)\}\}/g, (match, rawKey) => {
243
+ const key = rawKey.trim();
244
+ return Object.prototype.hasOwnProperty.call(variables, key) ? variables[key] : match;
245
+ });
246
+ }
247
+
248
+ async function ensureDirectory(targetDir, allowExisting = false) {
249
+ if (!fs.existsSync(targetDir)) {
250
+ await fsp.mkdir(targetDir, { recursive: true });
251
+ return;
252
+ }
253
+
254
+ if (allowExisting) {
255
+ return;
256
+ }
257
+
258
+ const entries = await fsp.readdir(targetDir);
259
+ if (entries.length > 0) {
260
+ throw new Error(`Target directory is not empty: ${targetDir}`);
261
+ }
262
+ }
263
+
264
+ async function copyTemplateDir(sourceDir, targetDir, variables) {
265
+ const entries = await fsp.readdir(sourceDir, { withFileTypes: true });
266
+ for (const entry of entries) {
267
+ const sourcePath = path.join(sourceDir, entry.name);
268
+ const destinationName = entry.name.endsWith(".mustache")
269
+ ? entry.name.slice(0, -".mustache".length)
270
+ : entry.name;
271
+ const destinationPath = path.join(targetDir, destinationName);
272
+
273
+ if (entry.isDirectory()) {
274
+ await fsp.mkdir(destinationPath, { recursive: true });
275
+ await copyTemplateDir(sourcePath, destinationPath, variables);
276
+ continue;
277
+ }
278
+
279
+ const content = await fsp.readFile(sourcePath, "utf8");
280
+ await fsp.writeFile(destinationPath, replaceVariables(content, variables), "utf8");
281
+ }
282
+ }
283
+
284
+ function buildReadme(templateId, variables, packageManager) {
285
+ return `# ${variables.title}
286
+
287
+ ${variables.description}
288
+
289
+ ## Template
290
+
291
+ ${templateId}
292
+
293
+ ## Development
294
+
295
+ \`\`\`bash
296
+ ${formatInstallCommand(packageManager)}
297
+ ${formatRunScript(packageManager, "start")}
298
+ \`\`\`
299
+
300
+ ## Build
301
+
302
+ \`\`\`bash
303
+ ${formatRunScript(packageManager, "build")}
304
+ \`\`\`
305
+
306
+ ## Type Sync
307
+
308
+ \`\`\`bash
309
+ ${formatRunScript(packageManager, "sync-types")}
310
+ \`\`\`
311
+
312
+ \`src/types.ts\` remains the source of truth for \`block.json\` and \`typia.manifest.json\`.
313
+ `;
314
+ }
315
+
316
+ function buildGitignore() {
317
+ return `# Dependencies
318
+ node_modules/
319
+
320
+ # Build
321
+ build/
322
+ dist/
323
+
324
+ # Editor
325
+ .vscode/
326
+ .idea/
327
+
328
+ # OS
329
+ .DS_Store
330
+ Thumbs.db
331
+
332
+ # WordPress
333
+ *.log
334
+ .wp-env/
335
+ `;
336
+ }
337
+
338
+ async function normalizePackageJson(targetDir, packageManagerId) {
339
+ const packageJsonPath = path.join(targetDir, "package.json");
340
+ if (!fs.existsSync(packageJsonPath)) {
341
+ return;
342
+ }
343
+
344
+ const packageManager = getPackageManager(packageManagerId);
345
+ const packageJson = JSON.parse(await fsp.readFile(packageJsonPath, "utf8"));
346
+ packageJson.packageManager = packageManager.packageManagerField;
347
+
348
+ if (packageJson.scripts) {
349
+ for (const [key, value] of Object.entries(packageJson.scripts)) {
350
+ if (typeof value === "string") {
351
+ packageJson.scripts[key] = transformPackageManagerText(value, packageManagerId);
352
+ }
353
+ }
354
+ }
355
+
356
+ await fsp.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, "\t")}\n`, "utf8");
357
+ }
358
+
359
+ async function removeUnexpectedLockfiles(targetDir, packageManagerId) {
360
+ const keep = new Set(LOCKFILES[packageManagerId] ?? []);
361
+ const allLockfiles = Object.values(LOCKFILES).flat();
362
+
363
+ await Promise.all(
364
+ allLockfiles.map(async (filename) => {
365
+ if (keep.has(filename)) {
366
+ return;
367
+ }
368
+
369
+ const filePath = path.join(targetDir, filename);
370
+ if (fs.existsSync(filePath)) {
371
+ await fsp.rm(filePath, { force: true });
372
+ }
373
+ }),
374
+ );
375
+ }
376
+
377
+ async function replaceTextRecursively(targetDir, packageManagerId) {
378
+ const textExtensions = new Set([
379
+ ".css",
380
+ ".js",
381
+ ".json",
382
+ ".jsx",
383
+ ".md",
384
+ ".php",
385
+ ".scss",
386
+ ".ts",
387
+ ".tsx",
388
+ ".txt",
389
+ ]);
390
+
391
+ async function visit(currentPath) {
392
+ const stats = await fsp.stat(currentPath);
393
+ if (stats.isDirectory()) {
394
+ const entries = await fsp.readdir(currentPath);
395
+ for (const entry of entries) {
396
+ await visit(path.join(currentPath, entry));
397
+ }
398
+ return;
399
+ }
400
+
401
+ if (path.basename(currentPath) === "package.json" || !textExtensions.has(path.extname(currentPath))) {
402
+ return;
403
+ }
404
+
405
+ const content = await fsp.readFile(currentPath, "utf8");
406
+ const nextContent = transformPackageManagerText(content, packageManagerId)
407
+ .replace(/yourusername\/wp-typia-boilerplate/g, "imjlk/wp-typia-templates")
408
+ .replace(/@wp-typia\/basic/g, "wp-typia-basic")
409
+ .replace(/@wp-typia\/full/g, "wp-typia-full")
410
+ .replace(/@wp-typia\/interactivity/g, "wp-typia-interactivity")
411
+ .replace(/@wp-typia\/advanced/g, "wp-typia-advanced");
412
+
413
+ if (nextContent !== content) {
414
+ await fsp.writeFile(currentPath, nextContent, "utf8");
415
+ }
416
+ }
417
+
418
+ await visit(targetDir);
419
+ }
420
+
421
+ async function defaultInstallDependencies({ projectDir, packageManager }) {
422
+ execSync(formatInstallCommand(packageManager), {
423
+ cwd: projectDir,
424
+ stdio: "inherit",
425
+ });
426
+ }
427
+
428
+ export async function scaffoldProject({
429
+ projectDir,
430
+ templateId,
431
+ answers,
432
+ packageManager,
433
+ allowExistingDir = false,
434
+ noInstall = false,
435
+ installDependencies,
436
+ }) {
437
+ const template = getTemplateById(templateId);
438
+ const resolvedPackageManager = getPackageManager(packageManager).id;
439
+
440
+ await ensureDirectory(projectDir, allowExistingDir);
441
+
442
+ const variables = getTemplateVariables(template.id, answers);
443
+
444
+ await copyTemplateDir(template.templateDir, projectDir, variables);
445
+ const readmePath = path.join(projectDir, "README.md");
446
+ if (!fs.existsSync(readmePath)) {
447
+ await fsp.writeFile(
448
+ readmePath,
449
+ buildReadme(template.id, variables, resolvedPackageManager),
450
+ "utf8",
451
+ );
452
+ }
453
+ await fsp.writeFile(path.join(projectDir, ".gitignore"), buildGitignore(), "utf8");
454
+ await normalizePackageJson(projectDir, resolvedPackageManager);
455
+ await removeUnexpectedLockfiles(projectDir, resolvedPackageManager);
456
+ await replaceTextRecursively(projectDir, resolvedPackageManager);
457
+
458
+ if (!noInstall) {
459
+ const installer = installDependencies ?? defaultInstallDependencies;
460
+ await installer({
461
+ projectDir,
462
+ packageManager: resolvedPackageManager,
463
+ });
464
+ }
465
+
466
+ return {
467
+ projectDir,
468
+ templateId: template.id,
469
+ packageManager: resolvedPackageManager,
470
+ variables,
471
+ };
472
+ }
473
+
474
+ function parseLegacyArgs(argv) {
475
+ const parsed = {
476
+ yes: false,
477
+ noInstall: false,
478
+ help: false,
479
+ packageManager: undefined,
480
+ };
481
+
482
+ for (let index = 0; index < argv.length; index += 1) {
483
+ const arg = argv[index];
484
+
485
+ if (arg === "--yes" || arg === "-y") {
486
+ parsed.yes = true;
487
+ continue;
488
+ }
489
+ if (arg === "--no-install") {
490
+ parsed.noInstall = true;
491
+ continue;
492
+ }
493
+ if (arg === "--help" || arg === "-h") {
494
+ parsed.help = true;
495
+ continue;
496
+ }
497
+ if (arg === "--package-manager" || arg === "-p") {
498
+ parsed.packageManager = argv[index + 1];
499
+ index += 1;
500
+ continue;
501
+ }
502
+ if (arg.startsWith("--package-manager=")) {
503
+ parsed.packageManager = arg.split("=", 2)[1];
504
+ continue;
505
+ }
506
+ throw new Error(`Unknown flag: ${arg}`);
507
+ }
508
+
509
+ return parsed;
510
+ }
511
+
512
+ export async function runLegacyCli(templateId, argv = process.argv.slice(2)) {
513
+ const args = parseLegacyArgs(argv);
514
+ const isInteractive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
515
+ if (args.help) {
516
+ console.log(
517
+ `Usage: wp-typia-${templateId} [--yes] [--no-install] [--package-manager <${PACKAGE_MANAGER_IDS.join("|")}>]`,
518
+ );
519
+ return;
520
+ }
521
+
522
+ const prompt = createReadlinePrompt();
523
+
524
+ try {
525
+ const packageManager = await resolvePackageManagerId({
526
+ packageManager: args.packageManager,
527
+ yes: args.yes,
528
+ isInteractive,
529
+ selectPackageManager: () =>
530
+ prompt.select("Choose a package manager:", getPackageManagerSelectOptions(), 1),
531
+ });
532
+
533
+ if (!args.yes && !isInteractive) {
534
+ throw new Error("Interactive answers require a TTY. Use --yes inside an existing directory.");
535
+ }
536
+
537
+ const projectDir = process.cwd();
538
+ const projectName = path.basename(projectDir);
539
+ const answers = await collectScaffoldAnswers({
540
+ projectName,
541
+ templateId,
542
+ yes: args.yes,
543
+ promptText: (message, defaultValue, validate) =>
544
+ prompt.text(message, defaultValue, validate),
545
+ });
546
+
547
+ const result = await scaffoldProject({
548
+ allowExistingDir: true,
549
+ answers,
550
+ noInstall: args.noInstall,
551
+ packageManager,
552
+ projectDir,
553
+ templateId,
554
+ });
555
+
556
+ console.log(`\n✅ Scaffolded ${result.variables.title} in the current directory`);
557
+ console.log("Next steps:");
558
+ if (args.noInstall) {
559
+ console.log(` ${formatInstallCommand(packageManager)}`);
560
+ }
561
+ console.log(` ${formatRunScript(packageManager, "start")}`);
562
+ } finally {
563
+ prompt.close();
564
+ }
565
+ }
@@ -0,0 +1,18 @@
1
+ export interface TemplateDefinition {
2
+ id: "basic" | "full" | "interactivity" | "advanced";
3
+ description: string;
4
+ defaultCategory: string;
5
+ features: string[];
6
+ templateDir: string;
7
+ }
8
+
9
+ export const TEMPLATE_REGISTRY: readonly TemplateDefinition[];
10
+ export const TEMPLATE_IDS: Array<TemplateDefinition["id"]>;
11
+
12
+ export function listTemplates(): readonly TemplateDefinition[];
13
+ export function getTemplateById(templateId: string): TemplateDefinition;
14
+ export function getTemplateSelectOptions(): Array<{
15
+ label: TemplateDefinition["id"];
16
+ value: TemplateDefinition["id"];
17
+ hint: string;
18
+ }>;
@@ -0,0 +1,58 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+
4
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
5
+ const TEMPLATE_ROOT = path.join(__dirname, "..", "templates");
6
+
7
+ export const TEMPLATE_REGISTRY = Object.freeze([
8
+ {
9
+ id: "basic",
10
+ description: "A lightweight WordPress block with Typia validation",
11
+ defaultCategory: "text",
12
+ features: ["Type-safe attributes", "Runtime validation", "Minimal setup"],
13
+ templateDir: path.join(TEMPLATE_ROOT, "basic"),
14
+ },
15
+ {
16
+ id: "full",
17
+ description: "A full-featured WordPress block with Typia validation and utilities",
18
+ defaultCategory: "widgets",
19
+ features: ["Advanced controls", "Custom hooks", "Style options"],
20
+ templateDir: path.join(TEMPLATE_ROOT, "full"),
21
+ },
22
+ {
23
+ id: "interactivity",
24
+ description: "An interactive WordPress block with Typia validation and Interactivity API",
25
+ defaultCategory: "widgets",
26
+ features: ["Interactivity API", "Client-side state", "Event handling"],
27
+ templateDir: path.join(TEMPLATE_ROOT, "interactivity"),
28
+ },
29
+ {
30
+ id: "advanced",
31
+ description: "An advanced WordPress block with Typia validation and migration tooling",
32
+ defaultCategory: "widgets",
33
+ features: ["Migration system", "Version tracking", "Admin dashboard"],
34
+ templateDir: path.join(TEMPLATE_ROOT, "advanced"),
35
+ },
36
+ ]);
37
+
38
+ export const TEMPLATE_IDS = TEMPLATE_REGISTRY.map((template) => template.id);
39
+
40
+ export function listTemplates() {
41
+ return TEMPLATE_REGISTRY;
42
+ }
43
+
44
+ export function getTemplateById(templateId) {
45
+ const template = TEMPLATE_REGISTRY.find((entry) => entry.id === templateId);
46
+ if (!template) {
47
+ throw new Error(`Unknown template "${templateId}". Expected one of: ${TEMPLATE_IDS.join(", ")}`);
48
+ }
49
+ return template;
50
+ }
51
+
52
+ export function getTemplateSelectOptions() {
53
+ return TEMPLATE_REGISTRY.map((template) => ({
54
+ label: template.id,
55
+ value: template.id,
56
+ hint: template.features.join(", "),
57
+ }));
58
+ }