@wp-typia/project-tools 0.22.10 → 0.23.1

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 (155) hide show
  1. package/dist/runtime/ai-feature-artifacts.js +4 -1
  2. package/dist/runtime/block-generator-service-spec.js +2 -1
  3. package/dist/runtime/cli-add-block-json.js +5 -1
  4. package/dist/runtime/cli-add-collision.d.ts +25 -0
  5. package/dist/runtime/cli-add-collision.js +76 -0
  6. package/dist/runtime/cli-add-help.js +12 -2
  7. package/dist/runtime/cli-add-kind-ids.d.ts +1 -1
  8. package/dist/runtime/cli-add-kind-ids.js +3 -0
  9. package/dist/runtime/cli-add-types.d.ts +129 -0
  10. package/dist/runtime/cli-add-types.js +26 -0
  11. package/dist/runtime/cli-add-validation.d.ts +97 -1
  12. package/dist/runtime/cli-add-validation.js +313 -1
  13. package/dist/runtime/cli-add-workspace-ability-scaffold.js +4 -1
  14. package/dist/runtime/cli-add-workspace-admin-view-scaffold.js +79 -20
  15. package/dist/runtime/cli-add-workspace-admin-view-source.js +11 -2
  16. package/dist/runtime/cli-add-workspace-admin-view-templates-core-data.d.ts +34 -0
  17. package/dist/runtime/cli-add-workspace-admin-view-templates-core-data.js +483 -0
  18. package/dist/runtime/cli-add-workspace-admin-view-templates-default.d.ts +30 -0
  19. package/dist/runtime/cli-add-workspace-admin-view-templates-default.js +310 -0
  20. package/dist/runtime/cli-add-workspace-admin-view-templates-rest.d.ts +25 -0
  21. package/dist/runtime/cli-add-workspace-admin-view-templates-rest.js +124 -0
  22. package/dist/runtime/cli-add-workspace-admin-view-templates-settings.d.ts +34 -0
  23. package/dist/runtime/cli-add-workspace-admin-view-templates-settings.js +370 -0
  24. package/dist/runtime/cli-add-workspace-admin-view-templates-shared.d.ts +49 -0
  25. package/dist/runtime/cli-add-workspace-admin-view-templates-shared.js +259 -0
  26. package/dist/runtime/cli-add-workspace-admin-view-templates.d.ts +19 -10
  27. package/dist/runtime/cli-add-workspace-admin-view-templates.js +31 -971
  28. package/dist/runtime/cli-add-workspace-admin-view-types.d.ts +21 -0
  29. package/dist/runtime/cli-add-workspace-admin-view-types.js +22 -0
  30. package/dist/runtime/cli-add-workspace-ai-anchors.js +125 -32
  31. package/dist/runtime/cli-add-workspace-ai-source-emitters.js +17 -1
  32. package/dist/runtime/cli-add-workspace-contract-source-emitters.d.ts +15 -0
  33. package/dist/runtime/cli-add-workspace-contract-source-emitters.js +42 -0
  34. package/dist/runtime/cli-add-workspace-contract.d.ts +15 -0
  35. package/dist/runtime/cli-add-workspace-contract.js +65 -0
  36. package/dist/runtime/cli-add-workspace-integration-env.d.ts +26 -0
  37. package/dist/runtime/cli-add-workspace-integration-env.js +428 -0
  38. package/dist/runtime/cli-add-workspace-post-meta-anchors.d.ts +23 -0
  39. package/dist/runtime/cli-add-workspace-post-meta-anchors.js +244 -0
  40. package/dist/runtime/cli-add-workspace-post-meta-source-emitters.d.ts +63 -0
  41. package/dist/runtime/cli-add-workspace-post-meta-source-emitters.js +179 -0
  42. package/dist/runtime/cli-add-workspace-post-meta.d.ts +15 -0
  43. package/dist/runtime/cli-add-workspace-post-meta.js +107 -0
  44. package/dist/runtime/cli-add-workspace-rest-anchors.d.ts +9 -0
  45. package/dist/runtime/cli-add-workspace-rest-anchors.js +326 -21
  46. package/dist/runtime/cli-add-workspace-rest-generated.d.ts +9 -0
  47. package/dist/runtime/cli-add-workspace-rest-generated.js +158 -0
  48. package/dist/runtime/cli-add-workspace-rest-manual.d.ts +8 -0
  49. package/dist/runtime/cli-add-workspace-rest-manual.js +279 -0
  50. package/dist/runtime/cli-add-workspace-rest-php-templates.d.ts +24 -0
  51. package/dist/runtime/cli-add-workspace-rest-php-templates.js +678 -0
  52. package/dist/runtime/cli-add-workspace-rest-source-emitters.d.ts +98 -2
  53. package/dist/runtime/cli-add-workspace-rest-source-emitters.js +323 -29
  54. package/dist/runtime/cli-add-workspace-rest-types.d.ts +108 -0
  55. package/dist/runtime/cli-add-workspace-rest-types.js +1 -0
  56. package/dist/runtime/cli-add-workspace-rest.d.ts +3 -7
  57. package/dist/runtime/cli-add-workspace-rest.js +34 -481
  58. package/dist/runtime/cli-add-workspace.d.ts +15 -0
  59. package/dist/runtime/cli-add-workspace.js +15 -0
  60. package/dist/runtime/cli-add.d.ts +1 -1
  61. package/dist/runtime/cli-add.js +1 -1
  62. package/dist/runtime/cli-core.d.ts +3 -2
  63. package/dist/runtime/cli-core.js +3 -2
  64. package/dist/runtime/cli-diagnostics.d.ts +3 -1
  65. package/dist/runtime/cli-diagnostics.js +17 -5
  66. package/dist/runtime/cli-doctor-environment.js +1 -3
  67. package/dist/runtime/cli-doctor-workspace-bindings.js +4 -1
  68. package/dist/runtime/cli-doctor-workspace-block-addons.d.ts +12 -0
  69. package/dist/runtime/cli-doctor-workspace-block-addons.js +134 -0
  70. package/dist/runtime/cli-doctor-workspace-block-iframe.d.ts +9 -0
  71. package/dist/runtime/cli-doctor-workspace-block-iframe.js +228 -0
  72. package/dist/runtime/cli-doctor-workspace-block-metadata.d.ts +11 -0
  73. package/dist/runtime/cli-doctor-workspace-block-metadata.js +111 -0
  74. package/dist/runtime/cli-doctor-workspace-blocks.js +6 -424
  75. package/dist/runtime/cli-doctor-workspace-features-abilities.d.ts +11 -0
  76. package/dist/runtime/cli-doctor-workspace-features-abilities.js +112 -0
  77. package/dist/runtime/cli-doctor-workspace-features-admin-views.d.ts +11 -0
  78. package/dist/runtime/cli-doctor-workspace-features-admin-views.js +128 -0
  79. package/dist/runtime/cli-doctor-workspace-features-ai.d.ts +11 -0
  80. package/dist/runtime/cli-doctor-workspace-features-ai.js +57 -0
  81. package/dist/runtime/cli-doctor-workspace-features-editor-plugins.d.ts +11 -0
  82. package/dist/runtime/cli-doctor-workspace-features-editor-plugins.js +80 -0
  83. package/dist/runtime/cli-doctor-workspace-features-post-meta.d.ts +11 -0
  84. package/dist/runtime/cli-doctor-workspace-features-post-meta.js +77 -0
  85. package/dist/runtime/cli-doctor-workspace-features-rest.d.ts +11 -0
  86. package/dist/runtime/cli-doctor-workspace-features-rest.js +120 -0
  87. package/dist/runtime/cli-doctor-workspace-features.js +14 -369
  88. package/dist/runtime/cli-doctor-workspace-package.d.ts +25 -3
  89. package/dist/runtime/cli-doctor-workspace-package.js +35 -13
  90. package/dist/runtime/cli-doctor-workspace-shared.d.ts +2 -0
  91. package/dist/runtime/cli-doctor-workspace-shared.js +2 -0
  92. package/dist/runtime/cli-doctor-workspace.js +8 -3
  93. package/dist/runtime/cli-doctor.d.ts +52 -3
  94. package/dist/runtime/cli-doctor.js +79 -8
  95. package/dist/runtime/cli-help.js +10 -0
  96. package/dist/runtime/cli-init-package-json.js +4 -2
  97. package/dist/runtime/cli-init-templates.js +11 -1
  98. package/dist/runtime/cli-prompt.d.ts +16 -2
  99. package/dist/runtime/cli-prompt.js +29 -12
  100. package/dist/runtime/cli-scaffold.d.ts +2 -1
  101. package/dist/runtime/cli-scaffold.js +19 -10
  102. package/dist/runtime/contract-artifacts.d.ts +14 -0
  103. package/dist/runtime/contract-artifacts.js +15 -0
  104. package/dist/runtime/external-template-guards.js +4 -6
  105. package/dist/runtime/index.d.ts +2 -2
  106. package/dist/runtime/index.js +1 -1
  107. package/dist/runtime/json-utils.d.ts +62 -4
  108. package/dist/runtime/json-utils.js +78 -4
  109. package/dist/runtime/local-dev-presets.js +4 -1
  110. package/dist/runtime/migration-ui-capability.js +4 -1
  111. package/dist/runtime/migration-utils.js +4 -1
  112. package/dist/runtime/package-managers.js +6 -1
  113. package/dist/runtime/package-versions.js +6 -1
  114. package/dist/runtime/rest-resource-artifacts.d.ts +57 -1
  115. package/dist/runtime/rest-resource-artifacts.js +97 -1
  116. package/dist/runtime/scaffold-bootstrap.js +7 -2
  117. package/dist/runtime/scaffold-package-manager-files.js +5 -1
  118. package/dist/runtime/scaffold-repository-reference.js +4 -2
  119. package/dist/runtime/scaffold-template-variables.js +2 -1
  120. package/dist/runtime/scaffold.d.ts +18 -1
  121. package/dist/runtime/scaffold.js +55 -2
  122. package/dist/runtime/temp-roots.js +4 -1
  123. package/dist/runtime/template-layers.js +4 -1
  124. package/dist/runtime/template-registry.js +9 -3
  125. package/dist/runtime/template-render.d.ts +1 -1
  126. package/dist/runtime/template-render.js +1 -1
  127. package/dist/runtime/template-source-cache-markers.d.ts +37 -0
  128. package/dist/runtime/template-source-cache-markers.js +125 -0
  129. package/dist/runtime/template-source-cache.d.ts +1 -4
  130. package/dist/runtime/template-source-cache.js +16 -122
  131. package/dist/runtime/template-source-contracts.d.ts +2 -0
  132. package/dist/runtime/template-source-external.d.ts +4 -2
  133. package/dist/runtime/template-source-external.js +4 -2
  134. package/dist/runtime/template-source-normalization.js +2 -1
  135. package/dist/runtime/template-source-remote.d.ts +8 -4
  136. package/dist/runtime/template-source-remote.js +26 -9
  137. package/dist/runtime/template-source-seeds.js +10 -3
  138. package/dist/runtime/workspace-inventory-mutations.js +54 -4
  139. package/dist/runtime/workspace-inventory-parser-entries.d.ts +17 -0
  140. package/dist/runtime/workspace-inventory-parser-entries.js +157 -0
  141. package/dist/runtime/workspace-inventory-parser-validation.d.ts +104 -0
  142. package/dist/runtime/workspace-inventory-parser-validation.js +34 -0
  143. package/dist/runtime/workspace-inventory-parser.d.ts +3 -44
  144. package/dist/runtime/workspace-inventory-parser.js +7 -464
  145. package/dist/runtime/workspace-inventory-read.d.ts +9 -2
  146. package/dist/runtime/workspace-inventory-read.js +9 -2
  147. package/dist/runtime/workspace-inventory-section-descriptors.d.ts +19 -0
  148. package/dist/runtime/workspace-inventory-section-descriptors.js +435 -0
  149. package/dist/runtime/workspace-inventory-templates.d.ts +16 -1
  150. package/dist/runtime/workspace-inventory-templates.js +75 -4
  151. package/dist/runtime/workspace-inventory-types.d.ts +52 -2
  152. package/dist/runtime/workspace-inventory.d.ts +2 -2
  153. package/dist/runtime/workspace-inventory.js +1 -1
  154. package/dist/runtime/workspace-project.js +4 -6
  155. package/package.json +2 -2
