wp-typia 0.20.5 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,10 @@
1
1
  // @bun
2
2
  import {
3
3
  getPackageVersions
4
- } from "./cli-tesygdnr.js";
4
+ } from "./cli-39er8888.js";
5
5
  import {
6
6
  seedProjectMigrations
7
- } from "./cli-2rev5hqm.js";
7
+ } from "./cli-e623rs7g.js";
8
8
  import {
9
9
  ensureMigrationDirectories,
10
10
  isPlainObject,
@@ -59,10 +59,14 @@ import {
59
59
  toTitleCase,
60
60
  validateBlockSlug,
61
61
  validateNamespace
62
- } from "./cli-3w3qxq9w.js";
62
+ } from "./cli-j180bk07.js";
63
63
  import {
64
64
  createManagedTempRoot
65
65
  } from "./cli-t73q5aqz.js";
66
+ import {
67
+ CLI_DIAGNOSTIC_CODES,
68
+ createCliDiagnosticCodeError
69
+ } from "./cli-p95wr1q8.js";
66
70
  import {
67
71
  __commonJS,
68
72
  __require,
@@ -5274,6 +5278,17 @@ function buildInteractivityTypesSource(variables, attributes) {
5274
5278
  { name: "maxClicks", typeExpression: "number" }
5275
5279
  ],
5276
5280
  name: `${variables.pascalCase}Context`
5281
+ },
5282
+ {
5283
+ members: [
5284
+ { name: "clicks", typeExpression: "number" },
5285
+ { name: "isAnimating", typeExpression: "boolean" },
5286
+ { name: "isVisible", typeExpression: "boolean" },
5287
+ { name: "progress", typeExpression: "number" },
5288
+ { name: "clampedClicks", typeExpression: "number" },
5289
+ { name: "isComplete", typeExpression: "boolean" }
5290
+ ],
5291
+ name: `${variables.pascalCase}State`
5277
5292
  }
5278
5293
  ],
5279
5294
  typeAliases: [
@@ -5803,13 +5818,13 @@ function resolveLocalCliPathOption(options) {
5803
5818
  }
5804
5819
  const resolvedPath = path5.resolve(options.cwd, normalizedValue);
5805
5820
  if (!fs3.existsSync(resolvedPath)) {
5806
- throw new Error(`\`${options.label}\` path does not exist: ${resolvedPath}. Check the path relative to ${options.cwd}.`);
5821
+ throw createCliDiagnosticCodeError(CLI_DIAGNOSTIC_CODES.INVALID_ARGUMENT, `\`${options.label}\` path does not exist: ${resolvedPath}. Check the path relative to ${options.cwd}.`);
5807
5822
  }
5808
5823
  return resolvedPath;
5809
5824
  }
5810
5825
  function assertExternalLayerCompositionOptions(options) {
5811
5826
  if (options.externalLayerId && !options.externalLayerSource) {
5812
- throw new Error("externalLayerId requires externalLayerSource when composing built-in template layers.");
5827
+ throw createCliDiagnosticCodeError(CLI_DIAGNOSTIC_CODES.INVALID_ARGUMENT, "externalLayerId requires externalLayerSource when composing built-in template layers.");
5813
5828
  }
5814
5829
  }
