wp-typia 0.23.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +3 -1
  2. package/bin/routing-metadata.generated.js +11 -0
  3. package/dist-bunli/.bunli/commands.gen.js +11884 -9017
  4. package/dist-bunli/{cli-9npd9was.js → cli-0v407aag.js} +12 -10
  5. package/dist-bunli/{cli-hhp1d348.js → cli-1170yyve.js} +8 -7
  6. package/dist-bunli/{cli-1meywwsy.js → cli-74y6z3yx.js} +1455 -819
  7. package/dist-bunli/{cli-qse6myha.js → cli-8hxf9qw6.js} +11 -3
  8. package/dist-bunli/{cli-8reep89s.js → cli-9fx0qgb7.js} +2 -2
  9. package/dist-bunli/{cli-add-21bvpfgw.js → cli-add-nmdraf20.js} +8542 -7667
  10. package/dist-bunli/{cli-52ke0ptp.js → cli-am5x7tb4.js} +8 -2
  11. package/dist-bunli/{cli-43mx1vfb.js → cli-bajwv85z.js} +2 -1
  12. package/dist-bunli/cli-ccax7s0s.js +34 -0
  13. package/dist-bunli/{cli-z5qkx2pn.js → cli-cwjdzq6n.js} +79 -13
  14. package/dist-bunli/{cli-diagnostics-5dvztm7q.js → cli-diagnostics-10drxh34.js} +1 -1
  15. package/dist-bunli/{cli-doctor-wy2yjsge.js → cli-doctor-pcss6ecx.js} +688 -459
  16. package/dist-bunli/{cli-2rqf6t0b.js → cli-e4bwd81c.js} +8 -11
  17. package/dist-bunli/{cli-init-xnsbxncv.js → cli-init-he7vm7kc.js} +15 -11
  18. package/dist-bunli/{cli-prompt-614tq57c.js → cli-prompt-ncyg68rn.js} +1 -1
  19. package/dist-bunli/{cli-bq2v559b.js → cli-rdcga1bd.js} +31 -13
  20. package/dist-bunli/{cli-scaffold-zhp2ym8z.js → cli-scaffold-an2k0fnm.js} +28 -16
  21. package/dist-bunli/{cli-c2acv5dv.js → cli-sw06c521.js} +2 -2
  22. package/dist-bunli/{cli-templates-hc71dfc2.js → cli-templates-g8t4fm11.js} +3 -2
  23. package/dist-bunli/{cli-p95wr1q8.js → cli-tq730sqt.js} +6 -3
  24. package/dist-bunli/{cli-ts9thts5.js → cli-v0nnagb3.js} +1513 -1053
  25. package/dist-bunli/{cli-agywa5n6.js → cli-y0a8nztv.js} +15 -6
  26. package/dist-bunli/cli-z48frc8t.js +229 -0
  27. package/dist-bunli/cli.js +5 -5
  28. package/dist-bunli/{command-list-aqrkx021.js → command-list-xaw5agks.js} +241 -64
  29. package/dist-bunli/{create-template-validation-rtec5sng.js → create-template-validation-4fr851vg.js} +5 -4
  30. package/dist-bunli/{migrations-bx0yvc2v.js → migrations-z7f4kxba.js} +10 -9
  31. package/dist-bunli/node-cli.js +661 -389
  32. package/dist-bunli/{workspace-project-csnnggz6.js → workspace-project-gmv2a71z.js} +4 -3
  33. package/package.json +2 -2
  34. package/dist-bunli/cli-j8et6jvr.js +0 -123
@@ -4,7 +4,7 @@ import {
4
4
  parseTemplateLocator,
5
5
  require_semver,
6
6
  validateExplicitCreateTemplateId
7
- } from "./cli-8reep89s.js";
7
+ } from "./cli-9fx0qgb7.js";
8
8
  import {
9
9
  getBuiltInSharedTemplateLayerDir,
10
10
  getBuiltInTemplateLayerDirs,
@@ -14,10 +14,11 @@ import {
14
14
  isOmittableBuiltInTemplateLayerDir,
15
15
  resolveBuiltInTemplateSource,
16
16
  resolveBuiltInTemplateSourceFromLayerDirs
17
- } from "./cli-c2acv5dv.js";
17
+ } from "./cli-sw06c521.js";
18
18
  import {
19
+ DEFAULT_WORDPRESS_ENV_VERSION,
19
20
  getPackageVersions
20
- } from "./cli-agywa5n6.js";
21
+ } from "./cli-y0a8nztv.js";
21
22
  import {
22
23
  BUILTIN_BLOCK_METADATA_VERSION,
23
24
  COMPOUND_CHILD_BLOCK_METADATA_DEFAULTS,
@@ -32,18 +33,20 @@ import {
32
33
  getTemplateById,
33
34
  isBuiltInTemplateId,
34
35
  normalizeTemplateLookupId
35
- } from "./cli-qse6myha.js";
36
+ } from "./cli-8hxf9qw6.js";
36
37
  import {
37
38
  seedProjectMigrations
38
- } from "./cli-9npd9was.js";
39
+ } from "./cli-0v407aag.js";
39
40
  import {
40
41
  ensureMigrationDirectories,
41
42
  isPlainObject,
42
43
  stableJsonStringify,
43
44
  writeInitialMigrationScaffold,
44
45
  writeMigrationConfig
45
- } from "./cli-2rqf6t0b.js";
46
+ } from "./cli-e4bwd81c.js";
46
47
  import {
48
+ assertValidGeneratedSlug,
49
+ assertValidIntegrationEnvService,
47
50
  buildBlockCssClassName,
48
51
  buildFrontendCssClassName,
49
52
  getNodeErrorCode,
@@ -52,20 +55,25 @@ import {
52
55
  pathExists,
53
56
  readOptionalUtf8File,
54
57
  resolveScaffoldIdentifiers,
58
+ rollbackWorkspaceMutation,
59
+ snapshotWorkspaceFiles,
55
60
  toPascalCase,
56
61
  toSegmentPascalCase,
57
62
  toSnakeCase,
58
63
  toTitleCase,
59
64
  validateBlockSlug,
60
65
  validateNamespace
61
- } from "./cli-ts9thts5.js";
66
+ } from "./cli-v0nnagb3.js";
62
67
  import {
63
68
  createManagedTempRoot
64
69
  } from "./cli-t73q5aqz.js";
65
70
  import {
66
71
  CLI_DIAGNOSTIC_CODES,
67
72
  createCliDiagnosticCodeError
68
- } from "./cli-p95wr1q8.js";
73
+ } from "./cli-tq730sqt.js";
74
+ import {
75
+ resolveWorkspaceProject
76
+ } from "./cli-1170yyve.js";
69
77
  import {
70
78
  PACKAGE_MANAGER_IDS,
71
79
  formatInstallCommand,
@@ -73,7 +81,12 @@ import {
73
81
  formatRunScript,
74
82
  getPackageManager,
75
83
  transformPackageManagerText
76
- } from "./cli-52ke0ptp.js";
84
+ } from "./cli-am5x7tb4.js";
85
+ import {
86
+ readJsonFile,
87
+ readJsonFileSync,
88
+ safeJsonParse
89
+ } from "./cli-ccax7s0s.js";
77
90
  import {
78
91
  __toESM
79
92
  } from "./cli-xnn9xjcy.js";
@@ -754,7 +767,9 @@ function getDevScript(packageManager, compoundPersistenceEnabled, templateId) {
754
767
  }
755
768
  async function mutatePackageJson(projectDir, mutate) {
756
769
  const packageJsonPath = path2.join(projectDir, "package.json");
757
- const packageJson = JSON.parse(await fsp2.readFile(packageJsonPath, "utf8"));
770
+ const packageJson = await readJsonFile(packageJsonPath, {
771
+ context: "local dev package manifest"
772
+ });
758
773
  mutate(packageJson);
759
774
  await fsp2.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, "\t")}
