@wp-typia/project-tools 0.16.11 → 0.16.12

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 (101) hide show
  1. package/README.md +9 -3
  2. package/dist/runtime/built-in-block-artifact-documents.d.ts +3 -0
  3. package/dist/runtime/built-in-block-artifact-documents.js +2 -0
  4. package/dist/runtime/built-in-block-artifact-types.d.ts +51 -0
  5. package/dist/runtime/built-in-block-artifact-types.js +304 -0
  6. package/dist/runtime/built-in-block-artifacts.js +4 -803
  7. package/dist/runtime/built-in-block-attribute-emitters.d.ts +71 -0
  8. package/dist/runtime/built-in-block-attribute-emitters.js +176 -0
  9. package/dist/runtime/built-in-block-attribute-specs.d.ts +38 -0
  10. package/dist/runtime/built-in-block-attribute-specs.js +358 -0
  11. package/dist/runtime/built-in-block-code-templates/basic.d.ts +4 -0
  12. package/dist/runtime/built-in-block-code-templates/basic.js +249 -0
  13. package/dist/runtime/built-in-block-code-templates/compound-child.d.ts +4 -0
  14. package/dist/runtime/built-in-block-code-templates/compound-child.js +138 -0
  15. package/dist/runtime/built-in-block-code-templates/compound-parent.d.ts +6 -0
  16. package/dist/runtime/built-in-block-code-templates/compound-parent.js +227 -0
  17. package/dist/runtime/built-in-block-code-templates/compound-persistence.d.ts +4 -0
  18. package/dist/runtime/built-in-block-code-templates/compound-persistence.js +478 -0
  19. package/dist/runtime/built-in-block-code-templates/compound.d.ts +3 -0
  20. package/dist/runtime/built-in-block-code-templates/compound.js +3 -0
  21. package/dist/runtime/built-in-block-code-templates/interactivity.d.ts +5 -0
  22. package/dist/runtime/built-in-block-code-templates/interactivity.js +547 -0
  23. package/dist/runtime/built-in-block-code-templates/persistence.d.ts +5 -0
  24. package/dist/runtime/built-in-block-code-templates/persistence.js +550 -0
  25. package/dist/runtime/built-in-block-code-templates/shared.d.ts +16 -0
  26. package/dist/runtime/built-in-block-code-templates/shared.js +53 -0
  27. package/dist/runtime/built-in-block-code-templates.d.ts +5 -32
  28. package/dist/runtime/built-in-block-code-templates.js +5 -2230
  29. package/dist/runtime/cli-add-block-config.d.ts +6 -0
  30. package/dist/runtime/cli-add-block-config.js +143 -0
  31. package/dist/runtime/cli-add-block-legacy-validator.d.ts +4 -0
  32. package/dist/runtime/cli-add-block-legacy-validator.js +168 -0
  33. package/dist/runtime/cli-add-block.js +3 -301
  34. package/dist/runtime/cli-add-workspace-assets.d.ts +38 -0
  35. package/dist/runtime/cli-add-workspace-assets.js +399 -0
  36. package/dist/runtime/cli-add-workspace.d.ts +2 -38
  37. package/dist/runtime/cli-add-workspace.js +5 -396
  38. package/dist/runtime/cli-doctor-environment.d.ts +12 -0
  39. package/dist/runtime/cli-doctor-environment.js +123 -0
  40. package/dist/runtime/cli-doctor-workspace.d.ts +14 -0
  41. package/dist/runtime/cli-doctor-workspace.js +296 -0
  42. package/dist/runtime/cli-doctor.d.ts +4 -2
  43. package/dist/runtime/cli-doctor.js +10 -405
  44. package/dist/runtime/migration-command-surface.d.ts +67 -0
  45. package/dist/runtime/migration-command-surface.js +189 -0
  46. package/dist/runtime/migration-diff-rename.d.ts +13 -0
  47. package/dist/runtime/migration-diff-rename.js +192 -0
  48. package/dist/runtime/migration-diff-transform.d.ts +14 -0
  49. package/dist/runtime/migration-diff-transform.js +105 -0
  50. package/dist/runtime/migration-diff.js +12 -297
  51. package/dist/runtime/migration-generated-artifacts.d.ts +3 -0
  52. package/dist/runtime/migration-generated-artifacts.js +41 -0
  53. package/dist/runtime/migration-maintenance.d.ts +51 -0
  54. package/dist/runtime/migration-maintenance.js +380 -0
  55. package/dist/runtime/migration-planning.d.ts +23 -0
  56. package/dist/runtime/migration-planning.js +131 -0
  57. package/dist/runtime/migration-project-config-source.d.ts +6 -0
  58. package/dist/runtime/migration-project-config-source.js +424 -0
  59. package/dist/runtime/migration-project-layout-discovery.d.ts +61 -0
  60. package/dist/runtime/migration-project-layout-discovery.js +337 -0
  61. package/dist/runtime/migration-project-layout-paths.d.ts +135 -0
  62. package/dist/runtime/migration-project-layout-paths.js +288 -0
  63. package/dist/runtime/migration-project-layout.d.ts +3 -0
  64. package/dist/runtime/migration-project-layout.js +2 -0
  65. package/dist/runtime/migration-project-workspace.d.ts +47 -0
  66. package/dist/runtime/migration-project-workspace.js +212 -0
  67. package/dist/runtime/migration-project.d.ts +4 -94
  68. package/dist/runtime/migration-project.js +3 -1101
  69. package/dist/runtime/migration-render-diff-rule.d.ts +5 -0
  70. package/dist/runtime/migration-render-diff-rule.js +120 -0
  71. package/dist/runtime/migration-render-execution.d.ts +3 -0
  72. package/dist/runtime/migration-render-execution.js +428 -0
  73. package/dist/runtime/migration-render-generated.d.ts +27 -0
  74. package/dist/runtime/migration-render-generated.js +230 -0
  75. package/dist/runtime/migration-render-support.d.ts +3 -0
  76. package/dist/runtime/migration-render-support.js +16 -0
  77. package/dist/runtime/migration-render.d.ts +3 -33
  78. package/dist/runtime/migration-render.js +3 -789
  79. package/dist/runtime/migrations.d.ts +24 -118
  80. package/dist/runtime/migrations.js +12 -700
  81. package/dist/runtime/scaffold-bootstrap.d.ts +45 -0
  82. package/dist/runtime/scaffold-bootstrap.js +185 -0
  83. package/dist/runtime/scaffold-package-manager-files.d.ts +35 -0
  84. package/dist/runtime/scaffold-package-manager-files.js +79 -0
  85. package/dist/runtime/scaffold.d.ts +1 -12
  86. package/dist/runtime/scaffold.js +10 -393
  87. package/dist/runtime/template-source-contracts.d.ts +81 -0
  88. package/dist/runtime/template-source-contracts.js +1 -0
  89. package/dist/runtime/template-source-external.d.ts +21 -0
  90. package/dist/runtime/template-source-external.js +184 -0
  91. package/dist/runtime/template-source-locators.d.ts +4 -0
  92. package/dist/runtime/template-source-locators.js +72 -0
  93. package/dist/runtime/template-source-normalization.d.ts +7 -0
  94. package/dist/runtime/template-source-normalization.js +53 -0
  95. package/dist/runtime/template-source-remote.d.ts +23 -0
  96. package/dist/runtime/template-source-remote.js +336 -0
  97. package/dist/runtime/template-source-seeds.d.ts +12 -0
  98. package/dist/runtime/template-source-seeds.js +243 -0
  99. package/dist/runtime/template-source.d.ts +4 -86
  100. package/dist/runtime/template-source.js +9 -828
  101. package/package.json +4 -4
