@wp-typia/project-tools 0.20.2 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/runtime/ai-feature-capability.js +2 -33
  2. package/dist/runtime/built-in-block-artifact-types.js +11 -0
  3. package/dist/runtime/built-in-block-code-artifacts.js +5 -1
  4. package/dist/runtime/built-in-block-code-templates/interactivity.d.ts +4 -3
  5. package/dist/runtime/built-in-block-code-templates/interactivity.js +259 -100
  6. package/dist/runtime/built-in-block-code-templates.d.ts +1 -1
  7. package/dist/runtime/built-in-block-code-templates.js +1 -1
  8. package/dist/runtime/cli-add-shared.d.ts +74 -5
  9. package/dist/runtime/cli-add-shared.js +61 -11
  10. package/dist/runtime/cli-add-workspace-ability.js +14 -61
  11. package/dist/runtime/cli-add-workspace-admin-view.d.ts +25 -0
  12. package/dist/runtime/cli-add-workspace-admin-view.js +1401 -0
  13. package/dist/runtime/cli-add-workspace-ai-anchors.js +2 -5
  14. package/dist/runtime/cli-add-workspace-ai-source-emitters.d.ts +0 -4
  15. package/dist/runtime/cli-add-workspace-ai-source-emitters.js +7 -17
  16. package/dist/runtime/cli-add-workspace-ai.js +4 -6
  17. package/dist/runtime/cli-add-workspace-assets.d.ts +13 -5
  18. package/dist/runtime/cli-add-workspace-assets.js +290 -106
  19. package/dist/runtime/cli-add-workspace-rest-anchors.js +2 -5
  20. package/dist/runtime/cli-add-workspace-rest-source-emitters.d.ts +0 -1
  21. package/dist/runtime/cli-add-workspace-rest-source-emitters.js +7 -14
  22. package/dist/runtime/cli-add-workspace-rest.js +4 -6
  23. package/dist/runtime/cli-add-workspace.d.ts +58 -1
  24. package/dist/runtime/cli-add-workspace.js +588 -18
  25. package/dist/runtime/cli-add.d.ts +1 -1
  26. package/dist/runtime/cli-add.js +1 -1
  27. package/dist/runtime/cli-core.d.ts +8 -5
  28. package/dist/runtime/cli-core.js +7 -4
  29. package/dist/runtime/cli-diagnostics.d.ts +83 -1
  30. package/dist/runtime/cli-diagnostics.js +85 -2
  31. package/dist/runtime/cli-doctor-workspace.js +553 -13
  32. package/dist/runtime/cli-doctor.d.ts +4 -2
  33. package/dist/runtime/cli-doctor.js +2 -1
  34. package/dist/runtime/cli-help.js +22 -9
  35. package/dist/runtime/cli-init.d.ts +67 -3
  36. package/dist/runtime/cli-init.js +603 -64
  37. package/dist/runtime/cli-validation.js +4 -3
  38. package/dist/runtime/external-layer-selection.d.ts +8 -2
  39. package/dist/runtime/external-layer-selection.js +3 -4
  40. package/dist/runtime/index.d.ts +9 -4
  41. package/dist/runtime/index.js +7 -3
  42. package/dist/runtime/package-json-types.d.ts +12 -0
  43. package/dist/runtime/package-json-types.js +1 -0
  44. package/dist/runtime/package-versions.d.ts +30 -2
  45. package/dist/runtime/package-versions.js +59 -1
  46. package/dist/runtime/php-utils.d.ts +16 -0
  47. package/dist/runtime/php-utils.js +59 -0
  48. package/dist/runtime/scaffold-answer-resolution.js +7 -6
  49. package/dist/runtime/scaffold-apply-utils.d.ts +2 -3
  50. package/dist/runtime/scaffold-apply-utils.js +3 -43
  51. package/dist/runtime/scaffold-compatibility.d.ts +2 -2
  52. package/dist/runtime/scaffold-compatibility.js +22 -48
  53. package/dist/runtime/template-source-cache.d.ts +112 -0
  54. package/dist/runtime/template-source-cache.js +434 -0
  55. package/dist/runtime/template-source-seeds.js +319 -53
  56. package/dist/runtime/version-floor.d.ts +26 -0
  57. package/dist/runtime/version-floor.js +56 -0
  58. package/dist/runtime/workspace-inventory.d.ts +44 -2
  59. package/dist/runtime/workspace-inventory.js +138 -5
  60. package/package.json +4 -3
@@ -6,6 +6,8 @@ import { spawnSync } from 'node:child_process';
6
6
  import semver from 'semver';
7
7
  import { x as extractTarball } from 'tar';