760
775
  `, "utf8");
@@ -810,7 +825,7 @@ async function applyGeneratedProjectDxPackageJson({
810
825
  } : {}
811
826
  };
812
827
  if (withWpEnv || withTestPreset) {
813
- packageJson.devDependencies["@wordpress/env"] = "^11.2.0";
828
+ packageJson.devDependencies["@wordpress/env"] = DEFAULT_WORDPRESS_ENV_VERSION;
814
829
  }
815
830
  if (withTestPreset) {
816
831
  packageJson.devDependencies["@playwright/test"] = "^1.54.2";
@@ -2189,7 +2204,9 @@ function readGeneratedPackageJson(projectDir) {
2189
2204
  if (!fs2.existsSync(packageJsonPath)) {
2190
2205
  return null;
2191
2206
  }
2192
- return JSON.parse(fs2.readFileSync(packageJsonPath, "utf8"));
2207
+ return readJsonFileSync(packageJsonPath, {
2208
+ context: "generated package manifest"
2209
+ });
2193
2210
  }
2194
2211
  function formatNonEmptyTargetDirectoryError(targetDir) {
2195
2212
  return `Target directory is not empty: ${targetDir}. Choose a new project directory, or empty this directory before rerunning the scaffold.`;
@@ -2231,7 +2248,9 @@ function isWorkspaceProject(projectDir) {
2231
2248
  }
2232
2249
  async function applyWorkspaceMigrationCapability(projectDir, packageManager) {
2233
2250
  const packageJsonPath = path4.join(projectDir, "package.json");
2234
- const packageJson = JSON.parse(await fsp3.readFile(packageJsonPath, "utf8"));
2251
+ const packageJson = await readJsonFile(packageJsonPath, {
2252
+ context: "workspace package manifest"
2253
+ });
2235
2254
  const wpTypiaPackageVersion = getPackageVersions().wpTypiaPackageVersion;
2236
2255
  const canonicalCliSpecifier = wpTypiaPackageVersion === "^0.0.0" ? "wp-typia" : `wp-typia@${wpTypiaPackageVersion.replace(/^[~^]/u, "")}`;
2237
2256
  const migrationCli = (args) => formatPackageExecCommand(packageManager, canonicalCliSpecifier, `migrate ${args}`);
@@ -2288,9 +2307,548 @@ async function withEphemeralScaffoldNodeModules(targetDir, callback) {
2288
2307
  }
2289
2308
  }
2290
2309
 
2310
+ // ../wp-typia-project-tools/src/runtime/cli-add-workspace-integration-env.ts
2311
+ import path7 from "path";
2312
+
2313
+ // ../wp-typia-project-tools/src/runtime/cli-add-workspace-integration-env-files.ts
2314
+ import { promises as fsp4 } from "fs";
2315
+ import path5 from "path";
2316
+ async function appendMissingLines(filePath, lines) {
2317
+ const current = await readOptionalUtf8File(filePath) ?? "";
2318
+ const existingLines = new Set(current.split(/\r?\n/u));
2319
+ const missingLines = lines.filter((line) => !existingLines.has(line));
2320
+ if (missingLines.length === 0) {
2321
+ return;
2322
+ }
2323
+ const separator = current.length === 0 || current.endsWith(`
2324
+ `) ? "" : `
2325
+ `;
2326
+ await fsp4.mkdir(path5.dirname(filePath), { recursive: true });
2327
+ await fsp4.writeFile(filePath, `${current}${separator}${missingLines.join(`
2328
+ `)}
2329
+ `, "utf8");
2330
+ }
2331
+ async function writeFileIfAbsent({
2332
+ filePath,
2333
+ source,
2334
+ warnings
2335
+ }) {
2336
+ await fsp4.mkdir(path5.dirname(filePath), { recursive: true });
2337
+ try {
2338
+ await fsp4.writeFile(filePath, source, { encoding: "utf8", flag: "wx" });
2339
+ } catch (error) {
2340
+ if (error.code === "EEXIST") {
2341
+ warnings.push(`Preserved existing ${path5.basename(filePath)}; review it manually if you need different local integration settings.`);
2342
+ return;
2343
+ }
2344
+ throw error;
2345
+ }
2346
+ }
2347
+ async function writeNewScaffoldFile(filePath, source) {
2348
+ await fsp4.mkdir(path5.dirname(filePath), { recursive: true });
2349
+ try {
2350
+ await fsp4.writeFile(filePath, source, { encoding: "utf8", flag: "wx" });
2351
+ } catch (error) {
2352
+ if (error.code === "EEXIST") {
2353
+ throw new Error(`An integration environment scaffold already exists at ${filePath}. Choose a different name.`);
2354
+ }
2355
+ throw error;
2356
+ }
2357
+ }
2358
+
2359
+ // ../wp-typia-project-tools/src/runtime/cli-add-workspace-integration-env-package-json.ts
2360
+ import { promises as fsp5 } from "fs";
2361
+ import path6 from "path";
2362
+ function addScriptIfMissing({
2363
+ scriptName,
2364
+ scripts,
2365
+ scriptValue,
2366
+ warnings
2367
+ }) {
2368
+ if (scripts[scriptName] === undefined) {
2369
+ scripts[scriptName] = scriptValue;
2370
+ return;
2371
+ }
2372
+ if (scripts[scriptName] !== scriptValue) {
2373
+ warnings.push(`Preserved existing package script "${scriptName}"; add "${scriptValue}" manually if you want the generated integration command.`);
2374
+ }
2375
+ }
2376
+ async function mutateIntegrationEnvPackageJson(projectDir, mutate) {
2377
+ const packageJsonPath = path6.join(projectDir, "package.json");
2378
+ const packageJson = await readJsonFile(packageJsonPath, {
2379
+ context: "integration env package manifest"
2380
+ });
2381
+ mutate(packageJson);
2382
+ await fsp5.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, "\t")}
2383
+ `, "utf8");
2384
+ }
2385
+ function addIntegrationEnvPackageJsonEntries({
2386
+ integrationEnvSlug,
2387
+ packageManager,
2388
+ packageJson,
2389
+ withReleaseZip,
2390
+ service,
2391
+ warnings,
2392
+ withWpEnv
2393
+ }) {
2394
+ const devDependencies = {
2395
+ ...packageJson.devDependencies ?? {}
2396
+ };
2397
+ if (withWpEnv && devDependencies["@wordpress/env"] === undefined) {
2398
+ devDependencies["@wordpress/env"] = DEFAULT_WORDPRESS_ENV_VERSION;
2399
+ }
2400
+ packageJson.devDependencies = devDependencies;
2401
+ const scripts = {
2402
+ ...packageJson.scripts ?? {}
2403
+ };
2404
+ addScriptIfMissing({
2405
+ scriptName: `smoke:${integrationEnvSlug}`,
2406
+ scriptValue: `node scripts/integration-smoke/${integrationEnvSlug}.mjs`,
2407
+ scripts,
2408
+ warnings
2409
+ });
2410
+ addScriptIfMissing({
2411
+ scriptName: "smoke:integration",
2412
+ scriptValue: formatRunScript(packageManager, `smoke:${integrationEnvSlug}`),
2413
+ scripts,
2414
+ warnings
2415
+ });
2416
+ if (withWpEnv) {
2417
+ addScriptIfMissing({
2418
+ scriptName: "wp-env:start",
2419
+ scriptValue: "wp-env start",
2420
+ scripts,
2421
+ warnings
2422
+ });
2423
+ addScriptIfMissing({
2424
+ scriptName: "wp-env:stop",
2425
+ scriptValue: "wp-env stop",
2426
+ scripts,
2427
+ warnings
2428
+ });
2429
+ addScriptIfMissing({
2430
+ scriptName: "wp-env:reset",
2431
+ scriptValue: "wp-env destroy all && wp-env start",
2432
+ scripts,
2433
+ warnings
2434
+ });
2435
+ }
2436
+ if (withReleaseZip) {
2437
+ addScriptIfMissing({
2438
+ scriptName: "release:zip",
2439
+ scriptValue: `${formatRunScript(packageManager, "sync-rest:package")} && ${formatRunScript(packageManager, "build")} && wp-scripts plugin-zip`,
2440
+ scripts,
2441
+ warnings
2442
+ });
2443
+ addScriptIfMissing({
2444
+ scriptName: "release:zip:check",
2445
+ scriptValue: `${formatRunScript(packageManager, "sync-rest:package:check")} && ${formatRunScript(packageManager, "build")}`,
2446
+ scripts,
2447
+ warnings
2448
+ });
2449
+ addScriptIfMissing({
2450
+ scriptName: "qa:check",
2451
+ scriptValue: `${formatRunScript(packageManager, "wp-typia:doctor:workspace")} && ${formatRunScript(packageManager, "release:zip:check")}`,
2452
+ scripts,
2453
+ warnings
2454
+ });
2455
+ }
2456
+ if (service === "docker-compose") {
2457
+ addScriptIfMissing({
2458
+ scriptName: "service:start",
2459
+ scriptValue: "docker compose -f docker-compose.integration.yml up -d",
2460
+ scripts,
2461
+ warnings
2462
+ });
2463
+ addScriptIfMissing({
2464
+ scriptName: "service:stop",
2465
+ scriptValue: "docker compose -f docker-compose.integration.yml down",
2466
+ scripts,
2467
+ warnings
2468
+ });
2469
+ }
2470
+ packageJson.scripts = scripts;
2471
+ }
2472
+
2473
+ // ../wp-typia-project-tools/src/runtime/cli-add-workspace-integration-env-source-emitters.ts
2474
+ function buildWpEnvConfigSource() {
2475
+ return `${JSON.stringify({
2476
+ $schema: "https://schemas.wp.org/trunk/wp-env.json",
2477
+ core: null,
2478
+ port: 8888,
2479
+ testsEnvironment: false,
2480
+ plugins: ["."],
2481
+ config: {
2482
+ WP_DEBUG: true,
2483
+ WP_DEBUG_LOG: true,
2484
+ WP_DEBUG_DISPLAY: false,
2485
+ SCRIPT_DEBUG: true,
2486
+ WP_ENVIRONMENT_TYPE: "local"
2487
+ }
2488
+ }, null, 2)}
2489
+ `;
2490
+ }
2491
+ function buildDockerComposeSource() {
2492
+ return `services:
2493
+ integration-service:
2494
+ image: node:22-alpine
2495
+ working_dir: /workspace
2496
+ volumes:
2497
+ - .:/workspace
2498
+ command: >
2499
+ node -e "require('node:http').createServer((request, response) => {
2500
+ response.writeHead(request.url === '/health' ? 200 : 404, {
2501
+ 'content-type': 'application/json'
2502
+ });
2503
+ response.end(JSON.stringify({
2504
+ ok: request.url === '/health',
2505
+ service: 'wp-typia-integration-starter'
2506
+ }));
2507
+ }).listen(3000, '0.0.0.0')"
2508
+ ports:
2509
+ - "3000:3000"
2510
+ `;
2511
+ }
2512
+ function buildIntegrationSmokeScriptSource(integrationEnvSlug) {
2513
+ return `import fs from "node:fs";
2514
+ import path from "node:path";
2515
+ import { fileURLToPath } from "node:url";
2516
+
2517
+ const ROOT_DIR = path.resolve(
2518
+ fileURLToPath(new URL("../..", import.meta.url)),
2519
+ );
2520
+ const ENV_FILE = path.join(ROOT_DIR, ".env");
2521
+
2522
+ function parseEnvValue(value) {
2523
+ const trimmed = value.trim();
2524
+ if (
2525
+ (trimmed.startsWith('"') && trimmed.endsWith('"')) ||
2526
+ (trimmed.startsWith("'") && trimmed.endsWith("'"))
2527
+ ) {
2528
+ return trimmed.slice(1, -1);
2529
+ }
2530
+
2531
+ return trimmed;
2532
+ }
2533
+
2534
+ function readEnvFile(filePath) {
2535
+ if (!fs.existsSync(filePath)) {
2536
+ return {};
2537
+ }
2538
+
2539
+ return Object.fromEntries(
2540
+ fs
2541
+ .readFileSync(filePath, "utf8")
2542
+ .split(/\\r?\\n/u)
2543
+ .map((line) => line.trim())
2544
+ .filter((line) => line.length > 0 && !line.startsWith("#"))
2545
+ .map((line) => {
2546
+ const separatorIndex = line.indexOf("=");
2547
+ if (separatorIndex === -1) {
2548
+ return null;
2549
+ }
2550
+
2551
+ return [
2552
+ line.slice(0, separatorIndex).trim(),
2553
+ parseEnvValue(line.slice(separatorIndex + 1)),
2554
+ ];
2555
+ })
2556
+ .filter(Boolean),
2557
+ );
2558
+ }
2559
+
2560
+ const envFile = readEnvFile(ENV_FILE);
2561
+
2562
+ function getEnv(name, fallback) {
2563
+ return process.env[name] ?? envFile[name] ?? fallback;
2564
+ }
2565
+
2566
+ function getPositiveIntegerEnv(name, fallback) {
2567
+ const value = Number.parseInt(getEnv(name, String(fallback)), 10);
2568
+ return Number.isFinite(value) && value > 0 ? value : fallback;
2569
+ }
2570
+
2571
+ function resolveEndpointUrl(baseUrl, endpointPath) {
2572
+ const normalizedBaseUrl = new URL(baseUrl);
2573
+ if (!normalizedBaseUrl.pathname.endsWith("/")) {
2574
+ normalizedBaseUrl.pathname = \`\${normalizedBaseUrl.pathname}/\`;
2575
+ }
2576
+
2577
+ const relativePath = endpointPath.replace(/^\\/+/u, "");
2578
+ return new URL(relativePath, normalizedBaseUrl);
2579
+ }
2580
+
2581
+ async function assertJsonEndpoint(label, url) {
2582
+ const requestTimeoutMs = getPositiveIntegerEnv(
2583
+ "WP_TYPIA_SMOKE_TIMEOUT_MS",
2584
+ 30_000,
2585
+ );
2586
+ const response = await fetch(url, {
2587
+ headers: {
2588
+ accept: "application/json",
2589
+ },
2590
+ signal: AbortSignal.timeout(requestTimeoutMs),
2591
+ });
2592
+
2593
+ if (!response.ok) {
2594
+ throw new Error(
2595
+ \`\${label} failed at \${url} with HTTP \${response.status}.\`,
2596
+ );
2597
+ }
2598
+
2599
+ const contentType = response.headers.get("content-type") ?? "";
2600
+ if (!contentType.includes("application/json")) {
2601
+ throw new Error(
2602
+ \`\${label} at \${url} did not return JSON (content-type: \${contentType || "missing"}).\`,
2603
+ );
2604
+ }
2605
+
2606
+ return response.json();
2607
+ }
2608
+
2609
+ const baseUrl = new URL(
2610
+ getEnv("WP_TYPIA_SMOKE_BASE_URL", "http://localhost:8888"),
2611
+ );
2612
+ const serviceUrl = getEnv("WP_TYPIA_SERVICE_URL", "").trim();
2613
+
2614
+ // Extend this starter with project-specific generated REST clients or schema
2615
+ // checks as the workspace grows. For example, read JSON schemas under
2616
+ // src/rest/<resource>/api-schemas or import TS clients through a tsx-powered
2617
+ // smoke runner when you need authenticated route coverage.
2618
+
2619
+ await assertJsonEndpoint(
2620
+ "WordPress REST index",
2621
+ resolveEndpointUrl(baseUrl, "wp-json/"),
2622
+ );
2623
+
2624
+ if (serviceUrl.length > 0) {
2625
+ await assertJsonEndpoint(
2626
+ "Local integration service healthcheck",
2627
+ resolveEndpointUrl(serviceUrl, "health"),
2628
+ );
2629
+ }
2630
+
2631
+ console.log("wp-typia integration smoke passed: ${integrationEnvSlug}");
2632
+ `;
2633
+ }
2634
+ function buildEnvExampleSource(service) {
2635
+ return [
2636
+ "# wp-typia integration smoke settings",
2637
+ "WP_TYPIA_SMOKE_BASE_URL=http://localhost:8888",
2638
+ "WP_TYPIA_SMOKE_USERNAME=admin",
2639
+ "WP_TYPIA_SMOKE_PASSWORD=password",
2640
+ "WP_TYPIA_SMOKE_TIMEOUT_MS=30000",
2641
+ ...service === "docker-compose" ? [
2642
+ "",
2643
+ "# Optional docker-compose integration service starter.",
2644
+ "WP_TYPIA_SERVICE_URL=http://localhost:3000"
2645
+ ] : [
2646
+ "",
2647
+ "# Set this when your smoke test needs a project-specific service.",
2648
+ "# WP_TYPIA_SERVICE_URL=http://localhost:3000"
2649
+ ],
2650
+ ""
2651
+ ].join(`
2652
+ `);
2653
+ }
2654
+ function buildIntegrationEnvReadmeSource({
2655
+ integrationEnvSlug,
2656
+ service,
2657
+ withReleaseZip,
2658
+ withWpEnv
2659
+ }) {
2660
+ const title = toTitleCase(integrationEnvSlug);
2661
+ const setupSteps = [
2662
+ "Copy `.env.example` to `.env` and adjust the URLs or credentials for your local project.",
2663
+ ...withWpEnv ? [
2664
+ "Run `npm run wp-env:start` to start the generated WordPress environment."
2665
+ ] : [
2666
+ "Point `WP_TYPIA_SMOKE_BASE_URL` at the WordPress environment you already run locally."
2667
+ ],
2668
+ ...service === "docker-compose" ? [
2669
+ "Run `npm run service:start` if you want the placeholder docker-compose service available at `WP_TYPIA_SERVICE_URL`."
2670
+ ] : [
2671
+ "Set `WP_TYPIA_SERVICE_URL` only when your integration smoke needs a local service dependency."
2672
+ ],
2673
+ `Run \`npm run smoke:${integrationEnvSlug}\` to execute the starter smoke check.`,
2674
+ ...withReleaseZip ? [
2675
+ "Run `npm run release:zip` after smoke checks pass to build a distributable plugin zip."
2676
+ ] : []
2677
+ ];
2678
+ return `# ${title} Integration Environment
2679
+
2680
+ This starter keeps local WordPress integration smoke checks opt-in and editable.
2681
+ It does not change default block scaffolds or require wp-env unless this add
2682
+ workflow was run with \`--wp-env\`.
2683
+
2684
+ ## Setup
2685
+
2686
+ ${setupSteps.map((step, index) => `${index + 1}. ${step}`).join(`
2687
+ `)}
2688
+
2689
+ ## Adapting the Starter
2690
+
2691
+ - Extend \`scripts/integration-smoke/${integrationEnvSlug}.mjs\` with the REST,
2692
+ editor, or service assertions that matter for this project.
2693
+ - Keep secrets in \`.env\`; \`.env.example\` should document only safe defaults.
2694
+ - If your project uses a real service stack, replace the placeholder
2695
+ \`docker-compose.integration.yml\` service with your database, queue, API, or
2696
+ emulator containers.
2697
+ - Keep the smoke script focused on high-signal integration checks so CI and
2698
+ local debugging stay fast.
2699
+ ${withReleaseZip ? "- Treat `release:zip:check` as a CI guard before packaging release artifacts.\n" : ""}
2700
+ `;
2701
+ }
2702
+
2703
+ // ../wp-typia-project-tools/src/runtime/cli-add-workspace-mutation.ts
2704
+ var DEFAULT_PHP_SNIPPET_INSERTION_ANCHORS = [
2705
+ /add_action\(\s*["']init["']\s*,\s*["'][^"']+_load_textdomain["']\s*\);\s*\n/u,
2706
+ /\?>\s*$/u
2707
+ ];
2708
+
2709
+ class WorkspaceMutationRollbackError extends Error {
2710
+ mutationError;
2711
+ rollbackError;
2712
+ constructor(mutationError, rollbackError) {
2713
+ super("Workspace mutation failed and rollback also failed.");
2714
+ this.name = "WorkspaceMutationRollbackError";
2715
+ this.mutationError = mutationError;
2716
+ this.rollbackError = rollbackError;
2717
+ }
2718
+ }
2719
+ async function executeWorkspaceMutationPlan({
2720
+ filePaths,
2721
+ run,
2722
+ snapshotDirs = [],
2723
+ targetPaths = []
2724
+ }) {
2725
+ const mutationSnapshot = {
2726
+ fileSources: await snapshotWorkspaceFiles(filePaths),
2727
+ snapshotDirs: [...snapshotDirs],
2728
+ targetPaths: [...targetPaths]
2729
+ };
2730
+ try {
2731
+ return await run();
2732
+ } catch (error) {
2733
+ try {
2734
+ await rollbackWorkspaceMutation(mutationSnapshot);
2735
+ } catch (rollbackError) {
2736
+ throw new WorkspaceMutationRollbackError(error, rollbackError);
2737
+ }
2738
+ throw error;
2739
+ }
2740
+ }
2741
+ function insertPhpSnippetBeforeWorkspaceAnchors(source, snippet) {
2742
+ for (const anchor of DEFAULT_PHP_SNIPPET_INSERTION_ANCHORS) {
2743
+ const candidate = source.replace(anchor, (match) => `${snippet}
2744
+ ${match}`);
2745
+ if (candidate !== source) {
2746
+ return candidate;
2747
+ }
2748
+ }
2749
+ return `${source.trimEnd()}
2750
+ ${snippet}
2751
+ `;
2752
+ }
2753
+ function appendPhpSnippetBeforeClosingTag(source, snippet) {
2754
+ const closingTagPattern = /\?>\s*$/u;
2755
+ if (closingTagPattern.test(source)) {
2756
+ return source.replace(closingTagPattern, `${snippet}
2757
+ ?>`);
2758
+ }
2759
+ return `${source.trimEnd()}
2760
+ ${snippet}
2761
+ `;
2762
+ }
2763
+
2764
+ // ../wp-typia-project-tools/src/runtime/cli-add-workspace-integration-env.ts
2765
+ async function runAddIntegrationEnvCommand({
2766
+ cwd = process.cwd(),
2767
+ integrationEnvName,
2768
+ service,
2769
+ withReleaseZip = false,
2770
+ withWpEnv = false
2771
+ }) {
2772
+ const workspace = resolveWorkspaceProject(cwd);
2773
+ const integrationEnvSlug = assertValidGeneratedSlug("Integration environment name", normalizeBlockSlug(integrationEnvName), "wp-typia add integration-env <name> [--wp-env] [--release-zip]");
2774
+ const serviceId = assertValidIntegrationEnvService(service);
2775
+ const warnings = [];
2776
+ const packageJsonPath = path7.join(workspace.projectDir, "package.json");
2777
+ const gitignorePath = path7.join(workspace.projectDir, ".gitignore");
2778
+ const envExamplePath = path7.join(workspace.projectDir, ".env.example");
2779
+ const wpEnvPath = path7.join(workspace.projectDir, ".wp-env.json");
2780
+ const dockerComposePath = path7.join(workspace.projectDir, "docker-compose.integration.yml");
2781
+ const smokeDir = path7.join(workspace.projectDir, "scripts", "integration-smoke");
2782
+ const docsDir = path7.join(workspace.projectDir, "docs", "integration-env");
2783
+ const smokeScriptPath = path7.join(smokeDir, `${integrationEnvSlug}.mjs`);
2784
+ const docsPath = path7.join(docsDir, `${integrationEnvSlug}.md`);
2785
+ const shouldRemoveSmokeDirOnRollback = !await pathExists(smokeDir);
2786
+ const shouldRemoveDocsDirOnRollback = !await pathExists(docsDir);
2787
+ await executeWorkspaceMutationPlan({
2788
+ filePaths: [
2789
+ packageJsonPath,
2790
+ gitignorePath,
2791
+ envExamplePath,
2792
+ ...withWpEnv ? [wpEnvPath] : [],
2793
+ ...serviceId === "docker-compose" ? [dockerComposePath] : []
2794
+ ],
2795
+ targetPaths: [
2796
+ smokeScriptPath,
2797
+ docsPath,
2798
+ ...shouldRemoveSmokeDirOnRollback ? [smokeDir] : [],
2799
+ ...shouldRemoveDocsDirOnRollback ? [docsDir] : []
2800
+ ],
2801
+ run: async () => {
2802
+ await writeNewScaffoldFile(smokeScriptPath, buildIntegrationSmokeScriptSource(integrationEnvSlug));
2803
+ await writeNewScaffoldFile(docsPath, buildIntegrationEnvReadmeSource({
2804
+ integrationEnvSlug,
2805
+ service: serviceId,
2806
+ withReleaseZip,
2807
+ withWpEnv
2808
+ }));
2809
+ await appendMissingLines(envExamplePath, [
2810
+ ...buildEnvExampleSource(serviceId).trimEnd().split(`
2811
+ `)
2812
+ ]);
2813
+ await appendMissingLines(gitignorePath, [".env", ".env.local"]);
2814
+ if (withWpEnv) {
2815
+ await writeFileIfAbsent({
2816
+ filePath: wpEnvPath,
2817
+ source: buildWpEnvConfigSource(),
2818
+ warnings
2819
+ });
2820
+ }
2821
+ if (serviceId === "docker-compose") {
2822
+ await writeFileIfAbsent({
2823
+ filePath: dockerComposePath,
2824
+ source: buildDockerComposeSource(),
2825
+ warnings
2826
+ });
2827
+ }
2828
+ await mutateIntegrationEnvPackageJson(workspace.projectDir, (packageJson) => addIntegrationEnvPackageJsonEntries({
2829
+ integrationEnvSlug,
2830
+ packageManager: workspace.packageManager,
2831
+ packageJson,
2832
+ service: serviceId,
2833
+ withReleaseZip,
2834
+ warnings,
2835
+ withWpEnv
2836
+ }));
2837
+ }
2838
+ });
2839
+ return {
2840
+ integrationEnvSlug,
2841
+ projectDir: workspace.projectDir,
2842
+ service: serviceId,
2843
+ warnings: warnings.length > 0 ? warnings : undefined,
2844
+ withReleaseZip,
2845
+ withWpEnv
2846
+ };
2847
+ }
2848
+
2291
2849
  // ../wp-typia-project-tools/src/runtime/cli-validation.ts
2292
2850
  import fs3 from "fs";
2293
- import path5 from "path";
2851
+ import path8 from "path";
2294
2852
  function normalizeOptionalCliString(value) {
2295
2853
  if (typeof value !== "string") {
2296
2854
  return;
@@ -2299,14 +2857,14 @@ function normalizeOptionalCliString(value) {
2299
2857
  return trimmed.length > 0 ? trimmed : undefined;
2300
2858
  }
2301
2859
  function looksLikeLocalCliPath(value) {
2302
- return path5.isAbsolute(value) || value.startsWith("./") || value.startsWith("../") || value.startsWith(".\\") || value.startsWith("..\\");
2860
+ return path8.isAbsolute(value) || value.startsWith("./") || value.startsWith("../") || value.startsWith(".\\") || value.startsWith("..\\");
2303
2861
  }
2304
2862
  function resolveLocalCliPathOption(options) {
2305
2863
  const normalizedValue = normalizeOptionalCliString(options.value);
2306
2864
  if (!normalizedValue || !looksLikeLocalCliPath(normalizedValue)) {
2307
2865
  return normalizedValue;
2308
2866
  }
2309
- const resolvedPath = path5.resolve(options.cwd, normalizedValue);
2867
+ const resolvedPath = path8.resolve(options.cwd, normalizedValue);
2310
2868
  if (!fs3.existsSync(resolvedPath)) {
2311
2869
  throw createCliDiagnosticCodeError(CLI_DIAGNOSTIC_CODES.INVALID_ARGUMENT, `\`${options.label}\` path does not exist: ${resolvedPath}. Check the path relative to ${options.cwd}.`);
2312
2870
  }
@@ -2557,35 +3115,37 @@ async function collectScaffoldAnswers({
2557
3115
  }
2558
3116
 
2559
3117
  // ../wp-typia-project-tools/src/runtime/scaffold.ts
2560
- import { promises as fsp12 } from "fs";
2561
- import path17 from "path";
3118
+ import { promises as fsp14 } from "fs";
3119
+ import path20 from "path";
2562
3120
 
2563
3121
  // ../wp-typia-project-tools/src/runtime/scaffold-apply-utils.ts
2564
- import { promises as fsp6 } from "fs";
2565
- import path9 from "path";
3122
+ import { promises as fsp8 } from "fs";
3123
+ import path12 from "path";
2566
3124
  import { execSync as execSync3 } from "child_process";
2567
3125
  import { fileURLToPath } from "url";
2568
3126
 
2569
3127
  // ../wp-typia-project-tools/src/runtime/migration-ui-capability.ts
2570
- import { promises as fsp4 } from "fs";
2571
- import path6 from "path";
3128
+ import { promises as fsp6 } from "fs";
3129
+ import path9 from "path";
2572
3130
  var INITIAL_MIGRATION_VERSION = "v1";
2573
3131
  var BLOCK_METADATA_IMPORT_LINE = "import metadata from './block-metadata';";
2574
3132
  var LEGACY_BLOCK_JSON_IMPORT_LINE = "import metadata from './block.json';";
2575
3133
  async function mutatePackageJson2(projectDir, mutate) {
2576
- const packageJsonPath = path6.join(projectDir, "package.json");
2577
- const packageJson = JSON.parse(await fsp4.readFile(packageJsonPath, "utf8"));
3134
+ const packageJsonPath = path9.join(projectDir, "package.json");
3135
+ const packageJson = await readJsonFile(packageJsonPath, {
3136
+ context: "migration UI package manifest"
3137
+ });
2578
3138
  mutate(packageJson);
2579
- await fsp4.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, "\t")}
3139
+ await fsp6.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, "\t")}
2580
3140
  `, "utf8");
2581
3141
  }
2582
3142
  async function patchFile(filePath, transform) {
2583
- const source = await fsp4.readFile(filePath, "utf8");
3143
+ const source = await fsp6.readFile(filePath, "utf8");
2584
3144
  const nextSource = transform(source);
2585
3145
  if (nextSource === source) {
2586
3146
  throw new Error(`Unable to apply migration UI patch for ${filePath}`);
2587
3147
  }
2588
- await fsp4.writeFile(filePath, nextSource, "utf8");
3148
+ await fsp6.writeFile(filePath, nextSource, "utf8");
2589
3149
  }
2590
3150
  function injectAfter(source, needle, insertion) {
2591
3151
  if (source.includes(insertion)) {
@@ -2647,8 +3207,8 @@ function buildMigrationBlocks(templateId, variables) {
2647
3207
  ];
2648
3208
  }
2649
3209
  async function applySingleBlockPatches(projectDir, variables) {
2650
- const editPath = path6.join(projectDir, "src", "edit.tsx");
2651
- const indexPath = path6.join(projectDir, "src", "index.tsx");
3210
+ const editPath = path9.join(projectDir, "src", "edit.tsx");
3211
+ const indexPath = path9.join(projectDir, "src", "index.tsx");
2652
3212
  const deprecatedImport = `import { deprecated } from './migrations/generated/${variables.slugKebabCase}/deprecated';`;
2653
3213
  const deprecatedLine = " deprecated,";
2654
3214
  const dashboardImport = `import { MigrationDashboard } from './admin/migration-dashboard';`;
@@ -2669,10 +3229,10 @@ async function applySingleBlockPatches(projectDir, variables) {
2669
3229
  });
2670
3230
  }
2671
3231
  async function applyCompoundPatches(projectDir, variables) {
2672
- const parentEditPath = path6.join(projectDir, "src", "blocks", variables.slugKebabCase, "edit.tsx");
2673
- const parentIndexPath = path6.join(projectDir, "src", "blocks", variables.slugKebabCase, "index.tsx");
2674
- const childIndexPath = path6.join(projectDir, "src", "blocks", `${variables.slugKebabCase}-item`, "index.tsx");
2675
- const addChildScriptPath = path6.join(projectDir, "scripts", "add-compound-child.ts");
3232
+ const parentEditPath = path9.join(projectDir, "src", "blocks", variables.slugKebabCase, "edit.tsx");
3233
+ const parentIndexPath = path9.join(projectDir, "src", "blocks", variables.slugKebabCase, "index.tsx");
3234
+ const childIndexPath = path9.join(projectDir, "src", "blocks", `${variables.slugKebabCase}-item`, "index.tsx");
3235
+ const addChildScriptPath = path9.join(projectDir, "scripts", "add-compound-child.ts");
2676
3236
  await patchFile(parentIndexPath, (source) => {
2677
3237
  let nextSource = injectAfterBlockMetadataImport(source, `import { deprecated } from '../../migrations/generated/${variables.slugKebabCase}/deprecated';`);
2678
3238
  nextSource = injectBefore(nextSource, "\tedit: Edit,", "\tdeprecated,");
@@ -2742,7 +3302,7 @@ async function applyMigrationUiCapability({
2742
3302
  templateId,
2743
3303
  variables
2744
3304
  }) {
2745
- const commonTemplateDir = path6.join(SHARED_MIGRATION_UI_TEMPLATE_ROOT, "common");
3305
+ const commonTemplateDir = path9.join(SHARED_MIGRATION_UI_TEMPLATE_ROOT, "common");
2746
3306
  await copyInterpolatedDirectory(commonTemplateDir, projectDir, variables);
2747
3307
  await mutatePackageJson2(projectDir, (packageJson) => {
2748
3308
  const wpTypiaPackageVersion = getPackageVersions().wpTypiaPackageVersion;
@@ -2996,9 +3556,9 @@ function mergeTextLines(primaryContent, existingContent) {
2996
3556
  }
2997
3557
 
2998
3558
  // ../wp-typia-project-tools/src/runtime/scaffold-package-manager-files.ts
2999
- import { promises as fsp5 } from "fs";
3559
+ import { promises as fsp7 } from "fs";
3000
3560
  import { execSync as execSync2 } from "child_process";
3001
- import path7 from "path";
3561
+ import path10 from "path";
3002
3562
  var LOCKFILES = {
3003
3563
  bun: ["bun.lock", "bun.lockb"],
3004
3564
  npm: ["package-lock.json"],
@@ -3006,22 +3566,25 @@ var LOCKFILES = {
3006
3566
  yarn: ["yarn.lock"]
3007
3567
  };
3008
3568
  async function normalizePackageManagerFiles(targetDir, packageManagerId) {
3009
- const yarnRcPath = path7.join(targetDir, ".yarnrc.yml");
3569
+ const yarnRcPath = path10.join(targetDir, ".yarnrc.yml");
3010
3570
  if (packageManagerId === "yarn") {
3011
- await fsp5.writeFile(yarnRcPath, `nodeLinker: node-modules
3571
+ await fsp7.writeFile(yarnRcPath, `nodeLinker: node-modules
3012
3572
  `, "utf8");
3013
3573
  return;
3014
3574
  }
3015
- await fsp5.rm(yarnRcPath, { force: true });
3575
+ await fsp7.rm(yarnRcPath, { force: true });
3016
3576
  }
3017
3577
  async function normalizePackageJson(targetDir, packageManagerId) {
3018
- const packageJsonPath = path7.join(targetDir, "package.json");
3578
+ const packageJsonPath = path10.join(targetDir, "package.json");
3019
3579
  const packageJsonSource = await readOptionalUtf8File(packageJsonPath);
3020
3580
  if (packageJsonSource === null) {
3021
3581
  return;
3022
3582
  }
3023
3583
  const packageManager = getPackageManager(packageManagerId);
3024
- const packageJson = JSON.parse(packageJsonSource);
3584
+ const packageJson = safeJsonParse(packageJsonSource, {
3585
+ context: "generated package manifest",
3586
+ filePath: packageJsonPath
3587
+ });
3025
3588
  if (packageManagerId === "npm") {
3026
3589
  delete packageJson.packageManager;
3027
3590
  } else {
@@ -3034,7 +3597,7 @@ async function normalizePackageJson(targetDir, packageManagerId) {
3034
3597
  }
3035
3598
  }
3036
3599
  }
3037
- await fsp5.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, "\t")}
3600
+ await fsp7.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, "\t")}
3038
3601
  `, "utf8");
3039
3602
  }
3040
3603
  async function removeUnexpectedLockfiles(targetDir, packageManagerId) {
@@ -3044,7 +3607,7 @@ async function removeUnexpectedLockfiles(targetDir, packageManagerId) {
3044
3607
  if (keep.has(filename)) {
3045
3608
  return;
3046
3609
  }
3047
- await fsp5.rm(path7.join(targetDir, filename), { force: true });
3610
+ await fsp7.rm(path10.join(targetDir, filename), { force: true });
3048
3611
  }));
3049
3612
  }
3050
3613
  async function defaultInstallDependencies({
@@ -3057,14 +3620,15 @@ async function defaultInstallDependencies({
3057
3620
  });
3058
3621
  }
3059
3622
  // ../wp-typia-project-tools/src/runtime/scaffold-repository-reference.ts
3060
- import fs4 from "fs";
3061
3623
  import { createRequire } from "module";
3062
- import path8 from "path";
3624
+ import path11 from "path";
3063
3625
  var require2 = createRequire(import.meta.url);
3064
3626
  var DEFAULT_SCAFFOLD_REPOSITORY_REFERENCE = "imjlk/wp-typia";
3065
3627
  function readRepositoryPackageManifest(packageJsonPath) {
3066
3628
  try {
3067
- return JSON.parse(fs4.readFileSync(packageJsonPath, "utf8"));
3629
+ return readJsonFileSync(packageJsonPath, {
3630
+ context: "repository package manifest"
3631
+ });
3068
3632
  } catch (error) {
3069
3633
  if (getOptionalNodeErrorCode(error) === "ENOENT") {
3070
3634
  return null;
@@ -3122,9 +3686,9 @@ function parseRepositoryReference(value) {
3122
3686
  }
3123
3687
  function getDefaultRepositoryManifestPaths() {
3124
3688
  const candidatePaths = [
3125
- path8.resolve(PROJECT_TOOLS_PACKAGE_ROOT, "..", "..", "package.json"),
3126
- path8.resolve(PROJECT_TOOLS_PACKAGE_ROOT, "..", "wp-typia", "package.json"),
3127
- path8.join(PROJECT_TOOLS_PACKAGE_ROOT, "package.json"),
3689
+ path11.resolve(PROJECT_TOOLS_PACKAGE_ROOT, "..", "..", "package.json"),
3690
+ path11.resolve(PROJECT_TOOLS_PACKAGE_ROOT, "..", "wp-typia", "package.json"),
3691
+ path11.join(PROJECT_TOOLS_PACKAGE_ROOT, "package.json"),
3128
3692
  resolveInstalledPackageManifestPath("wp-typia"),
3129
3693
  resolveInstalledPackageManifestPath("@wp-typia/project-tools")
3130
3694
  ].filter((candidatePath) => Boolean(candidatePath));
@@ -3151,7 +3715,7 @@ async function reportScaffoldProgress(onProgress, event) {
3151
3715
  await onProgress?.(event);
3152
3716
  }
3153
3717
  var EPHEMERAL_NODE_MODULES_LINK_TYPE2 = process.platform === "win32" ? "junction" : "dir";
3154
- var __dirname2 = path9.dirname(fileURLToPath(import.meta.url));
3718
+ var __dirname2 = path12.dirname(fileURLToPath(import.meta.url));
3155
3719
  var LOCKFILES2 = {
3156
3720
  bun: ["bun.lock", "bun.lockb"],
3157
3721
  npm: ["package-lock.json"],
@@ -3159,11 +3723,11 @@ var LOCKFILES2 = {
3159
3723
  yarn: ["yarn.lock"]
3160
3724
  };
3161
3725
  async function ensureDirectory(targetDir, allowExisting = false) {
3162
- await fsp6.mkdir(targetDir, { recursive: true });
3726
+ await fsp8.mkdir(targetDir, { recursive: true });
3163
3727
  if (allowExisting) {
3164
3728
  return;
3165
3729
  }
3166
- const entries = await fsp6.readdir(targetDir);
3730
+ const entries = await fsp8.readdir(targetDir);
3167
3731
  if (entries.length > 0) {
3168
3732
  throw new Error(formatNonEmptyTargetDirectoryError(targetDir));
3169
3733
  }
@@ -3174,42 +3738,42 @@ async function writeStarterManifestFiles2(targetDir, templateId, variables, arti
3174
3738
  relativePath: `${artifact.relativeDir}/typia.manifest.json`
3175
3739
  })) : getStarterManifestFiles(templateId, variables);
3176
3740
  for (const { document, relativePath } of manifests) {
3177
- const destinationPath = path9.join(targetDir, relativePath);
3178
- await fsp6.mkdir(path9.dirname(destinationPath), { recursive: true });
3179
- await fsp6.writeFile(destinationPath, stringifyStarterManifest(document), "utf8");
3741
+ const destinationPath = path12.join(targetDir, relativePath);
3742
+ await fsp8.mkdir(path12.dirname(destinationPath), { recursive: true });
3743
+ await fsp8.writeFile(destinationPath, stringifyStarterManifest(document), "utf8");
3180
3744
  }
3181
3745
  }
3182
3746
  async function writeBuiltInStructuralArtifacts(targetDir, artifacts) {
3183
3747
  for (const artifact of artifacts) {
3184
- const destinationDir = path9.join(targetDir, artifact.relativeDir);
3185
- await fsp6.mkdir(destinationDir, { recursive: true });
3186
- await fsp6.writeFile(path9.join(destinationDir, "types.ts"), artifact.typesSource, "utf8");
3187
- await fsp6.writeFile(path9.join(destinationDir, "block.json"), stringifyBuiltInBlockJsonDocument(artifact.blockJsonDocument), "utf8");
3748
+ const destinationDir = path12.join(targetDir, artifact.relativeDir);
3749
+ await fsp8.mkdir(destinationDir, { recursive: true });
3750
+ await fsp8.writeFile(path12.join(destinationDir, "types.ts"), artifact.typesSource, "utf8");
3751
+ await fsp8.writeFile(path12.join(destinationDir, "block.json"), stringifyBuiltInBlockJsonDocument(artifact.blockJsonDocument), "utf8");
3188
3752
  }
3189
3753
  }
3190
3754
  async function writeBuiltInCodeArtifacts(targetDir, codeArtifacts) {
3191
3755
  for (const artifact of codeArtifacts) {
3192
- const destinationPath = path9.join(targetDir, artifact.relativePath);
3193
- await fsp6.mkdir(path9.dirname(destinationPath), { recursive: true });
3194
- await fsp6.writeFile(destinationPath, artifact.source, "utf8");
3756
+ const destinationPath = path12.join(targetDir, artifact.relativePath);
3757
+ await fsp8.mkdir(path12.dirname(destinationPath), { recursive: true });
3758
+ await fsp8.writeFile(destinationPath, artifact.source, "utf8");
3195
3759
  }
3196
3760
  }
3197
3761
  async function resolveScaffoldGeneratorNodeModulesPath2() {
3198
- const projectToolsPackageRoot = path9.resolve(__dirname2, "..", "..");
3762
+ const projectToolsPackageRoot = path12.resolve(__dirname2, "..", "..");
3199
3763
  const candidates = [
3200
- path9.join(projectToolsPackageRoot, "node_modules"),
3201
- path9.resolve(projectToolsPackageRoot, "..", ".."),
3202
- path9.resolve(projectToolsPackageRoot, "..", "..", "node_modules")
3764
+ path12.join(projectToolsPackageRoot, "node_modules"),
3765
+ path12.resolve(projectToolsPackageRoot, "..", ".."),
3766
+ path12.resolve(projectToolsPackageRoot, "..", "..", "node_modules")
3203
3767
  ];
3204
3768
  for (const candidate of candidates) {
3205
- if (await pathExists(path9.join(candidate, "typia", "package.json"))) {
3769
+ if (await pathExists(path12.join(candidate, "typia", "package.json"))) {
3206
3770
  return candidate;
3207
3771
  }
3208
3772
  }
3209
3773
  return null;
3210
3774
  }
3211
3775
  async function withEphemeralScaffoldNodeModules2(targetDir, callback) {
3212
- const targetNodeModulesPath = path9.join(targetDir, "node_modules");
3776
+ const targetNodeModulesPath = path12.join(targetDir, "node_modules");
3213
3777
  if (await pathExists(targetNodeModulesPath)) {
3214
3778
  await callback();
3215
3779
  return;
@@ -3218,11 +3782,11 @@ async function withEphemeralScaffoldNodeModules2(targetDir, callback) {
3218
3782
  if (!sourceNodeModulesPath) {
3219
3783
  throw new Error("Unable to resolve a node_modules directory with typia for scaffold-time REST artifact generation.");
3220
3784
  }
3221
- await fsp6.symlink(sourceNodeModulesPath, targetNodeModulesPath, EPHEMERAL_NODE_MODULES_LINK_TYPE2);
3785
+ await fsp8.symlink(sourceNodeModulesPath, targetNodeModulesPath, EPHEMERAL_NODE_MODULES_LINK_TYPE2);
3222
3786
  try {
3223
3787
  await callback();
3224
3788
  } finally {
3225
- await fsp6.rm(targetNodeModulesPath, { force: true, recursive: true });
3789
+ await fsp8.rm(targetNodeModulesPath, { force: true, recursive: true });
3226
3790
  }
3227
3791
  }
3228
3792
  async function seedBuiltInPersistenceArtifacts2(targetDir, templateId, variables) {
@@ -3233,7 +3797,7 @@ async function seedBuiltInPersistenceArtifacts2(targetDir, templateId, variables
3233
3797
  await withEphemeralScaffoldNodeModules2(targetDir, async () => {
3234
3798
  if (templateId === "persistence") {
3235
3799
  await syncPersistenceRestArtifacts({
3236
- apiTypesFile: path9.join("src", "api-types.ts"),
3800
+ apiTypesFile: path12.join("src", "api-types.ts"),
3237
3801
  outputDir: "src",
3238
3802
  projectDir: targetDir,
3239
3803
  variables
@@ -3241,27 +3805,27 @@ async function seedBuiltInPersistenceArtifacts2(targetDir, templateId, variables
3241
3805
  return;
3242
3806
  }
3243
3807
  await syncPersistenceRestArtifacts({
3244
- apiTypesFile: path9.join("src", "blocks", variables.slugKebabCase, "api-types.ts"),
3245
- outputDir: path9.join("src", "blocks", variables.slugKebabCase),
3808
+ apiTypesFile: path12.join("src", "blocks", variables.slugKebabCase, "api-types.ts"),
3809
+ outputDir: path12.join("src", "blocks", variables.slugKebabCase),
3246
3810
  projectDir: targetDir,
3247
3811
  variables
3248
3812
  });
3249
3813
  });
3250
3814
  }
3251
3815
  async function normalizePackageManagerFiles2(targetDir, packageManagerId) {
3252
- const yarnRcPath = path9.join(targetDir, ".yarnrc.yml");
3816
+ const yarnRcPath = path12.join(targetDir, ".yarnrc.yml");
3253
3817
  if (packageManagerId === "yarn") {
3254
- await fsp6.writeFile(yarnRcPath, `nodeLinker: node-modules
3818
+ await fsp8.writeFile(yarnRcPath, `nodeLinker: node-modules
3255
3819
  `, "utf8");
