@wp-typia/project-tools 0.22.7 → 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 (41) 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.js +4 -3
  10. package/dist/runtime/cli-add-workspace-ability-scaffold.js +21 -15
  11. package/dist/runtime/cli-add-workspace-ability.js +2 -2
  12. package/dist/runtime/cli-add-workspace-admin-view-scaffold.js +17 -13
  13. package/dist/runtime/cli-add-workspace-admin-view.js +2 -2
  14. package/dist/runtime/cli-add-workspace-ai.js +2 -2
  15. package/dist/runtime/cli-add-workspace-assets.js +42 -48
  16. package/dist/runtime/cli-add-workspace-rest.js +2 -2
  17. package/dist/runtime/cli-add-workspace.js +6 -38
  18. package/dist/runtime/cli-diagnostics.js +6 -0
  19. package/dist/runtime/cli-scaffold.js +2 -1
  20. package/dist/runtime/create-template-validation.d.ts +10 -0
  21. package/dist/runtime/create-template-validation.js +121 -0
  22. package/dist/runtime/package-versions.js +15 -2
  23. package/dist/runtime/php-utils.js +66 -0
  24. package/dist/runtime/scaffold-answer-resolution.js +5 -108
  25. package/dist/runtime/scaffold-apply-utils.js +3 -2
  26. package/dist/runtime/scaffold-identifiers.js +4 -3
  27. package/dist/runtime/scaffold-template-assertions.d.ts +6 -0
  28. package/dist/runtime/scaffold-template-assertions.js +33 -0
  29. package/dist/runtime/scaffold-template-variable-groups.d.ts +2 -0
  30. package/dist/runtime/scaffold-template-variable-groups.js +7 -0
  31. package/dist/runtime/string-case.d.ts +2 -4
  32. package/dist/runtime/string-case.js +13 -4
  33. package/dist/runtime/template-source-cache-policy.d.ts +53 -0
  34. package/dist/runtime/template-source-cache-policy.js +135 -0
  35. package/dist/runtime/template-source-cache.d.ts +1 -45
  36. package/dist/runtime/template-source-cache.js +9 -152
  37. package/dist/runtime/ts-property-names.d.ts +11 -0
  38. package/dist/runtime/ts-property-names.js +16 -0
  39. package/dist/runtime/workspace-inventory.d.ts +13 -1
  40. package/dist/runtime/workspace-inventory.js +35 -9
  41. package/package.json +6 -1