@@ -3,11 +3,12 @@ import path from "node:path";
3
3
  import { getPackageManager } from "./package-managers.js";
4
4
  import { buildGitignore, buildReadme, mergeTextLines, removeQueryLoopPlaceholderFiles, replaceTextRecursively, } from "./scaffold-apply-utils.js";
5
5
  import { applyGeneratedProjectDxPackageJson, applyLocalDevPresetFiles, } from "./local-dev-presets.js";
6
+ import { runAddIntegrationEnvCommand } from "./cli-add-workspace-integration-env.js";
6
7
  import { applyMigrationUiCapability } from "./migration-ui-capability.js";
7
8
  import { applyWorkspaceMigrationCapability, ensureScaffoldDirectory, isWorkspaceProject, seedBuiltInPersistenceArtifacts, writeStarterManifestFiles, } from "./scaffold-bootstrap.js";
8
9
  import { defaultInstallDependencies, normalizePackageJson, normalizePackageManagerFiles, removeUnexpectedLockfiles, } from "./scaffold-package-manager-files.js";
9
10
  import { copyInterpolatedDirectory } from "./template-render.js";
10
- import { isBuiltInTemplateId, normalizeTemplateLookupId, } from "./template-registry.js";
11
+ import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, isBuiltInTemplateId, normalizeTemplateLookupId, } from "./template-registry.js";
11
12
  import { resolveTemplateSource } from "./template-source.js";
