@wp-typia/project-tools 0.22.6 → 0.22.8

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 (47) hide show
  1. package/dist/runtime/block-targets.d.ts +40 -0
  2. package/dist/runtime/block-targets.js +71 -0
  3. package/dist/runtime/built-in-block-artifact-types.js +2 -1
  4. package/dist/runtime/built-in-block-attribute-specs.js +2 -1
  5. package/dist/runtime/built-in-block-code-artifacts.js +2 -0
  6. package/dist/runtime/built-in-block-non-ts-family-artifacts.js +12 -9
  7. package/dist/runtime/built-in-block-non-ts-render-utils.js +2 -0
  8. package/dist/runtime/cli-add-block-config.js +2 -1
  9. package/dist/runtime/cli-add-block-json.d.ts +2 -2
  10. package/dist/runtime/cli-add-block-json.js +5 -4
  11. package/dist/runtime/cli-add-block.js +4 -3
  12. package/dist/runtime/cli-add-workspace-ability-scaffold.js +21 -15
  13. package/dist/runtime/cli-add-workspace-ability.js +2 -2
  14. package/dist/runtime/cli-add-workspace-admin-view-scaffold.js +17 -13
  15. package/dist/runtime/cli-add-workspace-admin-view.js +2 -2
  16. package/dist/runtime/cli-add-workspace-ai.js +2 -2
  17. package/dist/runtime/cli-add-workspace-assets.js +42 -48
  18. package/dist/runtime/cli-add-workspace-rest.js +2 -2
  19. package/dist/runtime/cli-add-workspace.js +14 -49
  20. package/dist/runtime/cli-diagnostics.js +6 -0
  21. package/dist/runtime/cli-init-plan-presentation.d.ts +16 -0
  22. package/dist/runtime/cli-init-plan-presentation.js +74 -0
  23. package/dist/runtime/cli-init-plan.js +5 -77
  24. package/dist/runtime/cli-scaffold.js +2 -1
  25. package/dist/runtime/create-template-validation.d.ts +10 -0
  26. package/dist/runtime/create-template-validation.js +121 -0
  27. package/dist/runtime/package-versions.d.ts +1 -1
  28. package/dist/runtime/package-versions.js +16 -3
  29. package/dist/runtime/php-utils.js +151 -148
  30. package/dist/runtime/scaffold-answer-resolution.js +5 -108
  31. package/dist/runtime/scaffold-apply-utils.js +3 -2
  32. package/dist/runtime/scaffold-identifiers.js +4 -3
  33. package/dist/runtime/scaffold-template-assertions.d.ts +6 -0
  34. package/dist/runtime/scaffold-template-assertions.js +33 -0
  35. package/dist/runtime/scaffold-template-variable-groups.d.ts +2 -0
  36. package/dist/runtime/scaffold-template-variable-groups.js +7 -0
  37. package/dist/runtime/string-case.d.ts +2 -4
  38. package/dist/runtime/string-case.js +15 -4
  39. package/dist/runtime/template-source-cache-policy.d.ts +53 -0
  40. package/dist/runtime/template-source-cache-policy.js +135 -0
  41. package/dist/runtime/template-source-cache.d.ts +1 -45
  42. package/dist/runtime/template-source-cache.js +9 -152
  43. package/dist/runtime/ts-property-names.d.ts +11 -0
  44. package/dist/runtime/ts-property-names.js +16 -0
  45. package/dist/runtime/workspace-inventory.d.ts +13 -1
  46. package/dist/runtime/workspace-inventory.js +35 -9
  47. package/package.json +6 -1
@@ -1,3 +1,4 @@
1
+ import { CLI_DIAGNOSTIC_CODES, createCliDiagnosticCodeError, } from "./cli-diagnostics.js";
1
2
  import { toKebabCase, toSnakeCase, } from "./string-case.js";
2
3
  const BLOCK_SLUG_PATTERN = /^[a-z][a-z0-9-]*$/;
3
4
  const PHP_PREFIX_PATTERN = /^[a-z_][a-z0-9_]*$/;
@@ -27,7 +28,7 @@ export function validatePhpPrefix(input) {
27
28
  export function assertValidIdentifier(label, value, validate) {
28
29
  const result = validate(value);
29
30
  if (result !== true) {
30
- throw new Error(typeof result === "string" ? `${label}: ${result}` : `${label} is invalid`);
31
+ throw createCliDiagnosticCodeError(CLI_DIAGNOSTIC_CODES.INVALID_ARGUMENT, typeof result === "string" ? `${label}: ${result}` : `${label} is invalid`);
31
32
  }
32
33
  return value;
33
34
  }
@@ -52,9 +53,9 @@ export function resolveNonEmptyNormalizedBlockSlug(options) {
52
53
  return normalizedSlug;
53
54
  }
54
55
  if (options.input.trim().length === 0) {
55
- throw new Error(`${options.label} is required. Use \`${options.usage}\`.`);
56
+ throw createCliDiagnosticCodeError(CLI_DIAGNOSTIC_CODES.MISSING_ARGUMENT, `${options.label} is required. Use \`${options.usage}\`.`);
56
57
  }
57
- throw new Error(`${options.label} "${options.input.trim()}" normalizes to an empty slug. Use letters or numbers so wp-typia can generate a block slug.`);
58
+ throw createCliDiagnosticCodeError(CLI_DIAGNOSTIC_CODES.INVALID_ARGUMENT, `${options.label} "${options.input.trim()}" normalizes to an empty slug. Use letters or numbers so wp-typia can generate a block slug.`);
58
59
  }