@@ -2,112 +2,24 @@ import fs from "node:fs";
2
2
  import { promises as fsp } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { execSync } from "node:child_process";
5
- import { PACKAGE_MANAGER_IDS, formatPackageExecCommand, formatInstallCommand, formatRunScript, getPackageManager, transformPackageManagerText, } from "./package-managers.js";
6
- import { replaceTextRecursively } from "./scaffold-apply-utils.js";
7
- import { applyGeneratedProjectDxPackageJson, applyLocalDevPresetFiles, getPrimaryDevelopmentScript, } from "./local-dev-presets.js";
5
+ import { PACKAGE_MANAGER_IDS, getPackageManager, } from "./package-managers.js";
6
+ import { buildGitignore, buildReadme, mergeTextLines, replaceTextRecursively, } from "./scaffold-apply-utils.js";
7
+ import { buildBlockCssClassName, buildFrontendCssClassName, normalizeBlockSlug, resolveScaffoldIdentifiers, validateBlockSlug, validateNamespace, } from "./scaffold-identifiers.js";
8
+ import { applyGeneratedProjectDxPackageJson, applyLocalDevPresetFiles, } from "./local-dev-presets.js";
8
9
  import { applyMigrationUiCapability } from "./migration-ui-capability.js";
9
10
  import { getPackageVersions } from "./package-versions.js";