5815
5830
  function createBuiltInVariantErrorMessage(options) {
@@ -5819,7 +5834,7 @@ function assertBuiltInTemplateVariantAllowed(options) {
5819
5834
  if (!options.variant) {
5820
5835
  return;
5821
5836
  }
5822
- throw new Error(createBuiltInVariantErrorMessage({
5837
+ throw createCliDiagnosticCodeError(CLI_DIAGNOSTIC_CODES.INVALID_ARGUMENT, createBuiltInVariantErrorMessage({
5823
5838
  templateId: options.templateId,
5824
5839
  variant: options.variant
5825
5840
  }));
@@ -6036,7 +6051,7 @@ function normalizeQueryPostType(value) {
6036
6051
  }
6037
6052
  const validationResult = validateQueryPostType(value);
6038
6053
  if (validationResult !== true) {
6039
- throw new Error(validationResult);
6054
+ throw createCliDiagnosticCodeError(CLI_DIAGNOSTIC_CODES.INVALID_ARGUMENT, validationResult);
6040
6055
  }
6041
6056
  return value.trim().toLowerCase();
6042
6057
  }
@@ -6108,7 +6123,7 @@ async function resolveTemplateId({
6108
6123
  if (templateId) {
6109
6124
  const normalizedTemplateId = normalizeTemplateSelection(templateId);
6110
6125
  if (isRemovedBuiltInTemplateId(templateId)) {
6111
- throw new Error(getRemovedBuiltInTemplateMessage(templateId));
6126
+ throw createCliDiagnosticCodeError(CLI_DIAGNOSTIC_CODES.UNKNOWN_TEMPLATE, getRemovedBuiltInTemplateMessage(templateId));
6112
6127
  }
6113
6128
  if (normalizedTemplateId === OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE) {
6114
6129
  return normalizedTemplateId;
@@ -6118,10 +6133,10 @@ async function resolveTemplateId({
6118
6133
  }
6119
6134
  const mistypedBuiltInTemplateMessage = getMistypedBuiltInTemplateMessage(templateId);
6120
6135
  if (mistypedBuiltInTemplateMessage) {
6121
- throw new Error(mistypedBuiltInTemplateMessage);
6136
+ throw createCliDiagnosticCodeError(CLI_DIAGNOSTIC_CODES.UNKNOWN_TEMPLATE, mistypedBuiltInTemplateMessage);
6122
6137
  }
6123
6138
  if (!looksLikeExplicitExternalTemplateLocator(normalizedTemplateId)) {
6124
- throw new Error(getUnknownTemplateMessage(templateId));
6139
+ throw createCliDiagnosticCodeError(CLI_DIAGNOSTIC_CODES.UNKNOWN_TEMPLATE, getUnknownTemplateMessage(templateId));
6125
6140
  }
6126
6141
  return normalizedTemplateId;
6127
6142
  }
@@ -6129,7 +6144,7 @@ async function resolveTemplateId({
6129
6144
  return "basic";
6130
6145
  }
6131
6146
  if (!isInteractive || !selectTemplate) {
6132
- throw new Error(`Template is required in non-interactive mode. Use ${TEMPLATE_SELECTION_HINT}.`);
6147
+ throw createCliDiagnosticCodeError(CLI_DIAGNOSTIC_CODES.MISSING_ARGUMENT, `Template is required in non-interactive mode. Use ${TEMPLATE_SELECTION_HINT}.`);
6133
6148
  }
6134
6149
  return normalizeTemplateSelection(await selectTemplate());
6135
6150
  }
@@ -6146,7 +6161,7 @@ async function resolvePackageManagerId({
6146
6161
  return "npm";
6147
6162
  }
6148
6163
  if (!isInteractive || !selectPackageManager) {
6149
- throw new Error(`Package manager is required in non-interactive mode. Use --package-manager <${PACKAGE_MANAGER_IDS.join("|")}>.`);
6164
+ throw createCliDiagnosticCodeError(CLI_DIAGNOSTIC_CODES.MISSING_ARGUMENT, `Package manager is required in non-interactive mode. Use --package-manager <${PACKAGE_MANAGER_IDS.join("|")}>.`);
6150
6165
  }
6151
6166
  return selectPackageManager();
6152
6167
  }
@@ -6204,9 +6219,9 @@ async function collectScaffoldAnswers({
6204
6219
  }
6205
6220
 
6206
6221
  // ../wp-typia-project-tools/src/runtime/scaffold.ts
6207
- import fs14 from "fs";
6208
- import { promises as fsp10 } from "fs";
6209
- import path17 from "path";
6222
+ import fs15 from "fs";
6223
+ import { promises as fsp11 } from "fs";
6224
+ import path18 from "path";
6210
6225
 
6211
6226
  // ../wp-typia-project-tools/src/runtime/scaffold-apply-utils.ts
6212
6227
  import fs6 from "fs";
@@ -6692,7 +6707,6 @@ async function defaultInstallDependencies({
6692
6707
  stdio: "inherit"
6693
6708
  });
6694
6709
  }
6695
-
6696
6710
  // ../wp-typia-project-tools/src/runtime/scaffold-repository-reference.ts
6697
6711
  import fs5 from "fs";
6698
6712
  import { createRequire } from "module";
@@ -7961,10 +7975,10 @@ async function detectTemplateSourceFormat(sourceDir) {
7961
7975
 
7962
7976
  // ../wp-typia-project-tools/src/runtime/template-source-seeds.ts
7963
7977
  var import_semver = __toESM(require_semver2(), 1);
7964
- import fs13 from "fs";
7965
- import { promises as fsp9 } from "fs";
7978
+ import fs14 from "fs";
7979
+ import { promises as fsp10 } from "fs";
7966
7980
  import { createRequire as createRequire2 } from "module";
7967
- import path16 from "path";
7981
+ import path17 from "path";
7968
7982
  import { spawnSync } from "child_process";
7969
7983
 
7970
7984
  // ../../node_modules/.bun/tar@7.5.13/node_modules/tar/dist/esm/index.min.js
@@ -11114,11 +11128,342 @@ var So = (s3) => {
11114
11128
  s3.mtimeCache || (s3.mtimeCache = new Map), s3.filter = t ? (e, i) => t(e, i) && !((s3.mtimeCache?.get(e) ?? i.mtime ?? 0) > (i.mtime ?? 0)) : (e, i) => !((s3.mtimeCache?.get(e) ?? i.mtime ?? 0) > (i.mtime ?? 0));
11115
11129
  };
11116
11130
 
11131
+ // ../wp-typia-project-tools/src/runtime/template-source-cache.ts
11132
+ import { createHash } from "crypto";
11133
+ import fs13 from "fs";
11134
+ import { promises as fsp9 } from "fs";
11135
+ import os2 from "os";
11136
+ import path16 from "path";
11137
+ var EXTERNAL_TEMPLATE_CACHE_ENV = "WP_TYPIA_EXTERNAL_TEMPLATE_CACHE";
11138
+ var EXTERNAL_TEMPLATE_CACHE_DIR_ENV = "WP_TYPIA_EXTERNAL_TEMPLATE_CACHE_DIR";
11139
+ var CACHE_MARKER_FILE = "wp-typia-template-cache.json";
11140
+ var PRIVATE_CACHE_DIRECTORY_MODE = 448;
11141
+ var REDACTED_CACHE_METADATA_VALUE = "[redacted]";
11142
+ var DISABLED_CACHE_VALUES = new Set(["0", "false", "no", "off"]);
11143
+ var CACHE_PUBLISH_RACE_ERROR_CODES = new Set(["EEXIST", "ENOTEMPTY"]);
11144
+ var CACHE_UNAVAILABLE_ERROR_CODES = new Set([
11145
+ "EACCES",
11146
+ "ENOSPC",
11147
+ "ENOTDIR",
11148
+ "EPERM",
11149
+ "EROFS"
11150
+ ]);
11151
+ var URL_LIKE_METADATA_KEY = /(url|uri|registry|tarball)/iu;
11152
+ var SAFE_CACHE_NAMESPACE_SEGMENT = /^[A-Za-z0-9_.-]+$/u;
11153
+ function isExternalTemplateCacheEnabled(env = process.env) {
11154
+ const rawValue2 = env[EXTERNAL_TEMPLATE_CACHE_ENV];
11155
+ if (rawValue2 === undefined) {
11156
+ return true;
11157
+ }
11158
+ return !DISABLED_CACHE_VALUES.has(rawValue2.trim().toLowerCase());
11159
+ }
11160
+ function getExternalTemplateCacheRoot(env = process.env) {
11161
+ const configuredCacheDir = env[EXTERNAL_TEMPLATE_CACHE_DIR_ENV]?.trim();
11162
+ if (configuredCacheDir) {
11163
+ return path16.resolve(configuredCacheDir);
11164
+ }
11165
+ return path16.join(os2.tmpdir(), `wp-typia-template-source-cache-${getCurrentUserCacheSegment()}`);
11166
+ }
11167
+ function createExternalTemplateCacheKey(keyParts) {
11168
+ return createHash("sha256").update(JSON.stringify(keyParts)).digest("hex");
11169
+ }
11170
+ async function pathExists(filePath) {
11171
+ try {
11172
+ await fsp9.access(filePath, fs13.constants.F_OK);
11173
+ return true;
11174
+ } catch {
11175
+ return false;
11176
+ }
11177
+ }
11178
+ async function isDirectoryPath(directory) {
11179
+ try {
11180
+ const stats = await fsp9.lstat(directory);
11181
+ return stats.isDirectory() && !stats.isSymbolicLink();
11182
+ } catch {
11183
+ return false;
11184
+ }
11185
+ }
11186
+ function getNodeErrorCode(error) {
11187
+ return typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
11188
+ }
11189
+ async function removeTemporaryCacheEntry(entryDir) {
11190
+ try {
11191
+ await fsp9.rm(entryDir, { force: true, recursive: true });
11192
+ } catch {}
11193
+ }
11194
+ function getCurrentUserCacheSegment() {
11195
+ if (typeof process.getuid === "function") {
11196
+ return String(process.getuid());
11197
+ }
11198
+ try {
11199
+ const safeUsername = os2.userInfo().username.trim().replace(/[^A-Za-z0-9._-]+/gu, "-");
11200
+ return safeUsername.length > 0 ? safeUsername : "user";
11201
+ } catch {
11202
+ return "user";
11203
+ }
11204
+ }
11205
+ function getCurrentUid() {
11206
+ return typeof process.getuid === "function" ? process.getuid() : null;
11207
+ }
11208
+ async function isPrivateCacheDirectory(directory) {
11209
+ try {
11210
+ const stats = await fsp9.lstat(directory);
11211
+ if (!stats.isDirectory() || stats.isSymbolicLink()) {
11212
+ return false;
11213
+ }
11214
+ const currentUid = getCurrentUid();
11215
+ if (currentUid !== null && stats.uid !== currentUid) {
11216
+ return false;
11217
+ }
11218
+ if (process.platform !== "win32" && (stats.mode & 63) !== 0) {
11219
+ return false;
11220
+ }
11221
+ return true;
11222
+ } catch {
11223
+ return false;
11224
+ }
11225
+ }
11226
+ async function ensurePrivateCacheDirectory(directory) {
11227
+ try {
11228
+ await fsp9.mkdir(directory, {
11229
+ mode: PRIVATE_CACHE_DIRECTORY_MODE,
11230
+ recursive: true
11231
+ });
11232
+ const stats = await fsp9.lstat(directory);
11233
+ if (!stats.isDirectory() || stats.isSymbolicLink()) {
11234
+ return false;
11235
+ }
11236
+ const currentUid = getCurrentUid();
11237
+ if (currentUid !== null && stats.uid !== currentUid) {
11238
+ return false;
11239
+ }
11240
+ if (process.platform !== "win32") {
11241
+ if ((stats.mode & 63) !== 0) {
11242
+ await fsp9.chmod(directory, PRIVATE_CACHE_DIRECTORY_MODE);
11243
+ }
11244
+ }
11245
+ return isPrivateCacheDirectory(directory);
11246
+ } catch {
11247
+ return false;
11248
+ }
11249
+ }
11250
+ function sanitizeCacheMetadataValue(key, value) {
11251
+ if (!URL_LIKE_METADATA_KEY.test(key)) {
11252
+ return value;
11253
+ }
11254
+ try {
11255
+ const url = new URL(value);
11256
+ url.username = "";
11257
+ url.password = "";
11258
+ url.search = "";
11259
+ url.hash = "";
11260
+ return url.toString();
11261
+ } catch {
11262
+ return REDACTED_CACHE_METADATA_VALUE;
11263
+ }
11264
+ }
11265
+ function sanitizeCacheMetadata(metadata) {
11266
+ return Object.fromEntries(Object.entries(metadata).map(([key, value]) => [
11267
+ key,
11268
+ value === null ? null : sanitizeCacheMetadataValue(key, value)
11269
+ ]));
11270
+ }
11271
+ function resolveCacheNamespaceDir(cacheRoot, namespace) {
11272
+ if (namespace === "." || namespace === ".." || !SAFE_CACHE_NAMESPACE_SEGMENT.test(namespace)) {
11273
+ return null;
11274
+ }
11275
+ const namespaceDir = path16.join(cacheRoot, namespace);
11276
+ const relativeNamespaceDir = path16.relative(cacheRoot, namespaceDir);
11277
+ if (relativeNamespaceDir.length === 0 || relativeNamespaceDir.startsWith("..") || path16.isAbsolute(relativeNamespaceDir)) {
11278
+ return null;
11279
+ }
11280
+ return namespaceDir;
11281
+ }
11282
+ function getCacheEntryPaths(descriptor) {
11283
+ const cacheKey = createExternalTemplateCacheKey(descriptor.keyParts);
11284
+ const cacheRoot = getExternalTemplateCacheRoot();
11285
+ const namespaceDir = resolveCacheNamespaceDir(cacheRoot, descriptor.namespace);
11286
+ if (!namespaceDir) {
11287
+ return null;
11288
+ }
11289
+ const entryDir = path16.join(namespaceDir, cacheKey);
11290
+ return {
11291
+ cacheKey,
11292
+ cacheRoot,
11293
+ entryDir,
11294
+ markerPath: path16.join(entryDir, CACHE_MARKER_FILE),
11295
+ namespaceDir,
11296
+ sourceDir: path16.join(entryDir, "source")
11297
+ };
11298
+ }
11299
+ async function isReusableCacheEntry(entryDir, markerPath, sourceDir) {
11300
+ return await isPrivateCacheDirectory(entryDir) && await pathExists(markerPath) && await isDirectoryPath(sourceDir);
11301
+ }
11302
+ function parseCacheMarkerMetadata(markerText) {
11303
+ let marker;
11304
+ try {
11305
+ marker = JSON.parse(markerText);
11306
+ } catch {
11307
+ return null;
11308
+ }
11309
+ if (typeof marker !== "object" || marker === null || Array.isArray(marker)) {
11310
+ return null;
11311
+ }
11312
+ const rawMetadata = marker.metadata;
11313
+ if (typeof rawMetadata !== "object" || rawMetadata === null || Array.isArray(rawMetadata)) {
11314
+ return null;
11315
+ }
11316
+ const metadata = {};
11317
+ for (const [key, value] of Object.entries(rawMetadata)) {
11318
+ if (typeof value !== "string" && value !== null) {
11319
+ return null;
11320
+ }
11321
+ metadata[key] = value;
11322
+ }
11323
+ const rawCreatedAt = marker.createdAt;
11324
+ const createdAtMs = typeof rawCreatedAt === "string" ? Date.parse(rawCreatedAt) : 0;
11325
+ return {
11326
+ createdAtMs: Number.isFinite(createdAtMs) ? createdAtMs : 0,
11327
+ metadata
11328
+ };
11329
+ }
11330
+ function cacheMetadataMatches(actual, expected) {
11331
+ return Object.entries(expected).every(([key, value]) => actual[key] === value);
11332
+ }
11333
+ async function findReusableExternalTemplateSourceCache(descriptor) {
11334
+ if (!isExternalTemplateCacheEnabled()) {
11335
+ return null;
11336
+ }
11337
+ const cacheRoot = getExternalTemplateCacheRoot();
11338
+ const namespaceDir = resolveCacheNamespaceDir(cacheRoot, descriptor.namespace);
11339
+ if (!namespaceDir) {
11340
+ return null;
11341
+ }
11342
+ if (!await isPrivateCacheDirectory(cacheRoot) || !await isPrivateCacheDirectory(namespaceDir)) {
11343
+ return null;
11344
+ }
11345
+ let entries;
11346
+ try {
11347
+ entries = await fsp9.readdir(namespaceDir, { withFileTypes: true });
11348
+ } catch {
11349
+ return null;
11350
+ }
11351
+ let bestEntry = null;
11352
+ for (const entry of entries) {
11353
+ if (!entry.isDirectory()) {
11354
+ continue;
11355
+ }
11356
+ const entryDir = path16.join(namespaceDir, entry.name);
11357
+ const markerPath = path16.join(entryDir, CACHE_MARKER_FILE);
11358
+ const sourceDir = path16.join(entryDir, "source");
11359
+ if (!await isReusableCacheEntry(entryDir, markerPath, sourceDir)) {
11360
+ continue;
11361
+ }
11362
+ let markerText;
11363
+ try {
11364
+ markerText = await fsp9.readFile(markerPath, "utf8");
11365
+ } catch {
11366
+ continue;
11367
+ }
11368
+ const marker = parseCacheMarkerMetadata(markerText);
11369
+ if (!marker || !cacheMetadataMatches(marker.metadata, descriptor.metadata)) {
11370
+ continue;
11371
+ }
11372
+ if (!bestEntry || marker.createdAtMs > bestEntry.createdAtMs) {
11373
+ bestEntry = {
11374
+ createdAtMs: marker.createdAtMs,
11375
+ sourceDir
11376
+ };
11377
+ }
11378
+ }
11379
+ return bestEntry ? {
11380
+ cacheHit: true,
11381
+ sourceDir: bestEntry.sourceDir
11382
+ } : null;
11383
+ }
11384
+ async function resolveExternalTemplateSourceCache(descriptor, populateSourceDir) {
11385
+ if (!isExternalTemplateCacheEnabled()) {
11386
+ return null;
11387
+ }
11388
+ const cacheEntryPaths = getCacheEntryPaths(descriptor);
11389
+ if (!cacheEntryPaths) {
11390
+ return null;
11391
+ }
11392
+ const { cacheKey, cacheRoot, entryDir, markerPath, namespaceDir, sourceDir } = cacheEntryPaths;
11393
+ if (!await ensurePrivateCacheDirectory(cacheRoot) || !await ensurePrivateCacheDirectory(namespaceDir)) {
11394
+ return null;
11395
+ }
11396
+ if (await isReusableCacheEntry(entryDir, markerPath, sourceDir)) {
11397
+ return {
11398
+ cacheHit: true,
11399
+ sourceDir
11400
+ };
11401
+ }
11402
+ const temporaryEntryDir = path16.join(namespaceDir, `.tmp-${cacheKey}-${process.pid}-${Date.now()}-${Math.random().toString(16).slice(2)}`);
11403
+ const temporarySourceDir = path16.join(temporaryEntryDir, "source");
11404
+ let populateFailed = false;
11405
+ try {
11406
+ await fsp9.mkdir(temporarySourceDir, {
11407
+ mode: PRIVATE_CACHE_DIRECTORY_MODE,
11408
+ recursive: true
11409
+ });
11410
+ if (process.platform !== "win32") {
11411
+ await fsp9.chmod(temporaryEntryDir, PRIVATE_CACHE_DIRECTORY_MODE);
11412
+ }
11413
+ try {
11414
+ await populateSourceDir(temporarySourceDir);
11415
+ } catch (error) {
11416
+ populateFailed = true;
11417
+ throw error;
11418
+ }
11419
+ await fsp9.writeFile(path16.join(temporaryEntryDir, CACHE_MARKER_FILE), `${JSON.stringify({
11420
+ createdAt: new Date().toISOString(),
11421
+ key: cacheKey,
11422
+ metadata: sanitizeCacheMetadata(descriptor.metadata),
11423
+ namespace: descriptor.namespace
11424
+ }, null, 2)}
11425
+ `, "utf8");
11426
+ await fsp9.rename(temporaryEntryDir, entryDir);
11427
+ return {
11428
+ cacheHit: false,
11429
+ sourceDir
11430
+ };
11431
+ } catch (error) {
11432
+ await removeTemporaryCacheEntry(temporaryEntryDir);
11433
+ if (populateFailed) {
11434
+ if (CACHE_UNAVAILABLE_ERROR_CODES.has(getNodeErrorCode(error))) {
11435
+ return null;
11436
+ }
11437
+ throw error;
11438
+ }
11439
+ const errorCode = getNodeErrorCode(error);
11440
+ if (CACHE_PUBLISH_RACE_ERROR_CODES.has(errorCode) && await isReusableCacheEntry(entryDir, markerPath, sourceDir)) {
11441
+ return {
11442
+ cacheHit: true,
11443
+ sourceDir
11444
+ };
11445
+ }
11446
+ if (CACHE_PUBLISH_RACE_ERROR_CODES.has(errorCode) || CACHE_UNAVAILABLE_ERROR_CODES.has(errorCode)) {
11447
+ return null;
11448
+ }
11449
+ throw error;
11450
+ }
11451
+ }
11452
+
11117
11453
  // ../wp-typia-project-tools/src/runtime/template-source-seeds.ts
11118
11454
  var USER_FACING_TEMPLATE_IDS2 = [
11119
11455
  ...TEMPLATE_IDS,
11120
11456
  OFFICIAL_WORKSPACE_TEMPLATE_ALIAS
11121
11457
  ];
11458
+ var GITHUB_TEMPLATE_CACHE_REVISION_RACE_CODE = "github-template-cache-revision-race";
11459
+ function createGitHubTemplateCacheRevisionRaceError(message) {
11460
+ const error = new Error(message);
11461
+ error.code = GITHUB_TEMPLATE_CACHE_REVISION_RACE_CODE;
11462
+ return error;
11463
+ }
11464
+ function isGitHubTemplateCacheRevisionRaceError(error) {
11465
+ return typeof error === "object" && error !== null && "code" in error && error.code === GITHUB_TEMPLATE_CACHE_REVISION_RACE_CODE;
11466
+ }
11122
11467
  function getUnknownNpmTemplateMessage(templateId) {
11123
11468
  return [
11124
11469
  `Unknown template "${templateId}". Expected one of: ${USER_FACING_TEMPLATE_IDS2.join(", ")}.`,
@@ -11126,6 +11471,43 @@ function getUnknownNpmTemplateMessage(templateId) {
11126
11471
  "If you meant an npm template package, verify the package name and configured npm registry."
11127
11472
  ].join(" ");
11128
11473
  }
11474
+ function readOptionalDistString(dist, key) {
11475
+ const value = dist[key];
11476
+ return typeof value === "string" && value.length > 0 ? value : null;
11477
+ }
11478
+ function normalizeNpmRegistryCacheKey(registryBase) {
11479
+ try {
11480
+ const url = new URL(registryBase);
11481
+ url.username = "";
11482
+ url.password = "";
11483
+ url.search = "";
11484
+ url.hash = "";
11485
+ return url.toString().replace(/\/$/u, "");
11486
+ } catch {
11487
+ return registryBase;
11488
+ }
11489
+ }
11490
+ async function downloadNpmTemplateTarball(locator, resolvedVersion, tarballUrl, unpackDir) {
11491
+ const tarballResponse = await fetchWithExternalTemplateTimeout(tarballUrl, {
11492
+ label: `downloading npm template tarball for ${locator.raw}@${resolvedVersion}`
11493
+ });
11494
+ if (!tarballResponse.ok) {
11495
+ throw new Error(`Failed to download npm template tarball for ${locator.raw}: ${tarballResponse.status}`);
11496
+ }
11497
+ const tarballPath = path17.join(path17.dirname(unpackDir), "template.tgz");
11498
+ await fsp10.mkdir(unpackDir, { recursive: true });
11499
+ await fsp10.writeFile(tarballPath, await readBufferResponseWithLimit(tarballResponse, {
11500
+ label: `npm template tarball for ${locator.raw}@${resolvedVersion}`,
11501
+ maxBytes: getExternalTemplateTarballMaxBytes()
11502
+ }));
11503
+ await co({
11504
+ cwd: unpackDir,
11505
+ file: tarballPath,
11506
+ strip: 1
11507
+ });
11508
+ await fsp10.rm(tarballPath, { force: true });
11509
+ await assertNoSymlinks2(unpackDir);
11510
+ }
11129
11511
  function selectRegistryVersion(metadata, locator) {
11130
11512
  const distTags = isPlainObject(metadata["dist-tags"]) ? metadata["dist-tags"] : {};
11131
11513
  const versions = isPlainObject(metadata.versions) ? metadata.versions : {};
@@ -11165,7 +11547,7 @@ async function fetchNpmTemplateSource(locator) {
11165
11547
  });
11166
11548
  if (!metadataResponse.ok) {
11167
11549
  if (metadataResponse.status === 404) {
11168
- throw new Error(getUnknownNpmTemplateMessage(locator.raw));
11550
+ throw createCliDiagnosticCodeError(CLI_DIAGNOSTIC_CODES.UNKNOWN_TEMPLATE, getUnknownNpmTemplateMessage(locator.raw));
11169
11551
  }
11170
11552
  throw new Error(`Failed to fetch npm template metadata for ${locator.raw}: ${metadataResponse.status}`);
11171
11553
  }
@@ -11183,27 +11565,43 @@ async function fetchNpmTemplateSource(locator) {
11183
11565
  if (typeof tarballUrl !== "string" || tarballUrl.length === 0) {
11184
11566
  throw new Error(`npm template metadata is missing tarball URL for ${locator.raw}@${resolvedVersion}.`);
11185
11567
  }
11568
+ const tarballIntegrity = readOptionalDistString(versionMetadata.dist, "integrity");
11569
+ const tarballShasum = readOptionalDistString(versionMetadata.dist, "shasum");
11570
+ if (tarballIntegrity || tarballShasum) {
11571
+ const registryCacheKey = normalizeNpmRegistryCacheKey(registryBase);
11572
+ const cachedSource = await resolveExternalTemplateSourceCache({
11573
+ keyParts: [
11574
+ "npm",
11575
+ registryCacheKey,
11576
+ locator.name,
11577
+ locator.raw,
11578
+ resolvedVersion,
11579
+ tarballIntegrity ?? "",
11580
+ tarballShasum ?? ""
11581
+ ],
11582
+ metadata: {
11583
+ integrity: tarballIntegrity,
11584
+ package: locator.name,
11585
+ raw: locator.raw,
11586
+ registry: registryBase,
11587
+ shasum: tarballShasum,
11588
+ tarball: tarballUrl,
11589
+ version: resolvedVersion
11590
+ },
11591
+ namespace: "npm"
11592
+ }, (unpackDir) => downloadNpmTemplateTarball(locator, resolvedVersion, tarballUrl, unpackDir));
11593
+ if (cachedSource) {
11594
+ await assertNoSymlinks2(cachedSource.sourceDir);
11595
+ return {
11596
+ blockDir: cachedSource.sourceDir,
11597
+ rootDir: cachedSource.sourceDir
11598
+ };
11599
+ }
11600
+ }
11186
11601
  const { path: tempRoot, cleanup } = await createManagedTempRoot("wp-typia-template-source-");
11187
11602
  try {
11188
- const tarballResponse = await fetchWithExternalTemplateTimeout(tarballUrl, {
11189
- label: `downloading npm template tarball for ${locator.raw}@${resolvedVersion}`
11190
- });
11191
- if (!tarballResponse.ok) {
11192
- throw new Error(`Failed to download npm template tarball for ${locator.raw}: ${tarballResponse.status}`);
11193
- }
11194
- const tarballPath = path16.join(tempRoot, "template.tgz");
11195
- const unpackDir = path16.join(tempRoot, "source");
11196
- await fsp9.mkdir(unpackDir, { recursive: true });
11197
- await fsp9.writeFile(tarballPath, await readBufferResponseWithLimit(tarballResponse, {
11198
- label: `npm template tarball for ${locator.raw}@${resolvedVersion}`,
11199
- maxBytes: getExternalTemplateTarballMaxBytes()
11200
- }));
11201
- await co({
11202
- cwd: unpackDir,
11203
- file: tarballPath,
11204
- strip: 1
11205
- });
11206
- await assertNoSymlinks2(unpackDir);
11603
+ const unpackDir = path17.join(tempRoot, "source");
11604
+ await downloadNpmTemplateTarball(locator, resolvedVersion, tarballUrl, unpackDir);
11207
11605
  return {
11208
11606
  blockDir: unpackDir,
11209
11607
  cleanup,
@@ -11218,20 +11616,20 @@ function resolveInstalledNpmTemplateSource(locator, cwd) {
11218
11616
  if (locator.rawSpec !== "" && locator.rawSpec !== "*") {
11219
11617
  return null;
11220
11618
  }
11221
- const workspacePackagesRoot = path16.resolve(PROJECT_TOOLS_PACKAGE_ROOT, "..");
11222
- if (fs13.existsSync(workspacePackagesRoot)) {
11223
- for (const entry of fs13.readdirSync(workspacePackagesRoot, {
11619
+ const workspacePackagesRoot = path17.resolve(PROJECT_TOOLS_PACKAGE_ROOT, "..");
11620
+ if (fs14.existsSync(workspacePackagesRoot)) {
11621
+ for (const entry of fs14.readdirSync(workspacePackagesRoot, {
11224
11622
  withFileTypes: true
11225
11623
  })) {
11226
11624
  if (!entry.isDirectory()) {
11227
11625
  continue;
11228
11626
  }
11229
- const packageDir = path16.join(workspacePackagesRoot, entry.name);
11230
- const packageJsonPath = path16.join(packageDir, "package.json");
11231
- if (!fs13.existsSync(packageJsonPath)) {
11627
+ const packageDir = path17.join(workspacePackagesRoot, entry.name);
11628
+ const packageJsonPath = path17.join(packageDir, "package.json");
11629
+ if (!fs14.existsSync(packageJsonPath)) {
11232
11630
  continue;
11233
11631
  }
11234
- const manifest = JSON.parse(fs13.readFileSync(packageJsonPath, "utf8"));
11632
+ const manifest = JSON.parse(fs14.readFileSync(packageJsonPath, "utf8"));
11235
11633
  if (manifest.name === locator.name) {
11236
11634
  return {
11237
11635
  blockDir: packageDir,
@@ -11240,10 +11638,10 @@ function resolveInstalledNpmTemplateSource(locator, cwd) {
11240
11638
  }
11241
11639
  }
11242
11640
  }
11243
- const workspaceRequire = createRequire2(path16.join(path16.resolve(cwd), "__wp_typia_template_resolver__.cjs"));
11641
+ const workspaceRequire = createRequire2(path17.join(path17.resolve(cwd), "__wp_typia_template_resolver__.cjs"));
11244
11642
  try {
11245
- const packageJsonPath = fs13.realpathSync(workspaceRequire.resolve(`${locator.name}/package.json`));
11246
- const sourceDir = path16.dirname(packageJsonPath);
11643
+ const packageJsonPath = fs14.realpathSync(workspaceRequire.resolve(`${locator.name}/package.json`));
11644
+ const sourceDir = path17.dirname(packageJsonPath);
11247
11645
  return {
11248
11646
  blockDir: sourceDir,
11249
11647
  rootDir: sourceDir
@@ -11252,11 +11650,11 @@ function resolveInstalledNpmTemplateSource(locator, cwd) {
11252
11650
  const errorCode = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
11253
11651
  if (errorCode === "MODULE_NOT_FOUND" || errorCode === "ERR_PACKAGE_PATH_NOT_EXPORTED") {
11254
11652
  for (const basePath of workspaceRequire.resolve.paths(locator.name) ?? []) {
11255
- const packageJsonPath = path16.join(basePath, locator.name, "package.json");
11256
- if (!fs13.existsSync(packageJsonPath)) {
11653
+ const packageJsonPath = path17.join(basePath, locator.name, "package.json");
11654
+ if (!fs14.existsSync(packageJsonPath)) {
11257
11655
  continue;
11258
11656
  }
11259
- const sourceDir = path16.dirname(fs13.realpathSync(packageJsonPath));
11657
+ const sourceDir = path17.dirname(fs14.realpathSync(packageJsonPath));
11260
11658
  return {
11261
11659
  blockDir: sourceDir,
11262
11660
  rootDir: sourceDir
@@ -11268,64 +11666,250 @@ function resolveInstalledNpmTemplateSource(locator, cwd) {
11268
11666
  }
11269
11667
  }
11270
11668
  function isOfficialWorkspaceTemplateSeed(seed) {
11271
- const packageJsonPath = path16.join(seed.rootDir, "package.json");
11272
- if (!fs13.existsSync(packageJsonPath)) {
11669
+ const packageJsonPath = path17.join(seed.rootDir, "package.json");
11670
+ if (!fs14.existsSync(packageJsonPath)) {
11273
11671
  return false;
11274
11672
  }
11275
11673
  try {
11276
- const packageJson = JSON.parse(fs13.readFileSync(packageJsonPath, "utf8"));
11674
+ const packageJson = JSON.parse(fs14.readFileSync(packageJsonPath, "utf8"));
11277
11675
  return packageJson.name === OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE;
11278
11676
  } catch {
11279
11677
  return false;
11280
11678
  }
11281
11679
  }
11282
11680
  async function assertNoSymlinks2(sourceDir) {
11283
- const stats = await fsp9.lstat(sourceDir);
11681
+ const stats = await fsp10.lstat(sourceDir);
11284
11682
  if (stats.isSymbolicLink()) {
11285
11683
  throw new Error(`Template sources may not include symbolic links: ${sourceDir}`);
11286
11684
  }
11287
11685
  if (!stats.isDirectory()) {
11288
11686
  return;
11289
11687
  }
11290
- for (const entry of await fsp9.readdir(sourceDir)) {
11291
- await assertNoSymlinks2(path16.join(sourceDir, entry));
11688
+ for (const entry of await fsp10.readdir(sourceDir)) {
11689
+ await assertNoSymlinks2(path17.join(sourceDir, entry));
11292
11690
  }
11293
11691
  }
11294
- async function resolveGitHubTemplateSource(locator) {
11295
- const { path: remoteRoot, cleanup } = await createManagedTempRoot("wp-typia-template-source-");
11296
- const checkoutDir = path16.join(remoteRoot, "source");
11297
- try {
11298
- const args = ["clone", "--depth", "1"];
11299
- if (locator.ref) {
11300
- args.push("--branch", locator.ref);
11301
- }
11302
- args.push(`https://github.com/${locator.owner}/${locator.repo}.git`, checkoutDir);
11303
- const cloneTimeoutMs = getExternalTemplateTimeoutMs();
11304
- const cloneResult = spawnSync("git", args, {
11305
- stdio: "ignore",
11306
- timeout: cloneTimeoutMs
11307
- });
11308
- if (cloneResult.error) {
11309
- const errorCode = typeof cloneResult.error === "object" && cloneResult.error !== null && "code" in cloneResult.error ? String(cloneResult.error.code) : "";
11310
- if (errorCode === "ETIMEDOUT") {
11311
- throw createExternalTemplateTimeoutError(`cloning GitHub template ${locator.owner}/${locator.repo}`, cloneTimeoutMs);
11312
- }
11313
- throw cloneResult.error;
11692
+ function runGitTemplateCommand(args, label, options = {}) {
11693
+ const timeoutMs = getExternalTemplateTimeoutMs();
11694
+ const result = options.captureOutput ? spawnSync("git", args, {
11695
+ encoding: "utf8",
11696
+ stdio: ["ignore", "pipe", "pipe"],
11697
+ timeout: timeoutMs
11698
+ }) : spawnSync("git", args, {
11699
+ stdio: "ignore",
11700
+ timeout: timeoutMs
11701
+ });
11702
+ if (result.error) {
11703
+ const errorCode = typeof result.error === "object" && result.error !== null && "code" in result.error ? String(result.error.code) : "";
11704
+ if (errorCode === "ETIMEDOUT") {
11705
+ throw createExternalTemplateTimeoutError(label, timeoutMs);
11706
+ }
11707
+ throw result.error;
11708
+ }
11709
+ if (result.signal === "SIGTERM" || result.signal === "SIGKILL") {
11710
+ throw createExternalTemplateTimeoutError(label, timeoutMs);
11711
+ }
11712
+ return result;
11713
+ }
11714
+ function getGitHubTemplateRepositoryUrl(locator) {
11715
+ return `https://github.com/${locator.owner}/${locator.repo}.git`;
11716
+ }
11717
+ function resolveGitHubTemplateDirectory(checkoutDir, locator) {
11718
+ const sourceDir = path17.resolve(checkoutDir, locator.sourcePath);
11719
+ const relativeSourceDir = path17.relative(checkoutDir, sourceDir);
11720
+ if (relativeSourceDir.startsWith("..") || path17.isAbsolute(relativeSourceDir)) {
11721
+ throw new Error("GitHub template path must stay within the cloned repository.");
11722
+ }
11723
+ if (!fs14.existsSync(sourceDir)) {
11724
+ throw new Error(`GitHub template path does not exist: ${locator.sourcePath}`);
11725
+ }
11726
+ return sourceDir;
11727
+ }
11728
+ function cloneGitHubTemplateSource(locator, checkoutDir) {
11729
+ const args = ["clone", "--depth", "1"];
11730
+ if (locator.ref) {
11731
+ args.push("--branch", locator.ref);
11732
+ }
11733
+ args.push(getGitHubTemplateRepositoryUrl(locator), checkoutDir);
11734
+ const cloneResult = runGitTemplateCommand(args, `cloning GitHub template ${locator.owner}/${locator.repo}`);
11735
+ if (cloneResult.status !== 0) {
11736
+ throw new Error(`Failed to clone GitHub template source ${locator.owner}/${locator.repo}.`);
11737
+ }
11738
+ }
11739
+ function readGitHubTemplateHeadRevision(checkoutDir) {
11740
+ const result = runGitTemplateCommand(["-C", checkoutDir, "rev-parse", "HEAD"], "reading GitHub template checkout revision", { captureOutput: true });
11741
+ if (result.status !== 0 || typeof result.stdout !== "string") {
11742
+ return null;
11743
+ }
11744
+ const revision = result.stdout.trim().split(/\s+/u)[0];
11745
+ return /^[0-9a-f]{40}$/iu.test(revision) ? revision.toLowerCase() : null;
11746
+ }
11747
+ function pinGitHubTemplateCacheRevision(locator, checkoutDir, cacheRevision) {
11748
+ const normalizedCacheRevision = cacheRevision.toLowerCase();
11749
+ if (readGitHubTemplateHeadRevision(checkoutDir) === normalizedCacheRevision) {
11750
+ return;
11751
+ }
11752
+ const fetchResult = runGitTemplateCommand(["-C", checkoutDir, "fetch", "--depth", "1", "origin", cacheRevision], `fetching GitHub template revision ${locator.owner}/${locator.repo}`);
11753
+ if (fetchResult.status !== 0) {
11754
+ throw createGitHubTemplateCacheRevisionRaceError(`Failed to fetch GitHub template revision ${cacheRevision} for ${locator.owner}/${locator.repo}.`);
11755
+ }
11756
+ const checkoutResult = runGitTemplateCommand(["-C", checkoutDir, "checkout", "--detach", cacheRevision], `checking out GitHub template revision ${locator.owner}/${locator.repo}`);
11757
+ if (checkoutResult.status !== 0) {
11758
+ throw createGitHubTemplateCacheRevisionRaceError(`Failed to check out GitHub template revision ${cacheRevision} for ${locator.owner}/${locator.repo}.`);
11759
+ }
11760
+ if (readGitHubTemplateHeadRevision(checkoutDir) !== normalizedCacheRevision) {
11761
+ throw createGitHubTemplateCacheRevisionRaceError(`GitHub template checkout did not match resolved revision ${cacheRevision} for ${locator.owner}/${locator.repo}.`);
11762
+ }
11763
+ }
11764
+ function getGitHubTemplateRevisionPatterns(locator) {
11765
+ const ref = locator.ref ?? "HEAD";
11766
+ if (!locator.ref) {
11767
+ return [ref];
11768
+ }
11769
+ if (ref.startsWith("refs/")) {
11770
+ return [ref, `${ref}^{}`];
11771
+ }
11772
+ return [ref, `refs/heads/${ref}`, `refs/tags/${ref}`, `refs/tags/${ref}^{}`];
11773
+ }
11774
+ function pickGitHubTemplateCacheRevision(locator, revisions) {
11775
+ const ref = locator.ref ?? "HEAD";
11776
+ if (!locator.ref) {
11777
+ return revisions[0]?.revision ?? null;
11778
+ }
11779
+ if (!ref.startsWith("refs/")) {
11780
+ const branchRevision = revisions.find((entry) => entry.resolvedRef === `refs/heads/${ref}`);
11781
+ if (branchRevision) {
11782
+ return branchRevision.revision;
11783
+ }
11784
+ const peeledTagRevision = revisions.find((entry) => entry.resolvedRef === `refs/tags/${ref}^{}`);
11785
+ if (peeledTagRevision) {
11786
+ return peeledTagRevision.revision;
11314
11787
  }
11315
- if (cloneResult.signal === "SIGTERM" || cloneResult.signal === "SIGKILL") {
11316
- throw createExternalTemplateTimeoutError(`cloning GitHub template ${locator.owner}/${locator.repo}`, cloneTimeoutMs);
11788
+ const tagRevision = revisions.find((entry) => entry.resolvedRef === `refs/tags/${ref}`);
11789
+ if (tagRevision) {
11790
+ return tagRevision.revision;
11791
+ }
11792
+ }
11793
+ if (ref.startsWith("refs/tags/")) {
11794
+ const peeledRevision = revisions.find((entry) => entry.resolvedRef === `${ref}^{}`);
11795
+ if (peeledRevision) {
11796
+ return peeledRevision.revision;
11797
+ }
11798
+ }
11799
+ const exactRevision = revisions.find((entry) => entry.resolvedRef === ref);
11800
+ return (exactRevision ?? revisions[0])?.revision ?? null;
11801
+ }
11802
+ function resolveGitHubTemplateCacheRevision(locator) {
11803
+ const result = runGitTemplateCommand([
11804
+ "ls-remote",
11805
+ getGitHubTemplateRepositoryUrl(locator),
11806
+ ...getGitHubTemplateRevisionPatterns(locator)
11807
+ ], `checking GitHub template revision ${locator.owner}/${locator.repo}`, { captureOutput: true });
11808
+ if (result.status !== 0 || typeof result.stdout !== "string") {
11809
+ return {
11810
+ lookupUnavailable: true,
11811
+ revision: null
11812
+ };
11813
+ }
11814
+ const revisions = result.stdout.split(`
11815
+ `).map((line) => {
11816
+ const [revision, resolvedRef] = line.trim().split(/\s+/u);
11817
+ if (!/^[0-9a-f]{40}$/iu.test(revision) || !resolvedRef) {
11818
+ return null;
11317
11819
  }
11318
- if (cloneResult.status !== 0) {
11319
- throw new Error(`Failed to clone GitHub template source ${locator.owner}/${locator.repo}.`);
11820
+ return {
11821
+ resolvedRef,
11822
+ revision: revision.toLowerCase()
11823
+ };
11824
+ }).filter((entry) => entry !== null);
11825
+ return {
11826
+ lookupUnavailable: false,
11827
+ revision: pickGitHubTemplateCacheRevision(locator, revisions)
11828
+ };
11829
+ }
11830
+ function getGitHubTemplateCacheMetadata(locator, revision) {
11831
+ return {
11832
+ ...revision ? { revision } : {},
11833
+ owner: locator.owner,
11834
+ ref: locator.ref,
11835
+ repo: locator.repo,
11836
+ sourcePath: locator.sourcePath
11837
+ };
11838
+ }
11839
+ async function reuseGitHubTemplateCacheByMetadata(locator) {
11840
+ const cachedSource = await findReusableExternalTemplateSourceCache({
11841
+ metadata: getGitHubTemplateCacheMetadata(locator),
11842
+ namespace: "github"
11843
+ });
11844
+ if (!cachedSource) {
11845
+ return null;
11846
+ }
11847
+ const sourceDir = resolveGitHubTemplateDirectory(cachedSource.sourceDir, locator);
11848
+ await assertNoSymlinks2(sourceDir);
11849
+ return {
11850
+ blockDir: sourceDir,
11851
+ rootDir: sourceDir
11852
+ };
11853
+ }
11854
+ async function resolveGitHubTemplateSource(locator) {
11855
+ const cacheEnabled = isExternalTemplateCacheEnabled();
11856
+ let cacheRevisionLookupUnavailable = false;
11857
+ let cacheRevision = null;
11858
+ if (cacheEnabled) {
11859
+ try {
11860
+ const resolvedRevision = resolveGitHubTemplateCacheRevision(locator);
11861
+ cacheRevision = resolvedRevision.revision;
11862
+ cacheRevisionLookupUnavailable = resolvedRevision.lookupUnavailable;
11863
+ } catch {
11864
+ cacheRevision = null;
11865
+ cacheRevisionLookupUnavailable = true;
11320
11866
  }
11321
- const sourceDir = path16.resolve(checkoutDir, locator.sourcePath);
11322
- const relativeSourceDir = path16.relative(checkoutDir, sourceDir);
11323
- if (relativeSourceDir.startsWith("..") || path16.isAbsolute(relativeSourceDir)) {
11324
- throw new Error("GitHub template path must stay within the cloned repository.");
11867
+ }
11868
+ if (cacheEnabled && !cacheRevision && cacheRevisionLookupUnavailable) {
11869
+ const cachedSource = await reuseGitHubTemplateCacheByMetadata(locator);
11870
+ if (cachedSource) {
11871
+ return cachedSource;
11325
11872
  }
11326
- if (!fs13.existsSync(sourceDir)) {
11327
- throw new Error(`GitHub template path does not exist: ${locator.sourcePath}`);
11873
+ }
11874
+ if (cacheRevision) {
11875
+ const resolvedCacheRevision = cacheRevision;
11876
+ try {
11877
+ const cachedSource = await resolveExternalTemplateSourceCache({
11878
+ keyParts: [
11879
+ "github",
11880
+ locator.owner,
11881
+ locator.repo,
11882
+ locator.sourcePath,
11883
+ locator.ref ?? "",
11884
+ resolvedCacheRevision
11885
+ ],
11886
+ metadata: getGitHubTemplateCacheMetadata(locator, resolvedCacheRevision),
11887
+ namespace: "github"
11888
+ }, async (checkoutDir2) => {
11889
+ cloneGitHubTemplateSource(locator, checkoutDir2);
11890
+ const sourceDir = resolveGitHubTemplateDirectory(checkoutDir2, locator);
11891
+ await assertNoSymlinks2(sourceDir);
11892
+ pinGitHubTemplateCacheRevision(locator, checkoutDir2, resolvedCacheRevision);
11893
+ });
11894
+ if (cachedSource) {
11895
+ const sourceDir = resolveGitHubTemplateDirectory(cachedSource.sourceDir, locator);
11896
+ await assertNoSymlinks2(sourceDir);
11897
+ return {
11898
+ blockDir: sourceDir,
11899
+ rootDir: sourceDir
11900
+ };
11901
+ }
11902
+ } catch (error) {
11903
+ if (!isGitHubTemplateCacheRevisionRaceError(error)) {
11904
+ throw error;
11905
+ }
11328
11906
  }
11907
+ }
11908
+ const { path: remoteRoot, cleanup } = await createManagedTempRoot("wp-typia-template-source-");
11909
+ const checkoutDir = path17.join(remoteRoot, "source");
11910
+ try {
11911
+ cloneGitHubTemplateSource(locator, checkoutDir);
11912
+ const sourceDir = resolveGitHubTemplateDirectory(checkoutDir, locator);
11329
11913
  await assertNoSymlinks2(sourceDir);
11330
11914
  return {
11331
11915
  blockDir: sourceDir,
@@ -11339,8 +11923,8 @@ async function resolveGitHubTemplateSource(locator) {
11339
11923
  }
11340
11924
  async function resolveTemplateSeed(locator, cwd) {
11341
11925
  if (locator.kind === "path") {
11342
- const sourceDir = path16.resolve(cwd, locator.templatePath);
11343
- if (!fs13.existsSync(sourceDir)) {
11926
+ const sourceDir = path17.resolve(cwd, locator.templatePath);
11927
+ if (!fs14.existsSync(sourceDir)) {
11344
11928
  throw new Error(`Template path does not exist: ${sourceDir}`);
11345
11929
  }
11346
11930
  await assertNoSymlinks2(sourceDir);
@@ -11464,6 +12048,41 @@ async function resolveTemplateSource(templateId, cwd, variables, variant) {
11464
12048
  }
11465
12049
  }
11466
12050
 
12051
+ // ../wp-typia-project-tools/src/runtime/version-floor.ts
12052
+ function parseVersionFloorParts(value) {
12053
+ return value.split(".").map((part, index) => {
12054
+ if (!/^\d+$/u.test(part)) {
12055
+ throw new Error(`parseVersionFloorParts received an invalid version floor "${value}" at segment ${index + 1}.`);
12056
+ }
12057
+ return Number.parseInt(part, 10);
12058
+ });
12059
+ }
12060
+ function compareVersionFloors(left, right) {
12061
+ const leftParts = parseVersionFloorParts(left);
12062
+ const rightParts = parseVersionFloorParts(right);
12063
+ const length = Math.max(leftParts.length, rightParts.length);
12064
+ for (let index = 0;index < length; index += 1) {
12065
+ const leftValue = leftParts[index] ?? 0;
12066
+ const rightValue = rightParts[index] ?? 0;
12067
+ if (leftValue > rightValue) {
12068
+ return 1;
12069
+ }
12070
+ if (leftValue < rightValue) {
12071
+ return -1;
12072
+ }
12073
+ }
12074
+ return 0;
12075
+ }
12076
+ function pickHigherVersionFloor(current, candidate) {
12077
+ if (!candidate) {
12078
+ return current;
12079
+ }
12080
+ if (!current) {
12081
+ return candidate;
12082
+ }
12083
+ return compareVersionFloors(current, candidate) >= 0 ? current : candidate;
12084
+ }
12085
+
11467
12086
  // ../wp-typia-project-tools/src/runtime/ai-feature-capability.ts
11468
12087
  var AI_FEATURE_DEFINITIONS = {
11469
12088
  wordpressAiClient: {
@@ -11528,39 +12147,6 @@ var DEFAULT_AI_FEATURE_REGISTRY = Object.values(AI_FEATURE_DEFINITIONS).reduce((
11528
12147
  accumulator[definition.id] = definition;
11529
12148
  return accumulator;
11530
12149
  }, {});
11531
- function parseVersionFloorParts(value) {
11532
- return value.split(".").map((part, index) => {
11533
- if (!/^\d+$/.test(part)) {
11534
- throw new Error(`parseVersionFloorParts received an invalid version floor "${value}" at segment ${index + 1}.`);
11535
- }
11536
- return Number.parseInt(part, 10);
11537
- });
11538
- }
11539
- function compareVersionFloors(left, right) {
11540
- const leftParts = parseVersionFloorParts(left);
11541
- const rightParts = parseVersionFloorParts(right);
11542
- const length = Math.max(leftParts.length, rightParts.length);
11543
- for (let index = 0;index < length; index += 1) {
11544
- const leftValue = leftParts[index] ?? 0;
11545
- const rightValue = rightParts[index] ?? 0;
11546
- if (leftValue > rightValue) {
11547
- return 1;
11548
- }
11549
- if (leftValue < rightValue) {
11550
- return -1;
11551
- }
11552
- }
11553
- return 0;
11554
- }
11555
- function pickHigherVersionFloor(current, candidate) {
11556
- if (!candidate) {
11557
- return current;
11558
- }
11559
- if (!current) {
11560
- return candidate;
11561
- }
11562
- return compareVersionFloors(current, candidate) >= 0 ? current : candidate;
11563
- }
11564
12150
  function normalizeSelections(selections) {
11565
12151
  const normalized = new Map;
11566
12152
  for (const selection of selections) {
@@ -11625,35 +12211,8 @@ var REQUIRED_WORKSPACE_ABILITY_COMPATIBILITY = [
11625
12211
  mode: "required"
11626
12212
  }
11627
12213
  ];
11628
- function parseVersionFloorParts2(value) {
11629
- return value.split(".").map((part, index) => {
11630
- if (!/^\d+$/u.test(part)) {
11631
- throw new Error(`parseVersionFloorParts received an invalid version floor "${value}" at segment ${index + 1}.`);
11632
- }
11633
- return Number.parseInt(part, 10);
11634
- });
11635
- }
11636
- function compareVersionFloors2(left, right) {
11637
- const leftParts = parseVersionFloorParts2(left);
11638
- const rightParts = parseVersionFloorParts2(right);
11639
- const length = Math.max(leftParts.length, rightParts.length);
11640
- for (let index = 0;index < length; index += 1) {
11641
- const leftValue = leftParts[index] ?? 0;
11642
- const rightValue = rightParts[index] ?? 0;
11643
- if (leftValue > rightValue) {
11644
- return 1;
11645
- }
11646
- if (leftValue < rightValue) {
11647
- return -1;
11648
- }
11649
- }
11650
- return 0;
11651
- }
11652
- function pickHigherVersionFloor2(current, candidate) {
11653
- if (!candidate) {
11654
- return current;
11655
- }
11656
- return compareVersionFloors2(current, candidate) >= 0 ? current : candidate;
12214
+ function pickHigherScaffoldVersionFloor(current, candidate) {
12215
+ return pickHigherVersionFloor(current, candidate) ?? current;
11657
12216
  }
11658
12217
  function pickHigherHeaderVersionFloor(policyValue, currentValue) {
11659
12218
  const normalizedCurrentValue = currentValue.trim();
@@ -11661,7 +12220,7 @@ function pickHigherHeaderVersionFloor(policyValue, currentValue) {
11661
12220
  return policyValue;
11662
12221
  }
11663
12222
  try {
11664
- return pickHigherVersionFloor2(policyValue, normalizedCurrentValue);
12223
+ return pickHigherScaffoldVersionFloor(policyValue, normalizedCurrentValue);
11665
12224
  } catch {
11666
12225
  return policyValue;
11667
12226
  }
@@ -11682,9 +12241,9 @@ function resolveScaffoldCompatibilityPolicy(selections, {
11682
12241
  baseline = DEFAULT_SCAFFOLD_COMPATIBILITY
11683
12242
  } = {}) {
11684
12243
  const capabilityPlan = resolveAiFeatureCapabilityPlan(selections);
11685
- const requiresAtLeast = pickHigherVersionFloor2(baseline.requiresAtLeast, capabilityPlan.hardMinimums.wordpress);
11686
- const requiresPhp = pickHigherVersionFloor2(baseline.requiresPhp, capabilityPlan.hardMinimums.php);
11687
- const testedUpTo = pickHigherVersionFloor2(baseline.testedUpTo, requiresAtLeast);
12244
+ const requiresAtLeast = pickHigherScaffoldVersionFloor(baseline.requiresAtLeast, capabilityPlan.hardMinimums.wordpress);
12245
+ const requiresPhp = pickHigherScaffoldVersionFloor(baseline.requiresPhp, capabilityPlan.hardMinimums.php);
12246
+ const testedUpTo = pickHigherScaffoldVersionFloor(baseline.testedUpTo, requiresAtLeast);
11688
12247
  return {
11689
12248
  capabilityPlan,
11690
12249
  pluginHeader: {
@@ -13487,6 +14046,7 @@ import { useBlockProps, InspectorControls, RichText, BlockControls, AlignmentToo
13487
14046
  import { PanelBody, RangeControl, Button, Notice } from '@wordpress/components';
13488
14047
  import { useState } from '@wordpress/element';
13489
14048
  import currentManifest from './manifest-document';
14049
+ import { {{slugCamelCase}}Store } from './interactivity-store';
13490
14050
  import {
13491
14051
  InspectorFromManifest,
13492
14052
  useEditorFields,
@@ -13567,17 +14127,22 @@ export default function Edit({ attributes, setAttributes, isSelected }: EditProp
13567
14127
 
13568
14128
  const blockProps = useBlockProps({
13569
14129
  className: \`{{cssClassName}} {{cssClassName}}--\${interactiveMode}\`,
13570
- 'data-wp-interactive': '{{slugKebabCase}}',
13571
- 'data-wp-context': JSON.stringify({
13572
- clicks: clickCount,
13573
- isAnimating,
13574
- isVisible,
13575
- animation,
13576
- maxClicks,
13577
- })
14130
+ 'data-wp-interactive': {{slugCamelCase}}Store.directive.interactive,
14131
+ 'data-wp-context': JSON.stringify(
14132
+ {{slugCamelCase}}Store.createContext({
14133
+ clicks: clickCount,
14134
+ isAnimating,
14135
+ isVisible,
14136
+ animation,
14137
+ maxClicks,
14138
+ })
14139
+ )
13578
14140
  });
13579
14141
  const previewContentStyle = { textAlign: alignmentValue };
13580
14142
  const progressBarStyle = { width: \`\${(clickCount / maxClicks) * 100}%\` };
14143
+ const clicksDirective = {{slugCamelCase}}Store.directive.state('clicks');
14144
+ const isAnimatingDirective = {{slugCamelCase}}Store.directive.state('isAnimating');
14145
+ const progressDirective = {{slugCamelCase}}Store.directive.state('progress') + " + '%'";
13581
14146
 
13582
14147
  const resetCounter = () => {
13583
14148
  updateField('clickCount', 0);
@@ -13674,9 +14239,9 @@ export default function Edit({ attributes, setAttributes, isSelected }: EditProp
13674
14239
  <div
13675
14240
  className={\`{{cssClassName}}__content \${isAnimating ? 'is-animating' : ''}\`}
13676
14241
  style={previewContentStyle}
13677
- data-wp-on--click={isPreviewing ? 'actions.handleClick' : undefined}
13678
- data-wp-on--mouseenter={isPreviewing && interactiveMode === 'hover' ? 'actions.handleMouseEnter' : undefined}
13679
- data-wp-on--mouseleave={isPreviewing && interactiveMode === 'hover' ? 'actions.handleMouseLeave' : undefined}
14242
+ data-wp-on--click={isPreviewing ? {{slugCamelCase}}Store.directive.action('handleClick') : undefined}
14243
+ data-wp-on--mouseenter={isPreviewing && interactiveMode === 'hover' ? {{slugCamelCase}}Store.directive.action('handleMouseEnter') : undefined}
14244
+ data-wp-on--mouseleave={isPreviewing && interactiveMode === 'hover' ? {{slugCamelCase}}Store.directive.action('handleMouseLeave') : undefined}
13680
14245
  >
13681
14246
  <RichText
13682
14247
  tagName="p"
@@ -13705,7 +14270,7 @@ export default function Edit({ attributes, setAttributes, isSelected }: EditProp
13705
14270
  </span>
13706
14271
  <span
13707
14272
  className="{{cssClassName}}__counter-value"
13708
- data-wp-text="state.clicks"
14273
+ data-wp-text={clicksDirective}
13709
14274
  >
13710
14275
  {clickCount}
13711
14276
  </span>
@@ -13717,7 +14282,7 @@ export default function Edit({ attributes, setAttributes, isSelected }: EditProp
13717
14282
  <div
13718
14283
  className="{{cssClassName}}__progress-bar"
13719
14284
  style={progressBarStyle}
13720
- data-wp-style--width="state.progress + '%'"
14285
+ data-wp-style--width={progressDirective}
13721
14286
  />
13722
14287
  </div>
13723
14288
  )}
@@ -13725,7 +14290,7 @@ export default function Edit({ attributes, setAttributes, isSelected }: EditProp
13725
14290
  {animation !== 'none' && (
13726
14291
  <div
13727
14292
  className={\`{{cssClassName}}__animation \${isAnimating ? 'is-active' : ''}\`}
13728
- data-wp-class--is-active="state.isAnimating"
14293
+ data-wp-class--is-active={isAnimatingDirective}
13729
14294
  >
13730
14295
  {animation}
13731
14296
  </div>
@@ -13738,6 +14303,7 @@ export default function Edit({ attributes, setAttributes, isSelected }: EditProp
13738
14303
  `;
13739
14304
  var INTERACTIVITY_SAVE_TEMPLATE = `import { useBlockProps, RichText } from '@wordpress/block-editor';
13740
14305
  import { __ } from '@wordpress/i18n';
14306
+ import { {{slugCamelCase}}Store } from './interactivity-store';
13741
14307
  import type { {{pascalCase}}Attributes } from './types';
13742
14308
 
13743
14309
  export default function Save({ attributes }: { attributes: {{pascalCase}}Attributes }) {
@@ -13749,27 +14315,41 @@ export default function Save({ attributes }: { attributes: {{pascalCase}}Attribu
13749
14315
  const maxClicks = attributes.maxClicks ?? 0;
13750
14316
  const showCounter = attributes.showCounter ?? true;
13751
14317
  const contentStyle = { textAlign: attributes.alignment };
14318
+ const clickActionDirective = {{slugCamelCase}}Store.directive.action('handleClick');
14319
+ const visibilityHiddenDirective = {{slugCamelCase}}Store.directive.negate(
14320
+ {{slugCamelCase}}Store.directive.state('isVisible')
14321
+ );
14322
+ const clicksDirective = {{slugCamelCase}}Store.directive.state('clicks');
14323
+ const clampedClicksDirective = {{slugCamelCase}}Store.directive.state('clampedClicks');
14324
+ const isAnimatingDirective = {{slugCamelCase}}Store.directive.state('isAnimating');
14325
+ const completionHiddenDirective = {{slugCamelCase}}Store.directive.negate(
14326
+ {{slugCamelCase}}Store.directive.state('isComplete')
14327
+ );
14328
+ const resetActionDirective = {{slugCamelCase}}Store.directive.action('reset');
13752
14329
  const blockProps = useBlockProps.save({
13753
14330
  className: \`{{cssClassName}} {{cssClassName}}--\${interactiveMode}\`,
13754
- 'data-wp-interactive': '{{slugKebabCase}}',
13755
- 'data-wp-context': JSON.stringify({
13756
- clicks: clickCount,
13757
- isAnimating,
13758
- isVisible,
13759
- animation,
13760
- maxClicks,
13761
- })
14331
+ 'data-wp-interactive': {{slugCamelCase}}Store.directive.interactive,
14332
+ 'data-wp-context': JSON.stringify(
14333
+ {{slugCamelCase}}Store.createContext({
14334
+ clicks: clickCount,
14335
+ isAnimating,
14336
+ isVisible,
14337
+ animation,
14338
+ maxClicks,
14339
+ })
14340
+ )
13762
14341
  });
14342
+ const progressDirective = {{slugCamelCase}}Store.directive.state('progress') + " + '%'";
13763
14343
 
13764
14344
  return (
13765
14345
  <div {...blockProps}>
13766
14346
  <div
13767
14347
  className={\`{{cssClassName}}__content \${isAnimating ? 'is-animating' : ''}\`}
13768
14348
  style={contentStyle}
13769
- data-wp-on--click="actions.handleClick"
13770
- data-wp-on--mouseenter={interactiveMode === 'hover' ? 'actions.handleMouseEnter' : undefined}
13771
- data-wp-on--mouseleave={interactiveMode === 'hover' ? 'actions.handleMouseLeave' : undefined}
13772
- data-wp-bind--hidden="!state.isVisible"
14349
+ data-wp-on--click={clickActionDirective}
14350
+ data-wp-on--mouseenter={interactiveMode === 'hover' ? {{slugCamelCase}}Store.directive.action('handleMouseEnter') : undefined}
14351
+ data-wp-on--mouseleave={interactiveMode === 'hover' ? {{slugCamelCase}}Store.directive.action('handleMouseLeave') : undefined}
14352
+ data-wp-bind--hidden={visibilityHiddenDirective}
13773
14353
  >
13774
14354
  <RichText.Content
13775
14355
  tagName="p"
@@ -13789,7 +14369,7 @@ export default function Save({ attributes }: { attributes: {{pascalCase}}Attribu
13789
14369
  </span>
13790
14370
  <span
13791
14371
  className="{{cssClassName}}__counter-value"
13792
- data-wp-text="state.clicks"
14372
+ data-wp-text={clicksDirective}
13793
14373
  >
13794
14374
  {clickCount}
13795
14375
  </span>
@@ -13805,8 +14385,8 @@ export default function Save({ attributes }: { attributes: {{pascalCase}}Attribu
13805
14385
  aria-valuemin={0}
13806
14386
  aria-valuemax={maxClicks}
13807
14387
  aria-valuenow={Math.min(clickCount, maxClicks)}
13808
- data-wp-bind--aria-valuenow="state.clampedClicks"
13809
- data-wp-style--width="state.progress + '%'"
14388
+ data-wp-bind--aria-valuenow={clampedClicksDirective}
14389
+ data-wp-style--width={progressDirective}
13810
14390
  />
13811
14391
  </div>
13812
14392
  )}
@@ -13814,7 +14394,7 @@ export default function Save({ attributes }: { attributes: {{pascalCase}}Attribu
13814
14394
  <div
13815
14395
  className={\`{{cssClassName}}__animation \${animation}\`}
13816
14396
  aria-hidden="true"
13817
- data-wp-class--is-active="state.isAnimating"
14397
+ data-wp-class--is-active={isAnimatingDirective}
13818
14398
  />
13819
14399
 
13820
14400
  {maxClicks > 0 && (
@@ -13823,7 +14403,7 @@ export default function Save({ attributes }: { attributes: {{pascalCase}}Attribu
13823
14403
  role="status"
13824
14404
  aria-live="polite"
13825
14405
  aria-atomic="true"
13826
- data-wp-bind--hidden="!state.isComplete"
14406
+ data-wp-bind--hidden={completionHiddenDirective}
13827
14407
  >
13828
14408
  { __( '\uD83C\uDF89 Complete!', '{{textDomain}}' ) }
13829
14409
  </div>
@@ -13831,7 +14411,7 @@ export default function Save({ attributes }: { attributes: {{pascalCase}}Attribu
13831
14411
 
13832
14412
  <button
13833
14413
  className="{{cssClassName}}__reset"
13834
- data-wp-on--click="actions.reset"
14414
+ data-wp-on--click={resetActionDirective}
13835
14415
  aria-label={ __( 'Reset counter', '{{textDomain}}' ) }
13836
14416
  >
13837
14417
  <span aria-hidden="true">\u21BB</span>
@@ -13881,20 +14461,214 @@ const registration = buildScaffoldBlockRegistration(
13881
14461
 
13882
14462
  registerScaffoldBlockType(registration.name, registration.settings);
13883
14463
  `;
14464
+ var INTERACTIVITY_STORE_TEMPLATE = `import type {
14465
+ {{pascalCase}}Context,
14466
+ {{pascalCase}}State,
14467
+ } from './types';
14468
+
14469
+ type InteractivityActionShape = object;
14470
+ type InteractivityCallbackShape = object;
14471
+ type InteractivityContextShape = object;
14472
+ type InteractivityStateShape = object;
14473
+ type InteractivityCallable = Function;
14474
+ type InteractivityKey<T extends object> = Extract<keyof T, string>;
14475
+ type InteractivityMethodKey<T extends object> = {
14476
+ [Key in InteractivityKey<T>]: T[Key] extends InteractivityCallable ? Key : never;
14477
+ }[InteractivityKey<T>];
14478
+
14479
+ type InteractivityDirectivePath<
14480
+ Root extends string,
14481
+ Key extends string,
14482
+ > = \`\${Root}.\${Key}\`;
14483
+
14484
+ type NegatedInteractivityDirectivePath<Path extends string> = \`!\${Path}\`;
14485
+
14486
+ export interface TypedInteractivityDirectiveHelpers<
14487
+ State extends InteractivityStateShape,
14488
+ Context extends InteractivityContextShape,
14489
+ Actions extends InteractivityActionShape,
14490
+ Callbacks extends InteractivityCallbackShape,
14491
+ Namespace extends string,
14492
+ > {
14493
+ readonly interactive: Namespace;
14494
+ action<Key extends InteractivityMethodKey<Actions>>(
14495
+ key: Key,
14496
+ ): InteractivityDirectivePath<'actions', Key>;
14497
+ callback<Key extends InteractivityMethodKey<Callbacks>>(
14498
+ key: Key,
14499
+ ): InteractivityDirectivePath<'callbacks', Key>;
14500
+ state<Key extends InteractivityKey<State>>(
14501
+ key: Key,
14502
+ ): InteractivityDirectivePath<'state', Key>;
14503
+ context<Key extends InteractivityKey<Context>>(
14504
+ key: Key,
14505
+ ): InteractivityDirectivePath<'context', Key>;
14506
+ negate<Path extends string>(
14507
+ path: Path,
14508
+ ): NegatedInteractivityDirectivePath<Path>;
14509
+ }
14510
+
14511
+ export interface TypedInteractivityStore<
14512
+ Namespace extends string,
14513
+ State extends InteractivityStateShape,
14514
+ Context extends InteractivityContextShape,
14515
+ Actions extends InteractivityActionShape,
14516
+ Callbacks extends InteractivityCallbackShape,
14517
+ > {
14518
+ readonly namespace: Namespace;
14519
+ readonly state: State;
14520
+ readonly context: Context;
14521
+ readonly actions: Actions;
14522
+ readonly callbacks: Callbacks;
14523
+ readonly directive: TypedInteractivityDirectiveHelpers<
14524
+ State,
14525
+ Context,
14526
+ Actions,
14527
+ Callbacks,
14528
+ Namespace
14529
+ >;
14530
+ createContext(value: Context): Context;
14531
+ }
14532
+
14533
+ export function defineInteractivityStore<
14534
+ Namespace extends string,
14535
+ State extends InteractivityStateShape,
14536
+ Context extends InteractivityContextShape,
14537
+ Actions extends InteractivityActionShape,
14538
+ Callbacks extends InteractivityCallbackShape,
14539
+ >(config: {
14540
+ readonly namespace: Namespace;
14541
+ readonly state: State;
14542
+ readonly context: Context;
14543
+ readonly actions: Actions;
14544
+ readonly callbacks: Callbacks;
14545
+ }): TypedInteractivityStore<Namespace, State, Context, Actions, Callbacks> {
14546
+ return {
14547
+ namespace: config.namespace,
14548
+ state: config.state,
14549
+ context: config.context,
14550
+ actions: config.actions,
14551
+ callbacks: config.callbacks,
14552
+ directive: {
14553
+ interactive: config.namespace,
14554
+ action<Key extends InteractivityMethodKey<Actions>>(key: Key) {
14555
+ return \`actions.\${key}\` as InteractivityDirectivePath<'actions', Key>;
14556
+ },
14557
+ callback<Key extends InteractivityMethodKey<Callbacks>>(key: Key) {
14558
+ return \`callbacks.\${key}\` as InteractivityDirectivePath<'callbacks', Key>;
14559
+ },
14560
+ state<Key extends InteractivityKey<State>>(key: Key) {
14561
+ return \`state.\${key}\` as InteractivityDirectivePath<'state', Key>;
14562
+ },
14563
+ context<Key extends InteractivityKey<Context>>(key: Key) {
14564
+ return \`context.\${key}\` as InteractivityDirectivePath<'context', Key>;
14565
+ },
14566
+ negate<Path extends string>(path: Path) {
14567
+ return \`!\${path}\` as NegatedInteractivityDirectivePath<Path>;
14568
+ },
14569
+ },
14570
+ createContext(value) {
14571
+ return value;
14572
+ },
14573
+ };
14574
+ }
14575
+
14576
+ type InteractivityActionHandler = Function;
14577
+
14578
+ export interface {{pascalCase}}StoreActions {
14579
+ handleClick: InteractivityActionHandler;
14580
+ handleMouseEnter: InteractivityActionHandler;
14581
+ handleMouseLeave: InteractivityActionHandler;
14582
+ reset: InteractivityActionHandler;
14583
+ }
14584
+
14585
+ export interface {{pascalCase}}StoreCallbacks {}
14586
+
14587
+ export const {{slugCamelCase}}Store = defineInteractivityStore({
14588
+ namespace: '{{slugKebabCase}}',
14589
+ state: {} as {{pascalCase}}State,
14590
+ context: {} as {{pascalCase}}Context,
14591
+ actions: {} as {{pascalCase}}StoreActions,
14592
+ callbacks: {} as {{pascalCase}}StoreCallbacks,
14593
+ });
14594
+ `;
13884
14595
  var INTERACTIVITY_SCRIPT_TEMPLATE = `/**
13885
14596
  * WordPress Interactivity API implementation for {{title}} block
13886
14597
  */
13887
14598
  import { store, getContext, getElement, withSyncEvent } from '@wordpress/interactivity';
13888
- import type { {{pascalCase}}Context } from './types';
14599
+ import {
14600
+ {{slugCamelCase}}Store,
14601
+ type {{pascalCase}}StoreActions,
14602
+ } from './interactivity-store';
14603
+ import type { {{pascalCase}}Context, {{pascalCase}}State } from './types';
13889
14604
 
13890
14605
  function getBlockContext() {
13891
14606
  return getContext<{{pascalCase}}Context>();
13892
14607
  }
13893
14608
 
13894
- // Store configuration
13895
- store('{{slugKebabCase}}', {
13896
- // State - reactive data that updates the UI
13897
- state: {
14609
+ const actions: {{pascalCase}}StoreActions = {
14610
+ // Handle block click
14611
+ handleClick: () => {
14612
+ const context = getBlockContext();
14613
+ const { ref } = getElement();
14614
+
14615
+ if (!ref) {
14616
+ return;
14617
+ }
14618
+
14619
+ if (context.maxClicks > 0 && context.clicks >= context.maxClicks) {
14620
+ return;
14621
+ }
14622
+
14623
+ const previousClicks = context.clicks;
14624
+
14625
+ // Increment click counter
14626
+ context.clicks += 1;
14627
+
14628
+ // Trigger animation
14629
+ if (context.animation !== 'none') {
14630
+ context.isAnimating = true;
14631
+ setTimeout(() => {
14632
+ context.isAnimating = false;
14633
+ }, 1000);
14634
+ }
14635
+
14636
+ // Emit custom event
14637
+ ref.dispatchEvent(new CustomEvent('{{slugKebabCase}}:click', {
14638
+ detail: { clicks: context.clicks }
14639
+ }));
14640
+
14641
+ // Check if max clicks reached
14642
+ if (context.maxClicks > 0 && previousClicks < context.maxClicks && context.clicks === context.maxClicks) {
14643
+ ref.dispatchEvent(new CustomEvent('{{slugKebabCase}}:complete', {
14644
+ detail: { totalClicks: context.clicks }
14645
+ }));
14646
+ }
14647
+ },
14648
+
14649
+ // Handle hover events
14650
+ handleMouseEnter: () => {
14651
+ const context = getBlockContext();
14652
+ if (context.animation === 'none') return;
14653
+ context.isAnimating = true;
14654
+ },
14655
+
14656
+ handleMouseLeave: () => {
14657
+ const context = getBlockContext();
14658
+ if (context.animation === 'none') return;
14659
+ context.isAnimating = false;
14660
+ },
14661
+
14662
+ // Reset counter
14663
+ reset: withSyncEvent((event: Event) => {
14664
+ event.stopPropagation();
14665
+ const context = getBlockContext();
14666
+ context.clicks = 0;
14667
+ context.isAnimating = false;
14668
+ })
14669
+ };
14670
+
14671
+ const state = {
13898
14672
  get clicks() {
13899
14673
  return getBlockContext().clicks;
13900
14674
  },
@@ -13922,70 +14696,14 @@ store('{{slugKebabCase}}', {
13922
14696
  const context = getBlockContext();
13923
14697
  return context.clicks >= context.maxClicks && context.maxClicks > 0;
13924
14698
  }
13925
- },
14699
+ } satisfies {{pascalCase}}State;
13926
14700
 
13927
- // Actions - user interactions
13928
- actions: {
13929
- // Handle block click
13930
- handleClick: () => {
13931
- const context = getBlockContext();
13932
- const { ref } = getElement();
13933
-
13934
- if (!ref) {
13935
- return;
13936
- }
13937
-
13938
- if (context.maxClicks > 0 && context.clicks >= context.maxClicks) {
13939
- return;
13940
- }
13941
-
13942
- const previousClicks = context.clicks;
13943
-
13944
- // Increment click counter
13945
- context.clicks += 1;
13946
-
13947
- // Trigger animation
13948
- if (context.animation !== 'none') {
13949
- context.isAnimating = true;
13950
- setTimeout(() => {
13951
- context.isAnimating = false;
13952
- }, 1000);
13953
- }
13954
-
13955
- // Emit custom event
13956
- ref.dispatchEvent(new CustomEvent('{{slugKebabCase}}:click', {
13957
- detail: { clicks: context.clicks }
13958
- }));
13959
-
13960
- // Check if max clicks reached
13961
- if (context.maxClicks > 0 && previousClicks < context.maxClicks && context.clicks === context.maxClicks) {
13962
- ref.dispatchEvent(new CustomEvent('{{slugKebabCase}}:complete', {
13963
- detail: { totalClicks: context.clicks }
13964
- }));
13965
- }
13966
- },
13967
-
13968
- // Handle hover events
13969
- handleMouseEnter: () => {
13970
- const context = getBlockContext();
13971
- if (context.animation === 'none') return;
13972
- context.isAnimating = true;
13973
- },
13974
-
13975
- handleMouseLeave: () => {
13976
- const context = getBlockContext();
13977
- if (context.animation === 'none') return;
13978
- context.isAnimating = false;
13979
- },
13980
-
13981
- // Reset counter
13982
- reset: withSyncEvent((event: Event) => {
13983
- event.stopPropagation();
13984
- const context = getBlockContext();
13985
- context.clicks = 0;
13986
- context.isAnimating = false;
13987
- })
13988
- }
14701
+ // Store configuration
14702
+ store({{slugCamelCase}}Store.namespace, {
14703
+ // State - reactive data that updates the UI
14704
+ state,
14705
+ actions,
14706
+ callbacks: {{slugCamelCase}}Store.callbacks,
13989
14707
  });
13990
14708
  `;
13991
14709
  var INTERACTIVITY_VALIDATORS_TEMPLATE = `import typia from 'typia';
@@ -15798,6 +16516,10 @@ function buildInteractivityCodeArtifacts(variables) {
15798
16516
  relativePath: "src/interactivity.ts",
15799
16517
  template: INTERACTIVITY_SCRIPT_TEMPLATE
15800
16518
  },
16519
+ {
16520
+ relativePath: "src/interactivity-store.ts",
16521
+ template: INTERACTIVITY_STORE_TEMPLATE
16522
+ },
15801
16523
  {
15802
16524
  relativePath: "src/validators.ts",
15803
16525
  template: INTERACTIVITY_VALIDATORS_TEMPLATE
@@ -16505,17 +17227,17 @@ async function scaffoldProject({
16505
17227
  phase: "finalize-project",
16506
17228
  title: "Finalizing scaffold output"
16507
17229
  });
16508
- const readmePath = path17.join(projectDir, "README.md");
16509
- if (!fs14.existsSync(readmePath)) {
16510
- await fsp10.writeFile(readmePath, buildReadme(resolvedTemplateId, variables, resolvedPackageManager, {
17230
+ const readmePath = path18.join(projectDir, "README.md");
17231
+ if (!fs15.existsSync(readmePath)) {
17232
+ await fsp11.writeFile(readmePath, buildReadme(resolvedTemplateId, variables, resolvedPackageManager, {
16511
17233
  withMigrationUi: isBuiltInTemplate || isWorkspace ? withMigrationUi : false,
16512
17234
  withTestPreset: isBuiltInTemplate ? withTestPreset : false,
16513
17235
  withWpEnv: isBuiltInTemplate ? withWpEnv : false
16514
17236
  }), "utf8");
16515
17237
  }
16516
- const gitignorePath = path17.join(projectDir, ".gitignore");
16517
- const existingGitignore = fs14.existsSync(gitignorePath) ? await fsp10.readFile(gitignorePath, "utf8") : "";
16518
- await fsp10.writeFile(gitignorePath, mergeTextLines(buildGitignore(), existingGitignore), "utf8");
17238
+ const gitignorePath = path18.join(projectDir, ".gitignore");
17239
+ const existingGitignore = fs15.existsSync(gitignorePath) ? await fsp11.readFile(gitignorePath, "utf8") : "";
17240
+ await fsp11.writeFile(gitignorePath, mergeTextLines(buildGitignore(), existingGitignore), "utf8");
16519
17241
  await normalizePackageJson(projectDir, resolvedPackageManager);
16520
17242
  if (isBuiltInTemplate) {
16521
17243
  const variableGroups = getScaffoldTemplateVariableGroups(variables);
@@ -16560,6 +17282,8 @@ async function resolveOptionalInteractiveExternalLayerId({
16560
17282
  callerCwd,
16561
17283
  externalLayerId,
16562
17284
  externalLayerSource,
17285
+ listExternalTemplateLayers = listSelectableExternalTemplateLayers,
17286
+ resolveExternalTemplateSeed = resolveTemplateSeed,
16563
17287
  selectExternalLayerId
16564
17288
  }) {
16565
17289
  if (!externalLayerSource || externalLayerId || !selectExternalLayerId) {
@@ -16568,9 +17292,9 @@ async function resolveOptionalInteractiveExternalLayerId({
16568
17292
  externalLayerSource
16569
17293
  };
16570
17294
  }
16571
- const layerSeed = await resolveTemplateSeed(parseTemplateLocator(externalLayerSource), callerCwd);
17295
+ const layerSeed = await resolveExternalTemplateSeed(parseTemplateLocator(externalLayerSource), callerCwd);
16572
17296
  try {
16573
- const selectableLayers = await listSelectableExternalTemplateLayers(layerSeed.rootDir);
17297
+ const selectableLayers = await listExternalTemplateLayers(layerSeed.rootDir);
16574
17298
  if (selectableLayers.length <= 1) {
16575
17299
  await layerSeed.cleanup?.();
16576
17300
  return {
@@ -16586,7 +17310,6 @@ async function resolveOptionalInteractiveExternalLayerId({
16586
17310
  externalLayerSource: layerSeed.rootDir
16587
17311
  };
16588
17312
  }
16589
- await layerSeed.cleanup?.();
16590
17313
  throw new Error(`Unknown external layer "${selectedLayerId}". Expected one of: ${selectableLayers.map((layer) => layer.id).join(", ")}`);
16591
17314
  } catch (error) {
16592
17315
  await layerSeed.cleanup?.();
@@ -16596,4 +17319,4 @@ async function resolveOptionalInteractiveExternalLayerId({
16596
17319
 
16597
17320
  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, renderScaffoldCompatibilityConfig, updatePluginHeaderCompatibility, getDefaultAnswers, resolveTemplateId, resolvePackageManagerId, collectScaffoldAnswers, DATA_STORAGE_MODES, PERSISTENCE_POLICIES, isDataStorageMode, isPersistencePolicy, scaffoldProject, resolveOptionalInteractiveExternalLayerId };
16598
17321
 
16599
- //# debugId=8415ABFCC6733BCA64756E2164756E21
17322
+ //# debugId=C53957BD2FAAAD8464756E2164756E21