wp-typia 0.22.5 → 0.22.7

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.
@@ -1,17 +1,17 @@
1
1
  // @bun
2
2
  import {
3
3
  getPackageVersions
4
- } from "./cli-1sm60g1z.js";
4
+ } from "./cli-2hsp17nd.js";
5
5
  import {
6
6
  seedProjectMigrations
7
- } from "./cli-xbzfx7qz.js";
7
+ } from "./cli-27v2qpjg.js";
8
8
  import {
9
9
  ensureMigrationDirectories,
10
10
  isPlainObject,
11
11
  stableJsonStringify,
12
12
  writeInitialMigrationScaffold,
13
13
  writeMigrationConfig
14
- } from "./cli-hb9vpsev.js";
14
+ } from "./cli-2rqf6t0b.js";
15
15
  import {
16
16
  getBuiltInSharedTemplateLayerDir,
17
17
  getBuiltInTemplateLayerDirs,
@@ -44,6 +44,8 @@ import {
44
44
  buildBlockCssClassName,
45
45
  buildFrontendCssClassName,
46
46
  normalizeBlockSlug,
47
+ pathExists,
48
+ readOptionalUtf8File,
47
49
  resolveScaffoldIdentifiers,
48
50
  toPascalCase,
49
51
  toSegmentPascalCase,
@@ -51,7 +53,7 @@ import {
51
53
  toTitleCase,
52
54
  validateBlockSlug,
53
55
  validateNamespace
54
- } from "./cli-j30rk466.js";
56
+ } from "./cli-ta3y0hp2.js";
55
57
  import {
56
58
  createManagedTempRoot
57
59
  } from "./cli-t73q5aqz.js";
@@ -66,7 +68,7 @@ import {
66
68
  formatRunScript,
67
69
  getPackageManager,
68
70
  transformPackageManagerText
69
- } from "./cli-6bhfzq5e.js";
71
+ } from "./cli-52ke0ptp.js";
70
72
  import {
71
73
  __commonJS,
72
74
  __require,
@@ -5682,10 +5684,7 @@ function stringifyStarterManifest(document) {
5682
5684
  // ../wp-typia-project-tools/src/runtime/scaffold-bootstrap.ts
5683
5685
  var EPHEMERAL_NODE_MODULES_LINK_TYPE = process.platform === "win32" ? "junction" : "dir";
5684
5686
  async function ensureScaffoldDirectory(targetDir, allowExisting = false) {
5685
- if (!fs2.existsSync(targetDir)) {
5686
- await fsp3.mkdir(targetDir, { recursive: true });
5687
- return;
5688
- }
5687
+ await fsp3.mkdir(targetDir, { recursive: true });
5689
5688
  if (allowExisting) {
5690
5689
  return;
5691
5690
  }
@@ -5767,14 +5766,14 @@ async function applyWorkspaceMigrationCapability(projectDir, packageManager) {
5767
5766
  ensureMigrationDirectories(projectDir, []);
5768
5767
  writeInitialMigrationScaffold(projectDir, "v1", []);
5769
5768
  }
5770
- function resolveScaffoldGeneratorNodeModulesPath() {
5769
+ async function resolveScaffoldGeneratorNodeModulesPath() {
5771
5770
  const candidates = [
5772
5771
  path4.join(PROJECT_TOOLS_PACKAGE_ROOT, "node_modules"),
5773
5772
  path4.resolve(PROJECT_TOOLS_PACKAGE_ROOT, "..", ".."),
5774
5773
  path4.resolve(PROJECT_TOOLS_PACKAGE_ROOT, "..", "..", "node_modules")
5775
5774
  ];
5776
5775
  for (const candidate of candidates) {
5777
- if (fs2.existsSync(path4.join(candidate, "typia", "package.json"))) {
5776
+ if (await pathExists(path4.join(candidate, "typia", "package.json"))) {
5778
5777
  return candidate;
5779
5778
  }
5780
5779
  }
@@ -5782,11 +5781,11 @@ function resolveScaffoldGeneratorNodeModulesPath() {
5782
5781
  }
5783
5782
  async function withEphemeralScaffoldNodeModules(targetDir, callback) {
5784
5783
  const targetNodeModulesPath = path4.join(targetDir, "node_modules");
5785
- if (fs2.existsSync(targetNodeModulesPath)) {
5784
+ if (await pathExists(targetNodeModulesPath)) {
5786
5785
  await callback();
5787
5786
  return;
5788
5787
  }
5789
- const sourceNodeModulesPath = resolveScaffoldGeneratorNodeModulesPath();
5788
+ const sourceNodeModulesPath = await resolveScaffoldGeneratorNodeModulesPath();
5790
5789
  if (!sourceNodeModulesPath) {
5791
5790
  throw new Error("Unable to resolve a node_modules directory with typia for scaffold-time REST artifact generation.");
5792
5791
  }
@@ -6219,12 +6218,11 @@ async function collectScaffoldAnswers({
6219
6218
  }
6220
6219
 
6221
6220
  // ../wp-typia-project-tools/src/runtime/scaffold.ts
6222
- import fs15 from "fs";
6223
- import { promises as fsp11 } from "fs";
6221
+ import fs12 from "fs";
6222
+ import { promises as fsp12 } from "fs";
6224
6223
  import path18 from "path";
6225
6224
 
6226
6225
  // ../wp-typia-project-tools/src/runtime/scaffold-apply-utils.ts
6227
- import fs6 from "fs";
6228
6226
  import { promises as fsp6 } from "fs";
6229
6227
  import path11 from "path";
6230
6228
  import { execSync as execSync3 } from "child_process";
@@ -6458,6 +6456,18 @@ function formatReadmeTemplateIdentity(templateId) {
6458
6456
  return [`- Template id: ${templateId}`, "- Type: custom or external scaffold"].join(`
6459
6457
  `);
6460
6458
  }
6459
+ function getPackageManagerInstallGuidance(packageManager) {
6460
+ if (packageManager !== "npm") {
6461
+ return "";
6462
+ }
6463
+ const installCommand = formatInstallCommand(packageManager);
6464
+ return [
6465
+ "",
6466
+ `> npm note: the scaffold uses \`${installCommand}\` for the first install so npm does not spend the initial create flow in the audit resolver. Run \`npm audit\` separately when you want npm vulnerability output.`,
6467
+ "> If npm prints React peer dependency noise from WordPress block-editor packages, validate with `npm run typecheck` and `npm run build` before changing WordPress package ranges."
6468
+ ].join(`
6469
+ `);
6470
+ }
6461
6471
  function buildReadme(templateId, variables, packageManager, {
6462
6472
  withMigrationUi = false,
6463
6473
  withTestPreset = false,
@@ -6552,6 +6562,7 @@ ${formatReadmeTemplateIdentity(templateId)}
6552
6562
  ${formatInstallCommand(packageManager)}
6553
6563
  ${formatRunScript(packageManager, developmentScript)}
6554
6564
  \`\`\`
6565
+ ${getPackageManagerInstallGuidance(packageManager)}
6555
6566
 
6556
6567
  ${getQuickStartWorkflowNote(packageManager, templateId, {
6557
6568
  compoundPersistenceEnabled
@@ -6647,7 +6658,6 @@ function mergeTextLines(primaryContent, existingContent) {
6647
6658
  }
6648
6659
 
6649
6660
  // ../wp-typia-project-tools/src/runtime/scaffold-package-manager-files.ts
6650
- import fs4 from "fs";
6651
6661
  import { promises as fsp5 } from "fs";
6652
6662
  import { execSync as execSync2 } from "child_process";
6653
6663
  import path9 from "path";
@@ -6668,11 +6678,12 @@ async function normalizePackageManagerFiles(targetDir, packageManagerId) {
6668
6678
  }
6669
6679
  async function normalizePackageJson(targetDir, packageManagerId) {
6670
6680
  const packageJsonPath = path9.join(targetDir, "package.json");
6671
- if (!fs4.existsSync(packageJsonPath)) {
6681
+ const packageJsonSource = await readOptionalUtf8File(packageJsonPath);
6682
+ if (packageJsonSource === null) {
6672
6683
  return;
6673
6684
  }
6674
6685
  const packageManager = getPackageManager(packageManagerId);
6675
- const packageJson = JSON.parse(await fsp5.readFile(packageJsonPath, "utf8"));
6686
+ const packageJson = JSON.parse(packageJsonSource);
6676
6687
  if (packageManagerId === "npm") {
6677
6688
  delete packageJson.packageManager;
6678
6689
  } else {
@@ -6708,7 +6719,7 @@ async function defaultInstallDependencies({
6708
6719
  });
6709
6720
  }
6710
6721
  // ../wp-typia-project-tools/src/runtime/scaffold-repository-reference.ts
6711
- import fs5 from "fs";
6722
+ import fs4 from "fs";
6712
6723
  import { createRequire } from "module";
6713
6724
  import path10 from "path";
6714
6725
  var require2 = createRequire(import.meta.url);
@@ -6718,7 +6729,7 @@ function getErrorCode(error) {
6718
6729
  }
6719
6730
  function readRepositoryPackageManifest(packageJsonPath) {
6720
6731
  try {
6721
- return JSON.parse(fs5.readFileSync(packageJsonPath, "utf8"));
6732
+ return JSON.parse(fs4.readFileSync(packageJsonPath, "utf8"));
6722
6733
  } catch (error) {
6723
6734
  if (getErrorCode(error) === "ENOENT") {
6724
6735
  return null;
@@ -6813,10 +6824,7 @@ var LOCKFILES2 = {
6813
6824
  yarn: ["yarn.lock"]
6814
6825
  };
6815
6826
  async function ensureDirectory(targetDir, allowExisting = false) {
6816
- if (!fs6.existsSync(targetDir)) {
6817
- await fsp6.mkdir(targetDir, { recursive: true });
6818
- return;
6819
- }
6827
+ await fsp6.mkdir(targetDir, { recursive: true });
6820
6828
  if (allowExisting) {
6821
6829
  return;
6822
6830
  }
@@ -6851,7 +6859,7 @@ async function writeBuiltInCodeArtifacts(targetDir, codeArtifacts) {
6851
6859
  await fsp6.writeFile(destinationPath, artifact.source, "utf8");
6852
6860
  }
6853
6861
  }
6854
- function resolveScaffoldGeneratorNodeModulesPath2() {
6862
+ async function resolveScaffoldGeneratorNodeModulesPath2() {
6855
6863
  const projectToolsPackageRoot = path11.resolve(__dirname2, "..", "..");
6856
6864
  const candidates = [
6857
6865
  path11.join(projectToolsPackageRoot, "node_modules"),
@@ -6859,7 +6867,7 @@ function resolveScaffoldGeneratorNodeModulesPath2() {
6859
6867
  path11.resolve(projectToolsPackageRoot, "..", "..", "node_modules")
6860
6868
  ];
6861
6869
  for (const candidate of candidates) {
6862
- if (fs6.existsSync(path11.join(candidate, "typia", "package.json"))) {
6870
+ if (await pathExists(path11.join(candidate, "typia", "package.json"))) {
6863
6871
  return candidate;
6864
6872
  }
6865
6873
  }
@@ -6867,11 +6875,11 @@ function resolveScaffoldGeneratorNodeModulesPath2() {
6867
6875
  }
6868
6876
  async function withEphemeralScaffoldNodeModules2(targetDir, callback) {
6869
6877
  const targetNodeModulesPath = path11.join(targetDir, "node_modules");
6870
- if (fs6.existsSync(targetNodeModulesPath)) {
6878
+ if (await pathExists(targetNodeModulesPath)) {
6871
6879
  await callback();
6872
6880
  return;
6873
6881
  }
6874
- const sourceNodeModulesPath = resolveScaffoldGeneratorNodeModulesPath2();
6882
+ const sourceNodeModulesPath = await resolveScaffoldGeneratorNodeModulesPath2();
6875
6883
  if (!sourceNodeModulesPath) {
6876
6884
  throw new Error("Unable to resolve a node_modules directory with typia for scaffold-time REST artifact generation.");
6877
6885
  }
@@ -6912,9 +6920,7 @@ async function normalizePackageManagerFiles2(targetDir, packageManagerId) {
6912
6920
  `, "utf8");
6913
6921
  return;
6914
6922
  }
6915
- if (fs6.existsSync(yarnRcPath)) {
6916
- await fsp6.rm(yarnRcPath, { force: true });
6917
- }
6923
+ await fsp6.rm(yarnRcPath, { force: true });
6918
6924
  }
6919
6925
  async function removeQueryLoopPlaceholderFiles(projectDir, templateId) {
6920
6926
  if (templateId !== "query-loop") {
@@ -6931,10 +6937,7 @@ async function removeUnexpectedLockfiles2(targetDir, packageManagerId) {
6931
6937
  if (keep.has(filename)) {
6932
6938
  return;
6933
6939
  }
6934
- const filePath = path11.join(targetDir, filename);
6935
- if (fs6.existsSync(filePath)) {
6936
- await fsp6.rm(filePath, { force: true });
6937
- }
6940
+ await fsp6.rm(path11.join(targetDir, filename), { force: true });
6938
6941
  }));
6939
6942
  }
6940
6943
  async function replaceTextRecursively(targetDir, packageManagerId, {
@@ -7044,7 +7047,7 @@ async function applyBuiltInScaffoldProjectFiles({
7044
7047
  title: "Finalizing scaffold output"
7045
7048
  });
7046
7049
  const readmePath = path11.join(projectDir, "README.md");
7047
- if (!fs6.existsSync(readmePath)) {
7050
+ if (!await pathExists(readmePath)) {
7048
7051
  await fsp6.writeFile(readmePath, readmeContent ?? buildReadme(templateId, variables, packageManager, {
7049
7052
  withMigrationUi,
7050
7053
  withTestPreset,
@@ -7052,7 +7055,7 @@ async function applyBuiltInScaffoldProjectFiles({
7052
7055
  }), "utf8");
7053
7056
  }
7054
7057
  const gitignorePath = path11.join(projectDir, ".gitignore");
7055
- const existingGitignore = fs6.existsSync(gitignorePath) ? await fsp6.readFile(gitignorePath, "utf8") : "";
7058
+ const existingGitignore = await readOptionalUtf8File(gitignorePath) ?? "";
7056
7059
  await fsp6.writeFile(gitignorePath, mergeTextLines(gitignoreContent ?? buildGitignore(), existingGitignore), "utf8");
7057
7060
  await normalizePackageJson(projectDir, packageManager);
7058
7061
  await applyGeneratedProjectDxPackageJson({
@@ -7084,11 +7087,10 @@ async function applyBuiltInScaffoldProjectFiles({
7084
7087
  }
7085
7088
 
7086
7089
  // ../wp-typia-project-tools/src/runtime/template-source-normalization.ts
7087
- import fs11 from "fs";
7088
7090
  import path15 from "path";
7089
7091
 
7090
7092
  // ../wp-typia-project-tools/src/runtime/template-layers.ts
7091
- import fs7 from "fs";
7093
+ import fs5 from "fs";
7092
7094
  import path12 from "path";
7093
7095
  import { promises as fsp7 } from "fs";
7094
7096
  var TEMPLATE_LAYER_MANIFEST_FILENAME = "wp-typia.layers.json";
@@ -7140,7 +7142,7 @@ function parseLayerDefinition(layerId, value) {
7140
7142
  }
7141
7143
  async function loadExternalTemplateLayerManifest(sourceRoot) {
7142
7144
  const manifestPath = path12.join(sourceRoot, TEMPLATE_LAYER_MANIFEST_FILENAME);
7143
- if (!fs7.existsSync(manifestPath)) {
7145
+ if (!fs5.existsSync(manifestPath)) {
7144
7146
  return null;
7145
7147
  }
7146
7148
  const raw = JSON.parse(await fsp7.readFile(manifestPath, "utf8"));
@@ -7275,12 +7277,13 @@ async function assertExternalTemplateLayersDoNotWriteProtectedOutputs({
7275
7277
  }
7276
7278
 
7277
7279
  // ../wp-typia-project-tools/src/runtime/template-source-external.ts
7278
- import fs9 from "fs";
7280
+ import fs7 from "fs";
7281
+ import { promises as fsp8 } from "fs";
7279
7282
  import path13 from "path";
7280
7283
  import { pathToFileURL } from "url";
7281
7284
 
7282
7285
  // ../wp-typia-project-tools/src/runtime/external-template-guards.ts
7283
- import fs8 from "fs";
7286
+ import fs6 from "fs";
7284
7287
  var TEMPLATE_SOURCE_TIMEOUT_CODE = "template-source-timeout";
7285
7288
  var TEMPLATE_SOURCE_TOO_LARGE_CODE = "template-source-too-large";
7286
7289
  var DEFAULT_EXTERNAL_TEMPLATE_TIMEOUT_MS = 20000;
@@ -7322,7 +7325,7 @@ function createExternalTemplateTooLargeError(label, maxBytes) {
7322
7325
  return createTemplateGuardError(TEMPLATE_SOURCE_TOO_LARGE_CODE, `${label} exceeded the external template size limit (${maxBytes} bytes).`);
7323
7326
  }
7324
7327
  function assertExternalTemplateFileSize(filePath, options) {
7325
- const stats = fs8.statSync(filePath);
7328
+ const stats = fs6.statSync(filePath);
7326
7329
  if (stats.size > options.maxBytes) {
7327
7330
  throw createExternalTemplateTooLargeError(options.label, options.maxBytes);
7328
7331
  }
@@ -7427,17 +7430,17 @@ function resolveSourceSubpath(sourceDir, relativePath) {
7427
7430
  }
7428
7431
  return targetPath;
7429
7432
  }
7430
- function getExternalTemplateEntry(sourceDir) {
7433
+ async function findExternalTemplateEntry(sourceDir) {
7431
7434
  for (const filename of EXTERNAL_TEMPLATE_ENTRY_CANDIDATES) {
7432
7435
  const candidate = path13.join(sourceDir, filename);
7433
- if (fs9.existsSync(candidate)) {
7436
+ if (await pathExists(candidate)) {
7434
7437
  return candidate;
7435
7438
  }
7436
7439
  }
7437
7440
  return null;
7438
7441
  }
7439
7442
  async function loadExternalTemplateConfig(sourceDir) {
7440
- const entryPath = getExternalTemplateEntry(sourceDir);
7443
+ const entryPath = await findExternalTemplateEntry(sourceDir);
7441
7444
  if (!entryPath) {
7442
7445
  throw new Error(`No external template config entry found in ${sourceDir}.`);
7443
7446
  }
@@ -7445,7 +7448,8 @@ async function loadExternalTemplateConfig(sourceDir) {
7445
7448
  label: `External template config "${entryPath}"`,
7446
7449
  maxBytes: getExternalTemplateConfigMaxBytes()
7447
7450
  });
7448
- const moduleUrl = `${pathToFileURL(entryPath).href}?mtime=${fs9.statSync(entryPath).mtimeMs}`;
7451
+ const entryStats = await fsp8.stat(entryPath);
7452
+ const moduleUrl = `${pathToFileURL(entryPath).href}?mtime=${entryStats.mtimeMs}`;
7449
7453
  const loadedModule = await withExternalTemplateTimeout(`loading external template config "${entryPath}"`, () => import(moduleUrl));
7450
7454
  const loadedConfig = loadedModule.default ?? loadedModule;
7451
7455
  if (!isPlainObject(loadedConfig)) {
@@ -7558,15 +7562,16 @@ async function renderCreateBlockExternalTemplate(sourceDir, context, requestedVa
7558
7562
  await copyRenderedDirectory(blockTemplateDir, blockDir, view, {
7559
7563
  filter: (sourcePath, _destinationPath, entry) => {
7560
7564
  const mustacheVariantPath = path13.join(path13.dirname(sourcePath), `${entry.name}.mustache`);
7561
- return !(entry.isFile() && (entry.name === "package.json" || entry.name === "README.md") && fs9.existsSync(mustacheVariantPath));
7565
+ return !(entry.isFile() && (entry.name === "package.json" || entry.name === "README.md") && fs7.existsSync(mustacheVariantPath));
7562
7566
  }
7563
7567
  });
7564
7568
  const assetsPath = typeof variantConfig.assetsPath === "string" ? variantConfig.assetsPath : config.assetsPath;
7565
7569
  if (typeof assetsPath === "string" && assetsPath.trim().length > 0) {
7566
7570
  await copyRawDirectory(resolveSourceSubpath(sourceDir, assetsPath), path13.join(tempRoot, "assets"));
7567
7571
  }
7572
+ const assetsDir = path13.join(tempRoot, "assets");
7568
7573
  return {
7569
- assetsDir: fs9.existsSync(path13.join(tempRoot, "assets")) ? path13.join(tempRoot, "assets") : undefined,
7574
+ assetsDir: await pathExists(assetsDir) ? assetsDir : undefined,
7570
7575
  blockDir,
7571
7576
  cleanup,
7572
7577
  formatHint,
@@ -7581,8 +7586,8 @@ async function renderCreateBlockExternalTemplate(sourceDir, context, requestedVa
7581
7586
  }
7582
7587
 
7583
7588
  // ../wp-typia-project-tools/src/runtime/template-source-remote.ts
7584
- import fs10 from "fs";
7585
- import { promises as fsp8 } from "fs";
7589
+ import fs8 from "fs";
7590
+ import { promises as fsp9 } from "fs";
7586
7591
  import path14 from "path";
7587
7592
  async function cleanupSeedRootPair(cleanup, seedCleanup) {
7588
7593
  let cleanupError;
@@ -7603,32 +7608,32 @@ async function cleanupSeedRootPair(cleanup, seedCleanup) {
7603
7608
  function getDefaultCategoryFromBlockJson(blockJson) {
7604
7609
  return typeof blockJson.category === "string" && blockJson.category.trim().length > 0 ? blockJson.category.trim() : "widgets";
7605
7610
  }
7606
- function readRemoteBlockJson(blockDir) {
7607
- const sourceRoot = fs10.existsSync(path14.join(blockDir, "src")) ? path14.join(blockDir, "src") : blockDir;
7611
+ async function readRemoteBlockJsonAsync(blockDir) {
7612
+ const sourceRoot = await getSeedSourceRoot(blockDir);
7608
7613
  for (const candidate of [
7609
7614
  path14.join(blockDir, "block.json"),
7610
7615
  path14.join(sourceRoot, "block.json")
7611
7616
  ]) {
7612
- if (fs10.existsSync(candidate)) {
7613
- return JSON.parse(fs10.readFileSync(candidate, "utf8"));
7617
+ if (await pathExists(candidate)) {
7618
+ return JSON.parse(await fsp9.readFile(candidate, "utf8"));
7614
7619
  }
7615
7620
  }
7616
7621
  throw new Error(`Unable to locate block.json in ${blockDir}`);
7617
7622
  }
7618
- function getDefaultCategory(sourceDir) {
7623
+ async function getDefaultCategoryAsync(sourceDir) {
7619
7624
  try {
7620
- const blockJson = readRemoteBlockJson(sourceDir);
7625
+ const blockJson = await readRemoteBlockJsonAsync(sourceDir);
7621
7626
  return getDefaultCategoryFromBlockJson(blockJson);
7622
7627
  } catch {
7623
7628
  return "widgets";
7624
7629
  }
7625
7630
  }
7626
- function readTemplatePackageJson(sourceDir) {
7631
+ async function readTemplatePackageJsonAsync(sourceDir) {
7627
7632
  for (const candidate of [
7628
7633
  path14.join(sourceDir, "package.json.mustache"),
7629
7634
  path14.join(sourceDir, "package.json")
7630
7635
  ]) {
7631
- if (!fs10.existsSync(candidate)) {
7636
+ if (!await pathExists(candidate)) {
7632
7637
  continue;
7633
7638
  }
7634
7639
  try {
@@ -7637,7 +7642,7 @@ function readTemplatePackageJson(sourceDir) {
7637
7642
  maxBytes: getExternalTemplatePackageJsonMaxBytes()
7638
7643
  });
7639
7644
  return {
7640
- packageJson: JSON.parse(fs10.readFileSync(candidate, "utf8")),
7645
+ packageJson: JSON.parse(await fsp9.readFile(candidate, "utf8")),
7641
7646
  sourcePath: candidate
7642
7647
  };
7643
7648
  } catch (error) {
@@ -7647,8 +7652,8 @@ function readTemplatePackageJson(sourceDir) {
7647
7652
  }
7648
7653
  return null;
7649
7654
  }
7650
- function getTemplateProjectType(sourceDir) {
7651
- const packageJsonEntry = readTemplatePackageJson(sourceDir);
7655
+ async function getTemplateProjectTypeAsync(sourceDir) {
7656
+ const packageJsonEntry = await readTemplatePackageJsonAsync(sourceDir);
7652
7657
  if (!packageJsonEntry) {
7653
7658
  return null;
7654
7659
  }
@@ -7668,11 +7673,11 @@ async function normalizeWpTypiaTemplateSeed(seed) {
7668
7673
  await copyRawDirectory(seed.blockDir, normalizedDir, {
7669
7674
  filter: (sourcePath, _targetPath, entry) => {
7670
7675
  const mustacheVariantPath = path14.join(path14.dirname(sourcePath), `${entry.name}.mustache`);
7671
- return !(entry.isFile() && (entry.name === "package.json" || entry.name === "README.md") && fs10.existsSync(mustacheVariantPath));
7676
+ return !(entry.isFile() && (entry.name === "package.json" || entry.name === "README.md") && fs8.existsSync(mustacheVariantPath));
7672
7677
  }
7673
7678
  });
7674
- if (seed.assetsDir && fs10.existsSync(seed.assetsDir)) {
7675
- await fsp8.cp(seed.assetsDir, path14.join(normalizedDir, "assets"), {
7679
+ if (seed.assetsDir && await pathExists(seed.assetsDir)) {
7680
+ await fsp9.cp(seed.assetsDir, path14.join(normalizedDir, "assets"), {
7676
7681
  recursive: true,
7677
7682
  force: true
7678
7683
  });
@@ -7763,9 +7768,9 @@ async function rewriteBlockJsonImports(directory) {
7763
7768
  const textExtensions = new Set([".js", ".jsx", ".ts", ".tsx"]);
7764
7769
  const targetBlockJsonPath = path14.join(directory, "block.json");
7765
7770
  async function visit(currentPath) {
7766
- const stats = await fsp8.stat(currentPath);
7771
+ const stats = await fsp9.stat(currentPath);
7767
7772
  if (stats.isDirectory()) {
7768
- const entries = await fsp8.readdir(currentPath);
7773
+ const entries = await fsp9.readdir(currentPath);
7769
7774
  for (const entry of entries) {
7770
7775
  await visit(path14.join(currentPath, entry));
7771
7776
  }
@@ -7774,19 +7779,19 @@ async function rewriteBlockJsonImports(directory) {
7774
7779
  if (!textExtensions.has(path14.extname(currentPath))) {
7775
7780
  return;
7776
7781
  }
7777
- const content = await fsp8.readFile(currentPath, "utf8");
7782
+ const content = await fsp9.readFile(currentPath, "utf8");
7778
7783
  const relativeSpecifier = path14.relative(path14.dirname(currentPath), targetBlockJsonPath).replace(/\\/g, "/");
7779
7784
  const normalizedSpecifier = relativeSpecifier.startsWith(".") ? relativeSpecifier : `./${relativeSpecifier}`;
7780
7785
  const next = content.replace(/(['"])\.{1,2}\/[^'"]*block\.json\1/g, `$1${normalizedSpecifier}$1`);
7781
7786
  if (next !== content) {
7782
- await fsp8.writeFile(currentPath, next, "utf8");
7787
+ await fsp9.writeFile(currentPath, next, "utf8");
7783
7788
  }
7784
7789
  }
7785
7790
  await visit(directory);
7786
7791
  }
7787
7792
  async function patchRemotePackageJson(templateDir, needsInteractivity) {
7788
7793
  const packageJsonPath = path14.join(templateDir, "package.json.mustache");
7789
- const packageJson = JSON.parse(await fsp8.readFile(packageJsonPath, "utf8"));
7794
+ const packageJson = JSON.parse(await fsp9.readFile(packageJsonPath, "utf8"));
7790
7795
  const existingDependencies = { ...packageJson.dependencies ?? {} };
7791
7796
  const existingDevDependencies = { ...packageJson.devDependencies ?? {} };
7792
7797
  delete existingDependencies["@wp-typia/project-tools"];
@@ -7806,18 +7811,18 @@ async function patchRemotePackageJson(templateDir, needsInteractivity) {
7806
7811
  } else {
7807
7812
  delete packageJson.dependencies;
7808
7813
  }
7809
- await fsp8.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}
7814
+ await fsp9.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}
7810
7815
  `, "utf8");
7811
7816
  }
7812
- function getSeedSourceRoot(blockDir) {
7813
- return fs10.existsSync(path14.join(blockDir, "src")) ? path14.join(blockDir, "src") : blockDir;
7817
+ async function getSeedSourceRoot(blockDir) {
7818
+ return await pathExists(path14.join(blockDir, "src")) ? path14.join(blockDir, "src") : blockDir;
7814
7819
  }
7815
- function findSeedRenderPhp(seed) {
7820
+ async function findSeedRenderPhp(seed) {
7816
7821
  for (const candidate of [
7817
7822
  path14.join(seed.blockDir, "render.php"),
7818
7823
  path14.join(seed.rootDir, "render.php")
7819
7824
  ]) {
7820
- if (fs10.existsSync(candidate)) {
7825
+ if (await pathExists(candidate)) {
7821
7826
  return candidate;
7822
7827
  }
7823
7828
  }
@@ -7856,47 +7861,54 @@ async function removeSeedEntryConflicts(templateDir) {
7856
7861
  "view.ts",
7857
7862
  "view.tsx"
7858
7863
  ]) {
7859
- await fsp8.rm(path14.join(templateDir, "src", filename), { force: true });
7864
+ await fsp9.rm(path14.join(templateDir, "src", filename), { force: true });
7860
7865
  }
7861
7866
  }
7862
7867
  async function normalizeCreateBlockSubset(seed, context) {
7863
7868
  const { path: tempRoot, cleanup } = await createManagedTempRoot("wp-typia-remote-template-");
7864
7869
  try {
7865
7870
  const templateDir = path14.join(tempRoot, "template");
7866
- const blockJson = readRemoteBlockJson(seed.blockDir);
7867
- const sourceRoot = getSeedSourceRoot(seed.blockDir);
7868
- await fsp8.mkdir(templateDir, { recursive: true });
7871
+ const blockJson = await readRemoteBlockJsonAsync(seed.blockDir);
7872
+ const sourceRoot = await getSeedSourceRoot(seed.blockDir);
7873
+ await fsp9.mkdir(templateDir, { recursive: true });
7869
7874
  for (const layerDir of getBuiltInTemplateLayerDirs("basic")) {
7870
- if (!fs10.existsSync(layerDir)) {
7875
+ if (!await pathExists(layerDir)) {
7871
7876
  if (isOmittableBuiltInTemplateLayerDir("basic", layerDir)) {
7872
7877
  continue;
7873
7878
  }
7874
7879
  throw new Error(`Built-in template layer is missing: ${layerDir}`);
7875
7880
  }
7876
- await fsp8.cp(layerDir, templateDir, {
7881
+ await fsp9.cp(layerDir, templateDir, {
7877
7882
  recursive: true,
7878
7883
  force: true
7879
7884
  });
7880
7885
  }
7881
7886
  await removeSeedEntryConflicts(templateDir);
7882
- await fsp8.cp(sourceRoot, path14.join(templateDir, "src"), {
7887
+ await fsp9.cp(sourceRoot, path14.join(templateDir, "src"), {
7883
7888
  recursive: true,
7884
7889
  force: true
7885
7890
  });
7886
- const remoteRenderPath = findSeedRenderPhp(seed);
7891
+ const remoteRenderPath = await findSeedRenderPhp(seed);
7887
7892
  if (remoteRenderPath) {
7888
- await fsp8.copyFile(remoteRenderPath, path14.join(templateDir, "render.php"));
7893
+ await fsp9.copyFile(remoteRenderPath, path14.join(templateDir, "render.php"));
7889
7894
  }
7890
- if (seed.assetsDir && fs10.existsSync(seed.assetsDir)) {
7891
- await fsp8.cp(seed.assetsDir, path14.join(templateDir, "assets"), {
7895
+ if (seed.assetsDir && await pathExists(seed.assetsDir)) {
7896
+ await fsp9.cp(seed.assetsDir, path14.join(templateDir, "assets"), {
7892
7897
  recursive: true,
7893
7898
  force: true
7894
7899
  });
7895
7900
  }
7896
- await fsp8.writeFile(path14.join(templateDir, "src", "types.ts"), buildRemoteTypesSource(blockJson, context), "utf8");
7897
- await fsp8.writeFile(path14.join(templateDir, "src", "block.json"), buildRemoteBlockJsonTemplate(blockJson), "utf8");
7901
+ await fsp9.writeFile(path14.join(templateDir, "src", "types.ts"), buildRemoteTypesSource(blockJson, context), "utf8");
7902
+ await fsp9.writeFile(path14.join(templateDir, "src", "block.json"), buildRemoteBlockJsonTemplate(blockJson), "utf8");
7898
7903
  await rewriteBlockJsonImports(path14.join(templateDir, "src"));
7899
- const needsInteractivity = typeof blockJson.viewScriptModule === "string" || typeof blockJson.viewScript === "string" || fs10.existsSync(path14.join(templateDir, "src", "view.js")) || fs10.existsSync(path14.join(templateDir, "src", "view.ts")) || fs10.existsSync(path14.join(templateDir, "src", "view.tsx")) || fs10.existsSync(path14.join(templateDir, "src", "interactivity.js")) || fs10.existsSync(path14.join(templateDir, "src", "interactivity.ts"));
7904
+ const needsInteractivity = typeof blockJson.viewScriptModule === "string" || typeof blockJson.viewScript === "string" || (await Promise.all([
7905
+ "view.js",
7906
+ "view.jsx",
7907
+ "view.ts",
7908
+ "view.tsx",
7909
+ "interactivity.js",
7910
+ "interactivity.ts"
7911
+ ].map((filename) => pathExists(path14.join(templateDir, "src", filename))))).some(Boolean);
7900
7912
  await patchRemotePackageJson(templateDir, needsInteractivity);
7901
7913
  return {
7902
7914
  id: "remote:create-block-subset",
@@ -7946,27 +7958,27 @@ function getTemplateVariableContext(variables) {
7946
7958
  };
7947
7959
  }
7948
7960
  async function detectTemplateSourceFormat(sourceDir) {
7949
- if (fs11.existsSync(path15.join(sourceDir, "package.json.mustache"))) {
7961
+ if (await pathExists(path15.join(sourceDir, "package.json.mustache"))) {
7950
7962
  return "wp-typia";
7951
7963
  }
7952
7964
  if (await loadExternalTemplateLayerManifest(sourceDir)) {
7953
7965
  throw new Error(`Template source at ${sourceDir} is an external layer package. External layers currently compose only through built-in scaffolds via the runtime API, not as standalone template ids.`);
7954
7966
  }
7955
- if (getExternalTemplateEntry(sourceDir)) {
7967
+ if (await findExternalTemplateEntry(sourceDir)) {
7956
7968
  return "create-block-external";
7957
7969
  }
7958
- if (getTemplateProjectType(sourceDir) !== null) {
7970
+ if (await getTemplateProjectTypeAsync(sourceDir) !== null) {
7959
7971
  return "wp-typia";
7960
7972
  }
7961
- const sourceRoot = fs11.existsSync(path15.join(sourceDir, "src")) ? path15.join(sourceDir, "src") : sourceDir;
7973
+ const sourceRoot = await pathExists(path15.join(sourceDir, "src")) ? path15.join(sourceDir, "src") : sourceDir;
7962
7974
  const blockJsonCandidates = [
7963
7975
  path15.join(sourceDir, "block.json"),
7964
7976
  path15.join(sourceRoot, "block.json")
7965
7977
  ];
7966
- const hasBlockJson = blockJsonCandidates.some((candidate) => fs11.existsSync(candidate));
7967
- const hasIndexFile = ["index.js", "index.jsx", "index.ts", "index.tsx"].some((filename) => fs11.existsSync(path15.join(sourceRoot, filename)));
7968
- const hasEditFile = ["edit.js", "edit.jsx", "edit.ts", "edit.tsx"].some((filename) => fs11.existsSync(path15.join(sourceRoot, filename)));
7969
- const hasSaveFile = ["save.js", "save.jsx", "save.ts", "save.tsx"].some((filename) => fs11.existsSync(path15.join(sourceRoot, filename)));
7978
+ const hasBlockJson = (await Promise.all(blockJsonCandidates.map((candidate) => pathExists(candidate)))).some(Boolean);
7979
+ const hasIndexFile = (await Promise.all(["index.js", "index.jsx", "index.ts", "index.tsx"].map((filename) => pathExists(path15.join(sourceRoot, filename))))).some(Boolean);
7980
+ const hasEditFile = (await Promise.all(["edit.js", "edit.jsx", "edit.ts", "edit.tsx"].map((filename) => pathExists(path15.join(sourceRoot, filename))))).some(Boolean);
7981
+ const hasSaveFile = (await Promise.all(["save.js", "save.jsx", "save.ts", "save.tsx"].map((filename) => pathExists(path15.join(sourceRoot, filename))))).some(Boolean);
7970
7982
  if (hasBlockJson && hasIndexFile && hasEditFile && hasSaveFile) {
7971
7983
  return "create-block-subset";
7972
7984
  }
@@ -7975,8 +7987,8 @@ async function detectTemplateSourceFormat(sourceDir) {
7975
7987
 
7976
7988
  // ../wp-typia-project-tools/src/runtime/template-source-seeds.ts
7977
7989
  var import_semver = __toESM(require_semver2(), 1);
7978
- import fs14 from "fs";
7979
- import { promises as fsp10 } from "fs";
7990
+ import fs11 from "fs";
7991
+ import { promises as fsp11 } from "fs";
7980
7992
  import { createRequire as createRequire2 } from "module";
7981
7993
  import path17 from "path";
7982
7994
  import { spawnSync } from "child_process";
@@ -10251,7 +10263,7 @@ var Vn = 512 * 1024;
10251
10263
  var $n = pr | ur | dr | mr;
10252
10264
  var lr = !fr && typeof ar == "number" ? ar | ur | dr | mr : null;
10253
10265
  var cs = lr !== null ? () => lr : Kn ? (s3) => s3 < Vn ? $n : "w" : () => "w";
10254
- var fs12 = (s3, t, e) => {
10266
+ var fs9 = (s3, t, e) => {
10255
10267
  try {
10256
10268
  return mi.lchownSync(s3, t, e);
10257
10269
  } catch (i) {
@@ -10300,7 +10312,7 @@ var ds = (s3, t, e, i) => {
10300
10312
  });
10301
10313
  };
10302
10314
  var qn = (s3, t, e, i) => {
10303
- t.isDirectory() && us(Ee.resolve(s3, t.name), e, i), fs12(Ee.resolve(s3, t.name), e, i);
10315
+ t.isDirectory() && us(Ee.resolve(s3, t.name), e, i), fs9(Ee.resolve(s3, t.name), e, i);
10304
10316
  };
10305
10317
  var us = (s3, t, e) => {
10306
10318
  let i;
@@ -10311,12 +10323,12 @@ var us = (s3, t, e) => {
10311
10323
  if (n?.code === "ENOENT")
10312
10324
  return;
10313
10325
  if (n?.code === "ENOTDIR" || n?.code === "ENOTSUP")
10314
- return fs12(s3, t, e);
10326
+ return fs9(s3, t, e);
10315
10327
  throw n;
10316
10328
  }
10317
10329
  for (let r of i)
10318
10330
  qn(s3, r, t, e);
10319
- return fs12(s3, t, e);
10331
+ return fs9(s3, t, e);
10320
10332
  };
10321
10333
  var we = class extends Error {
10322
10334
  path;
@@ -11130,15 +11142,18 @@ var So = (s3) => {
11130
11142
 
11131
11143
  // ../wp-typia-project-tools/src/runtime/template-source-cache.ts
11132
11144
  import { createHash } from "crypto";
11133
- import fs13 from "fs";
11134
- import { promises as fsp9 } from "fs";
11145
+ import fs10 from "fs";
11146
+ import { promises as fsp10 } from "fs";
11135
11147
  import os2 from "os";
11136
11148
  import path16 from "path";
11137
11149
  var EXTERNAL_TEMPLATE_CACHE_ENV = "WP_TYPIA_EXTERNAL_TEMPLATE_CACHE";
11138
11150
  var EXTERNAL_TEMPLATE_CACHE_DIR_ENV = "WP_TYPIA_EXTERNAL_TEMPLATE_CACHE_DIR";
11139
11151
  var EXTERNAL_TEMPLATE_CACHE_TTL_DAYS_ENV = "WP_TYPIA_EXTERNAL_TEMPLATE_CACHE_TTL_DAYS";
11152
+ var EXTERNAL_TEMPLATE_CACHE_PRUNE_INTERVAL_MS_ENV = "WP_TYPIA_EXTERNAL_TEMPLATE_CACHE_PRUNE_INTERVAL_MS";
11140
11153
  var CACHE_MARKER_FILE = "wp-typia-template-cache.json";
11154
+ var CACHE_PRUNE_MARKER_FILE = "wp-typia-template-cache-prune.json";
11141
11155
  var MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
11156
+ var DEFAULT_CACHE_PRUNE_INTERVAL_MS = 60 * 60 * 1000;
11142
11157
  var PRIVATE_CACHE_DIRECTORY_MODE = 448;
11143
11158
  var REDACTED_CACHE_METADATA_VALUE = "[redacted]";
11144
11159
  var DISABLED_CACHE_VALUES = new Set(["0", "false", "no", "off"]);
@@ -11186,12 +11201,33 @@ function resolveExternalTemplateCacheTtlMs(options = {}) {
11186
11201
  const ttlMs = ttlDays * MILLISECONDS_PER_DAY;
11187
11202
  return Number.isFinite(ttlMs) ? ttlMs : null;
11188
11203
  }
11204
+ function parseExternalTemplateCachePruneIntervalMs(value) {
11205
+ if (typeof value !== "string" && typeof value !== "number") {
11206
+ return null;
11207
+ }
11208
+ const intervalMs = typeof value === "number" ? value : Number(value.trim());
11209
+ if (!Number.isFinite(intervalMs) || intervalMs <= 0) {
11210
+ return null;
11211
+ }
11212
+ return intervalMs;
11213
+ }
11214
+ function resolveExternalTemplateCachePruneIntervalMs(options = {}) {
11215
+ if (options.pruneIntervalMs !== undefined) {
11216
+ return parseExternalTemplateCachePruneIntervalMs(options.pruneIntervalMs);
11217
+ }
11218
+ const env = options.env ?? process.env;
11219
+ const envValue = env[EXTERNAL_TEMPLATE_CACHE_PRUNE_INTERVAL_MS_ENV];
11220
+ if (envValue === undefined) {
11221
+ return DEFAULT_CACHE_PRUNE_INTERVAL_MS;
11222
+ }
11223
+ return parseExternalTemplateCachePruneIntervalMs(envValue);
11224
+ }
11189
11225
  function createExternalTemplateCacheKey(keyParts) {
11190
11226
  return createHash("sha256").update(JSON.stringify(keyParts)).digest("hex");
11191
11227
  }
11192
- async function pathExists(filePath) {
11228
+ async function pathExists2(filePath) {
11193
11229
  try {
11194
- await fsp9.access(filePath, fs13.constants.F_OK);
11230
+ await fsp10.access(filePath, fs10.constants.F_OK);
11195
11231
  return true;
11196
11232
  } catch {
11197
11233
  return false;
@@ -11199,7 +11235,7 @@ async function pathExists(filePath) {
11199
11235
  }
11200
11236
  async function isDirectoryPath(directory) {
11201
11237
  try {
11202
- const stats = await fsp9.lstat(directory);
11238
+ const stats = await fsp10.lstat(directory);
11203
11239
  return stats.isDirectory() && !stats.isSymbolicLink();
11204
11240
  } catch {
11205
11241
  return false;
@@ -11210,7 +11246,7 @@ function getNodeErrorCode(error) {
11210
11246
  }
11211
11247
  async function removeTemporaryCacheEntry(entryDir) {
11212
11248
  try {
11213
- await fsp9.rm(entryDir, { force: true, recursive: true });
11249
+ await fsp10.rm(entryDir, { force: true, recursive: true });
11214
11250
  } catch {}
11215
11251
  }
11216
11252
  function getCurrentUserCacheSegment() {
@@ -11229,7 +11265,7 @@ function getCurrentUid() {
11229
11265
  }
11230
11266
  async function isPrivateCacheDirectory(directory) {
11231
11267
  try {
11232
- const stats = await fsp9.lstat(directory);
11268
+ const stats = await fsp10.lstat(directory);
11233
11269
  if (!stats.isDirectory() || stats.isSymbolicLink()) {
11234
11270
  return false;
11235
11271
  }
@@ -11247,11 +11283,11 @@ async function isPrivateCacheDirectory(directory) {
11247
11283
  }
11248
11284
  async function ensurePrivateCacheDirectory(directory) {
11249
11285
  try {
11250
- await fsp9.mkdir(directory, {
11286
+ await fsp10.mkdir(directory, {
11251
11287
  mode: PRIVATE_CACHE_DIRECTORY_MODE,
11252
11288
  recursive: true
11253
11289
  });
11254
- const stats = await fsp9.lstat(directory);
11290
+ const stats = await fsp10.lstat(directory);
11255
11291
  if (!stats.isDirectory() || stats.isSymbolicLink()) {
11256
11292
  return false;
11257
11293
  }
@@ -11261,7 +11297,7 @@ async function ensurePrivateCacheDirectory(directory) {
11261
11297
  }
11262
11298
  if (process.platform !== "win32") {
11263
11299
  if ((stats.mode & 63) !== 0) {
11264
- await fsp9.chmod(directory, PRIVATE_CACHE_DIRECTORY_MODE);
11300
+ await fsp10.chmod(directory, PRIVATE_CACHE_DIRECTORY_MODE);
11265
11301
  }
11266
11302
  }
11267
11303
  return isPrivateCacheDirectory(directory);
@@ -11319,7 +11355,7 @@ function getCacheEntryPaths(descriptor) {
11319
11355
  };
11320
11356
  }
11321
11357
  async function isReusableCacheEntry(entryDir, markerPath, sourceDir) {
11322
- return await isPrivateCacheDirectory(entryDir) && await pathExists(markerPath) && await isDirectoryPath(sourceDir);
11358
+ return await isPrivateCacheDirectory(entryDir) && await pathExists2(markerPath) && await isDirectoryPath(sourceDir);
11323
11359
  }
11324
11360
  function parseCacheMarkerMetadata(markerText) {
11325
11361
  let marker;
@@ -11349,6 +11385,13 @@ function parseCacheMarkerMetadata(markerText) {
11349
11385
  metadata
11350
11386
  };
11351
11387
  }
11388
+ async function readCacheEntryMarker(markerPath) {
11389
+ try {
11390
+ return parseCacheMarkerMetadata(await fsp10.readFile(markerPath, "utf8"));
11391
+ } catch {
11392
+ return null;
11393
+ }
11394
+ }
11352
11395
  function cacheMetadataMatches(actual, expected) {
11353
11396
  return Object.entries(expected).every(([key, value]) => actual[key] === value);
11354
11397
  }
@@ -11356,6 +11399,19 @@ function getExternalTemplateCacheNowMs(now) {
11356
11399
  const nowMs = now instanceof Date ? now.getTime() : typeof now === "number" ? now : Date.now();
11357
11400
  return Number.isFinite(nowMs) ? nowMs : Date.now();
11358
11401
  }
11402
+ function isCacheEntryFreshForTtl(createdAtMs, nowMs, ttlMs) {
11403
+ return ttlMs === null || createdAtMs >= nowMs - ttlMs;
11404
+ }
11405
+ async function getReusableCacheEntryMarker(entryDir, markerPath, sourceDir) {
11406
+ if (!await isReusableCacheEntry(entryDir, markerPath, sourceDir)) {
11407
+ return null;
11408
+ }
11409
+ return readCacheEntryMarker(markerPath);
11410
+ }
11411
+ async function isReusableFreshCacheEntry(entryDir, markerPath, sourceDir, nowMs, ttlMs) {
11412
+ const marker = await getReusableCacheEntryMarker(entryDir, markerPath, sourceDir);
11413
+ return marker !== null && isCacheEntryFreshForTtl(marker.createdAtMs, nowMs, ttlMs);
11414
+ }
11359
11415
  function isPathInsideDirectory(directory, candidatePath) {
11360
11416
  const relativePath = path16.relative(directory, candidatePath);
11361
11417
  return relativePath.length > 0 && !relativePath.startsWith("..") && !path16.isAbsolute(relativePath);
@@ -11365,12 +11421,82 @@ async function removeCacheEntryWithinRoot(cacheRoot, entryDir) {
11365
11421
  return false;
11366
11422
  }
11367
11423
  try {
11368
- await fsp9.rm(entryDir, { force: true, recursive: true });
11424
+ await fsp10.rm(entryDir, { force: true, recursive: true });
11369
11425
  return true;
11370
11426
  } catch {
11371
11427
  return false;
11372
11428
  }
11373
11429
  }
11430
+ function getCachePruneMarkerPath(cacheRoot) {
11431
+ return path16.join(cacheRoot, CACHE_PRUNE_MARKER_FILE);
11432
+ }
11433
+ function parseCachePruneMarker(markerText) {
11434
+ let marker;
11435
+ try {
11436
+ marker = JSON.parse(markerText);
11437
+ } catch {
11438
+ return null;
11439
+ }
11440
+ if (typeof marker !== "object" || marker === null || Array.isArray(marker)) {
11441
+ return null;
11442
+ }
11443
+ const rawPrunedAt = marker.prunedAt;
11444
+ const prunedAtMs = typeof rawPrunedAt === "string" ? Date.parse(rawPrunedAt) : Number.NaN;
11445
+ const rawPruneIntervalMs = marker.pruneIntervalMs;
11446
+ const rawTtlMs = marker.ttlMs;
11447
+ if (typeof rawTtlMs !== "number" || !Number.isFinite(rawTtlMs)) {
11448
+ return null;
11449
+ }
11450
+ if (!Number.isFinite(prunedAtMs)) {
11451
+ return null;
11452
+ }
11453
+ if (rawPruneIntervalMs !== null && (typeof rawPruneIntervalMs !== "number" || !Number.isFinite(rawPruneIntervalMs))) {
11454
+ return null;
11455
+ }
11456
+ return {
11457
+ prunedAtMs,
11458
+ pruneIntervalMs: rawPruneIntervalMs ?? null,
11459
+ ttlMs: rawTtlMs
11460
+ };
11461
+ }
11462
+ async function shouldSkipExternalTemplateCachePrune({
11463
+ cacheRoot,
11464
+ force,
11465
+ nowMs,
11466
+ pruneIntervalMs,
11467
+ ttlMs
11468
+ }) {
11469
+ if (force || pruneIntervalMs === null) {
11470
+ return false;
11471
+ }
11472
+ let markerText;
11473
+ try {
11474
+ markerText = await fsp10.readFile(getCachePruneMarkerPath(cacheRoot), "utf8");
11475
+ } catch {
11476
+ return false;
11477
+ }
11478
+ const marker = parseCachePruneMarker(markerText);
11479
+ if (!marker || marker.ttlMs !== ttlMs || marker.pruneIntervalMs !== pruneIntervalMs) {
11480
+ return false;
11481
+ }
11482
+ const elapsedMs = nowMs - marker.prunedAtMs;
11483
+ return elapsedMs >= 0 && elapsedMs < pruneIntervalMs;
11484
+ }
11485
+ async function writeExternalTemplateCachePruneMarker({
11486
+ cacheRoot,
11487
+ nowMs,
11488
+ pruneIntervalMs,
11489
+ ttlMs
11490
+ }) {
11491
+ try {
11492
+ await fsp10.writeFile(getCachePruneMarkerPath(cacheRoot), `${JSON.stringify({
11493
+ prunedAt: new Date(nowMs).toISOString(),
11494
+ pruneIntervalMs,
11495
+ ttlMs
11496
+ }, null, 2)}
11497
+ `, "utf8");
11498
+ } catch {}
11499
+ }
11374
11500
  async function pruneExternalTemplateCache(options = {}) {
11375
11501
  const env = options.env ?? process.env;
11376
11502
  const cacheRoot = getExternalTemplateCacheRoot(env);
@@ -11378,23 +11504,39 @@ async function pruneExternalTemplateCache(options = {}) {
11378
11504
  env,
11379
11505
  ttlDays: options.ttlDays
11380
11506
  });
11507
+ const pruneIntervalMs = resolveExternalTemplateCachePruneIntervalMs({
11508
+ env,
11509
+ pruneIntervalMs: options.pruneIntervalMs
11510
+ });
11381
11511
  const result = {
11382
11512
  cacheRoot,
11383
11513
  prunedEntries: 0,
11384
11514
  scannedEntries: 0,
11385
11515
  skippedEntries: 0,
11516
+ skippedByThrottle: false,
11386
11517
  ttlMs
11387
11518
  };
11388
11519
  if (ttlMs === null || !await isPrivateCacheDirectory(cacheRoot)) {
11389
11520
  return result;
11390
11521
  }
11522
+ const nowMs = getExternalTemplateCacheNowMs(options.now);
11523
+ if (await shouldSkipExternalTemplateCachePrune({
11524
+ cacheRoot,
11525
+ force: options.force,
11526
+ nowMs,
11527
+ pruneIntervalMs,
11528
+ ttlMs
11529
+ })) {
11530
+ result.skippedByThrottle = true;
11531
+ return result;
11532
+ }
11391
11533
  let namespaceEntries;
11392
11534
  try {
11393
- namespaceEntries = await fsp9.readdir(cacheRoot, { withFileTypes: true });
11535
+ namespaceEntries = await fsp10.readdir(cacheRoot, { withFileTypes: true });
11394
11536
  } catch {
11395
11537
  return result;
11396
11538
  }
11397
- const expiresBeforeMs = getExternalTemplateCacheNowMs(options.now) - ttlMs;
11539
+ const expiresBeforeMs = nowMs - ttlMs;
11398
11540
  for (const namespaceEntry of namespaceEntries) {
11399
11541
  if (!namespaceEntry.isDirectory()) {
11400
11542
  continue;
@@ -11406,7 +11548,7 @@ async function pruneExternalTemplateCache(options = {}) {
11406
11548
  }
11407
11549
  let cacheEntries;
11408
11550
  try {
11409
- cacheEntries = await fsp9.readdir(namespaceDir, { withFileTypes: true });
11551
+ cacheEntries = await fsp10.readdir(namespaceDir, { withFileTypes: true });
11410
11552
  } catch {
11411
11553
  result.skippedEntries += 1;
11412
11554
  continue;
@@ -11427,18 +11569,7 @@ async function pruneExternalTemplateCache(options = {}) {
11427
11569
  }
11428
11570
  const markerPath = path16.join(entryDir, CACHE_MARKER_FILE);
11429
11571
  const sourceDir = path16.join(entryDir, "source");
11430
- if (!await isReusableCacheEntry(entryDir, markerPath, sourceDir)) {
11431
- result.skippedEntries += 1;
11432
- continue;
11433
- }
11434
- let markerText;
11435
- try {
11436
- markerText = await fsp9.readFile(markerPath, "utf8");
11437
- } catch {
11438
- result.skippedEntries += 1;
11439
- continue;
11440
- }
11441
- const marker = parseCacheMarkerMetadata(markerText);
11572
+ const marker = await getReusableCacheEntryMarker(entryDir, markerPath, sourceDir);
11442
11573
  if (!marker) {
11443
11574
  result.skippedEntries += 1;
11444
11575
  continue;
@@ -11452,6 +11583,12 @@ async function pruneExternalTemplateCache(options = {}) {
11452
11583
  }
11453
11584
  }
11454
11585
  }
11586
+ await writeExternalTemplateCachePruneMarker({
11587
+ cacheRoot,
11588
+ nowMs,
11589
+ pruneIntervalMs,
11590
+ ttlMs
11591
+ });
11455
11592
  return result;
11456
11593
  }
11457
11594
  async function findReusableExternalTemplateSourceCache(descriptor) {
@@ -11466,10 +11603,12 @@ async function findReusableExternalTemplateSourceCache(descriptor) {
11466
11603
  if (!await isPrivateCacheDirectory(cacheRoot) || !await isPrivateCacheDirectory(namespaceDir)) {
11467
11604
  return null;
11468
11605
  }
11606
+ const ttlMs = resolveExternalTemplateCacheTtlMs();
11607
+ const nowMs = getExternalTemplateCacheNowMs(undefined);
11469
11608
  await pruneExternalTemplateCache();
11470
11609
  let entries;
11471
11610
  try {
11472
- entries = await fsp9.readdir(namespaceDir, { withFileTypes: true });
11611
+ entries = await fsp10.readdir(namespaceDir, { withFileTypes: true });
11473
11612
  } catch {
11474
11613
  return null;
11475
11614
  }
@@ -11481,17 +11620,8 @@ async function findReusableExternalTemplateSourceCache(descriptor) {
11481
11620
  const entryDir = path16.join(namespaceDir, entry.name);
11482
11621
  const markerPath = path16.join(entryDir, CACHE_MARKER_FILE);
11483
11622
  const sourceDir = path16.join(entryDir, "source");
11484
- if (!await isReusableCacheEntry(entryDir, markerPath, sourceDir)) {
11485
- continue;
11486
- }
11487
- let markerText;
11488
- try {
11489
- markerText = await fsp9.readFile(markerPath, "utf8");
11490
- } catch {
11491
- continue;
11492
- }
11493
- const marker = parseCacheMarkerMetadata(markerText);
11494
- if (!marker || !cacheMetadataMatches(marker.metadata, descriptor.metadata)) {
11623
+ const marker = await getReusableCacheEntryMarker(entryDir, markerPath, sourceDir);
11624
+ if (!marker || !isCacheEntryFreshForTtl(marker.createdAtMs, nowMs, ttlMs) || !cacheMetadataMatches(marker.metadata, descriptor.metadata)) {
11495
11625
  continue;
11496
11626
  }
11497
11627
  if (!bestEntry || marker.createdAtMs > bestEntry.createdAtMs) {
@@ -11518,23 +11648,29 @@ async function resolveExternalTemplateSourceCache(descriptor, populateSourceDir)
11518
11648
  if (!await ensurePrivateCacheDirectory(cacheRoot) || !await ensurePrivateCacheDirectory(namespaceDir)) {
11519
11649
  return null;
11520
11650
  }
11651
+ const ttlMs = resolveExternalTemplateCacheTtlMs();
11652
+ const nowMs = getExternalTemplateCacheNowMs(undefined);
11521
11653
  await pruneExternalTemplateCache();
11522
- if (await isReusableCacheEntry(entryDir, markerPath, sourceDir)) {
11654
+ const existingMarker = await getReusableCacheEntryMarker(entryDir, markerPath, sourceDir);
11655
+ if (existingMarker && isCacheEntryFreshForTtl(existingMarker.createdAtMs, nowMs, ttlMs)) {
11523
11656
  return {
11524
11657
  cacheHit: true,
11525
11658
  sourceDir
11526
11659
  };
11527
11660
  }
11661
+ if (existingMarker) {
11662
+ await removeCacheEntryWithinRoot(cacheRoot, entryDir);
11663
+ }
11528
11664
  const temporaryEntryDir = path16.join(namespaceDir, `.tmp-${cacheKey}-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`);
11529
11665
  const temporarySourceDir = path16.join(temporaryEntryDir, "source");
11530
11666
  let populateFailed = false;
11531
11667
  try {
11532
- await fsp9.mkdir(temporarySourceDir, {
11668
+ await fsp10.mkdir(temporarySourceDir, {
11533
11669
  mode: PRIVATE_CACHE_DIRECTORY_MODE,
11534
11670
  recursive: true
11535
11671
  });
11536
11672
  if (process.platform !== "win32") {
11537
- await fsp9.chmod(temporaryEntryDir, PRIVATE_CACHE_DIRECTORY_MODE);
11673
+ await fsp10.chmod(temporaryEntryDir, PRIVATE_CACHE_DIRECTORY_MODE);
11538
11674
  }
11539
11675
  try {
11540
11676
  await populateSourceDir(temporarySourceDir);
@@ -11542,14 +11678,14 @@ async function resolveExternalTemplateSourceCache(descriptor, populateSourceDir)
11542
11678
  populateFailed = true;
11543
11679
  throw error;
11544
11680
  }
11545
- await fsp9.writeFile(path16.join(temporaryEntryDir, CACHE_MARKER_FILE), `${JSON.stringify({
11681
+ await fsp10.writeFile(path16.join(temporaryEntryDir, CACHE_MARKER_FILE), `${JSON.stringify({
11546
11682
  createdAt: new Date().toISOString(),
11547
11683
  key: cacheKey,
11548
11684
  metadata: sanitizeCacheMetadata(descriptor.metadata),
11549
11685
  namespace: descriptor.namespace
11550
11686
  }, null, 2)}
11551
11687
  `, "utf8");
11552
- await fsp9.rename(temporaryEntryDir, entryDir);
11688
+ await fsp10.rename(temporaryEntryDir, entryDir);
11553
11689
  return {
11554
11690
  cacheHit: false,
11555
11691
  sourceDir
@@ -11563,7 +11699,7 @@ async function resolveExternalTemplateSourceCache(descriptor, populateSourceDir)
11563
11699
  throw error;
11564
11700
  }
11565
11701
  const errorCode = getNodeErrorCode(error);
11566
- if (CACHE_PUBLISH_RACE_ERROR_CODES.has(errorCode) && await isReusableCacheEntry(entryDir, markerPath, sourceDir)) {
11702
+ if (CACHE_PUBLISH_RACE_ERROR_CODES.has(errorCode) && await isReusableFreshCacheEntry(entryDir, markerPath, sourceDir, nowMs, ttlMs)) {
11567
11703
  return {
11568
11704
  cacheHit: true,
11569
11705
  sourceDir
@@ -11621,8 +11757,8 @@ async function downloadNpmTemplateTarball(locator, resolvedVersion, tarballUrl,
11621
11757
  throw new Error(`Failed to download npm template tarball for ${locator.raw}: ${tarballResponse.status}`);
11622
11758
  }
11623
11759
  const tarballPath = path17.join(path17.dirname(unpackDir), "template.tgz");
11624
- await fsp10.mkdir(unpackDir, { recursive: true });
11625
- await fsp10.writeFile(tarballPath, await readBufferResponseWithLimit(tarballResponse, {
11760
+ await fsp11.mkdir(unpackDir, { recursive: true });
11761
+ await fsp11.writeFile(tarballPath, await readBufferResponseWithLimit(tarballResponse, {
11626
11762
  label: `npm template tarball for ${locator.raw}@${resolvedVersion}`,
11627
11763
  maxBytes: getExternalTemplateTarballMaxBytes()
11628
11764
  }));
@@ -11631,7 +11767,7 @@ async function downloadNpmTemplateTarball(locator, resolvedVersion, tarballUrl,
11631
11767
  file: tarballPath,
11632
11768
  strip: 1
11633
11769
  });
11634
- await fsp10.rm(tarballPath, { force: true });
11770
+ await fsp11.rm(tarballPath, { force: true });
11635
11771
  await assertNoSymlinks2(unpackDir);
11636
11772
  }
11637
11773
  function selectRegistryVersion(metadata, locator) {
@@ -11743,8 +11879,8 @@ function resolveInstalledNpmTemplateSource(locator, cwd) {
11743
11879
  return null;
11744
11880
  }
11745
11881
  const workspacePackagesRoot = path17.resolve(PROJECT_TOOLS_PACKAGE_ROOT, "..");
11746
- if (fs14.existsSync(workspacePackagesRoot)) {
11747
- for (const entry of fs14.readdirSync(workspacePackagesRoot, {
11882
+ if (fs11.existsSync(workspacePackagesRoot)) {
11883
+ for (const entry of fs11.readdirSync(workspacePackagesRoot, {
11748
11884
  withFileTypes: true
11749
11885
  })) {
11750
11886
  if (!entry.isDirectory()) {
@@ -11752,10 +11888,10 @@ function resolveInstalledNpmTemplateSource(locator, cwd) {
11752
11888
  }
11753
11889
  const packageDir = path17.join(workspacePackagesRoot, entry.name);
11754
11890
  const packageJsonPath = path17.join(packageDir, "package.json");
11755
- if (!fs14.existsSync(packageJsonPath)) {
11891
+ if (!fs11.existsSync(packageJsonPath)) {
11756
11892
  continue;
11757
11893
  }
11758
- const manifest = JSON.parse(fs14.readFileSync(packageJsonPath, "utf8"));
11894
+ const manifest = JSON.parse(fs11.readFileSync(packageJsonPath, "utf8"));
11759
11895
  if (manifest.name === locator.name) {
11760
11896
  return {
11761
11897
  blockDir: packageDir,
@@ -11766,7 +11902,7 @@ function resolveInstalledNpmTemplateSource(locator, cwd) {
11766
11902
  }
11767
11903
  const workspaceRequire = createRequire2(path17.join(path17.resolve(cwd), "__wp_typia_template_resolver__.cjs"));
11768
11904
  try {
11769
- const packageJsonPath = fs14.realpathSync(workspaceRequire.resolve(`${locator.name}/package.json`));
11905
+ const packageJsonPath = fs11.realpathSync(workspaceRequire.resolve(`${locator.name}/package.json`));
11770
11906
  const sourceDir = path17.dirname(packageJsonPath);
11771
11907
  return {
11772
11908
  blockDir: sourceDir,
@@ -11777,10 +11913,10 @@ function resolveInstalledNpmTemplateSource(locator, cwd) {
11777
11913
  if (errorCode === "MODULE_NOT_FOUND" || errorCode === "ERR_PACKAGE_PATH_NOT_EXPORTED") {
11778
11914
  for (const basePath of workspaceRequire.resolve.paths(locator.name) ?? []) {
11779
11915
  const packageJsonPath = path17.join(basePath, locator.name, "package.json");
11780
- if (!fs14.existsSync(packageJsonPath)) {
11916
+ if (!fs11.existsSync(packageJsonPath)) {
11781
11917
  continue;
11782
11918
  }
11783
- const sourceDir = path17.dirname(fs14.realpathSync(packageJsonPath));
11919
+ const sourceDir = path17.dirname(fs11.realpathSync(packageJsonPath));
11784
11920
  return {
11785
11921
  blockDir: sourceDir,
11786
11922
  rootDir: sourceDir
@@ -11793,25 +11929,25 @@ function resolveInstalledNpmTemplateSource(locator, cwd) {
11793
11929
  }
11794
11930
  function isOfficialWorkspaceTemplateSeed(seed) {
11795
11931
  const packageJsonPath = path17.join(seed.rootDir, "package.json");
11796
- if (!fs14.existsSync(packageJsonPath)) {
11932
+ if (!fs11.existsSync(packageJsonPath)) {
11797
11933
  return false;
11798
11934
  }
11799
11935
  try {
11800
- const packageJson = JSON.parse(fs14.readFileSync(packageJsonPath, "utf8"));
11936
+ const packageJson = JSON.parse(fs11.readFileSync(packageJsonPath, "utf8"));
11801
11937
  return packageJson.name === OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE;
11802
11938
  } catch {
11803
11939
  return false;
11804
11940
  }
11805
11941
  }
11806
11942
  async function assertNoSymlinks2(sourceDir) {
11807
- const stats = await fsp10.lstat(sourceDir);
11943
+ const stats = await fsp11.lstat(sourceDir);
11808
11944
  if (stats.isSymbolicLink()) {
11809
11945
  throw new Error(`Template sources may not include symbolic links: ${sourceDir}`);
11810
11946
  }
11811
11947
  if (!stats.isDirectory()) {
11812
11948
  return;
11813
11949
  }
11814
- for (const entry of await fsp10.readdir(sourceDir)) {
11950
+ for (const entry of await fsp11.readdir(sourceDir)) {
11815
11951
  await assertNoSymlinks2(path17.join(sourceDir, entry));
11816
11952
  }
11817
11953
  }
@@ -11846,7 +11982,7 @@ function resolveGitHubTemplateDirectory(checkoutDir, locator) {
11846
11982
  if (relativeSourceDir.startsWith("..") || path17.isAbsolute(relativeSourceDir)) {
11847
11983
  throw new Error("GitHub template path must stay within the cloned repository.");
11848
11984
  }
11849
- if (!fs14.existsSync(sourceDir)) {
11985
+ if (!fs11.existsSync(sourceDir)) {
11850
11986
  throw new Error(`GitHub template path does not exist: ${locator.sourcePath}`);
11851
11987
  }
11852
11988
  return sourceDir;
@@ -12050,7 +12186,7 @@ async function resolveGitHubTemplateSource(locator) {
12050
12186
  async function resolveTemplateSeed(locator, cwd) {
12051
12187
  if (locator.kind === "path") {
12052
12188
  const sourceDir = path17.resolve(cwd, locator.templatePath);
12053
- if (!fs14.existsSync(sourceDir)) {
12189
+ if (!fs11.existsSync(sourceDir)) {
12054
12190
  throw new Error(`Template path does not exist: ${sourceDir}`);
12055
12191
  }
12056
12192
  await assertNoSymlinks2(sourceDir);
@@ -12095,10 +12231,10 @@ async function resolveTemplateSource(templateId, cwd, variables, variant) {
12095
12231
  throw new Error(`--variant is only supported for official external template configs. Received variant "${variant}" for "${templateId}".`);
12096
12232
  }
12097
12233
  normalizedSeed = await normalizeWpTypiaTemplateSeed(seed);
12098
- const supportsMigrationUi = getTemplateProjectType(seed.blockDir) === "workspace";
12234
+ const supportsMigrationUi = await getTemplateProjectTypeAsync(seed.blockDir) === "workspace";
12099
12235
  return {
12100
12236
  id: templateId,
12101
- defaultCategory: getDefaultCategory(seed.blockDir),
12237
+ defaultCategory: await getDefaultCategoryAsync(seed.blockDir),
12102
12238
  description: "A remote wp-typia template source",
12103
12239
  features: ["Remote source", "wp-typia format"],
12104
12240
  format,
@@ -12115,28 +12251,37 @@ async function resolveTemplateSource(templateId, cwd, variables, variant) {
12115
12251
  const renderedFormat = normalizedSeed.formatHint ?? await detectTemplateSourceFormat(normalizedSeed.blockDir);
12116
12252
  if (renderedFormat === "wp-typia") {
12117
12253
  const normalized3 = await normalizeWpTypiaTemplateSeed(normalizedSeed);
12118
- const supportsMigrationUi = getTemplateProjectType(normalizedSeed.blockDir) === "workspace";
12119
- return {
12120
- cleanup: async () => {
12121
- await normalized3.cleanup?.();
12122
- await seed.cleanup?.();
12123
- },
12124
- defaultCategory: getDefaultCategory(normalizedSeed.blockDir),
12125
- description: "A wp-typia scaffold normalized from an official external template config",
12126
- features: [
12127
- "Remote source",
12128
- "official external template",
12129
- "wp-typia format",
12130
- ...supportsMigrationUi ? ["workspace-capable scaffold"] : []
12131
- ],
12132
- format,
12133
- id: "remote:create-block-external",
12134
- isOfficialWorkspaceTemplate,
12135
- selectedVariant: normalizedSeed.selectedVariant ?? null,
12136
- supportsMigrationUi,
12137
- templateDir: normalized3.blockDir,
12138
- warnings: normalizedSeed.warnings ?? []
12139
- };
12254
+ try {
12255
+ const [projectType, defaultCategory] = await Promise.all([
12256
+ getTemplateProjectTypeAsync(normalizedSeed.blockDir),
12257
+ getDefaultCategoryAsync(normalizedSeed.blockDir)
12258
+ ]);
12259
+ const supportsMigrationUi = projectType === "workspace";
12260
+ return {
12261
+ cleanup: async () => {
12262
+ await normalized3.cleanup?.();
12263
+ await seed.cleanup?.();
12264
+ },
12265
+ defaultCategory,
12266
+ description: "A wp-typia scaffold normalized from an official external template config",
12267
+ features: [
12268
+ "Remote source",
12269
+ "official external template",
12270
+ "wp-typia format",
12271
+ ...supportsMigrationUi ? ["workspace-capable scaffold"] : []
12272
+ ],
12273
+ format,
12274
+ id: "remote:create-block-external",
12275
+ isOfficialWorkspaceTemplate,
12276
+ selectedVariant: normalizedSeed.selectedVariant ?? null,
12277
+ supportsMigrationUi,
12278
+ templateDir: normalized3.blockDir,
12279
+ warnings: normalizedSeed.warnings ?? []
12280
+ };
12281
+ } catch (error) {
12282
+ await normalized3.cleanup?.();
12283
+ throw error;
12284
+ }
12140
12285
  }
12141
12286
  const normalized2 = await normalizeCreateBlockSubset(normalizedSeed, context);
12142
12287
  return {
@@ -12200,10 +12345,16 @@ function compareVersionFloors(left, right) {
12200
12345
  return 0;
12201
12346
  }
12202
12347
  function pickHigherVersionFloor(current, candidate) {
12203
- if (!candidate) {
12348
+ if (current !== undefined) {
12349
+ parseVersionFloorParts(current);
12350
+ }
12351
+ if (candidate !== undefined) {
12352
+ parseVersionFloorParts(candidate);
12353
+ }
12354
+ if (candidate === undefined) {
12204
12355
  return current;
12205
12356
  }
12206
- if (!current) {
12357
+ if (current === undefined) {
12207
12358
  return candidate;
12208
12359
  }
12209
12360
  return compareVersionFloors(current, candidate) >= 0 ? current : candidate;
@@ -12283,6 +12434,24 @@ function normalizeSelections(selections) {
12283
12434
  }
12284
12435
  return [...normalized.values()];
12285
12436
  }
12437
+ function validateFeatureMinimumVersions(definition) {
12438
+ for (const [platform, value] of [
12439
+ ["wordpress", definition.minimumVersions?.wordpress],
12440
+ ["php", definition.minimumVersions?.php]
12441
+ ]) {
12442
+ if (value === undefined) {
12443
+ continue;
12444
+ }
12445
+ try {
12446
+ pickHigherVersionFloor(value, undefined);
12447
+ } catch (error) {
12448
+ throw new Error([
12449
+ `Invalid ${platform} minimum version floor for AI feature "${definition.id}": "${value}".`,
12450
+ 'Expected dotted numeric segments such as "6.7" or "8.1.2".'
12451
+ ].join(" "), { cause: error });
12452
+ }
12453
+ }
12454
+ }
12286
12455
  function resolveAiFeatureCapabilityPlan(selections, registry = DEFAULT_AI_FEATURE_REGISTRY) {
12287
12456
  const requiredFeatures = [];
12288
12457
  const optionalFeatures = [];
@@ -12293,6 +12462,7 @@ function resolveAiFeatureCapabilityPlan(selections, registry = DEFAULT_AI_FEATUR
12293
12462
  if (!definition) {
12294
12463
  throw new Error(`Unknown AI feature capability "${selection.featureId}".`);
12295
12464
  }
12465
+ validateFeatureMinimumVersions(definition);
12296
12466
  const resolvedDefinition = {
12297
12467
  ...definition,
12298
12468
  mode: selection.mode
@@ -12340,17 +12510,44 @@ var REQUIRED_WORKSPACE_ABILITY_COMPATIBILITY = [
12340
12510
  function pickHigherScaffoldVersionFloor(current, candidate) {
12341
12511
  return pickHigherVersionFloor(current, candidate) ?? current;
12342
12512
  }
12343
- function pickHigherHeaderVersionFloor(policyValue, currentValue) {
12513
+ function pickHigherHeaderVersionFloor(policyValue, currentValue, {
12514
+ headerName,
12515
+ onWarning
12516
+ }) {
12344
12517
  const normalizedCurrentValue = currentValue.trim();
12345
12518
  if (!normalizedCurrentValue) {
12346
12519
  return policyValue;
12347
12520
  }
12348
12521
  try {
12349
12522
  return pickHigherScaffoldVersionFloor(policyValue, normalizedCurrentValue);
12350
- } catch {
12523
+ } catch (error) {
12524
+ const warning = [
12525
+ `Invalid plugin header version floor for ${headerName}: "${normalizedCurrentValue}".`,
12526
+ 'Expected dotted numeric segments such as "6.7" or "8.1.2".',
12527
+ `Replacing it with compatibility policy value "${policyValue}".`
12528
+ ].join(" ");
12529
+ if (!onWarning) {
12530
+ throw new Error(warning, { cause: error });
12531
+ }
12532
+ onWarning(warning);
12351
12533
  return policyValue;
12352
12534
  }
12353
12535
  }
12536
+ function assertPolicyVersionFloor(headerName, value) {
12537
+ try {
12538
+ parseVersionFloorParts(value);
12539
+ } catch (error) {
12540
+ throw new Error([
12541
+ `Invalid scaffold compatibility policy floor for ${headerName}: "${value}".`,
12542
+ 'Expected dotted numeric segments such as "6.7" or "8.1.2".'
12543
+ ].join(" "), { cause: error });
12544
+ }
12545
+ }
12546
+ function assertPluginHeaderPolicyVersionFloors(pluginHeader) {
12547
+ assertPolicyVersionFloor("Requires at least", pluginHeader.requiresAtLeast);
12548
+ assertPolicyVersionFloor("Tested up to", pluginHeader.testedUpTo);
12549
+ assertPolicyVersionFloor("Requires PHP", pluginHeader.requiresPhp);
12550
+ }
12354
12551
  function formatRuntimeGate(feature) {
12355
12552
  return (feature.runtimeGates ?? []).map((gate) => `${feature.label}: ${gate.kind} ${gate.value}`);
12356
12553
  }
@@ -12400,17 +12597,21 @@ function renderScaffoldCompatibilityConfig(policy, indent = "\t\t") {
12400
12597
  `).map((line, index) => index === 0 ? line : `${indent}${line}`).join(`
12401
12598
  `);
12402
12599
  }
12403
- function replacePluginHeaderVersionFloor(source, pattern, policyValue) {
12600
+ function replacePluginHeaderVersionFloor(source, pattern, policyValue, headerName, options) {
12404
12601
  return source.replace(pattern, (_match, prefix, currentValue, lineEnding) => {
12405
12602
  const versionPrefix = prefix.endsWith(":") ? `${prefix} ` : prefix;
12406
- return `${versionPrefix}${pickHigherHeaderVersionFloor(policyValue, currentValue)}${lineEnding}`;
12603
+ return `${versionPrefix}${pickHigherHeaderVersionFloor(policyValue, currentValue, {
12604
+ headerName,
12605
+ onWarning: options.onWarning
12606
+ })}${lineEnding}`;
12407
12607
  });
12408
12608
  }
12409
- function updatePluginHeaderCompatibility(source, policy) {
12609
+ function updatePluginHeaderCompatibility(source, policy, options = {}) {
12410
12610
  const { pluginHeader } = policy;
12411
- const nextSource = replacePluginHeaderVersionFloor(source, /(\* Requires at least:[^\S\r\n]*)([^\r\n]*)(\r?)/u, pluginHeader.requiresAtLeast);
12412
- const nextSourceWithTestedUpTo = replacePluginHeaderVersionFloor(nextSource, /(\* Tested up to:[^\S\r\n]*)([^\r\n]*)(\r?)/u, pluginHeader.testedUpTo);
12413
- return replacePluginHeaderVersionFloor(nextSourceWithTestedUpTo, /(\* Requires PHP:[^\S\r\n]*)([^\r\n]*)(\r?)/u, pluginHeader.requiresPhp);
12611
+ assertPluginHeaderPolicyVersionFloors(pluginHeader);
12612
+ const nextSource = replacePluginHeaderVersionFloor(source, /(\* Requires at least:[^\S\r\n]*)([^\r\n]*)(\r?)/u, pluginHeader.requiresAtLeast, "Requires at least", options);
12613
+ const nextSourceWithTestedUpTo = replacePluginHeaderVersionFloor(nextSource, /(\* Tested up to:[^\S\r\n]*)([^\r\n]*)(\r?)/u, pluginHeader.testedUpTo, "Tested up to", options);
12614
+ return replacePluginHeaderVersionFloor(nextSourceWithTestedUpTo, /(\* Requires PHP:[^\S\r\n]*)([^\r\n]*)(\r?)/u, pluginHeader.requiresPhp, "Requires PHP", options);
12414
12615
  }
12415
12616
 
12416
12617
  // ../wp-typia-project-tools/src/runtime/block-generator-service-spec.ts
@@ -17358,16 +17559,16 @@ async function scaffoldProject({
17358
17559
  title: "Finalizing scaffold output"
17359
17560
  });
17360
17561
  const readmePath = path18.join(projectDir, "README.md");
17361
- if (!fs15.existsSync(readmePath)) {
17362
- await fsp11.writeFile(readmePath, buildReadme(resolvedTemplateId, variables, resolvedPackageManager, {
17562
+ if (!fs12.existsSync(readmePath)) {
17563
+ await fsp12.writeFile(readmePath, buildReadme(resolvedTemplateId, variables, resolvedPackageManager, {
17363
17564
  withMigrationUi: isBuiltInTemplate || isWorkspace ? withMigrationUi : false,
17364
17565
  withTestPreset: isBuiltInTemplate ? withTestPreset : false,
17365
17566
  withWpEnv: isBuiltInTemplate ? withWpEnv : false
17366
17567
  }), "utf8");
17367
17568
  }
17368
17569
  const gitignorePath = path18.join(projectDir, ".gitignore");
17369
- const existingGitignore = fs15.existsSync(gitignorePath) ? await fsp11.readFile(gitignorePath, "utf8") : "";
17370
- await fsp11.writeFile(gitignorePath, mergeTextLines(buildGitignore(), existingGitignore), "utf8");
17570
+ const existingGitignore = fs12.existsSync(gitignorePath) ? await fsp12.readFile(gitignorePath, "utf8") : "";
17571
+ await fsp12.writeFile(gitignorePath, mergeTextLines(buildGitignore(), existingGitignore), "utf8");
17371
17572
  await normalizePackageJson(projectDir, resolvedPackageManager);
17372
17573
  if (isBuiltInTemplate) {
17373
17574
  const variableGroups = getScaffoldTemplateVariableGroups(variables);
@@ -17449,4 +17650,4 @@ async function resolveOptionalInteractiveExternalLayerId({
17449
17650
 
17450
17651
  export { syncPersistenceRestArtifacts, copyInterpolatedDirectory, listInterpolatedDirectoryOutputs, getPrimaryDevelopmentScript, getOptionalOnboardingSteps, getOptionalOnboardingNote, getOptionalOnboardingShortNote, formatNonEmptyTargetDirectoryError, require_semver2 as require_semver, parseTemplateLocator, resolveExternalTemplateLayers, resolveTemplateSeed, normalizeOptionalCliString, resolveLocalCliPathOption, assertExternalLayerCompositionOptions, assertBuiltInTemplateVariantAllowed, parseAlternateRenderTargets, parseCompoundInnerBlocksPreset, OPTIONAL_WORDPRESS_AI_CLIENT_COMPATIBILITY, REQUIRED_WORKSPACE_ABILITY_COMPATIBILITY, resolveScaffoldCompatibilityPolicy, createScaffoldCompatibilityConfig, renderScaffoldCompatibilityConfig, updatePluginHeaderCompatibility, getDefaultAnswers, resolveTemplateId, resolvePackageManagerId, collectScaffoldAnswers, DATA_STORAGE_MODES, PERSISTENCE_POLICIES, isDataStorageMode, isPersistencePolicy, scaffoldProject, resolveOptionalInteractiveExternalLayerId };
17451
17652
 
17452
- //# debugId=7E585E3F386D6E1964756E2164756E21
17653
+ //# debugId=F116A78569127ED064756E2164756E21