3256
3820
  return;
3257
3821
  }
3258
- await fsp6.rm(yarnRcPath, { force: true });
3822
+ await fsp8.rm(yarnRcPath, { force: true });
3259
3823
  }
3260
3824
  async function removeQueryLoopPlaceholderFiles(projectDir, templateId) {
3261
3825
  if (templateId !== "query-loop") {
3262
3826
  return;
3263
3827
  }
3264
- await fsp6.rm(path9.join(projectDir, "src", "validator-toolkit.ts"), {
3828
+ await fsp8.rm(path12.join(projectDir, "src", "validator-toolkit.ts"), {
3265
3829
  force: true
3266
3830
  });
3267
3831
  }
@@ -3272,7 +3836,7 @@ async function removeUnexpectedLockfiles2(targetDir, packageManagerId) {
3272
3836
  if (keep.has(filename)) {
3273
3837
  return;
3274
3838
  }
3275
- await fsp6.rm(path9.join(targetDir, filename), { force: true });
3839
+ await fsp8.rm(path12.join(targetDir, filename), { force: true });
3276
3840
  }));
3277
3841
  }
3278
3842
  async function replaceTextRecursively(targetDir, packageManagerId, {
@@ -3295,21 +3859,21 @@ async function replaceTextRecursively(targetDir, packageManagerId, {
3295
3859
  manifestPaths: repositoryManifestPaths
3296
3860
  });
3297
3861
  async function visit(currentPath) {
3298
- const stats = await fsp6.stat(currentPath);
3862
+ const stats = await fsp8.stat(currentPath);
3299
3863
  if (stats.isDirectory()) {
3300
- const entries = await fsp6.readdir(currentPath);
3864
+ const entries = await fsp8.readdir(currentPath);
3301
3865
  for (const entry of entries) {
3302
- await visit(path9.join(currentPath, entry));
3866
+ await visit(path12.join(currentPath, entry));
3303
3867
  }
3304
3868
  return;
3305
3869
  }
3306
- if (path9.basename(currentPath) === "package.json" || !textExtensions.has(path9.extname(currentPath))) {
3870
+ if (path12.basename(currentPath) === "package.json" || !textExtensions.has(path12.extname(currentPath))) {
3307
3871
  return;
3308
3872
  }
3309
- const content = await fsp6.readFile(currentPath, "utf8");
3873
+ const content = await fsp8.readFile(currentPath, "utf8");
3310
3874
  const nextContent = replaceRepositoryReferencePlaceholders(transformPackageManagerText(content, packageManagerId), resolvedRepositoryReference);
3311
3875
  if (nextContent !== content) {
3312
- await fsp6.writeFile(currentPath, nextContent, "utf8");
3876
+ await fsp8.writeFile(currentPath, nextContent, "utf8");
3313
3877
  }
3314
3878
  }
3315
3879
  await visit(targetDir);
@@ -3381,17 +3945,17 @@ async function applyBuiltInScaffoldProjectFiles({
3381
3945
  phase: "finalize-project",
3382
3946
  title: "Finalizing scaffold output"
3383
3947
  });
3384
- const readmePath = path9.join(projectDir, "README.md");
3948
+ const readmePath = path12.join(projectDir, "README.md");
3385
3949
  if (!await pathExists(readmePath)) {
3386
- await fsp6.writeFile(readmePath, readmeContent ?? buildReadme(templateId, variables, packageManager, {
3950
+ await fsp8.writeFile(readmePath, readmeContent ?? buildReadme(templateId, variables, packageManager, {
3387
3951
  withMigrationUi,
3388
3952
  withTestPreset,
3389
3953
  withWpEnv
3390
3954
  }), "utf8");
3391
3955
  }
3392
- const gitignorePath = path9.join(projectDir, ".gitignore");
3956
+ const gitignorePath = path12.join(projectDir, ".gitignore");
3393
3957
  const existingGitignore = await readOptionalUtf8File(gitignorePath) ?? "";
3394
- await fsp6.writeFile(gitignorePath, mergeTextLines(gitignoreContent ?? buildGitignore(), existingGitignore), "utf8");
3958
+ await fsp8.writeFile(gitignorePath, mergeTextLines(gitignoreContent ?? buildGitignore(), existingGitignore), "utf8");
3395
3959
  await normalizePackageJson(projectDir, packageManager);
3396
3960
  await applyGeneratedProjectDxPackageJson({
3397
3961
  compoundPersistenceEnabled: isCompoundPersistenceEnabled(variables),
@@ -3422,31 +3986,31 @@ async function applyBuiltInScaffoldProjectFiles({
3422
3986
  }
3423
3987
 
3424
3988
  // ../wp-typia-project-tools/src/runtime/template-source-normalization.ts
3425
- import path13 from "path";
3989
+ import path16 from "path";
3426
3990
 
3427
3991
  // ../wp-typia-project-tools/src/runtime/template-layers.ts
3428
- import path10 from "path";
3429
- import { promises as fsp7 } from "fs";
3992
+ import path13 from "path";
3993
+ import { promises as fsp9 } from "fs";
3430
3994
  var TEMPLATE_LAYER_MANIFEST_FILENAME = "wp-typia.layers.json";
3431
3995
  var TEMPLATE_LAYER_MANIFEST_VERSION = 1;
3432
3996
  function resolveLayerPath(sourceRoot, relativePath) {
3433
- const targetPath = path10.resolve(sourceRoot, relativePath);
3434
- const relativeTarget = path10.relative(sourceRoot, targetPath);
3435
- if (relativeTarget.startsWith("..") || path10.isAbsolute(relativeTarget)) {
3997
+ const targetPath = path13.resolve(sourceRoot, relativePath);
3998
+ const relativeTarget = path13.relative(sourceRoot, targetPath);
3999
+ if (relativeTarget.startsWith("..") || path13.isAbsolute(relativeTarget)) {
3436
4000
  throw new Error(`Template layer path "${relativePath}" must stay within ${sourceRoot}.`);
3437
4001
  }
3438
4002
  return targetPath;
3439
4003
  }
3440
4004
  async function assertNoSymlinks(sourceDir) {
3441
- const stats = await fsp7.lstat(sourceDir);
4005
+ const stats = await fsp9.lstat(sourceDir);
3442
4006
  if (stats.isSymbolicLink()) {
3443
4007
  throw new Error(`Template layer packages may not include symbolic links: ${sourceDir}`);
3444
4008
  }
3445
4009
  if (!stats.isDirectory()) {
3446
4010
  return;
3447
4011
  }
3448
- for (const entry of await fsp7.readdir(sourceDir)) {
3449
- await assertNoSymlinks(path10.join(sourceDir, entry));
4012
+ for (const entry of await fsp9.readdir(sourceDir)) {
4013
+ await assertNoSymlinks(path13.join(sourceDir, entry));
3450
4014
  }
3451
4015
  }
3452
4016
  function parseLayerDefinition(layerId, value) {
@@ -3475,11 +4039,13 @@ function parseLayerDefinition(layerId, value) {
3475
4039
  };
3476
4040
  }
3477
4041
  async function loadExternalTemplateLayerManifest(sourceRoot) {
3478
- const manifestPath = path10.join(sourceRoot, TEMPLATE_LAYER_MANIFEST_FILENAME);
4042
+ const manifestPath = path13.join(sourceRoot, TEMPLATE_LAYER_MANIFEST_FILENAME);
3479
4043
  if (!await pathExists(manifestPath)) {
3480
4044
  return null;
3481
4045
  }
3482
- const raw = JSON.parse(await fsp7.readFile(manifestPath, "utf8"));
4046
+ const raw = await readJsonFile(manifestPath, {
4047
+ context: "template layer manifest"
4048
+ });
3483
4049
  if (!isPlainObject(raw)) {
3484
4050
  throw new Error(`${TEMPLATE_LAYER_MANIFEST_FILENAME} must export a JSON object.`);
3485
4051
  }
@@ -3573,7 +4139,7 @@ async function resolveExternalTemplateLayers({
3573
4139
  return;
3574
4140
  }
3575
4141
  const layerDir = resolveLayerPath(sourceRoot, definition.path);
3576
- const stats = await fsp7.stat(layerDir).catch(() => null);
4142
+ const stats = await fsp9.stat(layerDir).catch(() => null);
3577
4143
  if (!stats || !stats.isDirectory()) {
3578
4144
  throw new Error(`Layer "${layerId}" points to a missing directory: ${definition.path}`);
3579
4145
  }
@@ -3611,12 +4177,12 @@ async function assertExternalTemplateLayersDoNotWriteProtectedOutputs({
3611
4177
  }
3612
4178
 
3613
4179
  // ../wp-typia-project-tools/src/runtime/template-source-external.ts
3614
- import { promises as fsp8 } from "fs";
3615
- import path11 from "path";
4180
+ import { promises as fsp10 } from "fs";
4181
+ import path14 from "path";
3616
4182
  import { pathToFileURL } from "url";
3617
4183
 
3618
4184
  // ../wp-typia-project-tools/src/runtime/external-template-guards.ts
3619
- import fs5 from "fs";
4185
+ import fs4 from "fs";
3620
4186
  var TEMPLATE_SOURCE_TIMEOUT_CODE = "template-source-timeout";
3621
4187
  var TEMPLATE_SOURCE_TOO_LARGE_CODE = "template-source-too-large";
3622
4188
  var DEFAULT_EXTERNAL_TEMPLATE_TIMEOUT_MS = 20000;
@@ -3658,7 +4224,7 @@ function createExternalTemplateTooLargeError(label, maxBytes) {
3658
4224
  return createTemplateGuardError(TEMPLATE_SOURCE_TOO_LARGE_CODE, `${label} exceeded the external template size limit (${maxBytes} bytes).`);
3659
4225
  }
3660
4226
  function assertExternalTemplateFileSize(filePath, options) {
3661
- const stats = fs5.statSync(filePath);
4227
+ const stats = fs4.statSync(filePath);
3662
4228
  if (stats.size > options.maxBytes) {
3663
4229
  throw createExternalTemplateTooLargeError(options.label, options.maxBytes);
3664
4230
  }
@@ -3734,11 +4300,9 @@ async function readResponseBodyWithLimit(response, options) {
3734
4300
  }
3735
4301
  async function readJsonResponseWithLimit(response, options) {
3736
4302
  const buffer = await readResponseBodyWithLimit(response, options);
3737
- try {
3738
- return JSON.parse(buffer.toString("utf8"));
3739
- } catch (error) {
3740
- throw new Error(`Failed to parse ${options.label}: ${error instanceof Error ? error.message : String(error)}`);
3741
- }
4303
+ return safeJsonParse(buffer.toString("utf8"), {
4304
+ context: options.label
4305
+ });
3742
4306
  }
3743
4307
  async function readBufferResponseWithLimit(response, options) {
3744
4308
  return readResponseBodyWithLimit(response, options);
@@ -3756,16 +4320,16 @@ function getTemplateWarning(key) {
3756
4320
  return `Ignoring external template config key "${key}": ${TEMPLATE_WARNING_MESSAGE}`;
3757
4321
  }
3758
4322
  function resolveSourceSubpath(sourceDir, relativePath) {
3759
- const targetPath = path11.resolve(sourceDir, relativePath);
3760
- const relativeTarget = path11.relative(sourceDir, targetPath);
3761
- if (relativeTarget.startsWith("..") || path11.isAbsolute(relativeTarget)) {
4323
+ const targetPath = path14.resolve(sourceDir, relativePath);
4324
+ const relativeTarget = path14.relative(sourceDir, targetPath);
4325
+ if (relativeTarget.startsWith("..") || path14.isAbsolute(relativeTarget)) {
3762
4326
  throw new Error(`Template path "${relativePath}" must stay within ${sourceDir}.`);
3763
4327
  }
3764
4328
  return targetPath;
3765
4329
  }
3766
4330
  async function findExternalTemplateEntry(sourceDir) {
3767
4331
  for (const filename of EXTERNAL_TEMPLATE_ENTRY_CANDIDATES) {
3768
- const candidate = path11.join(sourceDir, filename);
4332
+ const candidate = path14.join(sourceDir, filename);
3769
4333
  if (await pathExists(candidate)) {
3770
4334
  return candidate;
3771
4335
  }
@@ -3781,7 +4345,7 @@ async function loadExternalTemplateConfig(sourceDir) {
3781
4345
  label: `External template config "${entryPath}"`,
3782
4346
  maxBytes: getExternalTemplateConfigMaxBytes()
3783
4347
  });
3784
- const entryStats = await fsp8.stat(entryPath);
4348
+ const entryStats = await fsp10.stat(entryPath);
3785
4349
  const moduleUrl = `${pathToFileURL(entryPath).href}?mtime=${entryStats.mtimeMs}`;
3786
4350
  const loadedModule = await withExternalTemplateTimeout(`loading external template config "${entryPath}"`, () => import(moduleUrl));
3787
4351
  const loadedConfig = loadedModule.default ?? loadedModule;
@@ -3888,21 +4452,21 @@ async function renderCreateBlockExternalTemplate(sourceDir, context, requestedVa
3888
4452
  const { folderName, formatHint, templatePath } = resolveConfiguredTemplatePath(config, variantConfig);
3889
4453
  const { path: tempRoot, cleanup } = await createManagedTempRoot("wp-typia-create-block-external-");
3890
4454
  try {
3891
- const renderedRoot = path11.join(tempRoot, "rendered");
4455
+ const renderedRoot = path14.join(tempRoot, "rendered");
3892
4456
  const blockDir = resolveSourceSubpath(renderedRoot, folderName);
3893
4457
  const view = await buildExternalTemplateView(context, config, selectedVariant, variantConfig);
3894
4458
  const blockTemplateDir = resolveSourceSubpath(sourceDir, templatePath);
3895
4459
  await copyRenderedDirectory(blockTemplateDir, blockDir, view, {
3896
4460
  filter: async (sourcePath, _destinationPath, entry) => {
3897
- const mustacheVariantPath = path11.join(path11.dirname(sourcePath), `${entry.name}.mustache`);
4461
+ const mustacheVariantPath = path14.join(path14.dirname(sourcePath), `${entry.name}.mustache`);
3898
4462
  return !(entry.isFile() && (entry.name === "package.json" || entry.name === "README.md") && await pathExists(mustacheVariantPath));
3899
4463
  }
3900
4464
  });
3901
4465
  const assetsPath = typeof variantConfig.assetsPath === "string" ? variantConfig.assetsPath : config.assetsPath;
3902
4466
  if (typeof assetsPath === "string" && assetsPath.trim().length > 0) {
3903
- await copyRawDirectory(resolveSourceSubpath(sourceDir, assetsPath), path11.join(tempRoot, "assets"));
4467
+ await copyRawDirectory(resolveSourceSubpath(sourceDir, assetsPath), path14.join(tempRoot, "assets"));
3904
4468
  }
3905
- const assetsDir = path11.join(tempRoot, "assets");
4469
+ const assetsDir = path14.join(tempRoot, "assets");
3906
4470
  return {
3907
4471
  assetsDir: await pathExists(assetsDir) ? assetsDir : undefined,
3908
4472
  blockDir,
@@ -3919,8 +4483,8 @@ async function renderCreateBlockExternalTemplate(sourceDir, context, requestedVa
3919
4483
  }
3920
4484
 
3921
4485
  // ../wp-typia-project-tools/src/runtime/template-source-remote.ts
3922
- import { promises as fsp9 } from "fs";
3923
- import path12 from "path";
4486
+ import { promises as fsp11 } from "fs";
4487
+ import path15 from "path";
3924
4488
  async function cleanupSeedRootPair(cleanup, seedCleanup) {
3925
4489
  let cleanupError;
3926
4490
  try {
@@ -3943,11 +4507,13 @@ function getDefaultCategoryFromBlockJson(blockJson) {
3943
4507
  async function readRemoteBlockJsonAsync(blockDir) {
3944
4508
  const sourceRoot = await getSeedSourceRoot(blockDir);
3945
4509
  for (const candidate of [
3946
- path12.join(blockDir, "block.json"),
3947
- path12.join(sourceRoot, "block.json")
4510
+ path15.join(blockDir, "block.json"),
4511
+ path15.join(sourceRoot, "block.json")
3948
4512
  ]) {
3949
4513
  if (await pathExists(candidate)) {
3950
- return JSON.parse(await fsp9.readFile(candidate, "utf8"));
4514
+ return readJsonFile(candidate, {
4515
+ context: "remote block metadata"
4516
+ });
3951
4517
  }
3952
4518
  }
3953
4519
  throw new Error(`Unable to locate block.json in ${blockDir}`);
@@ -3962,8 +4528,8 @@ async function getDefaultCategoryAsync(sourceDir) {
3962
4528
  }
3963
4529
  async function readTemplatePackageJsonAsync(sourceDir) {
3964
4530
  for (const candidate of [
3965
- path12.join(sourceDir, "package.json.mustache"),
3966
- path12.join(sourceDir, "package.json")
4531
+ path15.join(sourceDir, "package.json.mustache"),
4532
+ path15.join(sourceDir, "package.json")
3967
4533
  ]) {
3968
4534
  if (!await pathExists(candidate)) {
3969
4535
  continue;
@@ -3974,7 +4540,10 @@ async function readTemplatePackageJsonAsync(sourceDir) {
3974
4540
  maxBytes: getExternalTemplatePackageJsonMaxBytes()
3975
4541
  });
3976
4542
  return {
3977
- packageJson: JSON.parse(await fsp9.readFile(candidate, "utf8")),
4543
+ packageJson: safeJsonParse(await fsp11.readFile(candidate, "utf8"), {
4544
+ context: "template metadata file",
4545
+ filePath: candidate
4546
+ }),
3978
4547
  sourcePath: candidate
3979
4548
  };
3980
4549
  } catch (error) {
@@ -4000,16 +4569,16 @@ async function getTemplateProjectTypeAsync(sourceDir) {
4000
4569
  }
4001
4570
  async function normalizeWpTypiaTemplateSeed(seed) {
4002
4571
  const { path: tempRoot, cleanup } = await createManagedTempRoot("wp-typia-template-source-");
4003
- const normalizedDir = path12.join(tempRoot, "template");
4572
+ const normalizedDir = path15.join(tempRoot, "template");
4004
4573
  try {
4005
4574
  await copyRawDirectory(seed.blockDir, normalizedDir, {
4006
4575
  filter: async (sourcePath, _targetPath, entry) => {
4007
- const mustacheVariantPath = path12.join(path12.dirname(sourcePath), `${entry.name}.mustache`);
4576
+ const mustacheVariantPath = path15.join(path15.dirname(sourcePath), `${entry.name}.mustache`);
4008
4577
  return !(entry.isFile() && (entry.name === "package.json" || entry.name === "README.md") && await pathExists(mustacheVariantPath));
4009
4578
  }
4010
4579
  });
4011
4580
  if (seed.assetsDir && await pathExists(seed.assetsDir)) {
4012
- await fsp9.cp(seed.assetsDir, path12.join(normalizedDir, "assets"), {
4581
+ await fsp11.cp(seed.assetsDir, path15.join(normalizedDir, "assets"), {
4013
4582
  recursive: true,
4014
4583
  force: true
4015
4584
  });
@@ -4098,32 +4667,34 @@ function buildRemoteBlockJsonTemplate(blockJson) {
4098
4667
  }
4099
4668
  async function rewriteBlockJsonImports(directory) {
4100
4669
  const textExtensions = new Set([".js", ".jsx", ".ts", ".tsx"]);
4101
- const targetBlockJsonPath = path12.join(directory, "block.json");
4670
+ const targetBlockJsonPath = path15.join(directory, "block.json");
4102
4671
  async function visit(currentPath) {
4103
- const stats = await fsp9.stat(currentPath);
4672
+ const stats = await fsp11.stat(currentPath);
4104
4673
  if (stats.isDirectory()) {
4105
- const entries = await fsp9.readdir(currentPath);
4674
+ const entries = await fsp11.readdir(currentPath);
4106
4675
  for (const entry of entries) {
4107
- await visit(path12.join(currentPath, entry));
4676
+ await visit(path15.join(currentPath, entry));
4108
4677
  }
4109
4678
  return;
4110
4679
  }
4111
- if (!textExtensions.has(path12.extname(currentPath))) {
4680
+ if (!textExtensions.has(path15.extname(currentPath))) {
4112
4681
  return;
4113
4682
  }
4114
- const content = await fsp9.readFile(currentPath, "utf8");
4115
- const relativeSpecifier = path12.relative(path12.dirname(currentPath), targetBlockJsonPath).replace(/\\/g, "/");
4683
+ const content = await fsp11.readFile(currentPath, "utf8");
4684
+ const relativeSpecifier = path15.relative(path15.dirname(currentPath), targetBlockJsonPath).replace(/\\/g, "/");
4116
4685
  const normalizedSpecifier = relativeSpecifier.startsWith(".") ? relativeSpecifier : `./${relativeSpecifier}`;
4117
4686
  const next = content.replace(/(['"])\.{1,2}\/[^'"]*block\.json\1/g, `$1${normalizedSpecifier}$1`);
4118
4687
  if (next !== content) {
4119
- await fsp9.writeFile(currentPath, next, "utf8");
4688
+ await fsp11.writeFile(currentPath, next, "utf8");
4120
4689
  }
4121
4690
  }
4122
4691
  await visit(directory);
4123
4692
  }
4124
4693
  async function patchRemotePackageJson(templateDir, needsInteractivity) {
4125
- const packageJsonPath = path12.join(templateDir, "package.json.mustache");
4126
- const packageJson = JSON.parse(await fsp9.readFile(packageJsonPath, "utf8"));
4694
+ const packageJsonPath = path15.join(templateDir, "package.json.mustache");
4695
+ const packageJson = await readJsonFile(packageJsonPath, {
4696
+ context: "remote package template manifest"
4697
+ });
4127
4698
  const existingDependencies = { ...packageJson.dependencies ?? {} };
4128
4699
  const existingDevDependencies = { ...packageJson.devDependencies ?? {} };
4129
4700
  delete existingDependencies["@wp-typia/project-tools"];
@@ -4143,16 +4714,16 @@ async function patchRemotePackageJson(templateDir, needsInteractivity) {
4143
4714
  } else {
4144
4715
  delete packageJson.dependencies;
4145
4716
  }
4146
- await fsp9.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}
4717
+ await fsp11.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}
4147
4718
  `, "utf8");
4148
4719
  }
4149
4720
  async function getSeedSourceRoot(blockDir) {
4150
- return await pathExists(path12.join(blockDir, "src")) ? path12.join(blockDir, "src") : blockDir;
4721
+ return await pathExists(path15.join(blockDir, "src")) ? path15.join(blockDir, "src") : blockDir;
4151
4722
  }
4152
4723
  async function findSeedRenderPhp(seed) {
4153
4724
  for (const candidate of [
4154
- path12.join(seed.blockDir, "render.php"),
4155
- path12.join(seed.rootDir, "render.php")
4725
+ path15.join(seed.blockDir, "render.php"),
4726
+ path15.join(seed.rootDir, "render.php")
4156
4727
  ]) {
4157
4728
  if (await pathExists(candidate)) {
4158
4729
  return candidate;
@@ -4193,16 +4764,16 @@ async function removeSeedEntryConflicts(templateDir) {
4193
4764
  "view.ts",
4194
4765
  "view.tsx"
4195
4766
  ]) {
4196
- await fsp9.rm(path12.join(templateDir, "src", filename), { force: true });
4767
+ await fsp11.rm(path15.join(templateDir, "src", filename), { force: true });
4197
4768
  }
4198
4769
  }
4199
4770
  async function normalizeCreateBlockSubset(seed, context) {
4200
4771
  const { path: tempRoot, cleanup } = await createManagedTempRoot("wp-typia-remote-template-");
4201
4772
  try {
4202
- const templateDir = path12.join(tempRoot, "template");
4773
+ const templateDir = path15.join(tempRoot, "template");
4203
4774
  const blockJson = await readRemoteBlockJsonAsync(seed.blockDir);
4204
4775
  const sourceRoot = await getSeedSourceRoot(seed.blockDir);
4205
- await fsp9.mkdir(templateDir, { recursive: true });
4776
+ await fsp11.mkdir(templateDir, { recursive: true });
4206
4777
  for (const layerDir of getBuiltInTemplateLayerDirs("basic")) {
4207
4778
  if (!await pathExists(layerDir)) {
4208
4779
  if (isOmittableBuiltInTemplateLayerDir("basic", layerDir)) {
@@ -4210,29 +4781,29 @@ async function normalizeCreateBlockSubset(seed, context) {
4210
4781
  }
4211
4782
  throw new Error(`Built-in template layer is missing: ${layerDir}`);
4212
4783
  }
4213
- await fsp9.cp(layerDir, templateDir, {
4784
+ await fsp11.cp(layerDir, templateDir, {
4214
4785
  recursive: true,
4215
4786
  force: true
4216
4787
  });
4217
4788
  }
4218
4789
  await removeSeedEntryConflicts(templateDir);
4219
- await fsp9.cp(sourceRoot, path12.join(templateDir, "src"), {
4790
+ await fsp11.cp(sourceRoot, path15.join(templateDir, "src"), {
4220
4791
  recursive: true,
4221
4792
  force: true
4222
4793
  });
4223
4794
  const remoteRenderPath = await findSeedRenderPhp(seed);
4224
4795
  if (remoteRenderPath) {
4225
- await fsp9.copyFile(remoteRenderPath, path12.join(templateDir, "render.php"));
4796
+ await fsp11.copyFile(remoteRenderPath, path15.join(templateDir, "render.php"));
4226
4797
  }
4227
4798
  if (seed.assetsDir && await pathExists(seed.assetsDir)) {
4228
- await fsp9.cp(seed.assetsDir, path12.join(templateDir, "assets"), {
4799
+ await fsp11.cp(seed.assetsDir, path15.join(templateDir, "assets"), {
4229
4800
  recursive: true,
4230
4801
  force: true
4231
4802
  });
4232
4803
  }
4233
- await fsp9.writeFile(path12.join(templateDir, "src", "types.ts"), buildRemoteTypesSource(blockJson, context), "utf8");
4234
- await fsp9.writeFile(path12.join(templateDir, "src", "block.json"), buildRemoteBlockJsonTemplate(blockJson), "utf8");
4235
- await rewriteBlockJsonImports(path12.join(templateDir, "src"));
4804
+ await fsp11.writeFile(path15.join(templateDir, "src", "types.ts"), buildRemoteTypesSource(blockJson, context), "utf8");
4805
+ await fsp11.writeFile(path15.join(templateDir, "src", "block.json"), buildRemoteBlockJsonTemplate(blockJson), "utf8");
4806
+ await rewriteBlockJsonImports(path15.join(templateDir, "src"));
4236
4807
  const needsInteractivity = typeof blockJson.viewScriptModule === "string" || typeof blockJson.viewScript === "string" || (await Promise.all([
4237
4808
  "view.js",
4238
4809
  "view.jsx",
@@ -4240,7 +4811,7 @@ async function normalizeCreateBlockSubset(seed, context) {
4240
4811
  "view.tsx",
4241
4812
  "interactivity.js",
4242
4813
  "interactivity.ts"
4243
- ].map((filename) => pathExists(path12.join(templateDir, "src", filename))))).some(Boolean);
4814
+ ].map((filename) => pathExists(path15.join(templateDir, "src", filename))))).some(Boolean);
4244
4815
  await patchRemotePackageJson(templateDir, needsInteractivity);
4245
4816
  return {
4246
4817
  id: "remote:create-block-subset",
@@ -4270,7 +4841,8 @@ function getTemplateVariableContext(variables) {
4270
4841
  blockRuntimePackageVersion,
4271
4842
  blockTypesPackageVersion,
4272
4843
  projectToolsPackageVersion,
4273
- restPackageVersion
4844
+ restPackageVersion,
4845
+ wpTypiaPackageVersion
4274
4846
  } = getPackageVersions();
4275
4847
  return {
4276
4848
  ...variables,
@@ -4278,6 +4850,7 @@ function getTemplateVariableContext(variables) {
4278
4850
  blockRuntimePackageVersion: variables.blockRuntimePackageVersion ?? blockRuntimePackageVersion,
4279
4851
  blockTypesPackageVersion: variables.blockTypesPackageVersion ?? blockTypesPackageVersion,
4280
4852
  projectToolsPackageVersion: variables.projectToolsPackageVersion ?? projectToolsPackageVersion,
4853
+ wpTypiaPackageVersion: variables.wpTypiaPackageVersion ?? wpTypiaPackageVersion,
4281
4854
  description: variables.description,
4282
4855
  keyword: variables.keyword,
4283
4856
  namespace: variables.namespace,
@@ -4290,7 +4863,7 @@ function getTemplateVariableContext(variables) {
4290
4863
  };
4291
4864
  }
4292
4865
  async function detectTemplateSourceFormat(sourceDir) {
4293
- if (await pathExists(path13.join(sourceDir, "package.json.mustache"))) {
4866
+ if (await pathExists(path16.join(sourceDir, "package.json.mustache"))) {
4294
4867
  return "wp-typia";
4295
4868
  }
4296
4869
  if (await loadExternalTemplateLayerManifest(sourceDir)) {
@@ -4302,15 +4875,15 @@ async function detectTemplateSourceFormat(sourceDir) {
4302
4875
  if (await getTemplateProjectTypeAsync(sourceDir) !== null) {
4303
4876
  return "wp-typia";
4304
4877
  }
4305
- const sourceRoot = await pathExists(path13.join(sourceDir, "src")) ? path13.join(sourceDir, "src") : sourceDir;
4878
+ const sourceRoot = await pathExists(path16.join(sourceDir, "src")) ? path16.join(sourceDir, "src") : sourceDir;
4306
4879
  const blockJsonCandidates = [
4307
- path13.join(sourceDir, "block.json"),
4308
- path13.join(sourceRoot, "block.json")
4880
+ path16.join(sourceDir, "block.json"),
4881
+ path16.join(sourceRoot, "block.json")
4309
4882
  ];
4310
4883
  const hasBlockJson = (await Promise.all(blockJsonCandidates.map((candidate) => pathExists(candidate)))).some(Boolean);
4311
- const hasIndexFile = (await Promise.all(["index.js", "index.jsx", "index.ts", "index.tsx"].map((filename) => pathExists(path13.join(sourceRoot, filename))))).some(Boolean);
4312
- const hasEditFile = (await Promise.all(["edit.js", "edit.jsx", "edit.ts", "edit.tsx"].map((filename) => pathExists(path13.join(sourceRoot, filename))))).some(Boolean);
4313
- const hasSaveFile = (await Promise.all(["save.js", "save.jsx", "save.ts", "save.tsx"].map((filename) => pathExists(path13.join(sourceRoot, filename))))).some(Boolean);
4884
+ const hasIndexFile = (await Promise.all(["index.js", "index.jsx", "index.ts", "index.tsx"].map((filename) => pathExists(path16.join(sourceRoot, filename))))).some(Boolean);
4885
+ const hasEditFile = (await Promise.all(["edit.js", "edit.jsx", "edit.ts", "edit.tsx"].map((filename) => pathExists(path16.join(sourceRoot, filename))))).some(Boolean);
4886
+ const hasSaveFile = (await Promise.all(["save.js", "save.jsx", "save.ts", "save.tsx"].map((filename) => pathExists(path16.join(sourceRoot, filename))))).some(Boolean);
4314
4887
  if (hasBlockJson && hasIndexFile && hasEditFile && hasSaveFile) {
4315
4888
  return "create-block-subset";
4316
4889
  }
@@ -4319,10 +4892,10 @@ async function detectTemplateSourceFormat(sourceDir) {
4319
4892
 
4320
4893
  // ../wp-typia-project-tools/src/runtime/template-source-seeds.ts
4321
4894
  var import_semver = __toESM(require_semver(), 1);
4322
- import fs7 from "fs";
4323
- import { promises as fsp11 } from "fs";
4895
+ import fs6 from "fs";
4896
+ import { promises as fsp13 } from "fs";
4324
4897
  import { createRequire as createRequire2 } from "module";
4325
- import path16 from "path";
4898
+ import path19 from "path";
4326
4899
  import { spawnSync } from "child_process";
4327
4900
 
4328
4901
  // ../../node_modules/.bun/tar@7.5.13/node_modules/tar/dist/esm/index.min.js
@@ -6595,7 +7168,7 @@ var Vn = 512 * 1024;
6595
7168
  var $n = pr | ur | dr | mr;
6596
7169
  var lr = !fr && typeof ar == "number" ? ar | ur | dr | mr : null;
6597
7170
  var cs = lr !== null ? () => lr : Kn ? (s3) => s3 < Vn ? $n : "w" : () => "w";
6598
- var fs6 = (s3, t, e) => {
7171
+ var fs5 = (s3, t, e) => {
6599
7172
  try {
6600
7173
  return mi.lchownSync(s3, t, e);
6601
7174
  } catch (i) {
@@ -6644,7 +7217,7 @@ var ds = (s3, t, e, i) => {
6644
7217
  });
6645
7218
  };
6646
7219
  var qn = (s3, t, e, i) => {
6647
- t.isDirectory() && us(Ee.resolve(s3, t.name), e, i), fs6(Ee.resolve(s3, t.name), e, i);
7220
+ t.isDirectory() && us(Ee.resolve(s3, t.name), e, i), fs5(Ee.resolve(s3, t.name), e, i);
6648
7221
  };
6649
7222
  var us = (s3, t, e) => {
6650
7223
  let i;
@@ -6655,12 +7228,12 @@ var us = (s3, t, e) => {
6655
7228
  if (n?.code === "ENOENT")
6656
7229
  return;
6657
7230
  if (n?.code === "ENOTDIR" || n?.code === "ENOTSUP")
6658
- return fs6(s3, t, e);
7231
+ return fs5(s3, t, e);
6659
7232
  throw n;
6660
7233
  }
6661
7234
  for (let r of i)
6662
7235
  qn(s3, r, t, e);
6663
- return fs6(s3, t, e);
7236
+ return fs5(s3, t, e);
6664
7237
  };
6665
7238
  var we = class extends Error {
6666
7239
  path;
@@ -7474,8 +8047,8 @@ var So = (s3) => {
7474
8047
 
7475
8048
  // ../wp-typia-project-tools/src/runtime/template-source-cache.ts
7476
8049
  import { createHash, randomUUID } from "crypto";
7477
- import { promises as fsp10 } from "fs";
7478
- import path15 from "path";
8050
+ import { promises as fsp12 } from "fs";
8051
+ import path18 from "path";
7479
8052
 
7480
8053
  // ../wp-typia-project-tools/src/runtime/template-source-cache-markers.ts
7481
8054
  var CACHE_MARKER_FILE = "wp-typia-template-cache.json";
@@ -7595,7 +8168,7 @@ function formatExternalTemplateCachePruneMarker({
7595
8168
 
7596
8169
  // ../wp-typia-project-tools/src/runtime/template-source-cache-policy.ts
7597
8170
  import os2 from "os";
7598
- import path14 from "path";
8171
+ import path17 from "path";
7599
8172
  var EXTERNAL_TEMPLATE_CACHE_ENV = "WP_TYPIA_EXTERNAL_TEMPLATE_CACHE";
7600
8173
  var EXTERNAL_TEMPLATE_CACHE_DIR_ENV = "WP_TYPIA_EXTERNAL_TEMPLATE_CACHE_DIR";
7601
8174
  var EXTERNAL_TEMPLATE_CACHE_TTL_DAYS_ENV = "WP_TYPIA_EXTERNAL_TEMPLATE_CACHE_TTL_DAYS";
@@ -7613,9 +8186,9 @@ function isExternalTemplateCacheEnabled(env = process.env) {
7613
8186
  function getExternalTemplateCacheRoot(env = process.env) {
7614
8187
  const configuredCacheDir = env[EXTERNAL_TEMPLATE_CACHE_DIR_ENV]?.trim();
7615
8188
  if (configuredCacheDir) {
7616
- return path14.resolve(configuredCacheDir);
8189
+ return path17.resolve(configuredCacheDir);
7617
8190
  }
7618
- return path14.join(os2.tmpdir(), `wp-typia-template-source-cache-${getCurrentUserCacheSegment()}`);
8191
+ return path17.join(os2.tmpdir(), `wp-typia-template-source-cache-${getCurrentUserCacheSegment()}`);
7619
8192
  }
7620
8193
  function parseExternalTemplateCacheTtlDays(value) {
7621
8194
  if (typeof value !== "string" && typeof value !== "number") {
@@ -7693,7 +8266,7 @@ function createExternalTemplateCacheKey(keyParts) {
7693
8266
  }
7694
8267
  async function isDirectoryPath(directory) {
7695
8268
  try {
7696
- const stats = await fsp10.lstat(directory);
8269
+ const stats = await fsp12.lstat(directory);
7697
8270
  return stats.isDirectory() && !stats.isSymbolicLink();
7698
8271
  } catch {
7699
8272
  return false;
@@ -7701,7 +8274,7 @@ async function isDirectoryPath(directory) {
7701
8274
  }
7702
8275
  async function removeTemporaryCacheEntry(entryDir) {
7703
8276
  try {
7704
- await fsp10.rm(entryDir, { force: true, recursive: true });
8277
+ await fsp12.rm(entryDir, { force: true, recursive: true });
7705
8278
  } catch {}
7706
8279
  }
7707
8280
  function getCurrentUid() {
@@ -7709,7 +8282,7 @@ function getCurrentUid() {
7709
8282
  }
7710
8283
  async function isPrivateCacheDirectory(directory) {
7711
8284
  try {
7712
- const stats = await fsp10.lstat(directory);
8285
+ const stats = await fsp12.lstat(directory);
7713
8286
  if (!stats.isDirectory() || stats.isSymbolicLink()) {
7714
8287
  return false;
7715
8288
  }
@@ -7727,11 +8300,11 @@ async function isPrivateCacheDirectory(directory) {
7727
8300
  }
7728
8301
  async function ensurePrivateCacheDirectory(directory) {
7729
8302
  try {
7730
- await fsp10.mkdir(directory, {
8303
+ await fsp12.mkdir(directory, {
7731
8304
  mode: PRIVATE_CACHE_DIRECTORY_MODE,
7732
8305
  recursive: true
7733
8306
  });
7734
- const stats = await fsp10.lstat(directory);
8307
+ const stats = await fsp12.lstat(directory);
7735
8308
  if (!stats.isDirectory() || stats.isSymbolicLink()) {
7736
8309
  return false;
7737
8310
  }
@@ -7741,7 +8314,7 @@ async function ensurePrivateCacheDirectory(directory) {
7741
8314
  }
7742
8315
  if (process.platform !== "win32") {
7743
8316
  if ((stats.mode & 63) !== 0) {
7744
- await fsp10.chmod(directory, PRIVATE_CACHE_DIRECTORY_MODE);
8317
+ await fsp12.chmod(directory, PRIVATE_CACHE_DIRECTORY_MODE);
7745
8318
  }
7746
8319
  }
7747
8320
  return isPrivateCacheDirectory(directory);
@@ -7753,9 +8326,9 @@ function resolveCacheNamespaceDir(cacheRoot, namespace) {
7753
8326
  if (namespace === "." || namespace === ".." || !SAFE_CACHE_NAMESPACE_SEGMENT.test(namespace)) {
7754
8327
  return null;
7755
8328
  }
7756
- const namespaceDir = path15.join(cacheRoot, namespace);
7757
- const relativeNamespaceDir = path15.relative(cacheRoot, namespaceDir);
7758
- if (relativeNamespaceDir.length === 0 || relativeNamespaceDir.startsWith("..") || path15.isAbsolute(relativeNamespaceDir)) {
8329
+ const namespaceDir = path18.join(cacheRoot, namespace);
8330
+ const relativeNamespaceDir = path18.relative(cacheRoot, namespaceDir);
8331
+ if (relativeNamespaceDir.length === 0 || relativeNamespaceDir.startsWith("..") || path18.isAbsolute(relativeNamespaceDir)) {
7759
8332
  return null;
7760
8333
  }
7761
8334
  return namespaceDir;
@@ -7767,14 +8340,14 @@ function getCacheEntryPaths(descriptor) {
7767
8340
  if (!namespaceDir) {
7768
8341
  return null;
7769
8342
  }
7770
- const entryDir = path15.join(namespaceDir, cacheKey);
8343
+ const entryDir = path18.join(namespaceDir, cacheKey);
7771
8344
  return {
7772
8345
  cacheKey,
7773
8346
  cacheRoot,
7774
8347
  entryDir,
7775
- markerPath: path15.join(entryDir, CACHE_MARKER_FILE),
8348
+ markerPath: path18.join(entryDir, CACHE_MARKER_FILE),
7776
8349
  namespaceDir,
7777
- sourceDir: path15.join(entryDir, "source")
8350
+ sourceDir: path18.join(entryDir, "source")
7778
8351
  };
7779
8352
  }
7780
8353
  async function isReusableCacheEntry(entryDir, markerPath, sourceDir) {
@@ -7782,7 +8355,7 @@ async function isReusableCacheEntry(entryDir, markerPath, sourceDir) {
7782
8355
  }
7783
8356
  async function readCacheEntryMarker(markerPath) {
7784
8357
  try {
7785
- return parseExternalTemplateCacheEntryMarker(await fsp10.readFile(markerPath, "utf8"));
8358
+ return parseExternalTemplateCacheEntryMarker(await fsp12.readFile(markerPath, "utf8"));
7786
8359
  } catch {
7787
8360
  return null;
7788
8361
  }
@@ -7798,22 +8371,22 @@ async function isReusableFreshCacheEntry(entryDir, markerPath, sourceDir, nowMs,
7798
8371
  return marker !== null && isExternalTemplateCacheEntryFreshForTtl(marker.createdAtMs, nowMs, ttlMs);
7799
8372
  }
7800
8373
  function isPathInsideDirectory(directory, candidatePath) {
7801
- const relativePath = path15.relative(directory, candidatePath);
7802
- return relativePath.length > 0 && !relativePath.startsWith("..") && !path15.isAbsolute(relativePath);
8374
+ const relativePath = path18.relative(directory, candidatePath);
8375
+ return relativePath.length > 0 && !relativePath.startsWith("..") && !path18.isAbsolute(relativePath);
7803
8376
  }
7804
8377
  async function removeCacheEntryWithinRoot(cacheRoot, entryDir) {
7805
8378
  if (!isPathInsideDirectory(cacheRoot, entryDir)) {
7806
8379
  return false;
7807
8380
  }
7808
8381
  try {
7809
- await fsp10.rm(entryDir, { force: true, recursive: true });
8382
+ await fsp12.rm(entryDir, { force: true, recursive: true });
7810
8383
  return true;
7811
8384
  } catch {
7812
8385
  return false;
7813
8386
  }
7814
8387
  }
7815
8388
  function getCachePruneMarkerPath(cacheRoot) {
7816
- return path15.join(cacheRoot, CACHE_PRUNE_MARKER_FILE);
8389
+ return path18.join(cacheRoot, CACHE_PRUNE_MARKER_FILE);
7817
8390
  }
7818
8391
  async function shouldSkipExternalTemplateCachePrune({
7819
8392
  cacheRoot,
@@ -7827,7 +8400,7 @@ async function shouldSkipExternalTemplateCachePrune({
7827
8400
  }
7828
8401
  let markerText;
7829
8402
  try {
7830
- markerText = await fsp10.readFile(getCachePruneMarkerPath(cacheRoot), "utf8");
8403
+ markerText = await fsp12.readFile(getCachePruneMarkerPath(cacheRoot), "utf8");
7831
8404
  } catch {
7832
8405
  return false;
7833
8406
  }
@@ -7845,7 +8418,7 @@ async function writeExternalTemplateCachePruneMarker({
7845
8418
  ttlMs
7846
8419
  }) {
7847
8420
  try {
7848
- await fsp10.writeFile(getCachePruneMarkerPath(cacheRoot), formatExternalTemplateCachePruneMarker({
8421
+ await fsp12.writeFile(getCachePruneMarkerPath(cacheRoot), formatExternalTemplateCachePruneMarker({
7849
8422
  nowMs,
7850
8423
  pruneIntervalMs,
7851
8424
  ttlMs
@@ -7887,7 +8460,7 @@ async function pruneExternalTemplateCache(options = {}) {
7887
8460
  }
7888
8461
  let namespaceEntries;
7889
8462
  try {
7890
- namespaceEntries = await fsp10.readdir(cacheRoot, { withFileTypes: true });
8463
+ namespaceEntries = await fsp12.readdir(cacheRoot, { withFileTypes: true });
7891
8464
  } catch {
7892
8465
  return result;
7893
8466
  }
@@ -7903,7 +8476,7 @@ async function pruneExternalTemplateCache(options = {}) {
7903
8476
  }
7904
8477
  let cacheEntries;
7905
8478
  try {
7906
- cacheEntries = await fsp10.readdir(namespaceDir, { withFileTypes: true });
8479
+ cacheEntries = await fsp12.readdir(namespaceDir, { withFileTypes: true });
7907
8480
  } catch {
7908
8481
  result.skippedEntries += 1;
7909
8482
  continue;
@@ -7916,14 +8489,14 @@ async function pruneExternalTemplateCache(options = {}) {
7916
8489
  result.skippedEntries += 1;
7917
8490
  continue;
7918
8491
  }
7919
- const entryDir = path15.join(namespaceDir, cacheEntry.name);
8492
+ const entryDir = path18.join(namespaceDir, cacheEntry.name);
7920
8493
  result.scannedEntries += 1;
7921
8494
  if (!isPathInsideDirectory(cacheRoot, entryDir)) {
7922
8495
  result.skippedEntries += 1;
7923
8496
  continue;
7924
8497
  }
7925
- const markerPath = path15.join(entryDir, CACHE_MARKER_FILE);
7926
- const sourceDir = path15.join(entryDir, "source");
8498
+ const markerPath = path18.join(entryDir, CACHE_MARKER_FILE);
8499
+ const sourceDir = path18.join(entryDir, "source");
7927
8500
  const marker = await getReusableCacheEntryMarker(entryDir, markerPath, sourceDir);
7928
8501
  if (!marker) {
7929
8502
  result.skippedEntries += 1;
@@ -7963,7 +8536,7 @@ async function findReusableExternalTemplateSourceCache(descriptor) {
7963
8536
  await pruneExternalTemplateCache();
7964
8537
  let entries;
7965
8538
  try {
7966
- entries = await fsp10.readdir(namespaceDir, { withFileTypes: true });
8539
+ entries = await fsp12.readdir(namespaceDir, { withFileTypes: true });
7967
8540
  } catch {
7968
8541
  return null;
7969
8542
  }
@@ -7972,9 +8545,9 @@ async function findReusableExternalTemplateSourceCache(descriptor) {
7972
8545
  if (!entry.isDirectory()) {
7973
8546
  continue;
7974
8547
  }
7975
- const entryDir = path15.join(namespaceDir, entry.name);
7976
- const markerPath = path15.join(entryDir, CACHE_MARKER_FILE);
7977
- const sourceDir = path15.join(entryDir, "source");
8548
+ const entryDir = path18.join(namespaceDir, entry.name);
8549
+ const markerPath = path18.join(entryDir, CACHE_MARKER_FILE);
8550
+ const sourceDir = path18.join(entryDir, "source");
7978
8551
  const marker = await getReusableCacheEntryMarker(entryDir, markerPath, sourceDir);
7979
8552
  if (!marker || !isExternalTemplateCacheEntryFreshForTtl(marker.createdAtMs, nowMs, ttlMs) || !externalTemplateCacheMetadataMatches(marker.metadata, descriptor.metadata)) {
7980
8553
  continue;
@@ -8016,16 +8589,16 @@ async function resolveExternalTemplateSourceCache(descriptor, populateSourceDir)
8016
8589
  if (existingMarker) {
8017
8590
  await removeCacheEntryWithinRoot(cacheRoot, entryDir);
8018
8591
  }
8019
- const temporaryEntryDir = path15.join(namespaceDir, createTemporaryCacheEntryDirName(cacheKey));
8020
- const temporarySourceDir = path15.join(temporaryEntryDir, "source");
8592
+ const temporaryEntryDir = path18.join(namespaceDir, createTemporaryCacheEntryDirName(cacheKey));
8593
+ const temporarySourceDir = path18.join(temporaryEntryDir, "source");
8021
8594
  let populateFailed = false;
8022
8595
  try {
8023
- await fsp10.mkdir(temporarySourceDir, {
8596
+ await fsp12.mkdir(temporarySourceDir, {
8024
8597
  mode: PRIVATE_CACHE_DIRECTORY_MODE,
8025
8598
  recursive: true
8026
8599
  });
8027
8600
  if (process.platform !== "win32") {
8028
- await fsp10.chmod(temporaryEntryDir, PRIVATE_CACHE_DIRECTORY_MODE);
8601
+ await fsp12.chmod(temporaryEntryDir, PRIVATE_CACHE_DIRECTORY_MODE);
8029
8602
  }
8030
8603
  try {
8031
8604
  await populateSourceDir(temporarySourceDir);
@@ -8033,13 +8606,13 @@ async function resolveExternalTemplateSourceCache(descriptor, populateSourceDir)
8033
8606
  populateFailed = true;
8034
8607
  throw error;
8035
8608
  }
8036
- await fsp10.writeFile(path15.join(temporaryEntryDir, CACHE_MARKER_FILE), formatExternalTemplateCacheEntryMarker({
8609
+ await fsp12.writeFile(path18.join(temporaryEntryDir, CACHE_MARKER_FILE), formatExternalTemplateCacheEntryMarker({
8037
8610
  cacheKey,
8038
8611
  createdAt: new Date,
8039
8612
  metadata: descriptor.metadata,
8040
8613
  namespace: descriptor.namespace
8041
8614
  }), "utf8");
8042
- await fsp10.rename(temporaryEntryDir, entryDir);
8615
+ await fsp12.rename(temporaryEntryDir, entryDir);
8043
8616
  return {
8044
8617
  cacheHit: false,
8045
8618
  sourceDir
@@ -8110,9 +8683,9 @@ async function downloadNpmTemplateTarball(locator, resolvedVersion, tarballUrl,
8110
8683
  if (!tarballResponse.ok) {
8111
8684
  throw new Error(`Failed to download npm template tarball for ${locator.raw}: ${tarballResponse.status}`);
8112
8685
  }
8113
- const tarballPath = path16.join(path16.dirname(unpackDir), "template.tgz");
8114
- await fsp11.mkdir(unpackDir, { recursive: true });
8115
- await fsp11.writeFile(tarballPath, await readBufferResponseWithLimit(tarballResponse, {
8686
+ const tarballPath = path19.join(path19.dirname(unpackDir), "template.tgz");
8687
+ await fsp13.mkdir(unpackDir, { recursive: true });
8688
+ await fsp13.writeFile(tarballPath, await readBufferResponseWithLimit(tarballResponse, {
8116
8689
  label: `npm template tarball for ${locator.raw}@${resolvedVersion}`,
8117
8690
  maxBytes: getExternalTemplateTarballMaxBytes()
8118
8691
  }));
@@ -8121,7 +8694,7 @@ async function downloadNpmTemplateTarball(locator, resolvedVersion, tarballUrl,
8121
8694
  file: tarballPath,
8122
8695
  strip: 1
8123
8696
  });
8124
- await fsp11.rm(tarballPath, { force: true });
8697
+ await fsp13.rm(tarballPath, { force: true });
8125
8698
  await assertNoSymlinks2(unpackDir);
8126
8699
  }
8127
8700
  function selectRegistryVersion(metadata, locator) {
@@ -8216,7 +8789,7 @@ async function fetchNpmTemplateSource(locator) {
8216
8789
  }
8217
8790
  const { path: tempRoot, cleanup } = await createManagedTempRoot("wp-typia-template-source-");
8218
8791
  try {
8219
- const unpackDir = path16.join(tempRoot, "source");
8792
+ const unpackDir = path19.join(tempRoot, "source");
8220
8793
  await downloadNpmTemplateTarball(locator, resolvedVersion, tarballUrl, unpackDir);
8221
8794
  return {
8222
8795
  blockDir: unpackDir,
@@ -8232,20 +8805,22 @@ function resolveInstalledNpmTemplateSource(locator, cwd) {
8232
8805
  if (locator.rawSpec !== "" && locator.rawSpec !== "*") {
8233
8806
  return null;
8234
8807
  }
8235
- const workspacePackagesRoot = path16.resolve(PROJECT_TOOLS_PACKAGE_ROOT, "..");
8236
- if (fs7.existsSync(workspacePackagesRoot)) {
8237
- for (const entry of fs7.readdirSync(workspacePackagesRoot, {
8808
+ const workspacePackagesRoot = path19.resolve(PROJECT_TOOLS_PACKAGE_ROOT, "..");
8809
+ if (fs6.existsSync(workspacePackagesRoot)) {
8810
+ for (const entry of fs6.readdirSync(workspacePackagesRoot, {
8238
8811
  withFileTypes: true
8239
8812
  })) {
8240
8813
  if (!entry.isDirectory()) {
8241
8814
  continue;
8242
8815
  }
8243
- const packageDir = path16.join(workspacePackagesRoot, entry.name);
8244
- const packageJsonPath = path16.join(packageDir, "package.json");
8245
- if (!fs7.existsSync(packageJsonPath)) {
8816
+ const packageDir = path19.join(workspacePackagesRoot, entry.name);
8817
+ const packageJsonPath = path19.join(packageDir, "package.json");
8818
+ if (!fs6.existsSync(packageJsonPath)) {
8246
8819
  continue;
8247
8820
  }
8248
- const manifest = JSON.parse(fs7.readFileSync(packageJsonPath, "utf8"));
8821
+ const manifest = readJsonFileSync(packageJsonPath, {
8822
+ context: "workspace template package manifest"
8823
+ });
8249
8824
  if (manifest.name === locator.name) {
8250
8825
  return {
8251
8826
  blockDir: packageDir,
@@ -8254,10 +8829,10 @@ function resolveInstalledNpmTemplateSource(locator, cwd) {
8254
8829
  }
8255
8830
  }
8256
8831
  }
8257
- const workspaceRequire = createRequire2(path16.join(path16.resolve(cwd), "__wp_typia_template_resolver__.cjs"));
8832
+ const workspaceRequire = createRequire2(path19.join(path19.resolve(cwd), "__wp_typia_template_resolver__.cjs"));
8258
8833
  try {
8259
- const packageJsonPath = fs7.realpathSync(workspaceRequire.resolve(`${locator.name}/package.json`));
8260
- const sourceDir = path16.dirname(packageJsonPath);
8834
+ const packageJsonPath = fs6.realpathSync(workspaceRequire.resolve(`${locator.name}/package.json`));
8835
+ const sourceDir = path19.dirname(packageJsonPath);
8261
8836
  return {
8262
8837
  blockDir: sourceDir,
8263
8838
  rootDir: sourceDir
@@ -8266,11 +8841,11 @@ function resolveInstalledNpmTemplateSource(locator, cwd) {
8266
8841
  const errorCode = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
8267
8842
  if (errorCode === "MODULE_NOT_FOUND" || errorCode === "ERR_PACKAGE_PATH_NOT_EXPORTED") {
8268
8843
  for (const basePath of workspaceRequire.resolve.paths(locator.name) ?? []) {
8269
- const packageJsonPath = path16.join(basePath, locator.name, "package.json");
8270
- if (!fs7.existsSync(packageJsonPath)) {
8844
+ const packageJsonPath = path19.join(basePath, locator.name, "package.json");
8845
+ if (!fs6.existsSync(packageJsonPath)) {
8271
8846
  continue;
8272
8847
  }
8273
- const sourceDir = path16.dirname(fs7.realpathSync(packageJsonPath));
8848
+ const sourceDir = path19.dirname(fs6.realpathSync(packageJsonPath));
8274
8849
  return {
8275
8850
  blockDir: sourceDir,
8276
8851
  rootDir: sourceDir
@@ -8282,27 +8857,29 @@ function resolveInstalledNpmTemplateSource(locator, cwd) {
8282
8857
  }
8283
8858
  }
8284
8859
  async function isOfficialWorkspaceTemplateSeedAsync(seed) {
8285
- const packageJsonPath = path16.join(seed.rootDir, "package.json");
8860
+ const packageJsonPath = path19.join(seed.rootDir, "package.json");
8286
8861
  if (!await pathExists(packageJsonPath)) {
8287
8862
  return false;
8288
8863
  }
8289
8864
  try {
8290
- const packageJson = JSON.parse(await fsp11.readFile(packageJsonPath, "utf8"));
8865
+ const packageJson = await readJsonFile(packageJsonPath, {
8866
+ context: "workspace template seed manifest"
8867
+ });
8291
8868
  return packageJson.name === OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE;
8292
8869
  } catch {
8293
8870
  return false;
8294
8871
  }
8295
8872
  }
8296
8873
  async function assertNoSymlinks2(sourceDir) {
8297
- const stats = await fsp11.lstat(sourceDir);
8874
+ const stats = await fsp13.lstat(sourceDir);
8298
8875
  if (stats.isSymbolicLink()) {
8299
8876
  throw new Error(`Template sources may not include symbolic links: ${sourceDir}`);
8300
8877
  }
8301
8878
  if (!stats.isDirectory()) {
8302
8879
  return;
8303
8880
  }
8304
- for (const entry of await fsp11.readdir(sourceDir)) {
8305
- await assertNoSymlinks2(path16.join(sourceDir, entry));
8881
+ for (const entry of await fsp13.readdir(sourceDir)) {
8882
+ await assertNoSymlinks2(path19.join(sourceDir, entry));
8306
8883
  }
8307
8884
  }
8308
8885
  function runGitTemplateCommand(args, label, options = {}) {
@@ -8331,9 +8908,9 @@ function getGitHubTemplateRepositoryUrl(locator) {
8331
8908
  return `https://github.com/${locator.owner}/${locator.repo}.git`;
8332
8909
  }
8333
8910
  async function resolveGitHubTemplateDirectory(checkoutDir, locator) {
8334
- const sourceDir = path16.resolve(checkoutDir, locator.sourcePath);
8335
- const relativeSourceDir = path16.relative(checkoutDir, sourceDir);
8336
- if (relativeSourceDir.startsWith("..") || path16.isAbsolute(relativeSourceDir)) {
8911
+ const sourceDir = path19.resolve(checkoutDir, locator.sourcePath);
8912
+ const relativeSourceDir = path19.relative(checkoutDir, sourceDir);
8913
+ if (relativeSourceDir.startsWith("..") || path19.isAbsolute(relativeSourceDir)) {
8337
8914
  throw new Error("GitHub template path must stay within the cloned repository.");
8338
8915
  }
8339
8916
  if (!await pathExists(sourceDir)) {
@@ -8522,7 +9099,7 @@ async function resolveGitHubTemplateSource(locator) {
8522
9099
  }
8523
9100
  }
8524
9101
  const { path: remoteRoot, cleanup } = await createManagedTempRoot("wp-typia-template-source-");
8525
- const checkoutDir = path16.join(remoteRoot, "source");
9102
+ const checkoutDir = path19.join(remoteRoot, "source");
8526
9103
  try {
8527
9104
  cloneGitHubTemplateSource(locator, checkoutDir);
8528
9105
  const sourceDir = await resolveGitHubTemplateDirectory(checkoutDir, locator);
@@ -8539,7 +9116,7 @@ async function resolveGitHubTemplateSource(locator) {
8539
9116
  }
8540
9117
  async function resolveTemplateSeed(locator, cwd) {
8541
9118
  if (locator.kind === "path") {
8542
- const sourceDir = path16.resolve(cwd, locator.templatePath);
9119
+ const sourceDir = path19.resolve(cwd, locator.templatePath);
8543
9120
  if (!await pathExists(sourceDir)) {
8544
9121
  throw new Error(`Template path does not exist: ${sourceDir}`);
8545
9122
  }
@@ -9073,7 +9650,8 @@ function buildTemplateVariablesFromBlockSpec(spec) {
9073
9650
  blockRuntimePackageVersion,
9074
9651
  blockTypesPackageVersion,
9075
9652
  projectToolsPackageVersion,
9076
- restPackageVersion
9653
+ restPackageVersion,
9654
+ wpTypiaPackageVersion
9077
9655
  } = getPackageVersions();
9078
9656
  const slug = spec.block.slug;
9079
9657
  const slugSnakeCase = toSnakeCase(slug);
@@ -9129,6 +9707,7 @@ function buildTemplateVariablesFromBlockSpec(spec) {
9129
9707
  requiresPhp: compatibility.pluginHeader.requiresPhp,
9130
9708
  testedUpTo: compatibility.pluginHeader.testedUpTo,
9131
9709
  projectToolsPackageVersion,
9710
+ wpTypiaPackageVersion,
9132
9711
  cssClassName,
9133
9712
  dashCase: slug,
9134
9713
  dataStorageMode,
@@ -9426,7 +10005,7 @@ function toPhpSingleQuotedString(value) {
9426
10005
  return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
9427
10006
  }
9428
10007
 
9429
- // ../wp-typia-project-tools/src/runtime/built-in-block-non-ts-family-artifacts.ts
10008
+ // ../wp-typia-project-tools/src/runtime/built-in-block-non-ts-basic-artifacts.ts
9430
10009
  var BASIC_STYLE_TEMPLATE = `/**
9431
10010
  * {{title}} Block Styles
9432
10011
  */
@@ -9497,14 +10076,485 @@ if ( ! defined( 'ABSPATH' ) ) {
9497
10076
  <?php esc_html_e( 'Server render placeholder.', '{{textDomain}}' ); ?>
9498
10077
  </div>
9499
10078
  `;
9500
- var INTERACTIVITY_STYLE_TEMPLATE = `.{{cssClassName}} {
9501
- position: relative;
9502
- padding: 1rem;
9503
- border: 1px solid #dcdcde;
9504
- border-radius: 0.75rem;
9505
- background: #fff;
9506
-
9507
- &__content {
10079
+ function buildBasicArtifacts(variables) {
10080
+ return [
10081
+ renderArtifact("src/editor.scss", BASIC_EDITOR_STYLE_TEMPLATE, variables),
10082
+ renderArtifact("src/style.scss", BASIC_STYLE_TEMPLATE, variables),
10083
+ renderArtifact("src/render.php", BASIC_RENDER_TEMPLATE, variables)
10084
+ ];
10085
+ }
10086
+ // ../wp-typia-project-tools/src/runtime/built-in-block-non-ts-compound-templates.ts
10087
+ var COMPOUND_PERSISTENCE_RENDER_TARGETS_TEMPLATE = `<?php
10088
+ /**
10089
+ * Alternate render target helpers for the {{title}} compound parent block.
10090
+ *
10091
+ * @package {{pascalCase}}
10092
+ */
10093
+
10094
+ if ( ! defined( 'ABSPATH' ) ) {
10095
+ exit;
10096
+ }
10097
+
10098
+ if ( ! function_exists( '{{phpPrefix}}_{{slugSnakeCase}}_build_render_context' ) ) {
10099
+ function {{phpPrefix}}_{{slugSnakeCase}}_build_render_context( $attributes, $content, $block ) {
10100
+ $validator_path = __DIR__ . '/typia-validator.php';
10101
+ if ( ! file_exists( $validator_path ) ) {
10102
+ return null;
10103
+ }
10104
+
10105
+ $validator = require $validator_path;
10106
+ if ( ! is_object( $validator ) || ! method_exists( $validator, 'apply_defaults' ) || ! method_exists( $validator, 'validate' ) ) {
10107
+ return null;
10108
+ }
10109
+
10110
+ $normalized = $validator->apply_defaults( is_array( $attributes ) ? $attributes : array() );
10111
+ $validation = $validator->validate( $normalized );
10112
+ $resource_key = isset( $normalized['resourceKey'] ) ? (string) $normalized['resourceKey'] : '';
10113
+ $heading = isset( $normalized['heading'] ) ? (string) $normalized['heading'] : {{titlePhpLiteral}};
10114
+ $intro = isset( $normalized['intro'] ) ? (string) $normalized['intro'] : '';
10115
+ $button_label = isset( $normalized['buttonLabel'] ) ? (string) $normalized['buttonLabel'] : 'Persist Count';
10116
+ $show_count = ! empty( $normalized['showCount'] );
10117
+ $show_dividers = ! empty( $normalized['showDividers'] );
10118
+ $post_id = is_object( $block ) && isset( $block->context['postId'] )
10119
+ ? (int) $block->context['postId']
10120
+ : (int) get_queried_object_id();
10121
+ $storage_mode = '{{dataStorageMode}}';
10122
+ $persistence_policy = '{{persistencePolicy}}';
10123
+
10124
+ $notice_message = 'authenticated' === $persistence_policy
10125
+ ? __( 'Sign in to persist this counter.', '{{textDomain}}' )
10126
+ : __( 'Public writes are temporarily unavailable.', '{{textDomain}}' );
10127
+
10128
+ if ( empty( $validation['valid'] ) || '' === $resource_key ) {
10129
+ return null;
10130
+ }
10131
+
10132
+ {{phpPrefix}}_record_rendered_block_instance(
10133
+ (int) $post_id,
10134
+ '{{namespace}}/{{slugKebabCase}}',
10135
+ $resource_key
10136
+ );
10137
+
10138
+ $web_context = array(
10139
+ 'bootstrapReady' => false,
10140
+ 'buttonLabel' => $button_label,
10141
+ 'canWrite' => false,
10142
+ 'client' => array(
10143
+ 'writeExpiry' => 0,
10144
+ 'writeNonce' => '',
10145
+ 'writeToken' => '',
10146
+ ),
10147
+ 'count' => 0,
10148
+ 'error' => '',
10149
+ 'isBootstrapping' => false,
10150
+ 'isLoading' => false,
10151
+ 'isSaving' => false,
10152
+ 'persistencePolicy' => $persistence_policy,
10153
+ 'postId' => (int) $post_id,
10154
+ 'resourceKey' => $resource_key,
10155
+ 'showCount' => $show_count,
10156
+ 'storage' => $storage_mode,
10157
+ );
10158
+
10159
+ $allowed_inner_html = wp_kses_allowed_html( 'post' );
10160
+
10161
+ foreach ( $allowed_inner_html as &$allowed_attributes ) {
10162
+ if ( ! is_array( $allowed_attributes ) ) {
10163
+ continue;
10164
+ }
10165
+
10166
+ $allowed_attributes['data-wp-bind--disabled'] = true;
10167
+ $allowed_attributes['data-wp-bind--hidden'] = true;
10168
+ $allowed_attributes['data-wp-bind--value'] = true;
10169
+ $allowed_attributes['data-wp-class'] = true;
10170
+ $allowed_attributes['data-wp-class--active'] = true;
10171
+ $allowed_attributes['data-wp-context'] = true;
10172
+ $allowed_attributes['data-wp-init'] = true;
10173
+ $allowed_attributes['data-wp-interactive'] = true;
10174
+ $allowed_attributes['data-wp-on--click'] = true;
10175
+ $allowed_attributes['data-wp-on--mouseenter'] = true;
10176
+ $allowed_attributes['data-wp-on--mouseleave'] = true;
10177
+ $allowed_attributes['data-wp-run--mounted'] = true;
10178
+ $allowed_attributes['data-wp-style--width'] = true;
10179
+ $allowed_attributes['data-wp-text'] = true;
10180
+ }
10181
+ unset( $allowed_attributes );
10182
+
10183
+ return array(
10184
+ 'buttonLabel' => $button_label,
10185
+ 'frontendClassName' => '{{cssClassName}}',
10186
+ 'heading' => $heading,
10187
+ 'intro' => $intro,
10188
+ 'isVisible' => true,
10189
+ 'normalized' => $normalized,
10190
+ 'noticeMessage' => $notice_message,
10191
+ 'postId' => (int) $post_id,
10192
+ 'resourceKey' => $resource_key,
10193
+ 'sanitizedContent' => wp_kses( $content, $allowed_inner_html ),
10194
+ 'showCount' => $show_count,
10195
+ 'showDividers' => $show_dividers,
10196
+ 'title' => {{titleJson}},
10197
+ 'webContext' => $web_context,
10198
+ );
10199
+ }
10200
+ }
10201
+
10202
+ if ( ! function_exists( '{{phpPrefix}}_{{slugSnakeCase}}_render_target' ) ) {
10203
+ function {{phpPrefix}}_{{slugSnakeCase}}_render_target( string $target, $attributes, $content = '', $block = null ): string {
10204
+ $context = {{phpPrefix}}_{{slugSnakeCase}}_build_render_context( $attributes, $content, $block );
10205
+ if ( null === $context ) {
10206
+ return '';
10207
+ }
10208
+
10209
+ $target_context = apply_filters(
10210
+ '{{phpPrefix}}_{{slugSnakeCase}}_render_target_context',
10211
+ $context,
10212
+ $target,
10213
+ $attributes,
10214
+ $block
10215
+ );
10216
+
10217
+ $markup = '';
10218
+ switch ( $target ) {
10219
+ case 'email':
10220
+ $parts = array(
10221
+ '<div class="' . esc_attr( $target_context['frontendClassName'] ) . '-email">',
10222
+ '<h3>' . esc_html( $target_context['heading'] ) . '</h3>',
10223
+ );
10224
+ if ( '' !== $target_context['intro'] ) {
10225
+ $parts[] = '<p>' . esc_html( $target_context['intro'] ) . '</p>';
10226
+ }
10227
+ if ( ! empty( $target_context['showCount'] ) ) {
10228
+ $parts[] = '<p><strong>' . esc_html__( 'Count', '{{textDomain}}' ) . ':</strong> ' . esc_html( (string) $target_context['webContext']['count'] ) . '</p>';
10229
+ }
10230
+ $parts[] = wp_kses_post( $target_context['sanitizedContent'] );
10231
+ $parts[] = '<p>' . esc_html( $target_context['noticeMessage'] ) . '</p>';
10232
+ $parts[] = '</div>';
10233
+ $markup = implode( '', $parts );
10234
+ break;
10235
+ case 'mjml':
10236
+ $markup = '<mjml><mj-body><mj-section><mj-column><mj-text font-weight="700">' . esc_html( $target_context['heading'] ) . '</mj-text>';
10237
+ if ( '' !== $target_context['intro'] ) {
10238
+ $markup .= '<mj-text>' . esc_html( $target_context['intro'] ) . '</mj-text>';
10239
+ }
10240
+ if ( ! empty( $target_context['showCount'] ) ) {
10241
+ $markup .= '<mj-text>' . esc_html__( 'Count', '{{textDomain}}' ) . ': ' . esc_html( (string) $target_context['webContext']['count'] ) . '</mj-text>';
10242
+ }
10243
+ $markup .= '<mj-text>' . esc_html( wp_strip_all_tags( $target_context['sanitizedContent'] ) ) . '</mj-text>';
10244
+ $markup .= '<mj-text>' . esc_html( $target_context['noticeMessage'] ) . '</mj-text></mj-column></mj-section></mj-body></mjml>';
10245
+ break;
10246
+ case 'plain-text':
10247
+ $markup = implode(
10248
+ "\\n",
10249
+ array_filter(
10250
+ array(
10251
+ $target_context['heading'],
10252
+ $target_context['intro'],
10253
+ wp_strip_all_tags( $target_context['sanitizedContent'] ),
10254
+ ! empty( $target_context['showCount'] )
10255
+ ? sprintf( '%s: %s', __( 'Count', '{{textDomain}}' ), (string) $target_context['webContext']['count'] )
10256
+ : null,
10257
+ $target_context['noticeMessage'],
10258
+ ),
10259
+ static fn( $value ) => is_string( $value ) && '' !== $value
10260
+ )
10261
+ );
10262
+ break;
10263
+ case 'web':
10264
+ default:
10265
+ $wrapper_attributes = get_block_wrapper_attributes(
10266
+ array(
10267
+ 'class' => '{{cssClassName}}',
10268
+ 'data-show-dividers' => $target_context['showDividers'] ? 'true' : 'false',
10269
+ 'data-wp-context' => wp_json_encode( $target_context['webContext'] ),
10270
+ 'data-wp-init' => 'callbacks.init',
10271
+ 'data-wp-interactive' => '{{slugKebabCase}}',
10272
+ 'data-wp-run--mounted' => 'callbacks.mounted',
10273
+ )
10274
+ );
10275
+ ob_start();
10276
+ ?>
10277
+ <div <?php echo $wrapper_attributes; ?>>
10278
+ <h3 class="{{cssClassName}}__heading"><?php echo esc_html( $target_context['heading'] ); ?></h3>
10279
+ <?php if ( '' !== $target_context['intro'] ) : ?>
10280
+ <p class="{{cssClassName}}__intro"><?php echo esc_html( $target_context['intro'] ); ?></p>
10281
+ <?php endif; ?>
10282
+ <p
10283
+ class="{{cssClassName}}__notice"
10284
+ data-wp-bind--hidden="!context.bootstrapReady || context.canWrite"
10285
+ hidden
10286
+ >
10287
+ <?php echo esc_html( $target_context['noticeMessage'] ); ?>
10288
+ </p>
10289
+ <p
10290
+ class="{{cssClassName}}__error"
10291
+ role="status"
10292
+ aria-live="polite"
10293
+ aria-atomic="true"
10294
+ data-wp-bind--hidden="!context.error"
10295
+ data-wp-text="context.error"
10296
+ hidden
10297
+ ></p>
10298
+ <?php if ( ! empty( $target_context['showCount'] ) ) : ?>
10299
+ <div class="{{cssClassName}}__counter">
10300
+ <span
10301
+ class="{{cssClassName}}__count"
10302
+ role="status"
10303
+ aria-live="polite"
10304
+ aria-atomic="true"
10305
+ data-wp-text="context.count"
10306
+ >0</span>
10307
+ <button
10308
+ type="button"
10309
+ disabled
10310
+ data-wp-bind--disabled="!context.canWrite"
10311
+ data-wp-on--click="actions.increment"
10312
+ >
10313
+ <?php echo esc_html( $target_context['buttonLabel'] ); ?>
10314
+ </button>
10315
+ </div>
10316
+ <?php endif; ?>
10317
+ <div class="{{cssClassName}}__items">
10318
+ <?php echo wp_kses_post( $target_context['sanitizedContent'] ); ?>
10319
+ </div>
10320
+ </div>
10321
+ <?php
10322
+ $markup = (string) ob_get_clean();
10323
+ break;
10324
+ }
10325
+
10326
+ return apply_filters(
10327
+ '{{phpPrefix}}_{{slugSnakeCase}}_render_target_markup',
10328
+ $markup,
10329
+ $target,
10330
+ $target_context
10331
+ );
10332
+ }
10333
+ }
10334
+ `;
10335
+ var COMPOUND_STYLE_TEMPLATE = `.{{cssClassName}} {
10336
+ border: 1px solid #dcdcde;
10337
+ border-radius: 12px;
10338
+ padding: 1.25rem;
10339
+ background: #fff;
10340
+ }
10341
+
10342
+ .{{cssClassName}}__heading {
10343
+ margin: 0 0 0.5rem;
10344
+ font-size: 1.2rem;
10345
+ }
10346
+
10347
+ .{{cssClassName}}__intro {
10348
+ margin: 0 0 1rem;
10349
+ color: #50575e;
10350
+ }
10351
+
10352
+ .{{cssClassName}}__items {
10353
+ display: grid;
10354
+ gap: 0.75rem;
10355
+ }
10356
+
10357
+ .{{cssClassName}}[data-show-dividers='true'] .{{compoundChildCssClassName}} {
10358
+ border-top: 1px solid #dcdcde;
10359
+ padding-top: 0.75rem;
10360
+ }
10361
+
10362
+ .{{cssClassName}}[data-show-dividers='true'] .{{compoundChildCssClassName}}:first-child {
10363
+ border-top: 0;
10364
+ padding-top: 0;
10365
+ }
10366
+ `;
10367
+ var COMPOUND_PERSISTENCE_RENDER_TEMPLATE = `<?php
10368
+ /**
10369
+ * Dynamic render entry for the {{title}} compound parent block.
10370
+ *
10371
+ * @package {{pascalCase}}
10372
+ */
10373
+
10374
+ if ( ! defined( 'ABSPATH' ) ) {
10375
+ exit;
10376
+ }
10377
+
10378
+ $validator_path = __DIR__ . '/typia-validator.php';
10379
+ if ( ! file_exists( $validator_path ) ) {
10380
+ return '';
10381
+ }
10382
+
10383
+ $validator = require $validator_path;
10384
+ if ( ! is_object( $validator ) || ! method_exists( $validator, 'apply_defaults' ) || ! method_exists( $validator, 'validate' ) ) {
10385
+ return '';
10386
+ }
10387
+
10388
+ $normalized = $validator->apply_defaults( is_array( $attributes ) ? $attributes : array() );
10389
+ $validation = $validator->validate( $normalized );
10390
+ $resource_key = isset( $normalized['resourceKey'] ) ? (string) $normalized['resourceKey'] : '';
10391
+ $heading = isset( $normalized['heading'] ) ? (string) $normalized['heading'] : {{titlePhpLiteral}};
10392
+ $intro = isset( $normalized['intro'] ) ? (string) $normalized['intro'] : '';
10393
+ $button_label = isset( $normalized['buttonLabel'] ) ? (string) $normalized['buttonLabel'] : 'Persist Count';
10394
+ $show_count = ! empty( $normalized['showCount'] );
10395
+ $show_dividers = ! empty( $normalized['showDividers'] );
10396
+ $post_id = is_object( $block ) && isset( $block->context['postId'] )
10397
+ ? (int) $block->context['postId']
10398
+ : (int) get_queried_object_id();
10399
+ $storage_mode = '{{dataStorageMode}}';
10400
+ $persistence_policy = '{{persistencePolicy}}';
10401
+
10402
+ $notice_message = 'authenticated' === $persistence_policy
10403
+ ? __( 'Sign in to persist this counter.', '{{textDomain}}' )
10404
+ : __( 'Public writes are temporarily unavailable.', '{{textDomain}}' );
10405
+
10406
+ if ( empty( $validation['valid'] ) || '' === $resource_key ) {
10407
+ return '';
10408
+ }
10409
+
10410
+ {{phpPrefix}}_record_rendered_block_instance(
10411
+ (int) $post_id,
10412
+ '{{namespace}}/{{slugKebabCase}}',
10413
+ $resource_key
10414
+ );
10415
+
10416
+ $context = array(
10417
+ 'bootstrapReady' => false,
10418
+ 'buttonLabel' => $button_label,
10419
+ 'canWrite' => false,
10420
+ 'client' => array(
10421
+ 'writeExpiry' => 0,
10422
+ 'writeNonce' => '',
10423
+ 'writeToken' => '',
10424
+ ),
10425
+ 'count' => 0,
10426
+ 'error' => '',
10427
+ 'isBootstrapping' => false,
10428
+ 'isLoading' => false,
10429
+ 'isSaving' => false,
10430
+ 'persistencePolicy' => $persistence_policy,
10431
+ 'postId' => (int) $post_id,
10432
+ 'resourceKey' => $resource_key,
10433
+ 'showCount' => $show_count,
10434
+ 'storage' => $storage_mode,
10435
+ );
10436
+
10437
+ $allowed_inner_html = wp_kses_allowed_html( 'post' );
10438
+
10439
+ foreach ( $allowed_inner_html as &$allowed_attributes ) {
10440
+ if ( ! is_array( $allowed_attributes ) ) {
10441
+ continue;
10442
+ }
10443
+
10444
+ $allowed_attributes['data-wp-bind--disabled'] = true;
10445
+ $allowed_attributes['data-wp-bind--hidden'] = true;
10446
+ $allowed_attributes['data-wp-bind--value'] = true;
10447
+ $allowed_attributes['data-wp-class'] = true;
10448
+ $allowed_attributes['data-wp-class--active'] = true;
10449
+ $allowed_attributes['data-wp-context'] = true;
10450
+ $allowed_attributes['data-wp-init'] = true;
10451
+ $allowed_attributes['data-wp-interactive'] = true;
10452
+ $allowed_attributes['data-wp-on--click'] = true;
10453
+ $allowed_attributes['data-wp-on--mouseenter'] = true;
10454
+ $allowed_attributes['data-wp-on--mouseleave'] = true;
10455
+ $allowed_attributes['data-wp-run--mounted'] = true;
10456
+ $allowed_attributes['data-wp-style--width'] = true;
10457
+ $allowed_attributes['data-wp-text'] = true;
10458
+ }
10459
+ unset( $allowed_attributes );
10460
+
10461
+ $sanitized_content = wp_kses( $content, $allowed_inner_html );
10462
+
10463
+ $wrapper_attributes = get_block_wrapper_attributes(
10464
+ array(
10465
+ 'class' => '{{cssClassName}}',
10466
+ 'data-show-dividers' => $show_dividers ? 'true' : 'false',
10467
+ 'data-wp-context' => wp_json_encode( $context ),
10468
+ 'data-wp-init' => 'callbacks.init',
10469
+ 'data-wp-interactive' => '{{slugKebabCase}}',
10470
+ 'data-wp-run--mounted' => 'callbacks.mounted',
10471
+ )
10472
+ );
10473
+ ?>
10474
+
10475
+ <div <?php echo $wrapper_attributes; ?>>
10476
+ <h3 class="{{cssClassName}}__heading"><?php echo esc_html( $heading ); ?></h3>
10477
+ <?php if ( '' !== $intro ) : ?>
10478
+ <p class="{{cssClassName}}__intro"><?php echo esc_html( $intro ); ?></p>
10479
+ <?php endif; ?>
10480
+ <p
10481
+ class="{{cssClassName}}__notice"
10482
+ data-wp-bind--hidden="!context.bootstrapReady || context.canWrite"
10483
+ hidden
10484
+ >
10485
+ <?php echo esc_html( $notice_message ); ?>
10486
+ </p>
10487
+ <p
10488
+ class="{{cssClassName}}__error"
10489
+ role="status"
10490
+ aria-live="polite"
10491
+ aria-atomic="true"
10492
+ data-wp-bind--hidden="!context.error"
10493
+ data-wp-text="context.error"
10494
+ hidden
10495
+ ></p>
10496
+ <?php if ( $show_count ) : ?>
10497
+ <div class="{{cssClassName}}__counter">
10498
+ <span
10499
+ class="{{cssClassName}}__count"
10500
+ role="status"
10501
+ aria-live="polite"
10502
+ aria-atomic="true"
10503
+ data-wp-text="context.count"
10504
+ >0</span>
10505
+ <button
10506
+ type="button"
10507
+ disabled
10508
+ data-wp-bind--disabled="!context.canWrite"
10509
+ data-wp-on--click="actions.increment"
10510
+ >
10511
+ <?php echo esc_html( $button_label ); ?>
10512
+ </button>
10513
+ </div>
10514
+ <?php endif; ?>
10515
+ <div class="{{cssClassName}}__items">
10516
+ <?php echo $sanitized_content; ?>
10517
+ </div>
10518
+ </div>
10519
+ `;
10520
+
10521
+ // ../wp-typia-project-tools/src/runtime/built-in-block-non-ts-compound-artifacts.ts
10522
+ function buildCompoundArtifacts(variables) {
10523
+ const alternateRenderTargets = getScaffoldAlternateRenderTargets(variables);
10524
+ const artifacts = [
10525
+ renderArtifact(`src/blocks/${variables.slugKebabCase}/style.scss`, COMPOUND_STYLE_TEMPLATE, variables)
10526
+ ];
10527
+ if (isCompoundPersistenceEnabled(variables)) {
10528
+ const renderView = {
10529
+ ...variables,
10530
+ titlePhpLiteral: toPhpSingleQuotedString(variables.title)
10531
+ };
10532
+ if (alternateRenderTargets.enabled) {
10533
+ artifacts.push(renderArtifact(`src/blocks/${variables.slugKebabCase}/render-targets.php`, COMPOUND_PERSISTENCE_RENDER_TARGETS_TEMPLATE, renderView), buildAlternateRenderEntryArtifact(`src/blocks/${variables.slugKebabCase}/render.php`, "web", variables));
10534
+ if (alternateRenderTargets.hasEmail) {
10535
+ artifacts.push(buildAlternateRenderEntryArtifact(`src/blocks/${variables.slugKebabCase}/render-email.php`, "email", variables));
10536
+ }
10537
+ if (alternateRenderTargets.hasMjml) {
10538
+ artifacts.push(buildAlternateRenderEntryArtifact(`src/blocks/${variables.slugKebabCase}/render-mjml.php`, "mjml", variables));
10539
+ }
10540
+ if (alternateRenderTargets.hasPlainText) {
10541
+ artifacts.push(buildAlternateRenderEntryArtifact(`src/blocks/${variables.slugKebabCase}/render-text.php`, "plain-text", variables));
10542
+ }
10543
+ return artifacts;
10544
+ }
10545
+ artifacts.push(renderArtifact(`src/blocks/${variables.slugKebabCase}/render.php`, COMPOUND_PERSISTENCE_RENDER_TEMPLATE, renderView));
10546
+ }
10547
+ return artifacts;
10548
+ }
10549
+ // ../wp-typia-project-tools/src/runtime/built-in-block-non-ts-interactivity-artifacts.ts
10550
+ var INTERACTIVITY_STYLE_TEMPLATE = `.{{cssClassName}} {
10551
+ position: relative;
10552
+ padding: 1rem;
10553
+ border: 1px solid #dcdcde;
10554
+ border-radius: 0.75rem;
10555
+ background: #fff;
10556
+
10557
+ &__content {
9508
10558
  display: grid;
9509
10559
  gap: 0.75rem;
9510
10560
  }
@@ -9567,6 +10617,13 @@ var INTERACTIVITY_EDITOR_STYLE_TEMPLATE = `/**
9567
10617
  outline-offset: -1px;
9568
10618
  }
9569
10619
  `;
10620
+ function buildInteractivityArtifacts(variables) {
10621
+ return [
10622
+ renderArtifact("src/editor.scss", INTERACTIVITY_EDITOR_STYLE_TEMPLATE, variables),
10623
+ renderArtifact("src/style.scss", INTERACTIVITY_STYLE_TEMPLATE, variables)
10624
+ ];
10625
+ }
10626
+ // ../wp-typia-project-tools/src/runtime/built-in-block-non-ts-persistence-templates.ts
9570
10627
  var PERSISTENCE_STYLE_TEMPLATE = `.{{cssClassName}} {
9571
10628
  border: 1px solid #dcdcde;
9572
10629
  border-radius: 12px;
@@ -9665,238 +10722,44 @@ $context = array(
9665
10722
  'bootstrapReady' => false,
9666
10723
  'buttonLabel' => $button_label,
9667
10724
  'canWrite' => false,
9668
- 'client' => array(
9669
- 'writeExpiry' => 0,
9670
- 'writeNonce' => '',
9671
- 'writeToken' => '',
9672
- ),
9673
- 'count' => 0,
9674
- 'error' => '',
9675
- 'isBootstrapping' => false,
9676
- 'isLoading' => false,
9677
- 'isSaving' => false,
9678
- 'isVisible' => ! empty( $normalized['isVisible'] ),
9679
- 'persistencePolicy' => $persistence_policy,
9680
- 'postId' => (int) $post_id,
9681
- 'resourceKey' => $resource_key,
9682
- 'storage' => $storage_mode,
9683
- );
9684
-
9685
- $wrapper_attributes = get_block_wrapper_attributes(
9686
- array(
9687
- 'data-wp-context' => wp_json_encode( $context ),
9688
- 'data-wp-interactive' => '{{slugKebabCase}}',
9689
- 'data-wp-init' => 'callbacks.init',
9690
- 'data-wp-run--mounted' => 'callbacks.mounted',
9691
- )
9692
- );
9693
- ?>
9694
-
9695
- <div <?php echo $wrapper_attributes; ?>>
9696
- <div class="{{frontendCssClassName}}">
9697
- <p class="{{frontendCssClassName}}__content" style="<?php echo esc_attr( 'text-align:' . $alignment ); ?>">
9698
- <?php echo esc_html( $content ); ?>
9699
- </p>
9700
- <p
9701
- class="{{frontendCssClassName}}__notice"
9702
- data-wp-bind--hidden="!context.bootstrapReady || context.canWrite"
9703
- hidden
9704
- >
9705
- <?php echo esc_html( $notice_message ); ?>
9706
- </p>
9707
- <p
9708
- class="{{frontendCssClassName}}__error"
9709
- role="status"
9710
- aria-live="polite"
9711
- aria-atomic="true"
9712
- data-wp-bind--hidden="!context.error"
9713
- data-wp-text="context.error"
9714
- hidden
9715
- ></p>
9716
- <?php if ( ! empty( $normalized['showCount'] ) ) : ?>
9717
- <span
9718
- class="{{frontendCssClassName}}__count"
9719
- role="status"
9720
- aria-live="polite"
9721
- aria-atomic="true"
9722
- data-wp-text="context.count"
9723
- >
9724
- 0
9725
- </span>
9726
- <?php endif; ?>
9727
- <button
9728
- type="button"
9729
- disabled
9730
- data-wp-bind--disabled="!context.canWrite"
9731
- data-wp-on--click="actions.increment"
9732
- >
9733
- <?php echo esc_html( $button_label ); ?>
9734
- </button>
9735
- </div>
9736
- </div>
9737
- `;
9738
- var PERSISTENCE_RENDER_TARGETS_TEMPLATE = `<?php
9739
- /**
9740
- * Alternate render target helpers for the {{title}} block.
9741
- *
9742
- * @package {{pascalCase}}
9743
- */
9744
-
9745
- if ( ! defined( 'ABSPATH' ) ) {
9746
- exit;
9747
- }
9748
-
9749
- if ( ! function_exists( '{{phpPrefix}}_{{slugSnakeCase}}_build_render_context' ) ) {
9750
- function {{phpPrefix}}_{{slugSnakeCase}}_build_render_context( $attributes, $content, $block ) {
9751
- $validator_path = __DIR__ . '/typia-validator.php';
9752
- if ( ! file_exists( $validator_path ) ) {
9753
- return null;
9754
- }
9755
-
9756
- $validator = require $validator_path;
9757
- if ( ! is_object( $validator ) || ! method_exists( $validator, 'apply_defaults' ) || ! method_exists( $validator, 'validate' ) ) {
9758
- return null;
9759
- }
9760
-
9761
- $normalized = $validator->apply_defaults( is_array( $attributes ) ? $attributes : array() );
9762
- $validation = $validator->validate( $normalized );
9763
- $resource_key = isset( $normalized['resourceKey'] ) ? (string) $normalized['resourceKey'] : '';
9764
-
9765
- if ( empty( $validation['valid'] ) || '' === $resource_key ) {
9766
- return null;
9767
- }
9768
-
9769
- $alignment = isset( $normalized['alignment'] ) ? (string) $normalized['alignment'] : 'left';
9770
- $button_label = isset( $normalized['buttonLabel'] ) ? (string) $normalized['buttonLabel'] : 'Persist Count';
9771
- $rendered_content = isset( $normalized['content'] ) ? (string) $normalized['content'] : '';
9772
- $post_id = is_object( $block ) && isset( $block->context['postId'] )
9773
- ? (int) $block->context['postId']
9774
- : (int) get_queried_object_id();
9775
- $storage_mode = '{{dataStorageMode}}';
9776
- $persistence_policy = '{{persistencePolicy}}';
9777
-
9778
- {{phpPrefix}}_record_rendered_block_instance(
9779
- (int) $post_id,
9780
- '{{namespace}}/{{slugKebabCase}}',
9781
- $resource_key
9782
- );
9783
-
9784
- $notice_message = 'authenticated' === $persistence_policy
9785
- ? __( 'Sign in to persist this counter.', '{{textDomain}}' )
9786
- : __( 'Public writes are temporarily unavailable.', '{{textDomain}}' );
9787
- $web_context = array(
9788
- 'bootstrapReady' => false,
9789
- 'buttonLabel' => $button_label,
9790
- 'canWrite' => false,
9791
- 'client' => array(
9792
- 'writeExpiry' => 0,
9793
- 'writeNonce' => '',
9794
- 'writeToken' => '',
9795
- ),
9796
- 'count' => 0,
9797
- 'error' => '',
9798
- 'isBootstrapping' => false,
9799
- 'isLoading' => false,
9800
- 'isSaving' => false,
9801
- 'isVisible' => ! empty( $normalized['isVisible'] ),
9802
- 'persistencePolicy' => $persistence_policy,
9803
- 'postId' => (int) $post_id,
9804
- 'resourceKey' => $resource_key,
9805
- 'storage' => $storage_mode,
9806
- );
9807
-
9808
- return array(
9809
- 'alignment' => $alignment,
9810
- 'buttonLabel' => $button_label,
9811
- 'content' => $rendered_content,
9812
- 'frontendClassName' => '{{frontendCssClassName}}',
9813
- 'isVisible' => ! empty( $normalized['isVisible'] ),
9814
- 'normalized' => $normalized,
9815
- 'noticeMessage' => $notice_message,
9816
- 'postId' => (int) $post_id,
9817
- 'resourceKey' => $resource_key,
9818
- 'showCount' => ! empty( $normalized['showCount'] ),
9819
- 'title' => {{titleJson}},
9820
- 'webContext' => $web_context,
9821
- );
9822
- }
9823
- }
9824
-
9825
- if ( ! function_exists( '{{phpPrefix}}_{{slugSnakeCase}}_render_target' ) ) {
9826
- function {{phpPrefix}}_{{slugSnakeCase}}_render_target( string $target, $attributes, $content = '', $block = null ): string {
9827
- $context = {{phpPrefix}}_{{slugSnakeCase}}_build_render_context( $attributes, $content, $block );
9828
- if ( null === $context || empty( $context['isVisible'] ) ) {
9829
- return '';
9830
- }
10725
+ 'client' => array(
10726
+ 'writeExpiry' => 0,
10727
+ 'writeNonce' => '',
10728
+ 'writeToken' => '',
10729
+ ),
10730
+ 'count' => 0,
10731
+ 'error' => '',
10732
+ 'isBootstrapping' => false,
10733
+ 'isLoading' => false,
10734
+ 'isSaving' => false,
10735
+ 'isVisible' => ! empty( $normalized['isVisible'] ),
10736
+ 'persistencePolicy' => $persistence_policy,
10737
+ 'postId' => (int) $post_id,
10738
+ 'resourceKey' => $resource_key,
10739
+ 'storage' => $storage_mode,
10740
+ );
9831
10741
 
9832
- $target_context = apply_filters(
9833
- '{{phpPrefix}}_{{slugSnakeCase}}_render_target_context',
9834
- $context,
9835
- $target,
9836
- $attributes,
9837
- $block
9838
- );
10742
+ $wrapper_attributes = get_block_wrapper_attributes(
10743
+ array(
10744
+ 'data-wp-context' => wp_json_encode( $context ),
10745
+ 'data-wp-interactive' => '{{slugKebabCase}}',
10746
+ 'data-wp-init' => 'callbacks.init',
10747
+ 'data-wp-run--mounted' => 'callbacks.mounted',
10748
+ )
10749
+ );
10750
+ ?>
9839
10751
 
9840
- $markup = '';
9841
- switch ( $target ) {
9842
- case 'email':
9843
- $parts = array(
9844
- '<div class="' . esc_attr( $target_context['frontendClassName'] ) . '-email">',
9845
- '<p style="text-align:' . esc_attr( $target_context['alignment'] ) . ';">' . esc_html( $target_context['content'] ) . '</p>',
9846
- );
9847
- if ( ! empty( $target_context['showCount'] ) ) {
9848
- $parts[] = '<p><strong>' . esc_html__( 'Count', '{{textDomain}}' ) . ':</strong> ' . esc_html( (string) $target_context['webContext']['count'] ) . '</p>';
9849
- }
9850
- $parts[] = '<p>' . esc_html( $target_context['noticeMessage'] ) . '</p>';
9851
- $parts[] = '</div>';
9852
- $markup = implode( '', $parts );
9853
- break;
9854
- case 'mjml':
9855
- $markup = '<mjml><mj-body><mj-section><mj-column><mj-text align="' . esc_attr( $target_context['alignment'] ) . '">' . esc_html( $target_context['content'] ) . '</mj-text>';
9856
- if ( ! empty( $target_context['showCount'] ) ) {
9857
- $markup .= '<mj-text>' . esc_html__( 'Count', '{{textDomain}}' ) . ': ' . esc_html( (string) $target_context['webContext']['count'] ) . '</mj-text>';
9858
- }
9859
- $markup .= '<mj-text>' . esc_html( $target_context['noticeMessage'] ) . '</mj-text></mj-column></mj-section></mj-body></mjml>';
9860
- break;
9861
- case 'plain-text':
9862
- $markup = implode(
9863
- "\\n",
9864
- array_filter(
9865
- array(
9866
- $target_context['title'],
9867
- $target_context['content'],
9868
- ! empty( $target_context['showCount'] )
9869
- ? sprintf( '%s: %s', __( 'Count', '{{textDomain}}' ), (string) $target_context['webContext']['count'] )
9870
- : null,
9871
- $target_context['noticeMessage'],
9872
- ),
9873
- static fn( $value ) => is_string( $value ) && '' !== $value
9874
- )
9875
- );
9876
- break;
9877
- case 'web':
9878
- default:
9879
- $wrapper_attributes = get_block_wrapper_attributes(
9880
- array(
9881
- 'data-wp-context' => wp_json_encode( $target_context['webContext'] ),
9882
- 'data-wp-interactive' => '{{slugKebabCase}}',
9883
- 'data-wp-init' => 'callbacks.init',
9884
- 'data-wp-run--mounted' => 'callbacks.mounted',
9885
- )
9886
- );
9887
- ob_start();
9888
- ?>
9889
10752
  <div <?php echo $wrapper_attributes; ?>>
9890
10753
  <div class="{{frontendCssClassName}}">
9891
- <p class="{{frontendCssClassName}}__content" style="<?php echo esc_attr( 'text-align:' . $target_context['alignment'] ); ?>">
9892
- <?php echo esc_html( $target_context['content'] ); ?>
10754
+ <p class="{{frontendCssClassName}}__content" style="<?php echo esc_attr( 'text-align:' . $alignment ); ?>">
10755
+ <?php echo esc_html( $content ); ?>
9893
10756
  </p>
9894
10757
  <p
9895
10758
  class="{{frontendCssClassName}}__notice"
9896
10759
  data-wp-bind--hidden="!context.bootstrapReady || context.canWrite"
9897
10760
  hidden
9898
10761
  >
9899
- <?php echo esc_html( $target_context['noticeMessage'] ); ?>
10762
+ <?php echo esc_html( $notice_message ); ?>
9900
10763
  </p>
9901
10764
  <p
9902
10765
  class="{{frontendCssClassName}}__error"
@@ -9907,7 +10770,7 @@ if ( ! function_exists( '{{phpPrefix}}_{{slugSnakeCase}}_render_target' ) ) {
9907
10770
  data-wp-text="context.error"
9908
10771
  hidden
9909
10772
  ></p>
9910
- <?php if ( ! empty( $target_context['showCount'] ) ) : ?>
10773
+ <?php if ( ! empty( $normalized['showCount'] ) ) : ?>
9911
10774
  <span
9912
10775
  class="{{frontendCssClassName}}__count"
9913
10776
  role="status"
@@ -9924,27 +10787,14 @@ if ( ! function_exists( '{{phpPrefix}}_{{slugSnakeCase}}_render_target' ) ) {
9924
10787
  data-wp-bind--disabled="!context.canWrite"
9925
10788
  data-wp-on--click="actions.increment"
9926
10789
  >
9927
- <?php echo esc_html( $target_context['buttonLabel'] ); ?>
10790
+ <?php echo esc_html( $button_label ); ?>
9928
10791
  </button>
9929
10792
  </div>
9930
10793
  </div>
9931
- <?php
9932
- $markup = (string) ob_get_clean();
9933
- break;
9934
- }
9935
-
9936
- return apply_filters(
9937
- '{{phpPrefix}}_{{slugSnakeCase}}_render_target_markup',
9938
- $markup,
9939
- $target,
9940
- $target_context
9941
- );
9942
- }
9943
- }
9944
10794
  `;
9945
- var COMPOUND_PERSISTENCE_RENDER_TARGETS_TEMPLATE = `<?php
10795
+ var PERSISTENCE_RENDER_TARGETS_TEMPLATE = `<?php
9946
10796
  /**
9947
- * Alternate render target helpers for the {{title}} compound parent block.
10797
+ * Alternate render target helpers for the {{title}} block.
9948
10798
  *
9949
10799
  * @package {{pascalCase}}
9950
10800
  */
@@ -9965,34 +10815,32 @@ if ( ! function_exists( '{{phpPrefix}}_{{slugSnakeCase}}_build_render_context' )
9965
10815
  return null;
9966
10816
  }
9967
10817
 
9968
- $normalized = $validator->apply_defaults( is_array( $attributes ) ? $attributes : array() );
9969
- $validation = $validator->validate( $normalized );
9970
- $resource_key = isset( $normalized['resourceKey'] ) ? (string) $normalized['resourceKey'] : '';
9971
- $heading = isset( $normalized['heading'] ) ? (string) $normalized['heading'] : {{titlePhpLiteral}};
9972
- $intro = isset( $normalized['intro'] ) ? (string) $normalized['intro'] : '';
10818
+ $normalized = $validator->apply_defaults( is_array( $attributes ) ? $attributes : array() );
10819
+ $validation = $validator->validate( $normalized );
10820
+ $resource_key = isset( $normalized['resourceKey'] ) ? (string) $normalized['resourceKey'] : '';
10821
+
10822
+ if ( empty( $validation['valid'] ) || '' === $resource_key ) {
10823
+ return null;
10824
+ }
10825
+
10826
+ $alignment = isset( $normalized['alignment'] ) ? (string) $normalized['alignment'] : 'left';
9973
10827
  $button_label = isset( $normalized['buttonLabel'] ) ? (string) $normalized['buttonLabel'] : 'Persist Count';
9974
- $show_count = ! empty( $normalized['showCount'] );
9975
- $show_dividers = ! empty( $normalized['showDividers'] );
10828
+ $rendered_content = isset( $normalized['content'] ) ? (string) $normalized['content'] : '';
9976
10829
  $post_id = is_object( $block ) && isset( $block->context['postId'] )
9977
10830
  ? (int) $block->context['postId']
9978
10831
  : (int) get_queried_object_id();
9979
10832
  $storage_mode = '{{dataStorageMode}}';
9980
10833
  $persistence_policy = '{{persistencePolicy}}';
9981
10834
 
9982
- $notice_message = 'authenticated' === $persistence_policy
9983
- ? __( 'Sign in to persist this counter.', '{{textDomain}}' )
9984
- : __( 'Public writes are temporarily unavailable.', '{{textDomain}}' );
9985
-
9986
- if ( empty( $validation['valid'] ) || '' === $resource_key ) {
9987
- return null;
9988
- }
9989
-
9990
10835
  {{phpPrefix}}_record_rendered_block_instance(
9991
10836
  (int) $post_id,
9992
10837
  '{{namespace}}/{{slugKebabCase}}',
9993
10838
  $resource_key
9994
10839
  );
9995
10840
 
10841
+ $notice_message = 'authenticated' === $persistence_policy
10842
+ ? __( 'Sign in to persist this counter.', '{{textDomain}}' )
10843
+ : __( 'Public writes are temporarily unavailable.', '{{textDomain}}' );
9996
10844
  $web_context = array(
9997
10845
  'bootstrapReady' => false,
9998
10846
  'buttonLabel' => $button_label,
@@ -10007,50 +10855,24 @@ if ( ! function_exists( '{{phpPrefix}}_{{slugSnakeCase}}_build_render_context' )
10007
10855
  'isBootstrapping' => false,
10008
10856
  'isLoading' => false,
10009
10857
  'isSaving' => false,
10858
+ 'isVisible' => ! empty( $normalized['isVisible'] ),
10010
10859
  'persistencePolicy' => $persistence_policy,
10011
10860
  'postId' => (int) $post_id,
10012
10861
  'resourceKey' => $resource_key,
10013
- 'showCount' => $show_count,
10014
10862
  'storage' => $storage_mode,
10015
10863
  );
10016
10864
 
10017
- $allowed_inner_html = wp_kses_allowed_html( 'post' );
10018
-
10019
- foreach ( $allowed_inner_html as &$allowed_attributes ) {
10020
- if ( ! is_array( $allowed_attributes ) ) {
10021
- continue;
10022
- }
10023
-
10024
- $allowed_attributes['data-wp-bind--disabled'] = true;
10025
- $allowed_attributes['data-wp-bind--hidden'] = true;
10026
- $allowed_attributes['data-wp-bind--value'] = true;
10027
- $allowed_attributes['data-wp-class'] = true;
10028
- $allowed_attributes['data-wp-class--active'] = true;
10029
- $allowed_attributes['data-wp-context'] = true;
10030
- $allowed_attributes['data-wp-init'] = true;
10031
- $allowed_attributes['data-wp-interactive'] = true;
10032
- $allowed_attributes['data-wp-on--click'] = true;
10033
- $allowed_attributes['data-wp-on--mouseenter'] = true;
10034
- $allowed_attributes['data-wp-on--mouseleave'] = true;
10035
- $allowed_attributes['data-wp-run--mounted'] = true;
10036
- $allowed_attributes['data-wp-style--width'] = true;
10037
- $allowed_attributes['data-wp-text'] = true;
10038
- }
10039
- unset( $allowed_attributes );
10040
-
10041
10865
  return array(
10866
+ 'alignment' => $alignment,
10042
10867
  'buttonLabel' => $button_label,
10043
- 'frontendClassName' => '{{cssClassName}}',
10044
- 'heading' => $heading,
10045
- 'intro' => $intro,
10046
- 'isVisible' => true,
10868
+ 'content' => $rendered_content,
10869
+ 'frontendClassName' => '{{frontendCssClassName}}',
10870
+ 'isVisible' => ! empty( $normalized['isVisible'] ),
10047
10871
  'normalized' => $normalized,
10048
10872
  'noticeMessage' => $notice_message,
10049
10873
  'postId' => (int) $post_id,
10050
10874
  'resourceKey' => $resource_key,
10051
- 'sanitizedContent' => wp_kses( $content, $allowed_inner_html ),
10052
- 'showCount' => $show_count,
10053
- 'showDividers' => $show_dividers,
10875
+ 'showCount' => ! empty( $normalized['showCount'] ),
10054
10876
  'title' => {{titleJson}},
10055
10877
  'webContext' => $web_context,
10056
10878
  );
@@ -10060,7 +10882,7 @@ if ( ! function_exists( '{{phpPrefix}}_{{slugSnakeCase}}_build_render_context' )
10060
10882
  if ( ! function_exists( '{{phpPrefix}}_{{slugSnakeCase}}_render_target' ) ) {
10061
10883
  function {{phpPrefix}}_{{slugSnakeCase}}_render_target( string $target, $attributes, $content = '', $block = null ): string {
10062
10884
  $context = {{phpPrefix}}_{{slugSnakeCase}}_build_render_context( $attributes, $content, $block );
10063
- if ( null === $context ) {
10885
+ if ( null === $context || empty( $context['isVisible'] ) ) {
10064
10886
  return '';
10065
10887
  }
10066
10888
 
@@ -10077,28 +10899,20 @@ if ( ! function_exists( '{{phpPrefix}}_{{slugSnakeCase}}_render_target' ) ) {
10077
10899
  case 'email':
10078
10900
  $parts = array(
10079
10901
  '<div class="' . esc_attr( $target_context['frontendClassName'] ) . '-email">',
10080
- '<h3>' . esc_html( $target_context['heading'] ) . '</h3>',
10902
+ '<p style="text-align:' . esc_attr( $target_context['alignment'] ) . ';">' . esc_html( $target_context['content'] ) . '</p>',
10081
10903
  );
10082
- if ( '' !== $target_context['intro'] ) {
10083
- $parts[] = '<p>' . esc_html( $target_context['intro'] ) . '</p>';
10084
- }
10085
10904
  if ( ! empty( $target_context['showCount'] ) ) {
10086
10905
  $parts[] = '<p><strong>' . esc_html__( 'Count', '{{textDomain}}' ) . ':</strong> ' . esc_html( (string) $target_context['webContext']['count'] ) . '</p>';
10087
10906
  }
10088
- $parts[] = wp_kses_post( $target_context['sanitizedContent'] );
10089
10907
  $parts[] = '<p>' . esc_html( $target_context['noticeMessage'] ) . '</p>';
10090
10908
  $parts[] = '</div>';
10091
10909
  $markup = implode( '', $parts );
10092
10910
  break;
10093
10911
  case 'mjml':
10094
- $markup = '<mjml><mj-body><mj-section><mj-column><mj-text font-weight="700">' . esc_html( $target_context['heading'] ) . '</mj-text>';
10095
- if ( '' !== $target_context['intro'] ) {
10096
- $markup .= '<mj-text>' . esc_html( $target_context['intro'] ) . '</mj-text>';
10097
- }
10912
+ $markup = '<mjml><mj-body><mj-section><mj-column><mj-text align="' . esc_attr( $target_context['alignment'] ) . '">' . esc_html( $target_context['content'] ) . '</mj-text>';
10098
10913
  if ( ! empty( $target_context['showCount'] ) ) {
10099
10914
  $markup .= '<mj-text>' . esc_html__( 'Count', '{{textDomain}}' ) . ': ' . esc_html( (string) $target_context['webContext']['count'] ) . '</mj-text>';
10100
10915
  }
10101
- $markup .= '<mj-text>' . esc_html( wp_strip_all_tags( $target_context['sanitizedContent'] ) ) . '</mj-text>';
10102
10916
  $markup .= '<mj-text>' . esc_html( $target_context['noticeMessage'] ) . '</mj-text></mj-column></mj-section></mj-body></mjml>';
10103
10917
  break;
10104
10918
  case 'plain-text':
@@ -10106,9 +10920,8 @@ if ( ! function_exists( '{{phpPrefix}}_{{slugSnakeCase}}_render_target' ) ) {
10106
10920
  "\\n",
10107
10921
  array_filter(
10108
10922
  array(
10109
- $target_context['heading'],
10110
- $target_context['intro'],
10111
- wp_strip_all_tags( $target_context['sanitizedContent'] ),
10923
+ $target_context['title'],
10924
+ $target_context['content'],
10112
10925
  ! empty( $target_context['showCount'] )
10113
10926
  ? sprintf( '%s: %s', __( 'Count', '{{textDomain}}' ), (string) $target_context['webContext']['count'] )
10114
10927
  : null,
@@ -10122,58 +10935,54 @@ if ( ! function_exists( '{{phpPrefix}}_{{slugSnakeCase}}_render_target' ) ) {
10122
10935
  default:
10123
10936
  $wrapper_attributes = get_block_wrapper_attributes(
10124
10937
  array(
10125
- 'class' => '{{cssClassName}}',
10126
- 'data-show-dividers' => $target_context['showDividers'] ? 'true' : 'false',
10127
10938
  'data-wp-context' => wp_json_encode( $target_context['webContext'] ),
10128
- 'data-wp-init' => 'callbacks.init',
10129
10939
  'data-wp-interactive' => '{{slugKebabCase}}',
10940
+ 'data-wp-init' => 'callbacks.init',
10130
10941
  'data-wp-run--mounted' => 'callbacks.mounted',
10131
10942
  )
10132
10943
  );
10133
10944
  ob_start();
10134
10945
  ?>
10135
10946
  <div <?php echo $wrapper_attributes; ?>>
10136
- <h3 class="{{cssClassName}}__heading"><?php echo esc_html( $target_context['heading'] ); ?></h3>
10137
- <?php if ( '' !== $target_context['intro'] ) : ?>
10138
- <p class="{{cssClassName}}__intro"><?php echo esc_html( $target_context['intro'] ); ?></p>
10139
- <?php endif; ?>
10140
- <p
10141
- class="{{cssClassName}}__notice"
10142
- data-wp-bind--hidden="!context.bootstrapReady || context.canWrite"
10143
- hidden
10144
- >
10145
- <?php echo esc_html( $target_context['noticeMessage'] ); ?>
10146
- </p>
10147
- <p
10148
- class="{{cssClassName}}__error"
10149
- role="status"
10150
- aria-live="polite"
10151
- aria-atomic="true"
10152
- data-wp-bind--hidden="!context.error"
10153
- data-wp-text="context.error"
10154
- hidden
10155
- ></p>
10156
- <?php if ( ! empty( $target_context['showCount'] ) ) : ?>
10157
- <div class="{{cssClassName}}__counter">
10947
+ <div class="{{frontendCssClassName}}">
10948
+ <p class="{{frontendCssClassName}}__content" style="<?php echo esc_attr( 'text-align:' . $target_context['alignment'] ); ?>">
10949
+ <?php echo esc_html( $target_context['content'] ); ?>
10950
+ </p>
10951
+ <p
10952
+ class="{{frontendCssClassName}}__notice"
10953
+ data-wp-bind--hidden="!context.bootstrapReady || context.canWrite"
10954
+ hidden
10955
+ >
10956
+ <?php echo esc_html( $target_context['noticeMessage'] ); ?>
10957
+ </p>
10958
+ <p
10959
+ class="{{frontendCssClassName}}__error"
10960
+ role="status"
10961
+ aria-live="polite"
10962
+ aria-atomic="true"
10963
+ data-wp-bind--hidden="!context.error"
10964
+ data-wp-text="context.error"
10965
+ hidden
10966
+ ></p>
10967
+ <?php if ( ! empty( $target_context['showCount'] ) ) : ?>
10158
10968
  <span
10159
- class="{{cssClassName}}__count"
10969
+ class="{{frontendCssClassName}}__count"
10160
10970
  role="status"
10161
10971
  aria-live="polite"
10162
10972
  aria-atomic="true"
10163
- data-wp-text="context.count"
10164
- >0</span>
10165
- <button
10166
- type="button"
10167
- disabled
10168
- data-wp-bind--disabled="!context.canWrite"
10169
- data-wp-on--click="actions.increment"
10170
- >
10171
- <?php echo esc_html( $target_context['buttonLabel'] ); ?>
10172
- </button>
10173
- </div>
10174
- <?php endif; ?>
10175
- <div class="{{cssClassName}}__items">
10176
- <?php echo wp_kses_post( $target_context['sanitizedContent'] ); ?>
10973
+ data-wp-text="context.count"
10974
+ >
10975
+ 0
10976
+ </span>
10977
+ <?php endif; ?>
10978
+ <button
10979
+ type="button"
10980
+ disabled
10981
+ data-wp-bind--disabled="!context.canWrite"
10982
+ data-wp-on--click="actions.increment"
10983
+ >
10984
+ <?php echo esc_html( $target_context['buttonLabel'] ); ?>
10985
+ </button>
10177
10986
  </div>
10178
10987
  </div>
10179
10988
  <?php
@@ -10190,51 +10999,8 @@ if ( ! function_exists( '{{phpPrefix}}_{{slugSnakeCase}}_render_target' ) ) {
10190
10999
  }
10191
11000
  }
10192
11001
  `;
10193
- var COMPOUND_STYLE_TEMPLATE = `.{{cssClassName}} {
10194
- border: 1px solid #dcdcde;
10195
- border-radius: 12px;
10196
- padding: 1.25rem;
10197
- background: #fff;
10198
- }
10199
-
10200
- .{{cssClassName}}__heading {
10201
- margin: 0 0 0.5rem;
10202
- font-size: 1.2rem;
10203
- }
10204
-
10205
- .{{cssClassName}}__intro {
10206
- margin: 0 0 1rem;
10207
- color: #50575e;
10208
- }
10209
-
10210
- .{{cssClassName}}__items {
10211
- display: grid;
10212
- gap: 0.75rem;
10213
- }
10214
-
10215
- .{{cssClassName}}[data-show-dividers='true'] .{{compoundChildCssClassName}} {
10216
- border-top: 1px solid #dcdcde;
10217
- padding-top: 0.75rem;
10218
- }
10219
11002
 
10220
- .{{cssClassName}}[data-show-dividers='true'] .{{compoundChildCssClassName}}:first-child {
10221
- border-top: 0;
10222
- padding-top: 0;
10223
- }
10224
- `;
10225
- function buildBasicArtifacts(variables) {
10226
- return [
10227
- renderArtifact("src/editor.scss", BASIC_EDITOR_STYLE_TEMPLATE, variables),
10228
- renderArtifact("src/style.scss", BASIC_STYLE_TEMPLATE, variables),
10229
- renderArtifact("src/render.php", BASIC_RENDER_TEMPLATE, variables)
10230
- ];
10231
- }
10232
- function buildInteractivityArtifacts(variables) {
10233
- return [
10234
- renderArtifact("src/editor.scss", INTERACTIVITY_EDITOR_STYLE_TEMPLATE, variables),
10235
- renderArtifact("src/style.scss", INTERACTIVITY_STYLE_TEMPLATE, variables)
10236
- ];
10237
- }
11003
+ // ../wp-typia-project-tools/src/runtime/built-in-block-non-ts-persistence-artifacts.ts
10238
11004
  function buildPersistenceArtifacts(variables) {
10239
11005
  const alternateRenderTargets = getScaffoldAlternateRenderTargets(variables);
10240
11006
  if (!alternateRenderTargets.enabled) {
@@ -10259,187 +11025,6 @@ function buildPersistenceArtifacts(variables) {
10259
11025
  }
10260
11026
  return artifacts;
10261
11027
  }
10262
- function buildCompoundArtifacts(variables) {
10263
- const alternateRenderTargets = getScaffoldAlternateRenderTargets(variables);
10264
- const artifacts = [
10265
- renderArtifact(`src/blocks/${variables.slugKebabCase}/style.scss`, COMPOUND_STYLE_TEMPLATE, variables)
10266
- ];
10267
- if (isCompoundPersistenceEnabled(variables)) {
10268
- const renderView = {
10269
- ...variables,
10270
- titlePhpLiteral: toPhpSingleQuotedString(variables.title)
10271
- };
10272
- if (alternateRenderTargets.enabled) {
10273
- artifacts.push(renderArtifact(`src/blocks/${variables.slugKebabCase}/render-targets.php`, COMPOUND_PERSISTENCE_RENDER_TARGETS_TEMPLATE, renderView), buildAlternateRenderEntryArtifact(`src/blocks/${variables.slugKebabCase}/render.php`, "web", variables));
10274
- if (alternateRenderTargets.hasEmail) {
10275
- artifacts.push(buildAlternateRenderEntryArtifact(`src/blocks/${variables.slugKebabCase}/render-email.php`, "email", variables));
10276
- }
10277
- if (alternateRenderTargets.hasMjml) {
10278
- artifacts.push(buildAlternateRenderEntryArtifact(`src/blocks/${variables.slugKebabCase}/render-mjml.php`, "mjml", variables));
10279
- }
10280
- if (alternateRenderTargets.hasPlainText) {
10281
- artifacts.push(buildAlternateRenderEntryArtifact(`src/blocks/${variables.slugKebabCase}/render-text.php`, "plain-text", variables));
10282
- }
10283
- return artifacts;
10284
- }
10285
- const renderSource = `<?php
10286
- /**
10287
- * Dynamic render entry for the {{title}} compound parent block.
10288
- *
10289
- * @package {{pascalCase}}
10290
- */
10291
-
10292
- if ( ! defined( 'ABSPATH' ) ) {
10293
- exit;
10294
- }
10295
-
10296
- $validator_path = __DIR__ . '/typia-validator.php';
10297
- if ( ! file_exists( $validator_path ) ) {
10298
- return '';
10299
- }
10300
-
10301
- $validator = require $validator_path;
10302
- if ( ! is_object( $validator ) || ! method_exists( $validator, 'apply_defaults' ) || ! method_exists( $validator, 'validate' ) ) {
10303
- return '';
10304
- }
10305
-
10306
- $normalized = $validator->apply_defaults( is_array( $attributes ) ? $attributes : array() );
10307
- $validation = $validator->validate( $normalized );
10308
- $resource_key = isset( $normalized['resourceKey'] ) ? (string) $normalized['resourceKey'] : '';
10309
- $heading = isset( $normalized['heading'] ) ? (string) $normalized['heading'] : {{titlePhpLiteral}};
10310
- $intro = isset( $normalized['intro'] ) ? (string) $normalized['intro'] : '';
10311
- $button_label = isset( $normalized['buttonLabel'] ) ? (string) $normalized['buttonLabel'] : 'Persist Count';
10312
- $show_count = ! empty( $normalized['showCount'] );
10313
- $show_dividers = ! empty( $normalized['showDividers'] );
10314
- $post_id = is_object( $block ) && isset( $block->context['postId'] )
10315
- ? (int) $block->context['postId']
10316
- : (int) get_queried_object_id();
10317
- $storage_mode = '{{dataStorageMode}}';
10318
- $persistence_policy = '{{persistencePolicy}}';
10319
-
10320
- $notice_message = 'authenticated' === $persistence_policy
10321
- ? __( 'Sign in to persist this counter.', '{{textDomain}}' )
10322
- : __( 'Public writes are temporarily unavailable.', '{{textDomain}}' );
10323
-
10324
- if ( empty( $validation['valid'] ) || '' === $resource_key ) {
10325
- return '';
10326
- }
10327
-
10328
- {{phpPrefix}}_record_rendered_block_instance(
10329
- (int) $post_id,
10330
- '{{namespace}}/{{slugKebabCase}}',
10331
- $resource_key
10332
- );
10333
-
10334
- $context = array(
10335
- 'bootstrapReady' => false,
10336
- 'buttonLabel' => $button_label,
10337
- 'canWrite' => false,
10338
- 'client' => array(
10339
- 'writeExpiry' => 0,
10340
- 'writeNonce' => '',
10341
- 'writeToken' => '',
10342
- ),
10343
- 'count' => 0,
10344
- 'error' => '',
10345
- 'isBootstrapping' => false,
10346
- 'isLoading' => false,
10347
- 'isSaving' => false,
10348
- 'persistencePolicy' => $persistence_policy,
10349
- 'postId' => (int) $post_id,
10350
- 'resourceKey' => $resource_key,
10351
- 'showCount' => $show_count,
10352
- 'storage' => $storage_mode,
10353
- );
10354
-
10355
- $allowed_inner_html = wp_kses_allowed_html( 'post' );
10356
-
10357
- foreach ( $allowed_inner_html as &$allowed_attributes ) {
10358
- if ( ! is_array( $allowed_attributes ) ) {
10359
- continue;
10360
- }
10361
-
10362
- $allowed_attributes['data-wp-bind--disabled'] = true;
10363
- $allowed_attributes['data-wp-bind--hidden'] = true;
10364
- $allowed_attributes['data-wp-bind--value'] = true;
10365
- $allowed_attributes['data-wp-class'] = true;
10366
- $allowed_attributes['data-wp-class--active'] = true;
10367
- $allowed_attributes['data-wp-context'] = true;
10368
- $allowed_attributes['data-wp-init'] = true;
10369
- $allowed_attributes['data-wp-interactive'] = true;
10370
- $allowed_attributes['data-wp-on--click'] = true;
10371
- $allowed_attributes['data-wp-on--mouseenter'] = true;
10372
- $allowed_attributes['data-wp-on--mouseleave'] = true;
10373
- $allowed_attributes['data-wp-run--mounted'] = true;
10374
- $allowed_attributes['data-wp-style--width'] = true;
10375
- $allowed_attributes['data-wp-text'] = true;
10376
- }
10377
- unset( $allowed_attributes );
10378
-
10379
- $sanitized_content = wp_kses( $content, $allowed_inner_html );
10380
-
10381
- $wrapper_attributes = get_block_wrapper_attributes(
10382
- array(
10383
- 'class' => '{{cssClassName}}',
10384
- 'data-show-dividers' => $show_dividers ? 'true' : 'false',
10385
- 'data-wp-context' => wp_json_encode( $context ),
10386
- 'data-wp-init' => 'callbacks.init',
10387
- 'data-wp-interactive' => '{{slugKebabCase}}',
10388
- 'data-wp-run--mounted' => 'callbacks.mounted',
10389
- )
10390
- );
10391
- ?>
10392
-
10393
- <div <?php echo $wrapper_attributes; ?>>
10394
- <h3 class="{{cssClassName}}__heading"><?php echo esc_html( $heading ); ?></h3>
10395
- <?php if ( '' !== $intro ) : ?>
10396
- <p class="{{cssClassName}}__intro"><?php echo esc_html( $intro ); ?></p>
10397
- <?php endif; ?>
10398
- <p
10399
- class="{{cssClassName}}__notice"
10400
- data-wp-bind--hidden="!context.bootstrapReady || context.canWrite"
10401
- hidden
10402
- >
10403
- <?php echo esc_html( $notice_message ); ?>
10404
- </p>
10405
- <p
10406
- class="{{cssClassName}}__error"
10407
- role="status"
10408
- aria-live="polite"
10409
- aria-atomic="true"
10410
- data-wp-bind--hidden="!context.error"
10411
- data-wp-text="context.error"
10412
- hidden
10413
- ></p>
10414
- <?php if ( $show_count ) : ?>
10415
- <div class="{{cssClassName}}__counter">
10416
- <span
10417
- class="{{cssClassName}}__count"
10418
- role="status"
10419
- aria-live="polite"
10420
- aria-atomic="true"
10421
- data-wp-text="context.count"
10422
- >0</span>
10423
- <button
10424
- type="button"
10425
- disabled
10426
- data-wp-bind--disabled="!context.canWrite"
10427
- data-wp-on--click="actions.increment"
10428
- >
10429
- <?php echo esc_html( $button_label ); ?>
10430
- </button>
10431
- </div>
10432
- <?php endif; ?>
10433
- <div class="{{cssClassName}}__items">
10434
- <?php echo $sanitized_content; ?>
10435
- </div>
10436
- </div>
10437
- `;
10438
- artifacts.push(renderArtifact(`src/blocks/${variables.slugKebabCase}/render.php`, renderSource, renderView));
10439
- }
10440
- return artifacts;
10441
- }
10442
-
10443
11028
  // ../wp-typia-project-tools/src/runtime/built-in-block-non-ts-artifacts.ts
10444
11029
  function buildBuiltInNonTsArtifacts({
10445
11030
  templateId,
@@ -13642,7 +14227,8 @@ function getTemplateVariables(templateId, answers) {
13642
14227
  blockRuntimePackageVersion,
13643
14228
  blockTypesPackageVersion,
13644
14229
  projectToolsPackageVersion,
13645
- restPackageVersion
14230
+ restPackageVersion,
14231
+ wpTypiaPackageVersion
13646
14232
  } = getPackageVersions();
13647
14233
  const template = isBuiltInTemplateId(templateId) ? getTemplateById(templateId) : null;
13648
14234
  const metadataDefaults = isBuiltInTemplateId(templateId) ? getBuiltInTemplateMetadataDefaults(templateId) : null;
@@ -13698,6 +14284,7 @@ function getTemplateVariables(templateId, answers) {
13698
14284
  hasAlternatePlainTextRenderTarget: "false",
13699
14285
  hasAlternateRenderTargets: "false",
13700
14286
  projectToolsPackageVersion,
14287
+ wpTypiaPackageVersion,
13701
14288
  requiresAtLeast: compatibility.pluginHeader.requiresAtLeast,
13702
14289
  requiresPhp: compatibility.pluginHeader.requiresPhp,
13703
14290
  cssClassName,
@@ -13801,15 +14388,55 @@ function getTemplateVariables(templateId, answers) {
13801
14388
  // ../wp-typia-project-tools/src/runtime/scaffold.ts
13802
14389
  var DATA_STORAGE_MODES = ["post-meta", "custom-table"];
13803
14390
  var PERSISTENCE_POLICIES = ["authenticated", "public"];
14391
+ var CREATE_PROFILE_IDS = ["plugin-qa"];
13804
14392
  function isDataStorageMode(value) {
13805
14393
  return DATA_STORAGE_MODES.includes(value);
13806
14394
  }
13807
14395
  function isPersistencePolicy(value) {
13808
14396
  return PERSISTENCE_POLICIES.includes(value);
13809
14397
  }
14398
+ function isCreateProfileId(value) {
14399
+ return CREATE_PROFILE_IDS.includes(value);
14400
+ }
14401
+ function resolveCreateProfileId(profile) {
14402
+ if (profile === undefined || profile.trim().length === 0) {
14403
+ return;
14404
+ }
14405
+ const normalizedProfile = profile.trim();
14406
+ if (isCreateProfileId(normalizedProfile)) {
14407
+ return normalizedProfile;
14408
+ }
14409
+ throw new Error(`Unknown create profile "${profile}". Expected one of: ${CREATE_PROFILE_IDS.join(", ")}.`);
14410
+ }
13810
14411
  function normalizeTemplateSelection(templateId) {
13811
14412
  return normalizeTemplateLookupId(templateId);
13812
14413
  }
14414
+ function assertCreateProfileTemplateCompatibility({
14415
+ profile,
14416
+ templateId
14417
+ }) {
14418
+ if (!profile) {
14419
+ return;
14420
+ }
14421
+ if (profile === "plugin-qa" && templateId !== OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE) {
14422
+ throw new Error("`--profile plugin-qa` currently supports only the official workspace template. Use `--template workspace` or add QA files later with `wp-typia add integration-env <name> --wp-env --release-zip`.");
14423
+ }
14424
+ }
14425
+ async function applyCreateProfile({
14426
+ profile,
14427
+ projectDir
14428
+ }) {
14429
+ if (profile !== "plugin-qa") {
14430
+ return;
14431
+ }
14432
+ await runAddIntegrationEnvCommand({
14433
+ cwd: projectDir,
14434
+ integrationEnvName: "local-smoke",
14435
+ service: "none",
14436
+ withReleaseZip: true,
14437
+ withWpEnv: true
14438
+ });
14439
+ }
13813
14440
  async function reportScaffoldProgress2(onProgress, event) {
13814
14441
  await onProgress?.(event);
13815
14442
  }
@@ -13824,6 +14451,7 @@ async function scaffoldProject({
13824
14451
  externalLayerId,
13825
14452
  externalLayerSource,
13826
14453
  externalLayerSourceLabel,
14454
+ profile,
13827
14455
  repositoryReference,
13828
14456
  cwd = process.cwd(),
13829
14457
  allowExistingDir = false,
@@ -13838,6 +14466,10 @@ async function scaffoldProject({
13838
14466
  const resolvedTemplateId = normalizeTemplateSelection(templateId);
13839
14467
  const resolvedPackageManager = getPackageManager(packageManager).id;
13840
14468
  const isBuiltInTemplate = isBuiltInTemplateId(resolvedTemplateId);
14469
+ assertCreateProfileTemplateCompatibility({
14470
+ profile,
14471
+ templateId: resolvedTemplateId
14472
+ });
13841
14473
  assertExternalLayerCompositionOptions({
13842
14474
  externalLayerId,
13843
14475
  externalLayerSource
@@ -13946,17 +14578,17 @@ async function scaffoldProject({
13946
14578
  phase: "finalize-project",
13947
14579
  title: "Finalizing scaffold output"
13948
14580
  });
13949
- const readmePath = path17.join(projectDir, "README.md");
14581
+ const readmePath = path20.join(projectDir, "README.md");
13950
14582
  if (!await pathExists(readmePath)) {
13951
- await fsp12.writeFile(readmePath, buildReadme(resolvedTemplateId, variables, resolvedPackageManager, {
14583
+ await fsp14.writeFile(readmePath, buildReadme(resolvedTemplateId, variables, resolvedPackageManager, {
13952
14584
  withMigrationUi: isBuiltInTemplate || isWorkspace ? withMigrationUi : false,
13953
14585
  withTestPreset: isBuiltInTemplate ? withTestPreset : false,
13954
14586
  withWpEnv: isBuiltInTemplate ? withWpEnv : false
13955
14587
  }), "utf8");
13956
14588
  }
13957
- const gitignorePath = path17.join(projectDir, ".gitignore");
13958
- const existingGitignore = await pathExists(gitignorePath) ? await fsp12.readFile(gitignorePath, "utf8") : "";
13959
- await fsp12.writeFile(gitignorePath, mergeTextLines(buildGitignore(), existingGitignore), "utf8");
14589
+ const gitignorePath = path20.join(projectDir, ".gitignore");
14590
+ const existingGitignore = await pathExists(gitignorePath) ? await fsp14.readFile(gitignorePath, "utf8") : "";
14591
+ await fsp14.writeFile(gitignorePath, mergeTextLines(buildGitignore(), existingGitignore), "utf8");
13960
14592
  await normalizePackageJson(projectDir, resolvedPackageManager);
13961
14593
  if (isBuiltInTemplate) {
13962
14594
  const variableGroups = getScaffoldTemplateVariableGroups(variables);
@@ -13969,6 +14601,10 @@ async function scaffoldProject({
13969
14601
  withWpEnv
13970
14602
  });
13971
14603
  }
14604
+ await applyCreateProfile({
14605
+ profile,
14606
+ projectDir
14607
+ });
13972
14608
  await normalizePackageManagerFiles(projectDir, resolvedPackageManager);
13973
14609
  await removeUnexpectedLockfiles(projectDir, resolvedPackageManager);
13974
14610
  await replaceTextRecursively(projectDir, resolvedPackageManager, {
@@ -14036,6 +14672,6 @@ async function resolveOptionalInteractiveExternalLayerId({
14036
14672
  }
14037
14673
  }
14038
14674
 
14039
- export { syncPersistenceRestArtifacts, copyInterpolatedDirectory, listInterpolatedDirectoryOutputs, getPrimaryDevelopmentScript, getOptionalOnboardingSteps, getOptionalOnboardingNote, getOptionalOnboardingShortNote, isCompoundPersistenceEnabled, formatNonEmptyTargetDirectoryError, 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 };
14675
+ export { syncPersistenceRestArtifacts, copyInterpolatedDirectory, listInterpolatedDirectoryOutputs, getPrimaryDevelopmentScript, getOptionalOnboardingSteps, getOptionalOnboardingNote, getOptionalOnboardingShortNote, isCompoundPersistenceEnabled, formatNonEmptyTargetDirectoryError, executeWorkspaceMutationPlan, insertPhpSnippetBeforeWorkspaceAnchors, appendPhpSnippetBeforeClosingTag, runAddIntegrationEnvCommand, 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, resolveCreateProfileId, scaffoldProject, resolveOptionalInteractiveExternalLayerId };
14040
14676
 
14041
- //# debugId=9368065E49298D9E64756E2164756E21
14677
+ //# debugId=5D6C751C245E365E64756E2164756E21