10
- import { ensureMigrationDirectories, writeInitialMigrationScaffold, writeMigrationConfig, } from "./migration-project.js";
11
- import { syncPersistenceRestArtifacts } from "./persistence-rest-artifacts.js";
12
- import { getCompoundExtensionWorkflowSection, getOptionalOnboardingNote, getOptionalOnboardingSteps, getPhpRestExtensionPointsSection, getTemplateSourceOfTruthNote, } from "./scaffold-onboarding.js";
13
- import { getStarterManifestFiles, stringifyStarterManifest } from "./starter-manifests.js";
14
- import { toKebabCase, toPascalCase, toSnakeCase, toTitleCase, } from "./string-case.js";
11
+ import { applyWorkspaceMigrationCapability, ensureScaffoldDirectory, isOfficialWorkspaceProject, seedBuiltInPersistenceArtifacts, writeStarterManifestFiles, } from "./scaffold-bootstrap.js";
12
+ import { defaultInstallDependencies, normalizePackageJson, normalizePackageManagerFiles, removeUnexpectedLockfiles, } from "./scaffold-package-manager-files.js";
13
+ import { toPascalCase, toSnakeCase, toTitleCase, } from "./string-case.js";
15
14
  import { BUILTIN_BLOCK_METADATA_VERSION, COMPOUND_CHILD_BLOCK_METADATA_DEFAULTS, getBuiltInTemplateMetadataDefaults, getRemovedBuiltInTemplateMessage, isRemovedBuiltInTemplateId, } from "./template-defaults.js";
16
15
  import { copyInterpolatedDirectory } from "./template-render.js";
17
- import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, PROJECT_TOOLS_PACKAGE_ROOT, TEMPLATE_IDS, getTemplateById, isBuiltInTemplateId, } from "./template-registry.js";
16
+ import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, TEMPLATE_IDS, getTemplateById, isBuiltInTemplateId, } from "./template-registry.js";
18
17
  import { resolveTemplateSource } from "./template-source.js";
19
18
  import { BlockGeneratorService, buildTemplateVariablesFromBlockSpec, createBuiltInBlockSpec, } from "./block-generator-service.js";
20
- const BLOCK_SLUG_PATTERN = /^[a-z][a-z0-9-]*$/;
21
- const PHP_PREFIX_PATTERN = /^[a-z_][a-z0-9_]*$/;
22
- const PHP_PREFIX_MAX_LENGTH = 50;
23
19
  const WORKSPACE_TEMPLATE_ALIAS = "workspace";
24
- const EPHEMERAL_NODE_MODULES_LINK_TYPE = process.platform === "win32" ? "junction" : "dir";
25
- const LOCKFILES = {
26
- bun: ["bun.lock", "bun.lockb"],
27
- npm: ["package-lock.json"],
28
- pnpm: ["pnpm-lock.yaml"],
29
- yarn: ["yarn.lock"],
30
- };
31
20
  export const DATA_STORAGE_MODES = ["post-meta", "custom-table"];
32
21
  export const PERSISTENCE_POLICIES = ["authenticated", "public"];