@@ -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
+ }
@@ -179,13 +179,25 @@ type WorkspaceInventoryUpdateOptions = {
179
179
  */
180
180
  export declare function parseWorkspaceInventorySource(source: string): Omit<WorkspaceInventory, "blockConfigPath">;
181
181
  /**
182
- * Read and parse the canonical workspace inventory file.
182
+ * Synchronously read and parse the canonical workspace inventory file.
183
+ *
184
+ * This compatibility helper is intentionally sync-only for callers that expose
185
+ * synchronous APIs. Prefer `readWorkspaceInventoryAsync()` from async command
186
+ * paths so workspace reads do not block the event loop.
183
187
  *
184
188
  * @param projectDir Workspace root directory.
185
189
  * @returns Parsed `WorkspaceInventory` including the resolved `blockConfigPath`.
186
190
  * @throws {Error} When `scripts/block-config.ts` is missing or invalid.
187
191
  */
188
192
  export declare function readWorkspaceInventory(projectDir: string): WorkspaceInventory;
193
+ /**
194
+ * Asynchronously read and parse the canonical workspace inventory file.
195
+ *
196
+ * @param projectDir Workspace root directory.
197
+ * @returns Parsed `WorkspaceInventory` including the resolved `blockConfigPath`.
198
+ * @throws {Error} When `scripts/block-config.ts` is missing or invalid.
199
+ */
200
+ export declare function readWorkspaceInventoryAsync(projectDir: string): Promise<WorkspaceInventory>;
189
201
  /**
190
202
  * Return select options for the current workspace block inventory.
191
203
  *
@@ -1,9 +1,10 @@
1
- import fs from "node:fs";
1
+ import { readFileSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import { readFile, writeFile } from "node:fs/promises";
4
4
  import ts from "typescript";
5
5
  import { REST_RESOURCE_METHOD_IDS } from "./cli-add-shared.js";
6
6
  import { escapeRegex } from "./php-utils.js";
7
+ import { getPropertyNameText } from "./ts-property-names.js";
7
8
  function defineInventoryEntryParser(descriptor) {
8
9
  return descriptor;
9
10
  }
@@ -524,12 +525,6 @@ const INVENTORY_SECTIONS = [
524
525
  },
525
526
  },
526
527
  ];
527
- function getPropertyNameText(name) {
528
- if (ts.isIdentifier(name) || ts.isStringLiteral(name)) {
529
- return name.text;
530
- }
531
- return null;
532
- }
533
528
  function findExportedArrayLiteral(sourceFile, exportName) {
534
529
  for (const statement of sourceFile.statements) {
535
530
  if (!ts.isVariableStatement(statement)) {
@@ -705,7 +700,11 @@ export function parseWorkspaceInventorySource(source) {
705
700
  return parsedInventory;
706
701
  }
707
702
  /**
708
- * Read and parse the canonical workspace inventory file.
703
+ * Synchronously read and parse the canonical workspace inventory file.
704
+ *
705
+ * This compatibility helper is intentionally sync-only for callers that expose
706
+ * synchronous APIs. Prefer `readWorkspaceInventoryAsync()` from async command
707
+ * paths so workspace reads do not block the event loop.
709
708
  *
710
709
  * @param projectDir Workspace root directory.
711
710
  * @returns Parsed `WorkspaceInventory` including the resolved `blockConfigPath`.
@@ -715,7 +714,34 @@ export function readWorkspaceInventory(projectDir) {
715
714
  const blockConfigPath = path.join(projectDir, "scripts", "block-config.ts");
716
715
  let source;
717
716
  try {
718
- source = fs.readFileSync(blockConfigPath, "utf8");
717
+ source = readFileSync(blockConfigPath, "utf8");
718
+ }
719
+ catch (error) {
720
+ if (typeof error === "object" &&
721
+ error !== null &&
722
+ "code" in error &&
723
+ error.code === "ENOENT") {
724
+ throw new Error(`Workspace inventory file is missing at ${blockConfigPath}. Expected scripts/block-config.ts to exist.`);
725
+ }
726
+ throw error;
727
+ }
728
+ return {
729
+ blockConfigPath,
730
+ ...parseWorkspaceInventorySource(source),
731
+ };
732
+ }
733
+ /**
734
+ * Asynchronously read and parse the canonical workspace inventory file.
735
+ *
736
+ * @param projectDir Workspace root directory.
737
+ * @returns Parsed `WorkspaceInventory` including the resolved `blockConfigPath`.
738
+ * @throws {Error} When `scripts/block-config.ts` is missing or invalid.
739
+ */
740
+ export async function readWorkspaceInventoryAsync(projectDir) {
741
+ const blockConfigPath = path.join(projectDir, "scripts", "block-config.ts");
742
+ let source;
743
+ try {
744
+ source = await readFile(blockConfigPath, "utf8");
719
745
  }
720
746
  catch (error) {
721
747
  if (typeof error === "object" &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wp-typia/project-tools",
3
- "version": "0.22.7",
3
+ "version": "0.22.8",
4
4
  "description": "Project orchestration and programmatic tooling for wp-typia",
5
5
  "packageManager": "bun@1.3.11",
6
6
  "type": "module",
@@ -47,6 +47,11 @@
47
47
  "import": "./dist/runtime/cli-scaffold.js",
48
48
  "default": "./dist/runtime/cli-scaffold.js"
49
49
  },
50
+ "./create-template-validation": {
51
+ "types": "./dist/runtime/create-template-validation.d.ts",
52
+ "import": "./dist/runtime/create-template-validation.js",
53
+ "default": "./dist/runtime/create-template-validation.js"
54
+ },
50
55
  "./ai-artifacts": {
51
56
  "types": "./dist/runtime/ai-artifacts.d.ts",
52
57
  "import": "./dist/runtime/ai-artifacts.js",