59
60
  export function resolveValidatedBlockSlug(value) {
60
61
  return assertValidIdentifier("Block slug", normalizeBlockSlug(value), validateBlockSlug);
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Revalidates target-language identifiers at the built-in template builder
3
+ * boundary so generated PHP and TypeScript never rely only on upstream CLI
4
+ * normalization.
5
+ */
6
+ export declare function assertScaffoldTemplateCodeIdentifiers(view: Record<string, unknown>): void;
@@ -0,0 +1,33 @@
1
+ const BLOCK_SLUG_PATTERN = /^[a-z][a-z0-9-]*$/u;
2
+ const PHP_IDENTIFIER_PATTERN = /^[a-z_][a-z0-9_]*$/u;
3
+ const PHP_CONSTANT_IDENTIFIER_PATTERN = /^[A-Z_][A-Z0-9_]*$/u;
4
+ const JAVASCRIPT_IDENTIFIER_PATTERN = /^[A-Za-z_$][\w$]*$/u;
5
+ const QUERY_POST_TYPE_PATTERN = /^[a-z0-9_-]{1,20}$/u;
6
+ function assertOptionalStringPattern(view, key, pattern, description) {
7
+ const value = view[key];
8
+ if (typeof value === "undefined") {
9
+ return;
10
+ }
11
+ if (typeof value !== "string" || !pattern.test(value)) {
12
+ throw new Error(`Unsafe scaffold template variable "${key}" for ${description}: ${JSON.stringify(value)}.`);
13
+ }
14
+ }
15
+ /**
16
+ * Revalidates target-language identifiers at the built-in template builder
17
+ * boundary so generated PHP and TypeScript never rely only on upstream CLI
18
+ * normalization.
19
+ */
20
+ export function assertScaffoldTemplateCodeIdentifiers(view) {
21
+ assertOptionalStringPattern(view, "namespace", BLOCK_SLUG_PATTERN, "block namespace");
22
+ assertOptionalStringPattern(view, "slug", BLOCK_SLUG_PATTERN, "block slug");
23
+ assertOptionalStringPattern(view, "slugKebabCase", BLOCK_SLUG_PATTERN, "block slug");
24
+ assertOptionalStringPattern(view, "textDomain", BLOCK_SLUG_PATTERN, "text domain");
25
+ assertOptionalStringPattern(view, "textdomain", BLOCK_SLUG_PATTERN, "text domain");
26
+ assertOptionalStringPattern(view, "queryPostType", QUERY_POST_TYPE_PATTERN, "query post type");
27
+ assertOptionalStringPattern(view, "phpPrefix", PHP_IDENTIFIER_PATTERN, "PHP identifier");
28
+ assertOptionalStringPattern(view, "slugSnakeCase", PHP_IDENTIFIER_PATTERN, "PHP identifier");
29
+ assertOptionalStringPattern(view, "phpPrefixUpper", PHP_CONSTANT_IDENTIFIER_PATTERN, "PHP constant identifier");
30
+ assertOptionalStringPattern(view, "pascalCase", JAVASCRIPT_IDENTIFIER_PATTERN, "JavaScript identifier");
31
+ assertOptionalStringPattern(view, "slugCamelCase", JAVASCRIPT_IDENTIFIER_PATTERN, "JavaScript identifier");
32
+ assertOptionalStringPattern(view, "titleCase", JAVASCRIPT_IDENTIFIER_PATTERN, "JavaScript identifier");
33
+ }
@@ -153,4 +153,6 @@ export type PersistenceScaffoldTemplateVariablesLike = ScaffoldTemplateVariableG
153
153
  };
154
154
  export declare function attachScaffoldTemplateVariableGroups<TVariables extends Record<string, string>>(variables: TVariables, groups: ScaffoldTemplateVariableGroups): TVariables & ScaffoldTemplateVariableGroupsCarrier;
155
155
  export declare function getScaffoldTemplateVariableGroups(variables: ScaffoldTemplateVariableGroupsCarrier): ScaffoldTemplateVariableGroups;
156
+ export declare function isCompoundPersistenceEnabled(variables: ScaffoldTemplateVariableGroupsCarrier): boolean;
157
+ export declare function getScaffoldAlternateRenderTargets(variables: ScaffoldTemplateVariableGroupsCarrier): ScaffoldAlternateRenderTargetVariableGroup;
156
158
  export {};