12
13
  import { BlockGeneratorService, } from "./block-generator-service.js";
13
14
  import { getTemplateVariables } from "./scaffold-template-variables.js";
@@ -16,6 +17,8 @@ import { assertExternalLayerCompositionOptions, } from "./cli-validation.js";
16
17
  import { pathExists } from "./fs-async.js";
17
18
  export const DATA_STORAGE_MODES = ["post-meta", "custom-table"];
18
19
  export const PERSISTENCE_POLICIES = ["authenticated", "public"];
20
+ /** Supported create profile ids for opt-in workspace scaffold presets. */
21
+ export const CREATE_PROFILE_IDS = ["plugin-qa"];
19
22
  export { buildBlockCssClassName } from "./scaffold-identifiers.js";
20
23
  export { collectScaffoldAnswers, detectAuthor, getDefaultAnswers, resolvePackageManagerId, resolveTemplateId, } from "./scaffold-answer-resolution.js";
21
24
  export { getTemplateVariables } from "./scaffold-template-variables.js";
@@ -26,16 +29,62 @@ export function isDataStorageMode(value) {
26
29
  export function isPersistencePolicy(value) {
27
30
  return PERSISTENCE_POLICIES.includes(value);
28
31
  }
32
+ /**
33
+ * Return whether a raw string is a supported create profile id.
34
+ */
35
+ export function isCreateProfileId(value) {
36
+ return CREATE_PROFILE_IDS.includes(value);
37
+ }
38
+ /**
39
+ * Resolve an optional create profile flag into a validated profile id.
40
+ *
41
+ * Empty input disables profile application; unknown ids throw with the allowed
42
+ * profile list for CLI diagnostics.
43
+ */
44
+ export function resolveCreateProfileId(profile) {
45
+ if (profile === undefined || profile.trim().length === 0) {
46
+ return undefined;
47
+ }
48
+ const normalizedProfile = profile.trim();
49
+ if (isCreateProfileId(normalizedProfile)) {
50
+ return normalizedProfile;
51
+ }
52
+ throw new Error(`Unknown create profile "${profile}". Expected one of: ${CREATE_PROFILE_IDS.join(", ")}.`);
53
+ }
29
54
  function normalizeTemplateSelection(templateId) {
30
55
  return normalizeTemplateLookupId(templateId);
31
56
  }
57
+ function assertCreateProfileTemplateCompatibility({ profile, templateId, }) {
58
+ if (!profile) {
59
+ return;
60
+ }
61
+ if (profile === "plugin-qa" && templateId !== OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE) {
62
+ 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`.");
63
+ }
64
+ }
65
+ async function applyCreateProfile({ profile, projectDir, }) {
66
+ if (profile !== "plugin-qa") {
67
+ return;
68
+ }
69
+ await runAddIntegrationEnvCommand({
70
+ cwd: projectDir,
71
+ integrationEnvName: "local-smoke",
72
+ service: "none",
73
+ withReleaseZip: true,
74
+ withWpEnv: true,
75
+ });
76
+ }
32
77
  async function reportScaffoldProgress(onProgress, event) {
33
78
  await onProgress?.(event);
34
79
  }
35
- export async function scaffoldProject({ projectDir, templateId, answers, alternateRenderTargets, dataStorageMode, persistencePolicy, packageManager, externalLayerId, externalLayerSource, externalLayerSourceLabel, repositoryReference, cwd = process.cwd(), allowExistingDir = false, noInstall = false, installDependencies = undefined, onProgress = undefined, variant, withMigrationUi = false, withTestPreset = false, withWpEnv = false, }) {
80
+ export async function scaffoldProject({ projectDir, templateId, answers, alternateRenderTargets, dataStorageMode, persistencePolicy, packageManager, externalLayerId, externalLayerSource, externalLayerSourceLabel, profile, repositoryReference, cwd = process.cwd(), allowExistingDir = false, noInstall = false, installDependencies = undefined, onProgress = undefined, variant, withMigrationUi = false, withTestPreset = false, withWpEnv = false, }) {
36
81
  const resolvedTemplateId = normalizeTemplateSelection(templateId);
37
82
  const resolvedPackageManager = getPackageManager(packageManager).id;
38
83
  const isBuiltInTemplate = isBuiltInTemplateId(resolvedTemplateId);
84
+ assertCreateProfileTemplateCompatibility({
85
+ profile,
86
+ templateId: resolvedTemplateId,
87
+ });
39
88
  assertExternalLayerCompositionOptions({
40
89
  externalLayerId,
41
90
  externalLayerSource,
@@ -174,6 +223,10 @@ export async function scaffoldProject({ projectDir, templateId, answers, alterna
174
223
  withWpEnv,
175
224
  });
176
225
  }
226
+ await applyCreateProfile({
227
+ profile,
228
+ projectDir,
229
+ });
177
230
  await normalizePackageManagerFiles(projectDir, resolvedPackageManager);
178
231
  await removeUnexpectedLockfiles(projectDir, resolvedPackageManager);
179
232
  await replaceTextRecursively(projectDir, resolvedPackageManager, {
@@ -23,7 +23,10 @@ function cleanupTrackedTempRootsSync() {
23
23
  try {
24
24
  fs.rmSync(tempRoot, { force: true, recursive: true });
25
25
  }
26
- catch { }
26
+ catch {
27
+ // Process-exit cleanup is best-effort: shutdown-time rm failures
28
+ // such as EPERM are intentionally ignored rather than made fatal.
29
+ }
27
30
  }
28
31
  }
29
32
  function installCleanupHandlers() {
@@ -1,6 +1,7 @@
1
1
  import path from "node:path";
2
2
  import { promises as fsp } from "node:fs";
3
3
  import { pathExists } from "./fs-async.js";
4
+ import { readJsonFile } from "./json-utils.js";
4
5
  import { isPlainObject } from "./object-utils.js";
5
6
  import { getBuiltInSharedTemplateLayerDir, isBuiltInSharedTemplateLayerId, } from "./template-builtins.js";
6
7
  import { listInterpolatedDirectoryOutputs } from "./template-render.js";
@@ -59,7 +60,9 @@ export async function loadExternalTemplateLayerManifest(sourceRoot) {
59
60
  if (!(await pathExists(manifestPath))) {
60
61
  return null;
61
62
  }
62
- const raw = JSON.parse(await fsp.readFile(manifestPath, "utf8"));
63
+ const raw = await readJsonFile(manifestPath, {
64
+ context: "template layer manifest",
65
+ });
63
66
  if (!isPlainObject(raw)) {
64
67
  throw new Error(`${TEMPLATE_LAYER_MANIFEST_FILENAME} must export a JSON object.`);
65
68
  }
@@ -1,6 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
+ import { readJsonFileSync } from "./json-utils.js";
4
5
  import { getBuiltInTemplateMetadataDefaults } from "./template-defaults.js";
5
6
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
7
  const PROJECT_TOOLS_PACKAGE_ROOT_ENV = "WP_TYPIA_PROJECT_TOOLS_PACKAGE_ROOT";
@@ -11,7 +12,9 @@ function resolveValidProjectToolsPackageRoot(candidateRoot) {
11
12
  return undefined;
12
13
  }
13
14
  try {
14
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
15
+ const packageJson = readJsonFileSync(packageJsonPath, {
16
+ context: "project-tools package manifest override",
17
+ });
15
18
  return packageJson.name === PROJECT_TOOLS_PACKAGE_NAME
16
19
  ? candidateRoot
17
20
  : undefined;
@@ -42,13 +45,16 @@ export function resolvePackageRoot(startDir) {
42
45
  const packageJsonPath = path.join(currentDir, "package.json");
43
46
  if (fs.existsSync(packageJsonPath)) {
44
47
  try {
45
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
48
+ const packageJson = readJsonFileSync(packageJsonPath, {
49
+ context: "project-tools package root discovery manifest",
50
+ });
46
51
  if (packageJson.name === PROJECT_TOOLS_PACKAGE_NAME) {
47
52
  return currentDir;
48
53
  }
49
54
  }
50
55
  catch {
51
- // Ignore malformed package.json while walking upward.
56
+ // Ignore malformed package.json while walking upward; discovery should
57
+ // keep searching parent directories instead of failing on unrelated files.
52
58
  }
53
59
  }
54
60
  const parentDir = path.dirname(currentDir);
@@ -55,4 +55,4 @@ export declare function copyInterpolatedDirectory(sourceDir: string, targetDir:
55
55
  * preview root.
56
56
  */
57
57
  export declare function listInterpolatedDirectoryOutputs(sourceDir: string, view: Record<string, string>): Promise<string[]>;
58
- export declare function pathExists(targetPath: string): boolean;
58
+ export declare function pathExistsSync(targetPath: string): boolean;
@@ -191,6 +191,6 @@ export async function listInterpolatedDirectoryOutputs(sourceDir, view) {
191
191
  });
192
192
  return outputs.sort((left, right) => left.localeCompare(right));
193
193
  }
194
- export function pathExists(targetPath) {
194
+ export function pathExistsSync(targetPath) {
195
195
  return fs.existsSync(targetPath);
196
196
  }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Marker file written after a cache entry is fully populated.
3
+ */
4
+ export declare const CACHE_MARKER_FILE = "wp-typia-template-cache.json";
5
+ /**
6
+ * Marker file written after a full TTL prune scan completes.
7
+ */
8
+ export declare const CACHE_PRUNE_MARKER_FILE = "wp-typia-template-cache-prune.json";
9
+ /**
10
+ * Serializable metadata recorded in cache markers for diagnostics.
11
+ */
12
+ export type ExternalTemplateCacheMetadata = Record<string, string | null>;
13
+ export interface ExternalTemplateCacheEntryMarker {
14
+ createdAtMs: number;
15
+ metadata: ExternalTemplateCacheMetadata;
16
+ }
17
+ export interface ExternalTemplateCachePruneMarker {
18
+ prunedAtMs: number;
19
+ pruneIntervalMs: number | null;
20
+ ttlMs: number;
21
+ }
22
+ export declare function sanitizeExternalTemplateCacheMetadata(metadata: ExternalTemplateCacheMetadata): ExternalTemplateCacheMetadata;
23
+ export declare function parseExternalTemplateCacheEntryMarker(markerText: string): ExternalTemplateCacheEntryMarker | null;
24
+ export declare function externalTemplateCacheMetadataMatches(actual: ExternalTemplateCacheMetadata, expected: ExternalTemplateCacheMetadata): boolean;
25
+ export declare function isExternalTemplateCacheEntryFreshForTtl(createdAtMs: number, nowMs: number, ttlMs: number | null): boolean;
26
+ export declare function parseExternalTemplateCachePruneMarker(markerText: string): ExternalTemplateCachePruneMarker | null;
27
+ export declare function formatExternalTemplateCacheEntryMarker({ cacheKey, createdAt, metadata, namespace, }: {
28
+ cacheKey: string;
29
+ createdAt: Date;
30
+ metadata: ExternalTemplateCacheMetadata;
31
+ namespace: string;
32
+ }): string;
33
+ export declare function formatExternalTemplateCachePruneMarker({ nowMs, pruneIntervalMs, ttlMs, }: {
34
+ nowMs: number;
35
+ pruneIntervalMs: number | null;
36
+ ttlMs: number;
37
+ }): string;
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Marker file written after a cache entry is fully populated.
3
+ */
4
+ export const CACHE_MARKER_FILE = 'wp-typia-template-cache.json';
5
+ /**
6
+ * Marker file written after a full TTL prune scan completes.
7
+ */
8
+ export const CACHE_PRUNE_MARKER_FILE = 'wp-typia-template-cache-prune.json';
9
+ /**
10
+ * Marker value used when URL-like metadata cannot be safely normalized.
11
+ */
12
+ const REDACTED_CACHE_METADATA_VALUE = '[redacted]';
13
+ /**
14
+ * Metadata fields that may contain credentialed or signed URLs.
15
+ */
16
+ const URL_LIKE_METADATA_KEY = /(url|uri|registry|tarball)/iu;
17
+ function sanitizeExternalTemplateCacheMetadataValue(key, value) {
18
+ if (!URL_LIKE_METADATA_KEY.test(key)) {
19
+ return value;
20
+ }
21
+ try {
22
+ const url = new URL(value);
23
+ url.username = '';
24
+ url.password = '';
25
+ url.search = '';
26
+ url.hash = '';
27
+ return url.toString();
28
+ }
29
+ catch {
30
+ return REDACTED_CACHE_METADATA_VALUE;
31
+ }
32
+ }
33
+ export function sanitizeExternalTemplateCacheMetadata(metadata) {
34
+ return Object.fromEntries(Object.entries(metadata).map(([key, value]) => [
35
+ key,
36
+ value === null
37
+ ? null
38
+ : sanitizeExternalTemplateCacheMetadataValue(key, value),
39
+ ]));
40
+ }
41
+ export function parseExternalTemplateCacheEntryMarker(markerText) {
42
+ let marker;
43
+ try {
44
+ marker = JSON.parse(markerText);
45
+ }
46
+ catch {
47
+ return null;
48
+ }
49
+ if (typeof marker !== 'object' || marker === null || Array.isArray(marker)) {
50
+ return null;
51
+ }
52
+ const rawMetadata = marker.metadata;
53
+ if (typeof rawMetadata !== 'object' ||
54
+ rawMetadata === null ||
55
+ Array.isArray(rawMetadata)) {
56
+ return null;
57
+ }
58
+ const metadata = {};
59
+ for (const [key, value] of Object.entries(rawMetadata)) {
60
+ if (typeof value !== 'string' && value !== null) {
61
+ return null;
62
+ }
63
+ metadata[key] = value;
64
+ }
65
+ const rawCreatedAt = marker.createdAt;
66
+ const createdAtMs = typeof rawCreatedAt === 'string' ? Date.parse(rawCreatedAt) : 0;
67
+ return {
68
+ createdAtMs: Number.isFinite(createdAtMs) ? createdAtMs : 0,
69
+ metadata,
70
+ };
71
+ }
72
+ export function externalTemplateCacheMetadataMatches(actual, expected) {
73
+ return Object.entries(expected).every(([key, value]) => actual[key] === value);
74
+ }
75
+ export function isExternalTemplateCacheEntryFreshForTtl(createdAtMs, nowMs, ttlMs) {
76
+ return ttlMs === null || createdAtMs >= nowMs - ttlMs;
77
+ }
78
+ export function parseExternalTemplateCachePruneMarker(markerText) {
79
+ let marker;
80
+ try {
81
+ marker = JSON.parse(markerText);
82
+ }
83
+ catch {
84
+ return null;
85
+ }
86
+ if (typeof marker !== 'object' || marker === null || Array.isArray(marker)) {
87
+ return null;
88
+ }
89
+ const rawPrunedAt = marker.prunedAt;
90
+ const prunedAtMs = typeof rawPrunedAt === 'string' ? Date.parse(rawPrunedAt) : Number.NaN;
91
+ const rawPruneIntervalMs = marker
92
+ .pruneIntervalMs;
93
+ const rawTtlMs = marker.ttlMs;
94
+ if (typeof rawTtlMs !== 'number' || !Number.isFinite(rawTtlMs)) {
95
+ return null;
96
+ }
97
+ if (!Number.isFinite(prunedAtMs)) {
98
+ return null;
99
+ }
100
+ if (rawPruneIntervalMs !== null &&
101
+ (typeof rawPruneIntervalMs !== 'number' ||
102
+ !Number.isFinite(rawPruneIntervalMs))) {
103
+ return null;
104
+ }
105
+ return {
106
+ prunedAtMs,
107
+ pruneIntervalMs: rawPruneIntervalMs ?? null,
108
+ ttlMs: rawTtlMs,
109
+ };
110
+ }
111
+ export function formatExternalTemplateCacheEntryMarker({ cacheKey, createdAt, metadata, namespace, }) {
112
+ return `${JSON.stringify({
113
+ createdAt: createdAt.toISOString(),
114
+ key: cacheKey,
115
+ metadata: sanitizeExternalTemplateCacheMetadata(metadata),
116
+ namespace,
117
+ }, null, 2)}\n`;
118
+ }
119
+ export function formatExternalTemplateCachePruneMarker({ nowMs, pruneIntervalMs, ttlMs, }) {
120
+ return `${JSON.stringify({
121
+ prunedAt: new Date(nowMs).toISOString(),
122
+ pruneIntervalMs,
123
+ ttlMs,
124
+ }, null, 2)}\n`;
125
+ }
@@ -1,8 +1,5 @@
1
+ import { type ExternalTemplateCacheMetadata } from './template-source-cache-markers.js';
1
2
  export { EXTERNAL_TEMPLATE_CACHE_DIR_ENV, EXTERNAL_TEMPLATE_CACHE_ENV, EXTERNAL_TEMPLATE_CACHE_PRUNE_INTERVAL_MS_ENV, EXTERNAL_TEMPLATE_CACHE_TTL_DAYS_ENV, getExternalTemplateCacheRoot, isExternalTemplateCacheEnabled, } from './template-source-cache-policy.js';
2
- /**
3
- * Serializable metadata recorded in the cache marker for diagnostics.
4
- */
5
- type ExternalTemplateCacheMetadata = Record<string, string | null>;
6
3
  /**
7
4
  * Describes a deterministic external template cache entry.
8
5
  *
@@ -2,24 +2,13 @@ import { createHash, randomUUID } from 'node:crypto';
2
2
  import { promises as fsp } from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { getNodeErrorCode, pathExists } from './fs-async.js';
5
+ import { CACHE_MARKER_FILE, CACHE_PRUNE_MARKER_FILE, externalTemplateCacheMetadataMatches, formatExternalTemplateCacheEntryMarker, formatExternalTemplateCachePruneMarker, isExternalTemplateCacheEntryFreshForTtl, parseExternalTemplateCacheEntryMarker, parseExternalTemplateCachePruneMarker, } from './template-source-cache-markers.js';
5
6
  import { getExternalTemplateCacheNowMs, getExternalTemplateCacheRoot, isExternalTemplateCacheEnabled, resolveExternalTemplateCachePruneIntervalMs, resolveExternalTemplateCacheTtlMs, } from './template-source-cache-policy.js';
6
7
  export { EXTERNAL_TEMPLATE_CACHE_DIR_ENV, EXTERNAL_TEMPLATE_CACHE_ENV, EXTERNAL_TEMPLATE_CACHE_PRUNE_INTERVAL_MS_ENV, EXTERNAL_TEMPLATE_CACHE_TTL_DAYS_ENV, getExternalTemplateCacheRoot, isExternalTemplateCacheEnabled, } from './template-source-cache-policy.js';
7
- /**
8
- * Marker file written after a cache entry is fully populated.
9
- */
10
- const CACHE_MARKER_FILE = 'wp-typia-template-cache.json';
11
- /**
12
- * Marker file written after a full TTL prune scan completes.
13
- */
14
- const CACHE_PRUNE_MARKER_FILE = 'wp-typia-template-cache-prune.json';
15
8
  /**
16
9
  * Private directory mode used for cache roots and entries on POSIX platforms.
17
10
  */
18
11
  const PRIVATE_CACHE_DIRECTORY_MODE = 0o700;
19
- /**
20
- * Marker value used when URL-like metadata cannot be safely normalized.
21
- */
22
- const REDACTED_CACHE_METADATA_VALUE = '[redacted]';
23
12
  /**
24
13
  * Filesystem errors that mean another writer published the same cache entry.
25
14
  */
@@ -34,10 +23,6 @@ const CACHE_UNAVAILABLE_ERROR_CODES = new Set([
34
23
  'EPERM',
35
24
  'EROFS',
36
25
  ]);
37
- /**
38
- * Metadata fields that may contain credentialed or signed URLs.
39
- */
40
- const URL_LIKE_METADATA_KEY = /(url|uri|registry|tarball)/iu;
41
26
  /**
42
27
  * Cache namespaces must stay within one path segment under the cache root.
43
28
  */
@@ -126,28 +111,6 @@ async function ensurePrivateCacheDirectory(directory) {
126
111
  return false;
127
112
  }
128
113
  }
129
- function sanitizeCacheMetadataValue(key, value) {
130
- if (!URL_LIKE_METADATA_KEY.test(key)) {
131
- return value;
132
- }
133
- try {
134
- const url = new URL(value);
135
- url.username = '';
136
- url.password = '';
137
- url.search = '';
138
- url.hash = '';
139
- return url.toString();
140
- }
141
- catch {
142
- return REDACTED_CACHE_METADATA_VALUE;
143
- }
144
- }
145
- function sanitizeCacheMetadata(metadata) {
146
- return Object.fromEntries(Object.entries(metadata).map(([key, value]) => [
147
- key,
148
- value === null ? null : sanitizeCacheMetadataValue(key, value),
149
- ]));
150
- }
151
114
  function resolveCacheNamespaceDir(cacheRoot, namespace) {
152
115
  if (namespace === '.' ||
153
116
  namespace === '..' ||
@@ -185,51 +148,14 @@ async function isReusableCacheEntry(entryDir, markerPath, sourceDir) {
185
148
  (await pathExists(markerPath)) &&
186
149
  (await isDirectoryPath(sourceDir)));
187
150
  }
188
- function parseCacheMarkerMetadata(markerText) {
189
- let marker;
190
- try {
191
- marker = JSON.parse(markerText);
192
- }
193
- catch {
194
- return null;
195
- }
196
- if (typeof marker !== 'object' || marker === null || Array.isArray(marker)) {
197
- return null;
198
- }
199
- const rawMetadata = marker.metadata;
200
- if (typeof rawMetadata !== 'object' ||
201
- rawMetadata === null ||
202
- Array.isArray(rawMetadata)) {
203
- return null;
204
- }
205
- const metadata = {};
206
- for (const [key, value] of Object.entries(rawMetadata)) {
207
- if (typeof value !== 'string' && value !== null) {
208
- return null;
209
- }
210
- metadata[key] = value;
211
- }
212
- const rawCreatedAt = marker.createdAt;
213
- const createdAtMs = typeof rawCreatedAt === 'string' ? Date.parse(rawCreatedAt) : 0;
214
- return {
215
- createdAtMs: Number.isFinite(createdAtMs) ? createdAtMs : 0,
216
- metadata,
217
- };
218
- }
219
151
  async function readCacheEntryMarker(markerPath) {
220
152
  try {
221
- return parseCacheMarkerMetadata(await fsp.readFile(markerPath, 'utf8'));
153
+ return parseExternalTemplateCacheEntryMarker(await fsp.readFile(markerPath, 'utf8'));
222
154
  }
223
155
  catch {
224
156
  return null;
225
157
  }
226
158
  }
227
- function cacheMetadataMatches(actual, expected) {
228
- return Object.entries(expected).every(([key, value]) => actual[key] === value);
229
- }
230
- function isCacheEntryFreshForTtl(createdAtMs, nowMs, ttlMs) {
231
- return ttlMs === null || createdAtMs >= nowMs - ttlMs;
232
- }
233
159
  async function getReusableCacheEntryMarker(entryDir, markerPath, sourceDir) {
234
160
  if (!(await isReusableCacheEntry(entryDir, markerPath, sourceDir))) {
235
161
  return null;
@@ -238,7 +164,8 @@ async function getReusableCacheEntryMarker(entryDir, markerPath, sourceDir) {
238
164
  }
239
165
  async function isReusableFreshCacheEntry(entryDir, markerPath, sourceDir, nowMs, ttlMs) {
240
166
  const marker = await getReusableCacheEntryMarker(entryDir, markerPath, sourceDir);
241
- return (marker !== null && isCacheEntryFreshForTtl(marker.createdAtMs, nowMs, ttlMs));
167
+ return (marker !== null &&
168
+ isExternalTemplateCacheEntryFreshForTtl(marker.createdAtMs, nowMs, ttlMs));
242
169
  }
243
170
  function isPathInsideDirectory(directory, candidatePath) {
244
171
  const relativePath = path.relative(directory, candidatePath);
@@ -261,39 +188,6 @@ async function removeCacheEntryWithinRoot(cacheRoot, entryDir) {
261
188
  function getCachePruneMarkerPath(cacheRoot) {
262
189
  return path.join(cacheRoot, CACHE_PRUNE_MARKER_FILE);
263
190
  }
264
- function parseCachePruneMarker(markerText) {
265
- let marker;
266
- try {
267
- marker = JSON.parse(markerText);
268
- }
269
- catch {
270
- return null;
271
- }
272
- if (typeof marker !== 'object' || marker === null || Array.isArray(marker)) {
273
- return null;
274
- }
275
- const rawPrunedAt = marker.prunedAt;
276
- const prunedAtMs = typeof rawPrunedAt === 'string' ? Date.parse(rawPrunedAt) : Number.NaN;
277
- const rawPruneIntervalMs = marker
278
- .pruneIntervalMs;
279
- const rawTtlMs = marker.ttlMs;
280
- if (typeof rawTtlMs !== 'number' || !Number.isFinite(rawTtlMs)) {
281
- return null;
282
- }
283
- if (!Number.isFinite(prunedAtMs)) {
284
- return null;
285
- }
286
- if (rawPruneIntervalMs !== null &&
287
- (typeof rawPruneIntervalMs !== 'number' ||
288
- !Number.isFinite(rawPruneIntervalMs))) {
289
- return null;
290
- }
291
- return {
292
- prunedAtMs,
293
- pruneIntervalMs: rawPruneIntervalMs ?? null,
294
- ttlMs: rawTtlMs,
295
- };
296
- }
297
191
  async function shouldSkipExternalTemplateCachePrune({ cacheRoot, force, nowMs, pruneIntervalMs, ttlMs, }) {
298
192
  if (force || pruneIntervalMs === null) {
299
193
  return false;
@@ -305,7 +199,7 @@ async function shouldSkipExternalTemplateCachePrune({ cacheRoot, force, nowMs, p
305
199
  catch {
306
200
  return false;
307
201
  }
308
- const marker = parseCachePruneMarker(markerText);
202
+ const marker = parseExternalTemplateCachePruneMarker(markerText);
309
203
  if (!marker ||
310
204
  marker.ttlMs !== ttlMs ||
311
205
  marker.pruneIntervalMs !== pruneIntervalMs) {
@@ -316,11 +210,11 @@ async function shouldSkipExternalTemplateCachePrune({ cacheRoot, force, nowMs, p
316
210
  }
317
211
  async function writeExternalTemplateCachePruneMarker({ cacheRoot, nowMs, pruneIntervalMs, ttlMs, }) {
318
212
  try {
319
- await fsp.writeFile(getCachePruneMarkerPath(cacheRoot), `${JSON.stringify({
320
- prunedAt: new Date(nowMs).toISOString(),
213
+ await fsp.writeFile(getCachePruneMarkerPath(cacheRoot), formatExternalTemplateCachePruneMarker({
214
+ nowMs,
321
215
  pruneIntervalMs,
322
216
  ttlMs,
323
- }, null, 2)}\n`, 'utf8');
217
+ }), 'utf8');
324
218
  }
325
219
  catch {
326
220
  // Prune markers are an optimization; failing to write one keeps TTL safe.
@@ -476,8 +370,8 @@ export async function findReusableExternalTemplateSourceCache(descriptor) {
476
370
  const sourceDir = path.join(entryDir, 'source');
477
371
  const marker = await getReusableCacheEntryMarker(entryDir, markerPath, sourceDir);
478
372
  if (!marker ||
479
- !isCacheEntryFreshForTtl(marker.createdAtMs, nowMs, ttlMs) ||
480
- !cacheMetadataMatches(marker.metadata, descriptor.metadata)) {
373
+ !isExternalTemplateCacheEntryFreshForTtl(marker.createdAtMs, nowMs, ttlMs) ||
374
+ !externalTemplateCacheMetadataMatches(marker.metadata, descriptor.metadata)) {
481
375
  continue;
482
376
  }
483
377
  if (!bestEntry || marker.createdAtMs > bestEntry.createdAtMs) {
@@ -523,7 +417,7 @@ export async function resolveExternalTemplateSourceCache(descriptor, populateSou
523
417
  await pruneExternalTemplateCache();
524
418
  const existingMarker = await getReusableCacheEntryMarker(entryDir, markerPath, sourceDir);
525
419
  if (existingMarker &&
526
- isCacheEntryFreshForTtl(existingMarker.createdAtMs, nowMs, ttlMs)) {
420
+ isExternalTemplateCacheEntryFreshForTtl(existingMarker.createdAtMs, nowMs, ttlMs)) {
527
421
  return {
528
422
  cacheHit: true,
529
423
  sourceDir,
@@ -550,12 +444,12 @@ export async function resolveExternalTemplateSourceCache(descriptor, populateSou
550
444
  populateFailed = true;
551
445
  throw error;
552
446
  }
553
- await fsp.writeFile(path.join(temporaryEntryDir, CACHE_MARKER_FILE), `${JSON.stringify({
554
- createdAt: new Date().toISOString(),
555
- key: cacheKey,
556
- metadata: sanitizeCacheMetadata(descriptor.metadata),
447
+ await fsp.writeFile(path.join(temporaryEntryDir, CACHE_MARKER_FILE), formatExternalTemplateCacheEntryMarker({
448
+ cacheKey,
449
+ createdAt: new Date(),
450
+ metadata: descriptor.metadata,
557
451
  namespace: descriptor.namespace,
558
- }, null, 2)}\n`, 'utf8');
452
+ }), 'utf8');
559
453
  await fsp.rename(temporaryEntryDir, entryDir);
560
454
  return {
561
455
  cacheHit: false,
@@ -11,6 +11,8 @@ export interface TemplateVariableContext extends UnknownRecord {
11
11
  blockRuntimePackageVersion: string;
12
12
  /** Version string for `@wp-typia/block-types` used in generated dependencies. */
13
13
  blockTypesPackageVersion: string;
14
+ /** Version string for `wp-typia` used when templates include the CLI binary locally. */
15
+ wpTypiaPackageVersion: string;
14
16
  /** PascalCase block type name derived from the scaffold slug. */
15
17
  pascalCase: string;
16
18
  /** Snake_case PHP symbol prefix used for generated functions, constants, and keys. */
@@ -7,8 +7,10 @@ export declare const EXTERNAL_TEMPLATE_TRUST_WARNING = "External template config
7
7
  /**
8
8
  * Search a source directory for the first supported external template entry.
9
9
  *
10
- * @deprecated Use `findExternalTemplateEntry()` from async template-source
11
- * paths. This synchronous helper remains only for compatibility callers.
10
+ * @deprecated Since 0.22.10. Use `findExternalTemplateEntry()` from async
11
+ * template-source paths. Removal target: not currently scheduled; this sync
12
+ * compatibility helper remains available until release notes announce a
13
+ * versioned target.
12
14
  *
13
15
  * @param sourceDir Directory that may contain an external template config entry.
14
16
  * @returns The first matching entry path, or null when no supported entry exists.
@@ -33,8 +33,10 @@ function resolveSourceSubpath(sourceDir, relativePath) {
33
33
  /**
34
34
  * Search a source directory for the first supported external template entry.
35
35
  *
36
- * @deprecated Use `findExternalTemplateEntry()` from async template-source
37
- * paths. This synchronous helper remains only for compatibility callers.
36
+ * @deprecated Since 0.22.10. Use `findExternalTemplateEntry()` from async
37
+ * template-source paths. Removal target: not currently scheduled; this sync
38
+ * compatibility helper remains available until release notes announce a
39
+ * versioned target.
38
40
  *
39
41
  * @param sourceDir Directory that may contain an external template config entry.
40
42
  * @returns The first matching entry path, or null when no supported entry exists.
@@ -8,13 +8,14 @@ import { pathExists } from './fs-async.js';
8
8
  export { renderCreateBlockExternalTemplate } from './template-source-external.js';
9
9
  export { getTemplateProjectType, getTemplateProjectTypeAsync, getDefaultCategory, getDefaultCategoryAsync, normalizeCreateBlockSubset, normalizeWpTypiaTemplateSeed, } from './template-source-remote.js';
10
10
  export function getTemplateVariableContext(variables) {
11
- const { apiClientPackageVersion, blockRuntimePackageVersion, blockTypesPackageVersion, projectToolsPackageVersion, restPackageVersion, } = getPackageVersions();
11
+ const { apiClientPackageVersion, blockRuntimePackageVersion, blockTypesPackageVersion, projectToolsPackageVersion, restPackageVersion, wpTypiaPackageVersion, } = getPackageVersions();
12
12
  return {
13
13
  ...variables,
14
14
  apiClientPackageVersion: variables.apiClientPackageVersion ?? apiClientPackageVersion,
15
15
  blockRuntimePackageVersion: variables.blockRuntimePackageVersion ?? blockRuntimePackageVersion,
16
16
  blockTypesPackageVersion: variables.blockTypesPackageVersion ?? blockTypesPackageVersion,
17
17
  projectToolsPackageVersion: variables.projectToolsPackageVersion ?? projectToolsPackageVersion,
18
+ wpTypiaPackageVersion: variables.wpTypiaPackageVersion ?? wpTypiaPackageVersion,
18
19
  description: variables.description,
19
20
  keyword: variables.keyword,
20
21
  namespace: variables.namespace,