33
- function validateBlockSlug(input) {
34
- return BLOCK_SLUG_PATTERN.test(input) || "Use lowercase letters, numbers, and hyphens only";
35
- }
36
- function validateNamespace(input) {
37
- return BLOCK_SLUG_PATTERN.test(toKebabCase(input))
38
- ? true
39
- : "Use lowercase letters, numbers, and hyphens only";
40
- }
41
- function validateTextDomain(input) {
42
- return BLOCK_SLUG_PATTERN.test(toKebabCase(input))
43
- ? true
44
- : "Use lowercase letters, numbers, and hyphens only";
45
- }
46
- function validatePhpPrefix(input) {
47
- const normalizedPrefix = toSnakeCase(input);
48
- if (normalizedPrefix.length > PHP_PREFIX_MAX_LENGTH) {
49
- return `Use ${PHP_PREFIX_MAX_LENGTH} characters or fewer to keep generated database identifiers within MySQL limits`;
50
- }
51
- return PHP_PREFIX_PATTERN.test(normalizedPrefix)
52
- ? true
53
- : "Use letters, numbers, and underscores only, starting with a letter";
54
- }
55
- function assertValidIdentifier(label, value, validate) {
56
- const result = validate(value);
57
- if (result !== true) {
58
- throw new Error(typeof result === "string" ? `${label}: ${result}` : `${label} is invalid`);
59
- }
60
- return value;
61
- }
62
- function normalizeBlockSlug(input) {
63
- return toKebabCase(input);
64
- }
65
- function resolveValidatedBlockSlug(value) {
66
- return assertValidIdentifier("Block slug", normalizeBlockSlug(value), validateBlockSlug);
67
- }
68
- function resolveValidatedNamespace(value) {
69
- return assertValidIdentifier("Namespace", toKebabCase(value), validateNamespace);
70
- }
71
- function resolveValidatedTextDomain(value) {
72
- return assertValidIdentifier("Text domain", toKebabCase(value), validateTextDomain);
73
- }
74
- function resolveValidatedPhpPrefix(value) {
75
- return assertValidIdentifier("PHP prefix", toSnakeCase(value), validatePhpPrefix);
76
- }
77
- /**
78
- * Builds the generated WordPress wrapper CSS class for a scaffolded block.
79
- *
80
- * Returns `wp-block-{namespace}-{slug}` when a non-empty namespace is present,
81
- * or `wp-block-{slug}` when the namespace is empty or undefined. When the
82
- * normalized namespace equals the normalized slug, appends `-block` so the
83
- * generated class avoids repeated namespace segments without colliding with the
84
- * default core wrapper classes. Both inputs are normalized and validated with
85
- * the same scaffold identifier rules used for block names.
86
- */
87
- export function buildBlockCssClassName(namespace, slug) {
88
- const normalizedSlug = resolveValidatedBlockSlug(slug);
89
- const normalizedNamespace = typeof namespace === "string" && namespace.trim().length > 0
90
- ? resolveValidatedNamespace(namespace)
91
- : "";
92
- if (normalizedNamespace === normalizedSlug) {
93
- return `wp-block-${normalizedSlug}-block`;
94
- }
95
- return normalizedNamespace.length > 0
96
- ? `wp-block-${normalizedNamespace}-${normalizedSlug}`
97
- : `wp-block-${normalizedSlug}`;
98
- }
99
- function buildFrontendCssClassName(blockCssClassName) {
100
- return `${blockCssClassName}-frontend`;
101
- }
102
- function resolveScaffoldIdentifiers({ namespace, phpPrefix, slug, textDomain, }) {
103
- const normalizedSlug = resolveValidatedBlockSlug(slug);
104
- return {
105
- namespace: resolveValidatedNamespace(namespace),
106
- phpPrefix: resolveValidatedPhpPrefix(phpPrefix ?? normalizedSlug),
107
- slug: normalizedSlug,
108
- textDomain: resolveValidatedTextDomain(textDomain ?? normalizedSlug),
109
- };
110
- }
22
+ export { buildBlockCssClassName } from "./scaffold-identifiers.js";
111
23
  export function isDataStorageMode(value) {
112
24
  return DATA_STORAGE_MODES.includes(value);
113
25
  }
@@ -313,301 +225,6 @@ export function getTemplateVariables(templateId, answers) {
313
225
  persistencePolicy,
314
226
  };
315
227
  }