8
8
  import { createExternalTemplateTimeoutError, fetchWithExternalTemplateTimeout, getExternalTemplateMetadataMaxBytes, getExternalTemplateTarballMaxBytes, getExternalTemplateTimeoutMs, readBufferResponseWithLimit, readJsonResponseWithLimit, } from './external-template-guards.js';
9
+ import { findReusableExternalTemplateSourceCache, isExternalTemplateCacheEnabled, resolveExternalTemplateSourceCache, } from './template-source-cache.js';
10
+ import { CLI_DIAGNOSTIC_CODES, createCliDiagnosticCodeError, } from './cli-diagnostics.js';
9
11
  import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, OFFICIAL_WORKSPACE_TEMPLATE_ALIAS, PROJECT_TOOLS_PACKAGE_ROOT, TEMPLATE_IDS, } from './template-registry.js';
10
12
  import { isPlainObject } from './object-utils.js';
11
13
  import { createManagedTempRoot } from './temp-roots.js';
@@ -13,6 +15,19 @@ const USER_FACING_TEMPLATE_IDS = [
13
15
  ...TEMPLATE_IDS,
14
16
  OFFICIAL_WORKSPACE_TEMPLATE_ALIAS,
15
17
  ];
18
+ const GITHUB_TEMPLATE_CACHE_REVISION_RACE_CODE = 'github-template-cache-revision-race';
19
+ function createGitHubTemplateCacheRevisionRaceError(message) {
20
+ const error = new Error(message);
21
+ error.code = GITHUB_TEMPLATE_CACHE_REVISION_RACE_CODE;
22
+ return error;
23
+ }
24
+ function isGitHubTemplateCacheRevisionRaceError(error) {
25
+ return (typeof error === 'object' &&
26
+ error !== null &&
27
+ 'code' in error &&
28
+ error.code ===
29
+ GITHUB_TEMPLATE_CACHE_REVISION_RACE_CODE);
30
+ }
16
31
  function getUnknownNpmTemplateMessage(templateId) {
17
32
  return [
18
33
  `Unknown template "${templateId}". Expected one of: ${USER_FACING_TEMPLATE_IDS.join(', ')}.`,
@@ -20,6 +35,44 @@ function getUnknownNpmTemplateMessage(templateId) {
20
35
  'If you meant an npm template package, verify the package name and configured npm registry.',
21
36
  ].join(' ');
22
37
  }
38
+ function readOptionalDistString(dist, key) {
39
+ const value = dist[key];
40
+ return typeof value === 'string' && value.length > 0 ? value : null;
41
+ }
42
+ function normalizeNpmRegistryCacheKey(registryBase) {
43
+ try {
44
+ const url = new URL(registryBase);
45
+ url.username = '';
46
+ url.password = '';
47
+ url.search = '';
48
+ url.hash = '';
49
+ return url.toString().replace(/\/$/u, '');
50
+ }
51
+ catch {
52
+ return registryBase;
53
+ }
54
+ }
55
+ async function downloadNpmTemplateTarball(locator, resolvedVersion, tarballUrl, unpackDir) {
56
+ const tarballResponse = await fetchWithExternalTemplateTimeout(tarballUrl, {
57
+ label: `downloading npm template tarball for ${locator.raw}@${resolvedVersion}`,
58
+ });
59
+ if (!tarballResponse.ok) {
60
+ throw new Error(`Failed to download npm template tarball for ${locator.raw}: ${tarballResponse.status}`);
61
+ }
62
+ const tarballPath = path.join(path.dirname(unpackDir), 'template.tgz');
63
+ await fsp.mkdir(unpackDir, { recursive: true });
64
+ await fsp.writeFile(tarballPath, await readBufferResponseWithLimit(tarballResponse, {
65
+ label: `npm template tarball for ${locator.raw}@${resolvedVersion}`,
66
+ maxBytes: getExternalTemplateTarballMaxBytes(),
67
+ }));
68
+ await extractTarball({
69
+ cwd: unpackDir,
70
+ file: tarballPath,
71
+ strip: 1,
72
+ });
73
+ await fsp.rm(tarballPath, { force: true });
74
+ await assertNoSymlinks(unpackDir);
75
+ }
23
76
  function selectRegistryVersion(metadata, locator) {
24
77
  const distTags = isPlainObject(metadata['dist-tags'])
25
78
  ? metadata['dist-tags']
@@ -61,7 +114,7 @@ async function fetchNpmTemplateSource(locator) {
61
114
  });
62
115
  if (!metadataResponse.ok) {
63
116
  if (metadataResponse.status === 404) {
64
- throw new Error(getUnknownNpmTemplateMessage(locator.raw));
117
+ throw createCliDiagnosticCodeError(CLI_DIAGNOSTIC_CODES.UNKNOWN_TEMPLATE, getUnknownNpmTemplateMessage(locator.raw));
65
118
  }
66
119
  throw new Error(`Failed to fetch npm template metadata for ${locator.raw}: ${metadataResponse.status}`);
67
120
  }
@@ -79,27 +132,43 @@ async function fetchNpmTemplateSource(locator) {
79
132
  if (typeof tarballUrl !== 'string' || tarballUrl.length === 0) {
80
133
  throw new Error(`npm template metadata is missing tarball URL for ${locator.raw}@${resolvedVersion}.`);
81
134
  }
135
+ const tarballIntegrity = readOptionalDistString(versionMetadata.dist, 'integrity');
136
+ const tarballShasum = readOptionalDistString(versionMetadata.dist, 'shasum');
137
+ if (tarballIntegrity || tarballShasum) {
138
+ const registryCacheKey = normalizeNpmRegistryCacheKey(registryBase);
139
+ const cachedSource = await resolveExternalTemplateSourceCache({
140
+ keyParts: [
141
+ 'npm',
142
+ registryCacheKey,
143
+ locator.name,
144
+ locator.raw,
145
+ resolvedVersion,
146
+ tarballIntegrity ?? '',
147
+ tarballShasum ?? '',
148
+ ],
149
+ metadata: {
150
+ integrity: tarballIntegrity,
151
+ package: locator.name,
152
+ raw: locator.raw,
153
+ registry: registryBase,
154
+ shasum: tarballShasum,
155
+ tarball: tarballUrl,
156
+ version: resolvedVersion,
157
+ },
158
+ namespace: 'npm',
159
+ }, (unpackDir) => downloadNpmTemplateTarball(locator, resolvedVersion, tarballUrl, unpackDir));
160
+ if (cachedSource) {
161
+ await assertNoSymlinks(cachedSource.sourceDir);
162
+ return {
163
+ blockDir: cachedSource.sourceDir,
164
+ rootDir: cachedSource.sourceDir,
165
+ };
166
+ }
167
+ }
82
168
  const { path: tempRoot, cleanup } = await createManagedTempRoot('wp-typia-template-source-');
83
169
  try {
84
- const tarballResponse = await fetchWithExternalTemplateTimeout(tarballUrl, {
85
- label: `downloading npm template tarball for ${locator.raw}@${resolvedVersion}`,
86
- });
87
- if (!tarballResponse.ok) {
88
- throw new Error(`Failed to download npm template tarball for ${locator.raw}: ${tarballResponse.status}`);
89
- }
90
- const tarballPath = path.join(tempRoot, 'template.tgz');
91
170
  const unpackDir = path.join(tempRoot, 'source');
92
- await fsp.mkdir(unpackDir, { recursive: true });
93
- await fsp.writeFile(tarballPath, await readBufferResponseWithLimit(tarballResponse, {
94
- label: `npm template tarball for ${locator.raw}@${resolvedVersion}`,
95
- maxBytes: getExternalTemplateTarballMaxBytes(),
96
- }));
97
- await extractTarball({
98
- cwd: unpackDir,
99
- file: tarballPath,
100
- strip: 1,
101
- });
102
- await assertNoSymlinks(unpackDir);
171
+ await downloadNpmTemplateTarball(locator, resolvedVersion, tarballUrl, unpackDir);
103
172
  return {
104
173
  blockDir: unpackDir,
105
174
  cleanup,
@@ -200,47 +269,244 @@ export async function assertNoSymlinks(sourceDir) {
200
269
  await assertNoSymlinks(path.join(sourceDir, entry));
201
270
  }
202
271
  }
203
- async function resolveGitHubTemplateSource(locator) {
204
- const { path: remoteRoot, cleanup } = await createManagedTempRoot('wp-typia-template-source-');
205
- const checkoutDir = path.join(remoteRoot, 'source');
206
- try {
207
- const args = ['clone', '--depth', '1'];
208
- if (locator.ref) {
209
- args.push('--branch', locator.ref);
210
- }
211
- args.push(`https://github.com/${locator.owner}/${locator.repo}.git`, checkoutDir);
212
- const cloneTimeoutMs = getExternalTemplateTimeoutMs();
213
- const cloneResult = spawnSync('git', args, {
272
+ function runGitTemplateCommand(args, label, options = {}) {
273
+ const timeoutMs = getExternalTemplateTimeoutMs();
274
+ const result = options.captureOutput
275
+ ? spawnSync('git', args, {
276
+ encoding: 'utf8',
277
+ stdio: ['ignore', 'pipe', 'pipe'],
278
+ timeout: timeoutMs,
279
+ })
280
+ : spawnSync('git', args, {
214
281
  stdio: 'ignore',
215
- timeout: cloneTimeoutMs,
282
+ timeout: timeoutMs,
216
283
  });
217
- if (cloneResult.error) {
218
- const errorCode = typeof cloneResult.error === 'object' &&
219
- cloneResult.error !== null &&
220
- 'code' in cloneResult.error
221
- ? String(cloneResult.error.code)
222
- : '';
223
- if (errorCode === 'ETIMEDOUT') {
224
- throw createExternalTemplateTimeoutError(`cloning GitHub template ${locator.owner}/${locator.repo}`, cloneTimeoutMs);
225
- }
226
- throw cloneResult.error;
284
+ if (result.error) {
285
+ const errorCode = typeof result.error === 'object' &&
286
+ result.error !== null &&
287
+ 'code' in result.error
288
+ ? String(result.error.code)
289
+ : '';
290
+ if (errorCode === 'ETIMEDOUT') {
291
+ throw createExternalTemplateTimeoutError(label, timeoutMs);
227
292
  }
228
- if (cloneResult.signal === 'SIGTERM' ||
229
- cloneResult.signal === 'SIGKILL') {
230
- throw createExternalTemplateTimeoutError(`cloning GitHub template ${locator.owner}/${locator.repo}`, cloneTimeoutMs);
293
+ throw result.error;
294
+ }
295
+ if (result.signal === 'SIGTERM' || result.signal === 'SIGKILL') {
296
+ throw createExternalTemplateTimeoutError(label, timeoutMs);
297
+ }
298
+ return result;
299
+ }
300
+ function getGitHubTemplateRepositoryUrl(locator) {
301
+ return `https://github.com/${locator.owner}/${locator.repo}.git`;
302
+ }
303
+ function resolveGitHubTemplateDirectory(checkoutDir, locator) {
304
+ const sourceDir = path.resolve(checkoutDir, locator.sourcePath);
305
+ const relativeSourceDir = path.relative(checkoutDir, sourceDir);
306
+ if (relativeSourceDir.startsWith('..') || path.isAbsolute(relativeSourceDir)) {
307
+ throw new Error('GitHub template path must stay within the cloned repository.');
308
+ }
309
+ if (!fs.existsSync(sourceDir)) {
310
+ throw new Error(`GitHub template path does not exist: ${locator.sourcePath}`);
311
+ }
312
+ return sourceDir;
313
+ }
314
+ function cloneGitHubTemplateSource(locator, checkoutDir) {
315
+ const args = ['clone', '--depth', '1'];
316
+ if (locator.ref) {
317
+ args.push('--branch', locator.ref);
318
+ }
319
+ args.push(getGitHubTemplateRepositoryUrl(locator), checkoutDir);
320
+ const cloneResult = runGitTemplateCommand(args, `cloning GitHub template ${locator.owner}/${locator.repo}`);
321
+ if (cloneResult.status !== 0) {
322
+ throw new Error(`Failed to clone GitHub template source ${locator.owner}/${locator.repo}.`);
323
+ }
324
+ }
325
+ function readGitHubTemplateHeadRevision(checkoutDir) {
326
+ const result = runGitTemplateCommand(['-C', checkoutDir, 'rev-parse', 'HEAD'], 'reading GitHub template checkout revision', { captureOutput: true });
327
+ if (result.status !== 0 || typeof result.stdout !== 'string') {
328
+ return null;
329
+ }
330
+ const revision = result.stdout.trim().split(/\s+/u)[0];
331
+ return /^[0-9a-f]{40}$/iu.test(revision) ? revision.toLowerCase() : null;
332
+ }
333
+ function pinGitHubTemplateCacheRevision(locator, checkoutDir, cacheRevision) {
334
+ const normalizedCacheRevision = cacheRevision.toLowerCase();
335
+ if (readGitHubTemplateHeadRevision(checkoutDir) === normalizedCacheRevision) {
336
+ return;
337
+ }
338
+ const fetchResult = runGitTemplateCommand(['-C', checkoutDir, 'fetch', '--depth', '1', 'origin', cacheRevision], `fetching GitHub template revision ${locator.owner}/${locator.repo}`);
339
+ if (fetchResult.status !== 0) {
340
+ throw createGitHubTemplateCacheRevisionRaceError(`Failed to fetch GitHub template revision ${cacheRevision} for ${locator.owner}/${locator.repo}.`);
341
+ }
342
+ const checkoutResult = runGitTemplateCommand(['-C', checkoutDir, 'checkout', '--detach', cacheRevision], `checking out GitHub template revision ${locator.owner}/${locator.repo}`);
343
+ if (checkoutResult.status !== 0) {
344
+ throw createGitHubTemplateCacheRevisionRaceError(`Failed to check out GitHub template revision ${cacheRevision} for ${locator.owner}/${locator.repo}.`);
345
+ }
346
+ if (readGitHubTemplateHeadRevision(checkoutDir) !== normalizedCacheRevision) {
347
+ throw createGitHubTemplateCacheRevisionRaceError(`GitHub template checkout did not match resolved revision ${cacheRevision} for ${locator.owner}/${locator.repo}.`);
348
+ }
349
+ }
350
+ function getGitHubTemplateRevisionPatterns(locator) {
351
+ const ref = locator.ref ?? 'HEAD';
352
+ if (!locator.ref) {
353
+ return [ref];
354
+ }
355
+ if (ref.startsWith('refs/')) {
356
+ return [ref, `${ref}^{}`];
357
+ }
358
+ return [ref, `refs/heads/${ref}`, `refs/tags/${ref}`, `refs/tags/${ref}^{}`];
359
+ }
360
+ function pickGitHubTemplateCacheRevision(locator, revisions) {
361
+ const ref = locator.ref ?? 'HEAD';
362
+ if (!locator.ref) {
363
+ return revisions[0]?.revision ?? null;
364
+ }
365
+ if (!ref.startsWith('refs/')) {
366
+ const branchRevision = revisions.find((entry) => entry.resolvedRef === `refs/heads/${ref}`);
367
+ if (branchRevision) {
368
+ return branchRevision.revision;
231
369
  }
232
- if (cloneResult.status !== 0) {
233
- throw new Error(`Failed to clone GitHub template source ${locator.owner}/${locator.repo}.`);
370
+ const peeledTagRevision = revisions.find((entry) => entry.resolvedRef === `refs/tags/${ref}^{}`);
371
+ if (peeledTagRevision) {
372
+ return peeledTagRevision.revision;
234
373
  }
235
- const sourceDir = path.resolve(checkoutDir, locator.sourcePath);
236
- const relativeSourceDir = path.relative(checkoutDir, sourceDir);
237
- if (relativeSourceDir.startsWith('..') ||
238
- path.isAbsolute(relativeSourceDir)) {
239
- throw new Error('GitHub template path must stay within the cloned repository.');
374
+ const tagRevision = revisions.find((entry) => entry.resolvedRef === `refs/tags/${ref}`);
375
+ if (tagRevision) {
376
+ return tagRevision.revision;
240
377
  }
241
- if (!fs.existsSync(sourceDir)) {
242
- throw new Error(`GitHub template path does not exist: ${locator.sourcePath}`);
378
+ }
379
+ if (ref.startsWith('refs/tags/')) {
380
+ const peeledRevision = revisions.find((entry) => entry.resolvedRef === `${ref}^{}`);
381
+ if (peeledRevision) {
382
+ return peeledRevision.revision;
383
+ }
384
+ }
385
+ const exactRevision = revisions.find((entry) => entry.resolvedRef === ref);
386
+ return (exactRevision ?? revisions[0])?.revision ?? null;
387
+ }
388
+ /**
389
+ * Resolves a GitHub template ref to a stable revision for cache keying.
390
+ *
391
+ * @param locator GitHub template locator containing owner, repo, and optional ref.
392
+ * @returns Revision lookup result, including whether `git ls-remote` was unavailable.
393
+ */
394
+ function resolveGitHubTemplateCacheRevision(locator) {
395
+ const result = runGitTemplateCommand([
396
+ 'ls-remote',
397
+ getGitHubTemplateRepositoryUrl(locator),
398
+ ...getGitHubTemplateRevisionPatterns(locator),
399
+ ], `checking GitHub template revision ${locator.owner}/${locator.repo}`, { captureOutput: true });
400
+ if (result.status !== 0 || typeof result.stdout !== 'string') {
401
+ return {
402
+ lookupUnavailable: true,
403
+ revision: null,
404
+ };
405
+ }
406
+ const revisions = result.stdout
407
+ .split('\n')
408
+ .map((line) => {
409
+ const [revision, resolvedRef] = line.trim().split(/\s+/u);
410
+ if (!/^[0-9a-f]{40}$/iu.test(revision) || !resolvedRef) {
411
+ return null;
412
+ }
413
+ return {
414
+ resolvedRef,
415
+ revision: revision.toLowerCase(),
416
+ };
417
+ })
418
+ .filter((entry) => entry !== null);
419
+ return {
420
+ lookupUnavailable: false,
421
+ revision: pickGitHubTemplateCacheRevision(locator, revisions),
422
+ };
423
+ }
424
+ function getGitHubTemplateCacheMetadata(locator, revision) {
425
+ return {
426
+ ...(revision ? { revision } : {}),
427
+ owner: locator.owner,
428
+ ref: locator.ref,
429
+ repo: locator.repo,
430
+ sourcePath: locator.sourcePath,
431
+ };
432
+ }
433
+ async function reuseGitHubTemplateCacheByMetadata(locator) {
434
+ const cachedSource = await findReusableExternalTemplateSourceCache({
435
+ metadata: getGitHubTemplateCacheMetadata(locator),
436
+ namespace: 'github',
437
+ });
438
+ if (!cachedSource) {
439
+ return null;
440
+ }
441
+ const sourceDir = resolveGitHubTemplateDirectory(cachedSource.sourceDir, locator);
442
+ await assertNoSymlinks(sourceDir);
443
+ return {
444
+ blockDir: sourceDir,
445
+ rootDir: sourceDir,
446
+ };
447
+ }
448
+ async function resolveGitHubTemplateSource(locator) {
449
+ const cacheEnabled = isExternalTemplateCacheEnabled();
450
+ let cacheRevisionLookupUnavailable = false;
451
+ let cacheRevision = null;
452
+ if (cacheEnabled) {
453
+ try {
454
+ const resolvedRevision = resolveGitHubTemplateCacheRevision(locator);
455
+ cacheRevision = resolvedRevision.revision;
456
+ cacheRevisionLookupUnavailable = resolvedRevision.lookupUnavailable;
457
+ }
458
+ catch {
459
+ cacheRevision = null;
460
+ cacheRevisionLookupUnavailable = true;
243
461
  }
462
+ }
463
+ if (cacheEnabled && !cacheRevision && cacheRevisionLookupUnavailable) {
464
+ const cachedSource = await reuseGitHubTemplateCacheByMetadata(locator);
465
+ if (cachedSource) {
466
+ return cachedSource;
467
+ }
468
+ }
469
+ if (cacheRevision) {
470
+ const resolvedCacheRevision = cacheRevision;
471
+ try {
472
+ const cachedSource = await resolveExternalTemplateSourceCache({
473
+ keyParts: [
474
+ 'github',
475
+ locator.owner,
476
+ locator.repo,
477
+ locator.sourcePath,
478
+ locator.ref ?? '',
479
+ resolvedCacheRevision,
480
+ ],
481
+ metadata: getGitHubTemplateCacheMetadata(locator, resolvedCacheRevision),
482
+ namespace: 'github',
483
+ }, async (checkoutDir) => {
484
+ cloneGitHubTemplateSource(locator, checkoutDir);
485
+ const sourceDir = resolveGitHubTemplateDirectory(checkoutDir, locator);
486
+ await assertNoSymlinks(sourceDir);
487
+ pinGitHubTemplateCacheRevision(locator, checkoutDir, resolvedCacheRevision);
488
+ });
489
+ if (cachedSource) {
490
+ const sourceDir = resolveGitHubTemplateDirectory(cachedSource.sourceDir, locator);
491
+ await assertNoSymlinks(sourceDir);
492
+ return {
493
+ blockDir: sourceDir,
494
+ rootDir: sourceDir,
495
+ };
496
+ }
497
+ }
498
+ catch (error) {
499
+ if (!isGitHubTemplateCacheRevisionRaceError(error)) {
500
+ throw error;
501
+ }
502
+ // Fall back to the existing uncached clone path if revision pinning races.
503
+ }
504
+ }
505
+ const { path: remoteRoot, cleanup } = await createManagedTempRoot('wp-typia-template-source-');
506
+ const checkoutDir = path.join(remoteRoot, 'source');
507
+ try {
508
+ cloneGitHubTemplateSource(locator, checkoutDir);
509
+ const sourceDir = resolveGitHubTemplateDirectory(checkoutDir, locator);
244
510
  await assertNoSymlinks(sourceDir);
245
511
  return {
246
512
  blockDir: sourceDir,
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Parse a dotted version floor into numeric parts for stable comparisons.
3
+ *
4
+ * @param value Human-authored version floor such as `7.0` or `8.1.2`.
5
+ * @returns Numeric version segments suitable for ordered comparison.
6
+ * @throws When any segment contains non-digit characters.
7
+ */
8
+ export declare function parseVersionFloorParts(value: string): number[];
9
+ /**
10
+ * Compare two dotted version floors.
11
+ *
12
+ * Missing trailing segments are treated as zero so `7.0` equals `7.0.0`.
13
+ *
14
+ * @param left Left-hand version floor.
15
+ * @param right Right-hand version floor.
16
+ * @returns `1` when left is higher, `-1` when right is higher, or `0` when equal.
17
+ */
18
+ export declare function compareVersionFloors(left: string, right: string): number;
19
+ /**
20
+ * Pick the higher version floor while tolerating undefined values.
21
+ *
22
+ * @param current Current resolved floor, when present.
23
+ * @param candidate Candidate floor to compare against the current floor.
24
+ * @returns The higher defined floor, or undefined when neither value exists.
25
+ */
26
+ export declare function pickHigherVersionFloor(current: string | undefined, candidate: string | undefined): string | undefined;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Parse a dotted version floor into numeric parts for stable comparisons.
3
+ *
4
+ * @param value Human-authored version floor such as `7.0` or `8.1.2`.
5
+ * @returns Numeric version segments suitable for ordered comparison.
6
+ * @throws When any segment contains non-digit characters.
7
+ */
8
+ export function parseVersionFloorParts(value) {
9
+ return value.split('.').map((part, index) => {
10
+ if (!/^\d+$/u.test(part)) {
11
+ throw new Error(`parseVersionFloorParts received an invalid version floor "${value}" at segment ${index + 1}.`);
12
+ }
13
+ return Number.parseInt(part, 10);
14
+ });
15
+ }
16
+ /**
17
+ * Compare two dotted version floors.
18
+ *
19
+ * Missing trailing segments are treated as zero so `7.0` equals `7.0.0`.
20
+ *
21
+ * @param left Left-hand version floor.
22
+ * @param right Right-hand version floor.
23
+ * @returns `1` when left is higher, `-1` when right is higher, or `0` when equal.
24
+ */
25
+ export function compareVersionFloors(left, right) {
26
+ const leftParts = parseVersionFloorParts(left);
27
+ const rightParts = parseVersionFloorParts(right);
28
+ const length = Math.max(leftParts.length, rightParts.length);
29
+ for (let index = 0; index < length; index += 1) {
30
+ const leftValue = leftParts[index] ?? 0;
31
+ const rightValue = rightParts[index] ?? 0;
32
+ if (leftValue > rightValue) {
33
+ return 1;
34
+ }
35
+ if (leftValue < rightValue) {
36
+ return -1;
37
+ }
38
+ }
39
+ return 0;
40
+ }
41
+ /**
42
+ * Pick the higher version floor while tolerating undefined values.
43
+ *
44
+ * @param current Current resolved floor, when present.
45
+ * @param candidate Candidate floor to compare against the current floor.
46
+ * @returns The higher defined floor, or undefined when neither value exists.
47
+ */
48
+ export function pickHigherVersionFloor(current, candidate) {
49
+ if (!candidate) {
50
+ return current;
51
+ }
52
+ if (!current) {
53
+ return candidate;
54
+ }
55
+ return compareVersionFloors(current, candidate) >= 0 ? current : candidate;
56
+ }
@@ -10,11 +10,25 @@ export interface WorkspaceVariationInventoryEntry {
10
10
  file: string;
11
11
  slug: string;
12
12
  }
13
+ export interface WorkspaceBlockStyleInventoryEntry {
14
+ block: string;
15
+ file: string;
16
+ slug: string;
17
+ }
18
+ export interface WorkspaceBlockTransformInventoryEntry {
19
+ block: string;
20
+ file: string;
21
+ from: string;
22
+ slug: string;
23
+ to: string;
24
+ }
13
25
  export interface WorkspacePatternInventoryEntry {
14
26
  file: string;
15
27
  slug: string;
16
28
  }
17
29
  export interface WorkspaceBindingSourceInventoryEntry {
30
+ attribute?: string;
31
+ block?: string;
18
32
  editorFile: string;
19
33
  serverFile: string;
20
34
  slug: string;
@@ -76,6 +90,21 @@ export interface WorkspaceAiFeatureInventoryEntry {
76
90
  typesFile: string;
77
91
  validatorsFile: string;
78
92
  }
93
+ /**
94
+ * DataViews admin-screen entry parsed from `scripts/block-config.ts`.
95
+ *
96
+ * @property file Relative path to the generated admin view shared entry file.
97
+ * @property phpFile Relative path to the generated WordPress admin page glue.
98
+ * @property slug Normalized admin view slug.
99
+ * @property source Optional source locator such as `rest-resource:products` or
100
+ * `core-data:postType/post`.
101
+ */
102
+ export interface WorkspaceAdminViewInventoryEntry {
103
+ file: string;
104
+ phpFile: string;
105
+ slug: string;
106
+ source?: string;
107
+ }
79
108
  /**
80
109
  * Editor-plugin entry parsed from `scripts/block-config.ts`.
81
110
  *
@@ -89,14 +118,20 @@ export interface WorkspaceEditorPluginInventoryEntry {
89
118
  slot: string;
90
119
  }
91
120
  export interface WorkspaceInventory {
121
+ adminViews: WorkspaceAdminViewInventoryEntry[];
92
122
  bindingSources: WorkspaceBindingSourceInventoryEntry[];
93
123
  blockConfigPath: string;
94
124
  blocks: WorkspaceBlockInventoryEntry[];
125
+ blockStyles: WorkspaceBlockStyleInventoryEntry[];
126
+ blockTransforms: WorkspaceBlockTransformInventoryEntry[];
95
127
  abilities: WorkspaceAbilityInventoryEntry[];
96
128
  aiFeatures: WorkspaceAiFeatureInventoryEntry[];
97
129
  hasAbilitiesSection: boolean;
130
+ hasAdminViewsSection: boolean;
98
131
  hasBindingSourcesSection: boolean;
99
132
  hasAiFeaturesSection: boolean;
133
+ hasBlockStylesSection: boolean;
134
+ hasBlockTransformsSection: boolean;
100
135
  hasEditorPluginsSection: boolean;
101
136
  hasPatternsSection: boolean;
102
137
  hasRestResourcesSection: boolean;
@@ -109,11 +144,14 @@ export interface WorkspaceInventory {
109
144
  }
110
145
  export declare const BLOCK_CONFIG_ENTRY_MARKER = "\t// wp-typia add block entries";
111
146
  export declare const VARIATION_CONFIG_ENTRY_MARKER = "\t// wp-typia add variation entries";
147
+ export declare const BLOCK_STYLE_CONFIG_ENTRY_MARKER = "\t// wp-typia add style entries";
148
+ export declare const BLOCK_TRANSFORM_CONFIG_ENTRY_MARKER = "\t// wp-typia add transform entries";
112
149
  export declare const PATTERN_CONFIG_ENTRY_MARKER = "\t// wp-typia add pattern entries";
113
150
  export declare const BINDING_SOURCE_CONFIG_ENTRY_MARKER = "\t// wp-typia add binding-source entries";
114
151
  export declare const REST_RESOURCE_CONFIG_ENTRY_MARKER = "\t// wp-typia add rest-resource entries";
115
152
  export declare const ABILITY_CONFIG_ENTRY_MARKER = "\t// wp-typia add ability entries";
116
153
  export declare const AI_FEATURE_CONFIG_ENTRY_MARKER = "\t// wp-typia add ai-feature entries";
154
+ export declare const ADMIN_VIEW_CONFIG_ENTRY_MARKER = "\t// wp-typia add admin-view entries";
117
155
  /**
118
156
  * Marker used to append generated editor-plugin entries into `EDITOR_PLUGINS`.
119
157
  */
@@ -152,7 +190,8 @@ export declare function getWorkspaceBlockSelectOptions(projectDir: string): Arra
152
190
  * Update `scripts/block-config.ts` source text with additional inventory entries.
153
191
  *
154
192
  * Missing inventory sections for variations, patterns, binding sources, REST
155
- * resources, workflow abilities, AI features, and editor plugins are created
193
+ * resources, workflow abilities, AI features, editor plugins, block styles, and
194
+ * block transforms are created
156
195
  * automatically before new entries are appended at their marker comments.
157
196
  * When provided, `transformSource` runs before any entries are inserted.
158
197
  *
@@ -160,10 +199,13 @@ export declare function getWorkspaceBlockSelectOptions(projectDir: string): Arra
160
199
  * @param options Entry lists plus an optional source transformer.
161
200
  * @returns Updated source text with all requested inventory entries appended.
162
201
  */
163
- export declare function updateWorkspaceInventorySource(source: string, { blockEntries, bindingSourceEntries, abilityEntries, aiFeatureEntries, editorPluginEntries, patternEntries, restResourceEntries, variationEntries, transformSource, }?: {
202
+ export declare function updateWorkspaceInventorySource(source: string, { blockEntries, blockStyleEntries, blockTransformEntries, bindingSourceEntries, abilityEntries, adminViewEntries, aiFeatureEntries, editorPluginEntries, patternEntries, restResourceEntries, variationEntries, transformSource, }?: {
164
203
  abilityEntries?: string[];
204
+ adminViewEntries?: string[];
165
205
  aiFeatureEntries?: string[];
166
206
  blockEntries?: string[];
207
+ blockStyleEntries?: string[];
208
+ blockTransformEntries?: string[];
167
209
  bindingSourceEntries?: string[];
168
210
  editorPluginEntries?: string[];
169
211
  patternEntries?: string[];