@@ -11,3 +11,10 @@ export function attachScaffoldTemplateVariableGroups(variables, groups) {
11
11
  export function getScaffoldTemplateVariableGroups(variables) {
12
12
  return variables[SCAFFOLD_TEMPLATE_VARIABLE_GROUPS];
13
13
  }
14
+ export function isCompoundPersistenceEnabled(variables) {
15
+ const compound = getScaffoldTemplateVariableGroups(variables).compound;
16
+ return compound.enabled && compound.persistenceEnabled;
17
+ }
18
+ export function getScaffoldAlternateRenderTargets(variables) {
19
+ return getScaffoldTemplateVariableGroups(variables).alternateRenderTargets;
20
+ }
@@ -1,10 +1,8 @@
1
1
  /**
2
2
  * Normalize arbitrary text into a kebab-case identifier.
3
3
  * Common acronym runs stay grouped, with a boundary before the next
4
- * capitalized word.
5
- * Ambiguous acronym+lowercase inputs like `URLslug` intentionally stay as one
6
- * word because there is no PascalCase boundary marker before the lowercase
7
- * suffix.
4
+ * capitalized word or before a narrow allow-list of lowercase WordPress slug
5
+ * terms. Naturalized words such as `RESTful` intentionally stay as one word.
8
6
  *
9
7
  * @param input Raw text that may contain spaces, punctuation, or camelCase.
10
8
  * @returns A lowercase kebab-case string with collapsed separators.
@@ -1,10 +1,14 @@
1
+ // Keep this list fixed so generated slugs and file paths do not drift when
2
+ // project config changes. Domain-specific acronyms should use separators.
1
3
  const COMMON_ACRONYM_PREFIXES = [
2
4
  'HTML',
3
5
  'HTTP',
4
6
  'JSON',
5
7
  'REST',
6
8
  'UUID',
9
+ 'AJAX',
7
10
  'API',
11
+ 'CPT',
8
12
  'CSS',
9
13
  'CTA',
10
14
  'DOM',
@@ -18,12 +22,18 @@ const COMMON_ACRONYM_PREFIXES = [
18
22
  'UI',
19
23
  'WP',
20
24
  ];
25
+ // Keep lowercase suffix splitting narrow so naturalized words such as
26
+ // `RESTful` remain stable while WordPress slug terms like `URLslug` read well.
27
+ const COMMON_ACRONYM_LOWERCASE_SUFFIXES = ['slug'];
21
28
  function capitalizeSegment(segment) {
22
29
  return segment.charAt(0).toUpperCase() + segment.slice(1);
23
30
  }
24
31
  function findCommonAcronymPrefix(segment) {
25
32
  return COMMON_ACRONYM_PREFIXES.find((prefix) => segment.startsWith(prefix));
26
33
  }
34
+ function isCommonAcronymLowercaseSuffix(suffix) {
35
+ return COMMON_ACRONYM_LOWERCASE_SUFFIXES.includes(suffix);
36
+ }
27
37
  function splitKnownAcronymSegment(segment) {
28
38
  const prefixes = [];
29
39
  let remaining = segment;
@@ -36,6 +46,9 @@ function splitKnownAcronymSegment(segment) {
36
46
  if (/^[A-Z][a-z]/.test(suffix)) {
37
47
  return [...prefixes, prefix, suffix].join('-');
38
48
  }
49
+ if (/^[a-z]+$/.test(suffix) && isCommonAcronymLowercaseSuffix(suffix)) {
50
+ return [...prefixes, prefix, suffix].join('-');
51
+ }
39
52
  if (!findCommonAcronymPrefix(suffix)) {
40
53
  break;
41
54
  }
@@ -50,10 +63,8 @@ function splitAcronymBoundary(value) {
50
63
  /**
51
64
  * Normalize arbitrary text into a kebab-case identifier.
52
65
  * Common acronym runs stay grouped, with a boundary before the next
53
- * capitalized word.
54
- * Ambiguous acronym+lowercase inputs like `URLslug` intentionally stay as one
55
- * word because there is no PascalCase boundary marker before the lowercase
56
- * suffix.
66
+ * capitalized word or before a narrow allow-list of lowercase WordPress slug
67
+ * terms. Naturalized words such as `RESTful` intentionally stay as one word.
57
68
  *
58
69
  * @param input Raw text that may contain spaces, punctuation, or camelCase.
59
70
  * @returns A lowercase kebab-case string with collapsed separators.
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Environment variable that disables external template cache reads and writes.
3
+ *
4
+ * Set to `0`, `false`, `no`, or `off` to bypass the cache.
5
+ */
6
+ export declare const EXTERNAL_TEMPLATE_CACHE_ENV = "WP_TYPIA_EXTERNAL_TEMPLATE_CACHE";
7
+ /**
8
+ * Environment variable that overrides the external template cache root.
9
+ */
10
+ export declare const EXTERNAL_TEMPLATE_CACHE_DIR_ENV = "WP_TYPIA_EXTERNAL_TEMPLATE_CACHE_DIR";
11
+ /**
12
+ * Environment variable that enables TTL-based external template cache pruning.
13
+ *
14
+ * Unset, empty, zero, negative, and non-numeric values keep pruning disabled.
15
+ */
16
+ export declare const EXTERNAL_TEMPLATE_CACHE_TTL_DAYS_ENV = "WP_TYPIA_EXTERNAL_TEMPLATE_CACHE_TTL_DAYS";
17
+ /**
18
+ * Environment variable that overrides how often TTL pruning may scan the cache.
19
+ *
20
+ * Unset values use the default interval. Zero, negative, and non-numeric values
21
+ * disable scan throttling.
22
+ */
23
+ export declare const EXTERNAL_TEMPLATE_CACHE_PRUNE_INTERVAL_MS_ENV = "WP_TYPIA_EXTERNAL_TEMPLATE_CACHE_PRUNE_INTERVAL_MS";
24
+ /**
25
+ * Checks whether remote external template source caching is enabled.
26
+ *
27
+ * Caching is enabled by default. Set `WP_TYPIA_EXTERNAL_TEMPLATE_CACHE` to
28
+ * `0`, `false`, `no`, or `off` to force uncached resolution.
29
+ *
30
+ * @param env Environment object to inspect, defaulting to `process.env`.
31
+ * @returns Whether external template source cache reads and writes are enabled.
32
+ */
33
+ export declare function isExternalTemplateCacheEnabled(env?: NodeJS.ProcessEnv): boolean;
34
+ /**
35
+ * Resolves the external template source cache root directory.
36
+ *
37
+ * `WP_TYPIA_EXTERNAL_TEMPLATE_CACHE_DIR` overrides the location. Without an
38
+ * override, wp-typia uses a per-user `wp-typia-template-source-cache-*`
39
+ * directory inside the operating system temp directory.
40
+ *
41
+ * @param env Environment object to inspect, defaulting to `process.env`.
42
+ * @returns Absolute cache root directory path.
43
+ */
44
+ export declare function getExternalTemplateCacheRoot(env?: NodeJS.ProcessEnv): string;
45
+ export declare function resolveExternalTemplateCacheTtlMs(options?: {
46
+ env?: NodeJS.ProcessEnv;
47
+ ttlDays?: number;
48
+ }): number | null;
49
+ export declare function resolveExternalTemplateCachePruneIntervalMs(options?: {
50
+ env?: NodeJS.ProcessEnv;
51
+ pruneIntervalMs?: number;
52
+ }): number | null;
53
+ export declare function getExternalTemplateCacheNowMs(now: Date | number | undefined): number;
@@ -0,0 +1,135 @@
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
+ /**
4
+ * Environment variable that disables external template cache reads and writes.
5
+ *
6
+ * Set to `0`, `false`, `no`, or `off` to bypass the cache.
7
+ */
8
+ export const EXTERNAL_TEMPLATE_CACHE_ENV = 'WP_TYPIA_EXTERNAL_TEMPLATE_CACHE';
9
+ /**
10
+ * Environment variable that overrides the external template cache root.
11
+ */
12
+ export const EXTERNAL_TEMPLATE_CACHE_DIR_ENV = 'WP_TYPIA_EXTERNAL_TEMPLATE_CACHE_DIR';
13
+ /**
14
+ * Environment variable that enables TTL-based external template cache pruning.
15
+ *
16
+ * Unset, empty, zero, negative, and non-numeric values keep pruning disabled.
17
+ */
18
+ export const EXTERNAL_TEMPLATE_CACHE_TTL_DAYS_ENV = 'WP_TYPIA_EXTERNAL_TEMPLATE_CACHE_TTL_DAYS';
19
+ /**
20
+ * Environment variable that overrides how often TTL pruning may scan the cache.
21
+ *
22
+ * Unset values use the default interval. Zero, negative, and non-numeric values
23
+ * disable scan throttling.
24
+ */
25
+ export const EXTERNAL_TEMPLATE_CACHE_PRUNE_INTERVAL_MS_ENV = 'WP_TYPIA_EXTERNAL_TEMPLATE_CACHE_PRUNE_INTERVAL_MS';
26
+ /**
27
+ * Milliseconds in one TTL day.
28
+ */
29
+ const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
30
+ /**
31
+ * Default minimum interval between full external template cache prune scans.
32
+ */
33
+ const DEFAULT_CACHE_PRUNE_INTERVAL_MS = 60 * 60 * 1000;
34
+ /**
35
+ * Normalized environment values that disable the cache.
36
+ */
37
+ const DISABLED_CACHE_VALUES = new Set(['0', 'false', 'no', 'off']);
38
+ /**
39
+ * Checks whether remote external template source caching is enabled.
40
+ *
41
+ * Caching is enabled by default. Set `WP_TYPIA_EXTERNAL_TEMPLATE_CACHE` to
42
+ * `0`, `false`, `no`, or `off` to force uncached resolution.
43
+ *
44
+ * @param env Environment object to inspect, defaulting to `process.env`.
45
+ * @returns Whether external template source cache reads and writes are enabled.
46
+ */
47
+ export function isExternalTemplateCacheEnabled(env = process.env) {
48
+ const rawValue = env[EXTERNAL_TEMPLATE_CACHE_ENV];
49
+ if (rawValue === undefined) {
50
+ return true;
51
+ }
52
+ return !DISABLED_CACHE_VALUES.has(rawValue.trim().toLowerCase());
53
+ }
54
+ /**
55
+ * Resolves the external template source cache root directory.
56
+ *
57
+ * `WP_TYPIA_EXTERNAL_TEMPLATE_CACHE_DIR` overrides the location. Without an
58
+ * override, wp-typia uses a per-user `wp-typia-template-source-cache-*`
59
+ * directory inside the operating system temp directory.
60
+ *
61
+ * @param env Environment object to inspect, defaulting to `process.env`.
62
+ * @returns Absolute cache root directory path.
63
+ */
64
+ export function getExternalTemplateCacheRoot(env = process.env) {
65
+ const configuredCacheDir = env[EXTERNAL_TEMPLATE_CACHE_DIR_ENV]?.trim();
66
+ if (configuredCacheDir) {
67
+ return path.resolve(configuredCacheDir);
68
+ }
69
+ return path.join(os.tmpdir(), `wp-typia-template-source-cache-${getCurrentUserCacheSegment()}`);
70
+ }
71
+ function parseExternalTemplateCacheTtlDays(value) {
72
+ if (typeof value !== 'string' && typeof value !== 'number') {
73
+ return null;
74
+ }
75
+ const ttlDays = typeof value === 'number' ? value : Number(value.trim());
76
+ if (!Number.isFinite(ttlDays) || ttlDays <= 0) {
77
+ return null;
78
+ }
79
+ return ttlDays;
80
+ }
81
+ export function resolveExternalTemplateCacheTtlMs(options = {}) {
82
+ const env = options.env ?? process.env;
83
+ const ttlDays = options.ttlDays === undefined
84
+ ? parseExternalTemplateCacheTtlDays(env[EXTERNAL_TEMPLATE_CACHE_TTL_DAYS_ENV])
85
+ : parseExternalTemplateCacheTtlDays(options.ttlDays);
86
+ if (ttlDays === null) {
87
+ return null;
88
+ }
89
+ const ttlMs = ttlDays * MILLISECONDS_PER_DAY;
90
+ return Number.isFinite(ttlMs) ? ttlMs : null;
91
+ }
92
+ function parseExternalTemplateCachePruneIntervalMs(value) {
93
+ if (typeof value !== 'string' && typeof value !== 'number') {
94
+ return null;
95
+ }
96
+ const intervalMs = typeof value === 'number' ? value : Number(value.trim());
97
+ if (!Number.isFinite(intervalMs) || intervalMs <= 0) {
98
+ return null;
99
+ }
100
+ return intervalMs;
101
+ }
102
+ export function resolveExternalTemplateCachePruneIntervalMs(options = {}) {
103
+ if (options.pruneIntervalMs !== undefined) {
104
+ return parseExternalTemplateCachePruneIntervalMs(options.pruneIntervalMs);
105
+ }
106
+ const env = options.env ?? process.env;
107
+ const envValue = env[EXTERNAL_TEMPLATE_CACHE_PRUNE_INTERVAL_MS_ENV];
108
+ if (envValue === undefined) {
109
+ return DEFAULT_CACHE_PRUNE_INTERVAL_MS;
110
+ }
111
+ return parseExternalTemplateCachePruneIntervalMs(envValue);
112
+ }
113
+ export function getExternalTemplateCacheNowMs(now) {
114
+ const nowMs = now instanceof Date
115
+ ? now.getTime()
116
+ : typeof now === 'number'
117
+ ? now
118
+ : Date.now();
119
+ return Number.isFinite(nowMs) ? nowMs : Date.now();
120
+ }
121
+ function getCurrentUserCacheSegment() {
122
+ if (typeof process.getuid === 'function') {
123
+ return String(process.getuid());
124
+ }
125
+ try {
126
+ const safeUsername = os
127
+ .userInfo()
128
+ .username.trim()
129
+ .replace(/[^A-Za-z0-9._-]+/gu, '-');
130
+ return safeUsername.length > 0 ? safeUsername : 'user';
131
+ }
132
+ catch {
133
+ return 'user';
134
+ }
135
+ }
@@ -1,26 +1,4 @@
1
- /**
2
- * Environment variable that disables external template cache reads and writes.
3
- *
4
- * Set to `0`, `false`, `no`, or `off` to bypass the cache.
5
- */
6
- export declare const EXTERNAL_TEMPLATE_CACHE_ENV = "WP_TYPIA_EXTERNAL_TEMPLATE_CACHE";
7
- /**
8
- * Environment variable that overrides the external template cache root.
9
- */
10
- export declare const EXTERNAL_TEMPLATE_CACHE_DIR_ENV = "WP_TYPIA_EXTERNAL_TEMPLATE_CACHE_DIR";
11
- /**
12
- * Environment variable that enables TTL-based external template cache pruning.
13
- *
14
- * Unset, empty, zero, negative, and non-numeric values keep pruning disabled.
15
- */
16
- export declare const EXTERNAL_TEMPLATE_CACHE_TTL_DAYS_ENV = "WP_TYPIA_EXTERNAL_TEMPLATE_CACHE_TTL_DAYS";
17
- /**
18
- * Environment variable that overrides how often TTL pruning may scan the cache.
19
- *
20
- * Unset values use the default interval. Zero, negative, and non-numeric values
21
- * disable scan throttling.
22
- */
23
- export declare const EXTERNAL_TEMPLATE_CACHE_PRUNE_INTERVAL_MS_ENV = "WP_TYPIA_EXTERNAL_TEMPLATE_CACHE_PRUNE_INTERVAL_MS";
1
+ 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';
24
2
  /**
25
3
  * Serializable metadata recorded in the cache marker for diagnostics.
26
4
  */
@@ -125,27 +103,6 @@ export interface ExternalTemplateCachePruneResult {
125
103
  */
126
104
  ttlMs: number | null;
127
105
  }
128
- /**
129
- * Checks whether remote external template source caching is enabled.
130
- *
131
- * Caching is enabled by default. Set `WP_TYPIA_EXTERNAL_TEMPLATE_CACHE` to
132
- * `0`, `false`, `no`, or `off` to force uncached resolution.
133
- *
134
- * @param env Environment object to inspect, defaulting to `process.env`.
135
- * @returns Whether external template source cache reads and writes are enabled.
136
- */
137
- export declare function isExternalTemplateCacheEnabled(env?: NodeJS.ProcessEnv): boolean;
138
- /**
139
- * Resolves the external template source cache root directory.
140
- *
141
- * `WP_TYPIA_EXTERNAL_TEMPLATE_CACHE_DIR` overrides the location. Without an
142
- * override, wp-typia uses a per-user `wp-typia-template-source-cache-*`
143
- * directory inside the operating system temp directory.
144
- *
145
- * @param env Environment object to inspect, defaulting to `process.env`.
146
- * @returns Absolute cache root directory path.
147
- */
148
- export declare function getExternalTemplateCacheRoot(env?: NodeJS.ProcessEnv): string;
149
106
  /**
150
107
  * Creates a deterministic cache key from source identity and integrity parts.
151
108
  *
@@ -187,4 +144,3 @@ export declare function findReusableExternalTemplateSourceCache(descriptor: Exte
187
144
  * @returns Cache resolution details, or `null` when caching is disabled.
188
145
  */
189
146
  export declare function resolveExternalTemplateSourceCache(descriptor: ExternalTemplateCacheDescriptor, populateSourceDir: (sourceDir: string) => Promise<void>): Promise<ExternalTemplateCacheResolution | null>;
190
- export {};
@@ -1,31 +1,9 @@
1
- import { createHash } from 'node:crypto';
2
- import fs from 'node:fs';
1
+ import { createHash, randomUUID } from 'node:crypto';
3
2
  import { promises as fsp } from 'node:fs';
4
- import os from 'node:os';
5
3
  import path from 'node:path';
6
- /**
7
- * Environment variable that disables external template cache reads and writes.
8
- *
9
- * Set to `0`, `false`, `no`, or `off` to bypass the cache.
10
- */
11
- export const EXTERNAL_TEMPLATE_CACHE_ENV = 'WP_TYPIA_EXTERNAL_TEMPLATE_CACHE';
12
- /**
13
- * Environment variable that overrides the external template cache root.
14
- */
15
- export const EXTERNAL_TEMPLATE_CACHE_DIR_ENV = 'WP_TYPIA_EXTERNAL_TEMPLATE_CACHE_DIR';
16
- /**
17
- * Environment variable that enables TTL-based external template cache pruning.
18
- *
19
- * Unset, empty, zero, negative, and non-numeric values keep pruning disabled.
20
- */
21
- export const EXTERNAL_TEMPLATE_CACHE_TTL_DAYS_ENV = 'WP_TYPIA_EXTERNAL_TEMPLATE_CACHE_TTL_DAYS';
22
- /**
23
- * Environment variable that overrides how often TTL pruning may scan the cache.
24
- *
25
- * Unset values use the default interval. Zero, negative, and non-numeric values
26
- * disable scan throttling.
27
- */
28
- export const EXTERNAL_TEMPLATE_CACHE_PRUNE_INTERVAL_MS_ENV = 'WP_TYPIA_EXTERNAL_TEMPLATE_CACHE_PRUNE_INTERVAL_MS';
4
+ import { getNodeErrorCode, pathExists } from './fs-async.js';
5
+ import { getExternalTemplateCacheNowMs, getExternalTemplateCacheRoot, isExternalTemplateCacheEnabled, resolveExternalTemplateCachePruneIntervalMs, resolveExternalTemplateCacheTtlMs, } from './template-source-cache-policy.js';
6
+ 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';
29
7
  /**
30
8
  * Marker file written after a cache entry is fully populated.
31
9
  */
@@ -34,14 +12,6 @@ const CACHE_MARKER_FILE = 'wp-typia-template-cache.json';
34
12
  * Marker file written after a full TTL prune scan completes.
35
13
  */
36
14
  const CACHE_PRUNE_MARKER_FILE = 'wp-typia-template-cache-prune.json';
37
- /**
38
- * Milliseconds in one TTL day.
39
- */
40
- const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
41
- /**
42
- * Default minimum interval between full external template cache prune scans.
43
- */
44
- const DEFAULT_CACHE_PRUNE_INTERVAL_MS = 60 * 60 * 1000;
45
15
  /**
46
16
  * Private directory mode used for cache roots and entries on POSIX platforms.
47
17
  */
@@ -50,10 +20,6 @@ const PRIVATE_CACHE_DIRECTORY_MODE = 0o700;
50
20
  * Marker value used when URL-like metadata cannot be safely normalized.
51
21
  */
52
22
  const REDACTED_CACHE_METADATA_VALUE = '[redacted]';
53
- /**
54
- * Normalized environment values that disable the cache.
55
- */
56
- const DISABLED_CACHE_VALUES = new Set(['0', 'false', 'no', 'off']);
57
23
  /**
58
24
  * Filesystem errors that mean another writer published the same cache entry.
59
25
  */
@@ -80,80 +46,10 @@ const SAFE_CACHE_NAMESPACE_SEGMENT = /^[A-Za-z0-9_.-]+$/u;
80
46
  * Cache entries are deterministic SHA-256 digest directory names.
81
47
  */
82
48
  const SAFE_CACHE_ENTRY_SEGMENT = /^[a-f0-9]{64}$/u;
83
- /**
84
- * Checks whether remote external template source caching is enabled.
85
- *
86
- * Caching is enabled by default. Set `WP_TYPIA_EXTERNAL_TEMPLATE_CACHE` to
87
- * `0`, `false`, `no`, or `off` to force uncached resolution.
88
- *
89
- * @param env Environment object to inspect, defaulting to `process.env`.
90
- * @returns Whether external template source cache reads and writes are enabled.
91
- */
92
- export function isExternalTemplateCacheEnabled(env = process.env) {
93
- const rawValue = env[EXTERNAL_TEMPLATE_CACHE_ENV];
94
- if (rawValue === undefined) {
95
- return true;
96
- }
97
- return !DISABLED_CACHE_VALUES.has(rawValue.trim().toLowerCase());
98
- }
99
- /**
100
- * Resolves the external template source cache root directory.
101
- *
102
- * `WP_TYPIA_EXTERNAL_TEMPLATE_CACHE_DIR` overrides the location. Without an
103
- * override, wp-typia uses a per-user `wp-typia-template-source-cache-*`
104
- * directory inside the operating system temp directory.
105
- *
106
- * @param env Environment object to inspect, defaulting to `process.env`.
107
- * @returns Absolute cache root directory path.
108
- */
109
- export function getExternalTemplateCacheRoot(env = process.env) {
110
- const configuredCacheDir = env[EXTERNAL_TEMPLATE_CACHE_DIR_ENV]?.trim();
111
- if (configuredCacheDir) {
112
- return path.resolve(configuredCacheDir);
113
- }
114
- return path.join(os.tmpdir(), `wp-typia-template-source-cache-${getCurrentUserCacheSegment()}`);
115
- }
116
- function parseExternalTemplateCacheTtlDays(value) {
117
- if (typeof value !== 'string' && typeof value !== 'number') {
118
- return null;
119
- }
120
- const ttlDays = typeof value === 'number' ? value : Number(value.trim());
121
- if (!Number.isFinite(ttlDays) || ttlDays <= 0) {
122
- return null;
123
- }
124
- return ttlDays;
125
- }
126
- function resolveExternalTemplateCacheTtlMs(options = {}) {
127
- const env = options.env ?? process.env;
128
- const ttlDays = options.ttlDays === undefined
129
- ? parseExternalTemplateCacheTtlDays(env[EXTERNAL_TEMPLATE_CACHE_TTL_DAYS_ENV])
130
- : parseExternalTemplateCacheTtlDays(options.ttlDays);
131
- if (ttlDays === null) {
132
- return null;
133
- }
134
- const ttlMs = ttlDays * MILLISECONDS_PER_DAY;
135
- return Number.isFinite(ttlMs) ? ttlMs : null;
136
- }
137
- function parseExternalTemplateCachePruneIntervalMs(value) {
138
- if (typeof value !== 'string' && typeof value !== 'number') {
139
- return null;
140
- }
141
- const intervalMs = typeof value === 'number' ? value : Number(value.trim());
142
- if (!Number.isFinite(intervalMs) || intervalMs <= 0) {
143
- return null;
144
- }
145
- return intervalMs;
146
- }
147
- function resolveExternalTemplateCachePruneIntervalMs(options = {}) {
148
- if (options.pruneIntervalMs !== undefined) {
149
- return parseExternalTemplateCachePruneIntervalMs(options.pruneIntervalMs);
150
- }
151
- const env = options.env ?? process.env;
152
- const envValue = env[EXTERNAL_TEMPLATE_CACHE_PRUNE_INTERVAL_MS_ENV];
153
- if (envValue === undefined) {
154
- return DEFAULT_CACHE_PRUNE_INTERVAL_MS;
155
- }
156
- return parseExternalTemplateCachePruneIntervalMs(envValue);
49
+ function createTemporaryCacheEntryDirName(cacheKey) {
50
+ // Crypto randomness keeps concurrent cache populators from colliding on the
51
+ // staging directory before the final atomic rename publishes the cache entry.
52
+ return `.tmp-${cacheKey}-${process.pid}-${Date.now()}-${randomUUID()}`;
157
53
  }
158
54
  /**
159
55
  * Creates a deterministic cache key from source identity and integrity parts.
@@ -166,15 +62,6 @@ export function createExternalTemplateCacheKey(keyParts) {
166
62
  .update(JSON.stringify(keyParts))
167
63
  .digest('hex');
168
64
  }
169
- async function pathExists(filePath) {
170
- try {
171
- await fsp.access(filePath, fs.constants.F_OK);
172
- return true;
173
- }
174
- catch {
175
- return false;
176
- }
177
- }
178
65
  async function isDirectoryPath(directory) {
179
66
  try {
180
67
  const stats = await fsp.lstat(directory);
@@ -184,11 +71,6 @@ async function isDirectoryPath(directory) {
184
71
  return false;
185
72
  }
186
73
  }
187
- function getNodeErrorCode(error) {
188
- return typeof error === 'object' && error !== null && 'code' in error
189
- ? String(error.code)
190
- : '';
191
- }
192
74
  async function removeTemporaryCacheEntry(entryDir) {
193
75
  try {
194
76
  await fsp.rm(entryDir, { force: true, recursive: true });
@@ -197,21 +79,6 @@ async function removeTemporaryCacheEntry(entryDir) {
197
79
  // Cache cleanup is best-effort; the caller can still continue uncached.
198
80
  }
199
81
  }
200
- function getCurrentUserCacheSegment() {
201
- if (typeof process.getuid === 'function') {
202
- return String(process.getuid());
203
- }
204
- try {
205
- const safeUsername = os
206
- .userInfo()
207
- .username.trim()
208
- .replace(/[^A-Za-z0-9._-]+/gu, '-');
209
- return safeUsername.length > 0 ? safeUsername : 'user';
210
- }
211
- catch {
212
- return 'user';
213
- }
214
- }
215
82
  function getCurrentUid() {
216
83
  return typeof process.getuid === 'function' ? process.getuid() : null;
217
84
  }
@@ -360,14 +227,6 @@ async function readCacheEntryMarker(markerPath) {
360
227
  function cacheMetadataMatches(actual, expected) {
361
228
  return Object.entries(expected).every(([key, value]) => actual[key] === value);
362
229
  }
363
- function getExternalTemplateCacheNowMs(now) {
364
- const nowMs = now instanceof Date
365
- ? now.getTime()
366
- : typeof now === 'number'
367
- ? now
368
- : Date.now();
369
- return Number.isFinite(nowMs) ? nowMs : Date.now();
370
- }
371
230
  function isCacheEntryFreshForTtl(createdAtMs, nowMs, ttlMs) {
372
231
  return ttlMs === null || createdAtMs >= nowMs - ttlMs;
373
232
  }
@@ -673,9 +532,7 @@ export async function resolveExternalTemplateSourceCache(descriptor, populateSou
673
532
  if (existingMarker) {
674
533
  await removeCacheEntryWithinRoot(cacheRoot, entryDir);
675
534
  }
676
- const temporaryEntryDir = path.join(namespaceDir, `.tmp-${cacheKey}-${process.pid}-${Date.now()}-${Math.random()
677
- .toString(16)
678
- .slice(2)}`);
535
+ const temporaryEntryDir = path.join(namespaceDir, createTemporaryCacheEntryDirName(cacheKey));
679
536
  const temporarySourceDir = path.join(temporaryEntryDir, 'source');
680
537
  let populateFailed = false;
681
538
  try {
@@ -0,0 +1,11 @@
1
+ import ts from "typescript";
2
+ /**
3
+ * Extract the literal text for TypeScript property names this runtime supports.
4
+ *
5
+ * Computed property names are intentionally not resolved because their runtime
6
+ * values are not statically knowable from the syntax node alone.
7
+ *
8
+ * @param name TypeScript property name node.
9
+ * @returns Identifier, string-literal, or numeric-literal text; otherwise `null`.
10
+ */
11
+ export declare function getPropertyNameText(name: ts.PropertyName): string | null;
@@ -0,0 +1,16 @@
1
+ import ts from "typescript";
2
+ /**
3
+ * Extract the literal text for TypeScript property names this runtime supports.
4
+ *
5
+ * Computed property names are intentionally not resolved because their runtime
6
+ * values are not statically knowable from the syntax node alone.
7
+ *
8
+ * @param name TypeScript property name node.
9
+ * @returns Identifier, string-literal, or numeric-literal text; otherwise `null`.
10
+ */
11
+ export function getPropertyNameText(name) {
12
+ if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
13
+ return name.text;
14
+ }
15
+ return null;
16
+ }