316
- async function ensureDirectory(targetDir, allowExisting = false) {
317
- if (!fs.existsSync(targetDir)) {
318
- await fsp.mkdir(targetDir, { recursive: true });
319
- return;
320
- }
321
- if (allowExisting) {
322
- return;
323
- }
324
- const entries = await fsp.readdir(targetDir);
325
- if (entries.length > 0) {
326
- throw new Error(`Target directory is not empty: ${targetDir}`);
327
- }
328
- }
329
- function buildReadme(templateId, variables, packageManager, { withMigrationUi = false, withTestPreset = false, withWpEnv = false, } = {}) {
330
- const optionalOnboardingSteps = getOptionalOnboardingSteps(packageManager, templateId, {
331
- compoundPersistenceEnabled: variables.compoundPersistenceEnabled === "true",
332
- });
333
- const sourceOfTruthNote = getTemplateSourceOfTruthNote(templateId, {
334
- compoundPersistenceEnabled: variables.compoundPersistenceEnabled === "true",
335
- });
336
- const compoundPersistenceEnabled = variables.compoundPersistenceEnabled === "true";
337
- const publicPersistencePolicyNote = variables.isPublicPersistencePolicy === "true"
338
- ? "Public persistence writes use signed short-lived tokens, per-request ids, and coarse rate limiting by default. Add application-specific abuse controls before using the same pattern for high-value metrics or experiments."
339
- : null;
340
- const compoundExtensionWorkflowSection = getCompoundExtensionWorkflowSection(packageManager, templateId);
341
- const phpRestExtensionPointsSection = getPhpRestExtensionPointsSection(templateId, {
342
- compoundPersistenceEnabled,
343
- slug: variables.slug,
344
- });
345
- const developmentScript = getPrimaryDevelopmentScript(templateId);
346
- const wpEnvSection = withWpEnv
347
- ? `## Local WordPress\n\n\`\`\`bash\n${formatRunScript(packageManager, "wp-env:start")}\n${formatRunScript(packageManager, "wp-env:stop")}\n${formatRunScript(packageManager, "wp-env:reset")}\n\`\`\``
348
- : "";
349
- const testPresetSection = withTestPreset
350
- ? `## Local Test Preset\n\n\`\`\`bash\n${formatRunScript(packageManager, "wp-env:start:test")}\n${formatRunScript(packageManager, "wp-env:wait:test")}\n${formatRunScript(packageManager, "test:e2e")}\n\`\`\`\n\nThe generated smoke test uses \`.wp-env.test.json\` and verifies that the scaffolded block registers in the WordPress editor.`
351
- : "";
352
- const migrationSection = withMigrationUi
353
- ? `## Migration UI\n\nThis scaffold already includes an initialized migration workspace at \`v1\`, generated deprecated/runtime artifacts, and an editor-embedded migration dashboard. Migration versions are schema lineage labels and are separate from your package or plugin release version. Use the existing CLI commands to snapshot, diff, scaffold, verify, and fuzz future schema changes.\n\n\`\`\`bash\n${formatRunScript(packageManager, "migration:doctor")}\n${formatRunScript(packageManager, "migration:verify")}\n${formatRunScript(packageManager, "migration:fuzz")}\n\`\`\`\n\nRun \`migration:init\` only when retrofitting migration support into an older project that was not scaffolded with \`--with-migration-ui\`.`
354
- : "";
355
- return `# ${variables.title}
356
-
357
- ${variables.description}
358
-
359
- ## Template
360
-
361
- ${templateId}
362
-
363
- ## Development
364
-
365
- \`\`\`bash
366
- ${formatInstallCommand(packageManager)}
367
- ${formatRunScript(packageManager, developmentScript)}
368
- \`\`\`
369
-
370
- ## Build
371
-
372
- \`\`\`bash
373
- ${formatRunScript(packageManager, "build")}
374
- \`\`\`
375
-
376
- ## Optional First Sync
377
-
378
- \`\`\`bash
379
- ${optionalOnboardingSteps.join("\n")}
380
- \`\`\`
381
-
382
- ${getOptionalOnboardingNote(packageManager, templateId, {
383
- compoundPersistenceEnabled,
384
- })}
385
-
386
- ${sourceOfTruthNote}${publicPersistencePolicyNote ? `\n\n${publicPersistencePolicyNote}` : ""}${migrationSection ? `\n\n${migrationSection}` : ""}${compoundExtensionWorkflowSection ? `\n\n${compoundExtensionWorkflowSection}` : ""}${wpEnvSection ? `\n\n${wpEnvSection}` : ""}${testPresetSection ? `\n\n${testPresetSection}` : ""}${phpRestExtensionPointsSection ? `\n\n${phpRestExtensionPointsSection}` : ""}
387
- `;
388
- }
389
- function buildGitignore() {
390
- return `# Dependencies
391
- node_modules/
392
- .yarn/
393
- .pnp.*
394
-
395
- # Build
396
- build/
397
- dist/
398
-
399
- # Editor
400
- .vscode/
401
- .idea/
402
-
403
- # OS
404
- .DS_Store
405
- Thumbs.db
406
-
407
- # WordPress
408
- *.log
409
- .wp-env/
410
- `;
411
- }
412
- function mergeTextLines(primaryContent, existingContent) {
413
- const normalizedPrimary = primaryContent.replace(/\r\n/g, "\n").trimEnd();
414
- const normalizedExisting = existingContent.replace(/\r\n/g, "\n").trimEnd();
415
- const mergedLines = [];
416
- const seen = new Set();
417
- for (const line of [...normalizedPrimary.split("\n"), ...normalizedExisting.split("\n")]) {
418
- if (line.length === 0 && mergedLines[mergedLines.length - 1] === "") {
419
- continue;
420
- }
421
- if (line.length > 0 && seen.has(line)) {
422
- continue;
423
- }
424
- if (line.length > 0) {
425
- seen.add(line);
426
- }
427
- mergedLines.push(line);
428
- }
429
- return `${mergedLines.join("\n").replace(/\n{3,}/g, "\n\n")}\n`;
430
- }
431
- async function writeStarterManifestFiles(targetDir, templateId, variables) {
432
- const manifests = getStarterManifestFiles(templateId, variables);
433
- for (const { document, relativePath } of manifests) {
434
- const destinationPath = path.join(targetDir, relativePath);
435
- await fsp.mkdir(path.dirname(destinationPath), { recursive: true });
436
- await fsp.writeFile(destinationPath, stringifyStarterManifest(document), "utf8");
437
- }
438
- }
439
- /**
440
- * Seed REST-derived persistence artifacts into a newly scaffolded built-in
441
- * project before the first manual `sync-rest` run.
442
- *
443
- * @param targetDir Absolute scaffold target directory.
444
- * @param templateId Built-in template id being scaffolded.
445
- * @param variables Resolved scaffold template variables for the project.
446
- * @returns A promise that resolves after any required persistence artifacts are generated.
447
- */
448
- async function seedBuiltInPersistenceArtifacts(targetDir, templateId, variables) {
449
- const needsPersistenceArtifacts = templateId === "persistence" ||
450
- (templateId === "compound" && variables.compoundPersistenceEnabled === "true");
451
- if (!needsPersistenceArtifacts) {
452
- return;
453
- }
454
- await withEphemeralScaffoldNodeModules(targetDir, async () => {
455
- if (templateId === "persistence") {
456
- await syncPersistenceRestArtifacts({
457
- apiTypesFile: path.join("src", "api-types.ts"),
458
- outputDir: "src",
459
- projectDir: targetDir,
460
- variables,
461
- });
462
- return;
463
- }
464
- await syncPersistenceRestArtifacts({
465
- apiTypesFile: path.join("src", "blocks", variables.slugKebabCase, "api-types.ts"),
466
- outputDir: path.join("src", "blocks", variables.slugKebabCase),
467
- projectDir: targetDir,
468
- variables,
469
- });
470
- });
471
- }
472
- /**
473
- * Locate a node_modules directory containing `typia` relative to the project
474
- * tools package root.
475
- *
476
- * Search order:
477
- * 1. `PROJECT_TOOLS_PACKAGE_ROOT/node_modules`
478
- * 2. The monorepo root resolved from `PROJECT_TOOLS_PACKAGE_ROOT`
479
- * 3. The monorepo root `node_modules`
480
- *
481
- * @returns The first matching path, or `null` when no candidate contains `typia`.
482
- */
483
- function resolveScaffoldGeneratorNodeModulesPath() {
484
- const candidates = [
485
- path.join(PROJECT_TOOLS_PACKAGE_ROOT, "node_modules"),
486
- path.resolve(PROJECT_TOOLS_PACKAGE_ROOT, "..", ".."),
487
- path.resolve(PROJECT_TOOLS_PACKAGE_ROOT, "..", "..", "node_modules"),
488
- ];
489
- for (const candidate of candidates) {
490
- if (fs.existsSync(path.join(candidate, "typia", "package.json"))) {
491
- return candidate;
492
- }
493
- }
494
- return null;
495
- }
496
- /**
497
- * Temporarily symlink a scaffold generator node_modules directory into the
498
- * target project while running an async callback.
499
- *
500
- * The helper resolves the source path via `resolveScaffoldGeneratorNodeModulesPath()`
501
- * and uses `EPHEMERAL_NODE_MODULES_LINK_TYPE` for the symlink. The temporary
502
- * link is removed in the `finally` block so cleanup still happens if the
503
- * callback throws.
504
- *
505
- * @param targetDir Absolute scaffold target directory.
506
- * @param callback Async work that requires a resolvable `node_modules`.
507
- * @returns A promise that resolves after the callback and cleanup complete.
508
- */
509
- async function withEphemeralScaffoldNodeModules(targetDir, callback) {
510
- const targetNodeModulesPath = path.join(targetDir, "node_modules");
511
- if (fs.existsSync(targetNodeModulesPath)) {
512
- await callback();
513
- return;
514
- }
515
- const sourceNodeModulesPath = resolveScaffoldGeneratorNodeModulesPath();
516
- if (!sourceNodeModulesPath) {
517
- throw new Error("Unable to resolve a node_modules directory with typia for scaffold-time REST artifact generation.");
518
- }
519
- await fsp.symlink(sourceNodeModulesPath, targetNodeModulesPath, EPHEMERAL_NODE_MODULES_LINK_TYPE);
520
- try {
521
- await callback();
522
- }
523
- finally {
524
- await fsp.rm(targetNodeModulesPath, { force: true, recursive: true });
525
- }
526
- }
527
- async function normalizePackageManagerFiles(targetDir, packageManagerId) {
528
- const yarnRcPath = path.join(targetDir, ".yarnrc.yml");
529
- if (packageManagerId === "yarn") {
530
- await fsp.writeFile(yarnRcPath, "nodeLinker: node-modules\n", "utf8");
531
- return;
532
- }
533
- if (fs.existsSync(yarnRcPath)) {
534
- await fsp.rm(yarnRcPath, { force: true });
535
- }
536
- }
537
- async function normalizePackageJson(targetDir, packageManagerId) {
538
- const packageJsonPath = path.join(targetDir, "package.json");
539
- if (!fs.existsSync(packageJsonPath)) {
540
- return;
541
- }
542
- const packageManager = getPackageManager(packageManagerId);
543
- const packageJson = JSON.parse(await fsp.readFile(packageJsonPath, "utf8"));
544
- packageJson.packageManager = packageManager.packageManagerField;
545
- if (packageJson.scripts) {
546
- for (const [key, value] of Object.entries(packageJson.scripts)) {
547
- if (typeof value === "string") {
548
- packageJson.scripts[key] = transformPackageManagerText(value, packageManagerId);
549
- }
550
- }
551
- }
552
- await fsp.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, "\t")}\n`, "utf8");
553
- }
554
- async function removeUnexpectedLockfiles(targetDir, packageManagerId) {
555
- const keep = new Set(LOCKFILES[packageManagerId] ?? []);
556
- const allLockfiles = Object.values(LOCKFILES).flat();
557
- await Promise.all(allLockfiles.map(async (filename) => {
558
- if (keep.has(filename)) {
559
- return;
560
- }
561
- const filePath = path.join(targetDir, filename);
562
- if (fs.existsSync(filePath)) {
563
- await fsp.rm(filePath, { force: true });
564
- }
565
- }));
566
- }
567
- async function defaultInstallDependencies({ projectDir, packageManager, }) {
568
- execSync(formatInstallCommand(packageManager), {
569
- cwd: projectDir,
570
- stdio: "inherit",
571
- });
572
- }
573
- function isOfficialWorkspaceProject(projectDir) {
574
- const packageJsonPath = path.join(projectDir, "package.json");
575
- if (!fs.existsSync(packageJsonPath)) {
576
- return false;
577
- }
578
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
579
- return (packageJson.wpTypia?.projectType === "workspace" &&
580
- packageJson.wpTypia?.templatePackage === OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE);
581
- }
582
- async function applyWorkspaceMigrationCapability(projectDir, packageManager) {
583
- const packageJsonPath = path.join(projectDir, "package.json");
584
- const packageJson = JSON.parse(await fsp.readFile(packageJsonPath, "utf8"));
585
- const wpTypiaPackageVersion = getPackageVersions().wpTypiaPackageVersion;
586
- const canonicalCliSpecifier = wpTypiaPackageVersion === "^0.0.0"
587
- ? "wp-typia"
588
- : `wp-typia@${wpTypiaPackageVersion.replace(/^[~^]/u, "")}`;
589
- const migrationCli = (args) => formatPackageExecCommand(packageManager, canonicalCliSpecifier, `migrate ${args}`);
590
- packageJson.scripts = {
591
- ...(packageJson.scripts ?? {}),
592
- "migration:init": migrationCli("init --current-migration-version v1"),
593
- "migration:snapshot": migrationCli("snapshot"),
594
- "migration:diff": migrationCli("diff"),
595
- "migration:scaffold": migrationCli("scaffold"),
596
- "migration:doctor": migrationCli("doctor --all"),
597
- "migration:fixtures": migrationCli("fixtures --all"),
598
- "migration:verify": migrationCli("verify --all"),
599
- "migration:fuzz": migrationCli("fuzz --all"),
600
- };
601
- await fsp.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, "\t")}\n`, "utf8");
602
- writeMigrationConfig(projectDir, {
603
- blocks: [],
604
- currentMigrationVersion: "v1",
605
- snapshotDir: "src/migrations/versions",
606
- supportedMigrationVersions: ["v1"],
607
- });
608
- ensureMigrationDirectories(projectDir, []);
609
- writeInitialMigrationScaffold(projectDir, "v1", []);
610
- }
611
228
  export async function scaffoldProject({ projectDir, templateId, answers, dataStorageMode, persistencePolicy, packageManager, externalLayerId, externalLayerSource, externalLayerSourceLabel, repositoryReference, cwd = process.cwd(), allowExistingDir = false, noInstall = false, installDependencies = undefined, variant, withMigrationUi = false, withTestPreset = false, withWpEnv = false, }) {
612
229
  const resolvedTemplateId = normalizeTemplateSelection(templateId);
613
230
  const resolvedPackageManager = getPackageManager(packageManager).id;
@@ -658,7 +275,7 @@ export async function scaffoldProject({ projectDir, templateId, answers, dataSto
658
275
  throw new Error("`--with-migration-ui` is currently supported only for built-in templates and @wp-typia/create-workspace-template.");
659
276
  }
660
277
  try {
661
- await ensureDirectory(projectDir, allowExistingDir);
278
+ await ensureScaffoldDirectory(projectDir, allowExistingDir);
662
279
  await copyInterpolatedDirectory(templateSource.templateDir, projectDir, variables);
663
280
  }
664
281
  finally {
@@ -0,0 +1,81 @@
1
+ import type { UnknownRecord } from './object-utils.js';
2
+ export type TemplateSourceFormat = 'wp-typia' | 'create-block-external' | 'create-block-subset';
3
+ /**
4
+ * Public template variables exposed to external template seeds before wp-typia
5
+ * normalizes them into a scaffold project.
6
+ */
7
+ export interface TemplateVariableContext extends UnknownRecord {
8
+ /** Version string for `@wp-typia/api-client` used in generated dependencies. */
9
+ apiClientPackageVersion: string;
10
+ /** Version string for `@wp-typia/block-runtime` used in generated dependencies. */
11
+ blockRuntimePackageVersion: string;
12
+ /** Version string for `@wp-typia/block-types` used in generated dependencies. */
13
+ blockTypesPackageVersion: string;
14
+ /** PascalCase block type name derived from the scaffold slug. */
15
+ pascalCase: string;
16
+ /** Snake_case PHP symbol prefix used for generated functions, constants, and keys. */
17
+ phpPrefix: string;
18
+ /** Human-readable block title. */
19
+ title: string;
20
+ /** Human-readable project or block description. */
21
+ description: string;
22
+ /** Keyword string derived from the slug for generated block metadata. */
23
+ keyword: string;
24
+ /** Block namespace used in generated block names such as `namespace/slug`. */
25
+ namespace: string;
26
+ /** Kebab-case scaffold slug used for package names, paths, and block slugs. */
27
+ slug: string;
28
+ /** Kebab-case text domain used for generated i18n strings and plugin headers. */
29
+ textDomain: string;
30
+ }
31
+ export interface ResolvedTemplateSource {
32
+ id: string;
33
+ defaultCategory: string;
34
+ description: string;
35
+ features: string[];
36
+ format: TemplateSourceFormat;
37
+ isOfficialWorkspaceTemplate?: boolean;
38
+ templateDir: string;
39
+ cleanup?: () => Promise<void>;
40
+ selectedVariant?: string | null;
41
+ warnings?: string[];
42
+ }
43
+ export interface GitHubTemplateLocator {
44
+ owner: string;
45
+ repo: string;
46
+ ref: string | null;
47
+ sourcePath: string;
48
+ }
49
+ export interface NpmTemplateLocator {
50
+ fetchSpec: string;
51
+ name: string;
52
+ raw: string;
53
+ rawSpec: string;
54
+ type: string;
55
+ }
56
+ export interface ExternalTemplateConfig<TView extends UnknownRecord = TemplateVariableContext> {
57
+ assetsPath?: string;
58
+ blockTemplatesPath?: string;
59
+ defaultValues?: Partial<TView>;
60
+ folderName?: string;
61
+ transformer?: (view: TView) => UnknownRecord | Promise<UnknownRecord>;
62
+ variants?: Record<string, Partial<TView>>;
63
+ }
64
+ export interface SeedSource {
65
+ assetsDir?: string;
66
+ blockDir: string;
67
+ cleanup?: () => Promise<void>;
68
+ rootDir: string;
69
+ selectedVariant?: string | null;
70
+ warnings?: string[];
71
+ }
72
+ export type RemoteTemplateLocator = {
73
+ kind: 'github';
74
+ locator: GitHubTemplateLocator;
75
+ } | {
76
+ kind: 'npm';
77
+ locator: NpmTemplateLocator;
78
+ } | {
79
+ kind: 'path';
80
+ templatePath: string;
81
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,21 @@
1
+ import type { SeedSource, TemplateVariableContext } from './template-source-contracts.js';
2
+ /**
3
+ * Candidate filenames for official external template config entrypoints.
4
+ */
5
+ export declare const EXTERNAL_TEMPLATE_ENTRY_CANDIDATES: readonly ["index.js", "index.cjs", "index.mjs"];
6
+ /**
7
+ * Search a source directory for the first supported external template entry.
8
+ *
9
+ * @param sourceDir Directory that may contain an external template config entry.
10
+ * @returns The first matching entry path, or null when no supported entry exists.
11
+ */
12
+ export declare function getExternalTemplateEntry(sourceDir: string): string | null;
13
+ /**
14
+ * Load an official external create-block template config and render its seed.
15
+ *
16
+ * @param sourceDir Source directory that contains the external template config.
17
+ * @param context Template render context used for the selected variant.
18
+ * @param requestedVariant Optional explicit variant override.
19
+ * @returns A rendered temporary seed directory with optional assets and cleanup.
20
+ */
21
+ export declare function renderCreateBlockExternalTemplate(sourceDir: string, context: TemplateVariableContext, requestedVariant?: string): Promise<SeedSource>;