@wp-typia/project-tools 0.22.8 → 0.22.10

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 (50) hide show
  1. package/dist/runtime/ai-artifacts.js +3 -4
  2. package/dist/runtime/ai-feature-artifacts.js +2 -4
  3. package/dist/runtime/cli-add-block.js +12 -1
  4. package/dist/runtime/cli-add-filesystem.js +2 -15
  5. package/dist/runtime/cli-add-types.d.ts +8 -0
  6. package/dist/runtime/cli-add-types.js +13 -0
  7. package/dist/runtime/cli-add.d.ts +3 -2
  8. package/dist/runtime/cli-add.js +2 -2
  9. package/dist/runtime/cli-core.d.ts +4 -2
  10. package/dist/runtime/cli-core.js +3 -2
  11. package/dist/runtime/cli-doctor-workspace-shared.js +3 -0
  12. package/dist/runtime/cli-doctor-workspace.d.ts +1 -1
  13. package/dist/runtime/cli-doctor-workspace.js +8 -3
  14. package/dist/runtime/cli-doctor.js +1 -1
  15. package/dist/runtime/cli-scaffold.js +3 -3
  16. package/dist/runtime/create-template-validation.js +2 -28
  17. package/dist/runtime/fs-async.d.ts +7 -0
  18. package/dist/runtime/fs-async.js +11 -2
  19. package/dist/runtime/id-suggestions.d.ts +21 -0
  20. package/dist/runtime/id-suggestions.js +48 -0
  21. package/dist/runtime/index.d.ts +5 -2
  22. package/dist/runtime/index.js +3 -1
  23. package/dist/runtime/migration-maintenance-verify.js +5 -0
  24. package/dist/runtime/migration-render-generated.js +4 -0
  25. package/dist/runtime/package-versions.js +3 -7
  26. package/dist/runtime/scaffold-repository-reference.js +3 -7
  27. package/dist/runtime/scaffold.js +3 -3
  28. package/dist/runtime/template-builtins.js +11 -8
  29. package/dist/runtime/template-layers.js +2 -2
  30. package/dist/runtime/template-source-external.d.ts +3 -0
  31. package/dist/runtime/template-source-external.js +5 -2
  32. package/dist/runtime/template-source-remote.d.ts +6 -0
  33. package/dist/runtime/template-source-remote.js +8 -2
  34. package/dist/runtime/template-source-seeds.d.ts +13 -0
  35. package/dist/runtime/template-source-seeds.js +36 -8
  36. package/dist/runtime/template-source.js +2 -2
  37. package/dist/runtime/typia-llm.js +3 -4
  38. package/dist/runtime/workspace-inventory-mutations.d.ts +24 -0
  39. package/dist/runtime/workspace-inventory-mutations.js +132 -0
  40. package/dist/runtime/workspace-inventory-parser.d.ts +52 -0
  41. package/dist/runtime/workspace-inventory-parser.js +511 -0
  42. package/dist/runtime/workspace-inventory-read.d.ts +44 -0
  43. package/dist/runtime/workspace-inventory-read.js +91 -0
  44. package/dist/runtime/workspace-inventory-templates.d.ts +35 -0
  45. package/dist/runtime/workspace-inventory-templates.js +198 -0
  46. package/dist/runtime/workspace-inventory-types.d.ts +171 -0
  47. package/dist/runtime/workspace-inventory-types.js +1 -0
  48. package/dist/runtime/workspace-inventory.d.ts +5 -238
  49. package/dist/runtime/workspace-inventory.js +4 -901
  50. package/package.json +7 -2
@@ -1,6 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import { createRequire } from "node:module";
3
3
  import path from "node:path";
4
+ import { getOptionalNodeErrorCode } from "./fs-async.js";
4
5
  import { PROJECT_TOOLS_PACKAGE_ROOT } from "./template-registry.js";
5
6
  const require = createRequire(import.meta.url);
6
7
  /**
@@ -8,17 +9,12 @@ const require = createRequire(import.meta.url);
8
9
  * can be resolved from the current runtime package manifests.
9
10
  */
10
11
  export const DEFAULT_SCAFFOLD_REPOSITORY_REFERENCE = "imjlk/wp-typia";
11
- function getErrorCode(error) {
12
- return typeof error === "object" && error !== null && "code" in error
13
- ? String(error.code)
14
- : undefined;
15
- }
16
12
  function readRepositoryPackageManifest(packageJsonPath) {
17
13
  try {
18
14
  return JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
19
15
  }
20
16
  catch (error) {
21
- if (getErrorCode(error) === "ENOENT") {
17
+ if (getOptionalNodeErrorCode(error) === "ENOENT") {
22
18
  return null;
23
19
  }
24
20
  throw error;
@@ -29,7 +25,7 @@ function resolveInstalledPackageManifestPath(packageName) {
29
25
  return require.resolve(`${packageName}/package.json`);
30
26
  }
31
27
  catch (error) {
32
- if (getErrorCode(error) === "MODULE_NOT_FOUND") {
28
+ if (getOptionalNodeErrorCode(error) === "MODULE_NOT_FOUND") {
33
29
  return null;
34
30
  }
35
31
  throw error;
@@ -1,4 +1,3 @@
1
- import fs from "node:fs";
2
1
  import { promises as fsp } from "node:fs";
3
2
  import path from "node:path";
4
3
  import { getPackageManager } from "./package-managers.js";
@@ -14,6 +13,7 @@ import { BlockGeneratorService, } from "./block-generator-service.js";
14
13
  import { getTemplateVariables } from "./scaffold-template-variables.js";
15
14
  import { getScaffoldTemplateVariableGroups } from "./scaffold-template-variable-groups.js";
16
15
  import { assertExternalLayerCompositionOptions, } from "./cli-validation.js";
16
+ import { pathExists } from "./fs-async.js";
17
17
  export const DATA_STORAGE_MODES = ["post-meta", "custom-table"];
18
18
  export const PERSISTENCE_POLICIES = ["authenticated", "public"];
19
19
  export { buildBlockCssClassName } from "./scaffold-identifiers.js";
@@ -149,7 +149,7 @@ export async function scaffoldProject({ projectDir, templateId, answers, alterna
149
149
  title: "Finalizing scaffold output",
150
150
  });
151
151
  const readmePath = path.join(projectDir, "README.md");
152
- if (!fs.existsSync(readmePath)) {
152
+ if (!(await pathExists(readmePath))) {
153
153
  await fsp.writeFile(readmePath, buildReadme(resolvedTemplateId, variables, resolvedPackageManager, {
154
154
  withMigrationUi: isBuiltInTemplate || isWorkspace ? withMigrationUi : false,
155
155
  withTestPreset: isBuiltInTemplate ? withTestPreset : false,
@@ -157,7 +157,7 @@ export async function scaffoldProject({ projectDir, templateId, answers, alterna
157
157
  }), "utf8");
158
158
  }
159
159
  const gitignorePath = path.join(projectDir, ".gitignore");
160
- const existingGitignore = fs.existsSync(gitignorePath)
160
+ const existingGitignore = (await pathExists(gitignorePath))
161
161
  ? await fsp.readFile(gitignorePath, "utf8")
162
162
  : "";
163
163
  await fsp.writeFile(gitignorePath, mergeTextLines(buildGitignore(), existingGitignore), "utf8");
@@ -1,6 +1,6 @@
1
- import fs from "node:fs";
2
1
  import path from "node:path";
3
2
  import { promises as fsp } from "node:fs";
3
+ import { pathExists } from "./fs-async.js";
4
4
  import { createManagedTempRoot } from "./temp-roots.js";
5
5
  import { getTemplateById, SHARED_BASE_TEMPLATE_ROOT, SHARED_COMPOUND_TEMPLATE_ROOT, SHARED_MIGRATION_UI_TEMPLATE_ROOT, SHARED_PERSISTENCE_TEMPLATE_ROOT, SHARED_PRESET_TEMPLATE_ROOT, SHARED_REST_HELPER_TEMPLATE_ROOT, SHARED_WORKSPACE_TEMPLATE_ROOT, } from "./template-registry.js";
6
6
  const BUILT_IN_SHARED_TEMPLATE_LAYERS = Object.freeze([
@@ -138,20 +138,23 @@ export function isOmittableBuiltInTemplateLayerDir(templateId, layerDir) {
138
138
  return (OMITTABLE_BUILT_IN_OVERLAY_TEMPLATE_IDS.has(templateId) &&
139
139
  layerDir === getTemplateById(templateId).templateDir);
140
140
  }
141
- function resolveMaterializedTemplateLayerDirs(templateId, layerDirs) {
142
- return layerDirs.flatMap((layerDir) => {
143
- if (fs.existsSync(layerDir)) {
144
- return [layerDir];
141
+ async function resolveMaterializedTemplateLayerDirs(templateId, layerDirs) {
142
+ const materializedLayerDirs = [];
143
+ for (const layerDir of layerDirs) {
144
+ if (await pathExists(layerDir)) {
145
+ materializedLayerDirs.push(layerDir);
146
+ continue;
145
147
  }
146
148
  if (isOmittableBuiltInTemplateLayerDir(templateId, layerDir)) {
147
- return [];
149
+ continue;
148
150
  }
149
151
  throw new Error(`Built-in template layer is missing: ${layerDir}`);
150
- });
152
+ }
153
+ return materializedLayerDirs;
151
154
  }
152
155
  async function materializeBuiltInTemplateSource(templateId, layerDirs) {
153
156
  const template = getTemplateById(templateId);
154
- const materializedLayerDirs = resolveMaterializedTemplateLayerDirs(templateId, layerDirs);
157
+ const materializedLayerDirs = await resolveMaterializedTemplateLayerDirs(templateId, layerDirs);
155
158
  const { path: tempRoot, cleanup } = await createManagedTempRoot("wp-typia-template-");
156
159
  const templateDir = path.join(tempRoot, templateId);
157
160
  try {
@@ -1,6 +1,6 @@
1
- import fs from "node:fs";
2
1
  import path from "node:path";
3
2
  import { promises as fsp } from "node:fs";
3
+ import { pathExists } from "./fs-async.js";
4
4
  import { isPlainObject } from "./object-utils.js";
5
5
  import { getBuiltInSharedTemplateLayerDir, isBuiltInSharedTemplateLayerId, } from "./template-builtins.js";
6
6
  import { listInterpolatedDirectoryOutputs } from "./template-render.js";
@@ -56,7 +56,7 @@ function parseLayerDefinition(layerId, value) {
56
56
  }
57
57
  export async function loadExternalTemplateLayerManifest(sourceRoot) {
58
58
  const manifestPath = path.join(sourceRoot, TEMPLATE_LAYER_MANIFEST_FILENAME);
59
- if (!fs.existsSync(manifestPath)) {
59
+ if (!(await pathExists(manifestPath))) {
60
60
  return null;
61
61
  }
62
62
  const raw = JSON.parse(await fsp.readFile(manifestPath, "utf8"));
@@ -7,6 +7,9 @@ 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.
12
+ *
10
13
  * @param sourceDir Directory that may contain an external template config entry.
11
14
  * @returns The first matching entry path, or null when no supported entry exists.
12
15
  */
@@ -33,6 +33,9 @@ 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.
38
+ *
36
39
  * @param sourceDir Directory that may contain an external template config entry.
37
40
  * @returns The first matching entry path, or null when no supported entry exists.
38
41
  */
@@ -205,11 +208,11 @@ export async function renderCreateBlockExternalTemplate(sourceDir, context, requ
205
208
  const view = await buildExternalTemplateView(context, config, selectedVariant, variantConfig);
206
209
  const blockTemplateDir = resolveSourceSubpath(sourceDir, templatePath);
207
210
  await copyRenderedDirectory(blockTemplateDir, blockDir, view, {
208
- filter: (sourcePath, _destinationPath, entry) => {
211
+ filter: async (sourcePath, _destinationPath, entry) => {
209
212
  const mustacheVariantPath = path.join(path.dirname(sourcePath), `${entry.name}.mustache`);
210
213
  return !(entry.isFile() &&
211
214
  (entry.name === 'package.json' || entry.name === 'README.md') &&
212
- fs.existsSync(mustacheVariantPath));
215
+ (await pathExists(mustacheVariantPath)));
213
216
  },
214
217
  });
215
218
  const assetsPath = typeof variantConfig.assetsPath === 'string'
@@ -2,6 +2,9 @@ import type { ResolvedTemplateSource, SeedSource, TemplateVariableContext } from
2
2
  /**
3
3
  * Read a remote block source and return its default block category.
4
4
  *
5
+ * @deprecated Use `getDefaultCategoryAsync()` from async template-source paths.
6
+ * This synchronous helper remains only for compatibility callers.
7
+ *
5
8
  * @param sourceDir Block source directory that may contain a block.json file.
6
9
  * @returns The declared block category, or "widgets" when detection fails.
7
10
  */
@@ -16,6 +19,9 @@ export declare function getDefaultCategoryAsync(sourceDir: string): Promise<stri
16
19
  /**
17
20
  * Read `wpTypia.projectType` from a rendered or source template package
18
21
  * manifest and return it when present.
22
+ *
23
+ * @deprecated Use `getTemplateProjectTypeAsync()` from async template-source
24
+ * paths. This synchronous helper remains only for compatibility callers.
19
25
  */
20
26
  export declare function getTemplateProjectType(sourceDir: string): string | null;
21
27
  /**
@@ -59,6 +59,9 @@ async function readRemoteBlockJsonAsync(blockDir) {
59
59
  /**
60
60
  * Read a remote block source and return its default block category.
61
61
  *
62
+ * @deprecated Use `getDefaultCategoryAsync()` from async template-source paths.
63
+ * This synchronous helper remains only for compatibility callers.
64
+ *
62
65
  * @param sourceDir Block source directory that may contain a block.json file.
63
66
  * @returns The declared block category, or "widgets" when detection fails.
64
67
  */
@@ -139,6 +142,9 @@ async function readTemplatePackageJsonAsync(sourceDir) {
139
142
  /**
140
143
  * Read `wpTypia.projectType` from a rendered or source template package
141
144
  * manifest and return it when present.
145
+ *
146
+ * @deprecated Use `getTemplateProjectTypeAsync()` from async template-source
147
+ * paths. This synchronous helper remains only for compatibility callers.
142
148
  */
143
149
  export function getTemplateProjectType(sourceDir) {
144
150
  const packageJsonEntry = readTemplatePackageJson(sourceDir);
@@ -185,11 +191,11 @@ export async function normalizeWpTypiaTemplateSeed(seed) {
185
191
  const normalizedDir = path.join(tempRoot, 'template');
186
192
  try {
187
193
  await copyRawDirectory(seed.blockDir, normalizedDir, {
188
- filter: (sourcePath, _targetPath, entry) => {
194
+ filter: async (sourcePath, _targetPath, entry) => {
189
195
  const mustacheVariantPath = path.join(path.dirname(sourcePath), `${entry.name}.mustache`);
190
196
  return !(entry.isFile() &&
191
197
  (entry.name === 'package.json' || entry.name === 'README.md') &&
192
- fs.existsSync(mustacheVariantPath));
198
+ (await pathExists(mustacheVariantPath)));
193
199
  },
194
200
  });
195
201
  if (seed.assetsDir && (await pathExists(seed.assetsDir))) {
@@ -1,5 +1,18 @@
1
1
  import type { RemoteTemplateLocator, SeedSource } from './template-source-contracts.js';
2
+ /**
3
+ * Synchronously identify the bundled workspace template seed.
4
+ *
5
+ * This remains sync-only for compatibility callers. Async template resolution
6
+ * paths should use `isOfficialWorkspaceTemplateSeedAsync()`.
7
+ */
2
8
  export declare function isOfficialWorkspaceTemplateSeed(seed: SeedSource): boolean;
9
+ /**
10
+ * Asynchronously identify the bundled workspace template seed.
11
+ *
12
+ * @param seed Seed source to inspect.
13
+ * @returns Whether the seed resolves to the official workspace template package.
14
+ */
15
+ export declare function isOfficialWorkspaceTemplateSeedAsync(seed: SeedSource): Promise<boolean>;
3
16
  export declare function assertNoSymlinks(sourceDir: string): Promise<void>;
4
17
  /**
5
18
  * Resolves a template locator into a local seed source directory.
@@ -8,6 +8,7 @@ import { x as extractTarball } from 'tar';
8
8
  import { createExternalTemplateTimeoutError, fetchWithExternalTemplateTimeout, getExternalTemplateMetadataMaxBytes, getExternalTemplateTarballMaxBytes, getExternalTemplateTimeoutMs, readBufferResponseWithLimit, readJsonResponseWithLimit, } from './external-template-guards.js';
9
9
  import { findReusableExternalTemplateSourceCache, isExternalTemplateCacheEnabled, resolveExternalTemplateSourceCache, } from './template-source-cache.js';
10
10
  import { CLI_DIAGNOSTIC_CODES, createCliDiagnosticCodeError, } from './cli-diagnostics.js';
11
+ import { pathExists } from './fs-async.js';
11
12
  import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, OFFICIAL_WORKSPACE_TEMPLATE_ALIAS, PROJECT_TOOLS_PACKAGE_ROOT, TEMPLATE_IDS, } from './template-registry.js';
12
13
  import { isPlainObject } from './object-utils.js';
13
14
  import { createManagedTempRoot } from './temp-roots.js';
@@ -184,7 +185,9 @@ async function fetchNpmTemplateSource(locator) {
184
185
  * Resolve a locally installed npm template package from the caller workspace.
185
186
  *
186
187
  * Bare package ids are preferred here so monorepo and offline workflows can
187
- * use an already-installed template without forcing a registry fetch.
188
+ * use an already-installed template without forcing a registry fetch. This
189
+ * path intentionally keeps Node's synchronous `require.resolve` semantics
190
+ * because the module resolver itself has no async equivalent.
188
191
  */
189
192
  function resolveInstalledNpmTemplateSource(locator, cwd) {
190
193
  if (locator.rawSpec !== '' && locator.rawSpec !== '*') {
@@ -244,6 +247,12 @@ function resolveInstalledNpmTemplateSource(locator, cwd) {
244
247
  throw error;
245
248
  }
246
249
  }
250
+ /**
251
+ * Synchronously identify the bundled workspace template seed.
252
+ *
253
+ * This remains sync-only for compatibility callers. Async template resolution
254
+ * paths should use `isOfficialWorkspaceTemplateSeedAsync()`.
255
+ */
247
256
  export function isOfficialWorkspaceTemplateSeed(seed) {
248
257
  const packageJsonPath = path.join(seed.rootDir, 'package.json');
249
258
  if (!fs.existsSync(packageJsonPath)) {
@@ -257,6 +266,25 @@ export function isOfficialWorkspaceTemplateSeed(seed) {
257
266
  return false;
258
267
  }
259
268
  }
269
+ /**
270
+ * Asynchronously identify the bundled workspace template seed.
271
+ *
272
+ * @param seed Seed source to inspect.
273
+ * @returns Whether the seed resolves to the official workspace template package.
274
+ */
275
+ export async function isOfficialWorkspaceTemplateSeedAsync(seed) {
276
+ const packageJsonPath = path.join(seed.rootDir, 'package.json');
277
+ if (!(await pathExists(packageJsonPath))) {
278
+ return false;
279
+ }
280
+ try {
281
+ const packageJson = JSON.parse(await fsp.readFile(packageJsonPath, 'utf8'));
282
+ return packageJson.name === OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE;
283
+ }
284
+ catch {
285
+ return false;
286
+ }
287
+ }
260
288
  export async function assertNoSymlinks(sourceDir) {
261
289
  const stats = await fsp.lstat(sourceDir);
262
290
  if (stats.isSymbolicLink()) {
@@ -300,13 +328,13 @@ function runGitTemplateCommand(args, label, options = {}) {
300
328
  function getGitHubTemplateRepositoryUrl(locator) {
301
329
  return `https://github.com/${locator.owner}/${locator.repo}.git`;
302
330
  }
303
- function resolveGitHubTemplateDirectory(checkoutDir, locator) {
331
+ async function resolveGitHubTemplateDirectory(checkoutDir, locator) {
304
332
  const sourceDir = path.resolve(checkoutDir, locator.sourcePath);
305
333
  const relativeSourceDir = path.relative(checkoutDir, sourceDir);
306
334
  if (relativeSourceDir.startsWith('..') || path.isAbsolute(relativeSourceDir)) {
307
335
  throw new Error('GitHub template path must stay within the cloned repository.');
308
336
  }
309
- if (!fs.existsSync(sourceDir)) {
337
+ if (!(await pathExists(sourceDir))) {
310
338
  throw new Error(`GitHub template path does not exist: ${locator.sourcePath}`);
311
339
  }
312
340
  return sourceDir;
@@ -438,7 +466,7 @@ async function reuseGitHubTemplateCacheByMetadata(locator) {
438
466
  if (!cachedSource) {
439
467
  return null;
440
468
  }
441
- const sourceDir = resolveGitHubTemplateDirectory(cachedSource.sourceDir, locator);
469
+ const sourceDir = await resolveGitHubTemplateDirectory(cachedSource.sourceDir, locator);
442
470
  await assertNoSymlinks(sourceDir);
443
471
  return {
444
472
  blockDir: sourceDir,
@@ -482,12 +510,12 @@ async function resolveGitHubTemplateSource(locator) {
482
510
  namespace: 'github',
483
511
  }, async (checkoutDir) => {
484
512
  cloneGitHubTemplateSource(locator, checkoutDir);
485
- const sourceDir = resolveGitHubTemplateDirectory(checkoutDir, locator);
513
+ const sourceDir = await resolveGitHubTemplateDirectory(checkoutDir, locator);
486
514
  await assertNoSymlinks(sourceDir);
487
515
  pinGitHubTemplateCacheRevision(locator, checkoutDir, resolvedCacheRevision);
488
516
  });
489
517
  if (cachedSource) {
490
- const sourceDir = resolveGitHubTemplateDirectory(cachedSource.sourceDir, locator);
518
+ const sourceDir = await resolveGitHubTemplateDirectory(cachedSource.sourceDir, locator);
491
519
  await assertNoSymlinks(sourceDir);
492
520
  return {
493
521
  blockDir: sourceDir,
@@ -506,7 +534,7 @@ async function resolveGitHubTemplateSource(locator) {
506
534
  const checkoutDir = path.join(remoteRoot, 'source');
507
535
  try {
508
536
  cloneGitHubTemplateSource(locator, checkoutDir);
509
- const sourceDir = resolveGitHubTemplateDirectory(checkoutDir, locator);
537
+ const sourceDir = await resolveGitHubTemplateDirectory(checkoutDir, locator);
510
538
  await assertNoSymlinks(sourceDir);
511
539
  return {
512
540
  blockDir: sourceDir,
@@ -530,7 +558,7 @@ async function resolveGitHubTemplateSource(locator) {
530
558
  export async function resolveTemplateSeed(locator, cwd) {
531
559
  if (locator.kind === 'path') {
532
560
  const sourceDir = path.resolve(cwd, locator.templatePath);
533
- if (!fs.existsSync(sourceDir)) {
561
+ if (!(await pathExists(sourceDir))) {
534
562
  throw new Error(`Template path does not exist: ${sourceDir}`);
535
563
  }
536
564
  await assertNoSymlinks(sourceDir);
@@ -2,7 +2,7 @@ import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, isBuiltInTemplateId, } from './tem
2
2
  import { resolveBuiltInTemplateSource } from './template-builtins.js';
3
3
  import { parseTemplateLocator, } from './template-source-locators.js';
4
4
  import { detectTemplateSourceFormat, getTemplateProjectTypeAsync, getDefaultCategoryAsync, getTemplateVariableContext, normalizeCreateBlockSubset, normalizeWpTypiaTemplateSeed, renderCreateBlockExternalTemplate, } from './template-source-normalization.js';
5
- import { isOfficialWorkspaceTemplateSeed, resolveTemplateSeed, } from './template-source-seeds.js';
5
+ import { isOfficialWorkspaceTemplateSeedAsync, resolveTemplateSeed, } from './template-source-seeds.js';
6
6
  import { assertBuiltInTemplateVariantAllowed, } from './cli-validation.js';
7
7
  import { getScaffoldTemplateVariableGroups } from './scaffold-template-variable-groups.js';
8
8
  export { parseGitHubTemplateLocator, parseNpmTemplateLocator, parseTemplateLocator, } from './template-source-locators.js';
@@ -27,7 +27,7 @@ export async function resolveTemplateSource(templateId, cwd, variables, variant)
27
27
  const context = getTemplateVariableContext(variables);
28
28
  const seed = await resolveTemplateSeed(locator, cwd);
29
29
  const isOfficialWorkspaceTemplate = templateId === OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE ||
30
- isOfficialWorkspaceTemplateSeed(seed);
30
+ (await isOfficialWorkspaceTemplateSeedAsync(seed));
31
31
  let normalizedSeed = null;
32
32
  try {
33
33
  const format = await detectTemplateSourceFormat(seed.blockDir);
@@ -1,5 +1,6 @@
1
1
  import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
+ import { getOptionalNodeErrorCode, isFileNotFoundError, } from './fs-async.js';
3
4
  import { cloneJsonValue } from './json-utils.js';
4
5
  import { normalizeEndpointAuthDefinition, } from './schema-core.js';
5
6
  const TYPESCRIPT_RESERVED_WORDS = new Set([
@@ -433,13 +434,11 @@ async function reconcileGeneratedTypiaLlmArtifacts(artifacts, check) {
433
434
  }
434
435
  }
435
436
  catch (error) {
436
- const code = error && typeof error === 'object' && 'code' in error
437
- ? error.code
438
- : undefined;
439
- if (code === 'ENOENT') {
437
+ if (isFileNotFoundError(error)) {
440
438
  issues.push(`- ${artifact.filePath} (missing)`);
441
439
  continue;
442
440
  }
441
+ const code = getOptionalNodeErrorCode(error);
443
442
  issues.push(`- ${artifact.filePath} (unreadable: ${error instanceof Error ? error.message : code ?? 'unknown'})`);
444
443
  }
445
444
  }
@@ -0,0 +1,24 @@
1
+ import type { WorkspaceInventoryUpdateOptions } from "./workspace-inventory-types.js";
2
+ /**
3
+ * Update `scripts/block-config.ts` source text with additional inventory entries.
4
+ *
5
+ * Missing inventory sections for variations, patterns, binding sources, REST
6
+ * resources, workflow abilities, AI features, editor plugins, block styles, and
7
+ * block transforms are created
8
+ * automatically before new entries are appended at their marker comments.
9
+ * When provided, `transformSource` runs before any entries are inserted.
10
+ *
11
+ * @param source Existing `scripts/block-config.ts` source.
12
+ * @param options Entry lists plus an optional source transformer.
13
+ * @returns Updated source text with all requested inventory entries appended.
14
+ */
15
+ export declare function updateWorkspaceInventorySource(source: string, options?: WorkspaceInventoryUpdateOptions): string;
16
+ /**
17
+ * Append new entries to the canonical workspace inventory file on disk.
18
+ *
19
+ * @param projectDir Workspace root directory.
20
+ * @param options Entry lists and optional source transform passed through to
21
+ * `updateWorkspaceInventorySource`.
22
+ * @returns Resolves once `scripts/block-config.ts` has been updated if needed.
23
+ */
24
+ export declare function appendWorkspaceInventoryEntries(projectDir: string, options: Parameters<typeof updateWorkspaceInventorySource>[1]): Promise<void>;
@@ -0,0 +1,132 @@
1
+ import path from "node:path";
2
+ import { readFile, writeFile } from "node:fs/promises";
3
+ import { escapeRegex } from "./php-utils.js";
4
+ import { BLOCK_INVENTORY_SECTION, INVENTORY_SECTIONS, } from "./workspace-inventory-parser.js";
5
+ import { WORKSPACE_COMPATIBILITY_CONFIG_FIELD } from "./workspace-inventory-templates.js";
6
+ function ensureWorkspaceInventorySections(source) {
7
+ let nextSource = source.trimEnd();
8
+ for (const section of INVENTORY_SECTIONS) {
9
+ if (section.interface &&
10
+ !hasExportedInterface(nextSource, section.interface.name)) {
11
+ nextSource += section.interface.section;
12
+ }
13
+ if (section.value && !hasExportedConst(nextSource, section.value.name)) {
14
+ nextSource += section.value.section;
15
+ }
16
+ }
17
+ return `${nextSource}\n`;
18
+ }
19
+ function hasExportedInterface(source, interfaceName) {
20
+ return new RegExp(`export\\s+interface\\s+${escapeRegex(interfaceName)}\\b`, "u").test(source);
21
+ }
22
+ function hasExportedConst(source, constName) {
23
+ return new RegExp(`export\\s+const\\s+${escapeRegex(constName)}\\b`, "u").test(source);
24
+ }
25
+ function appendEntriesAtMarker(source, marker, entries) {
26
+ if (entries.length === 0) {
27
+ return source;
28
+ }
29
+ if (!source.includes(marker)) {
30
+ throw new Error(`Workspace inventory marker "${marker}" is missing in scripts/block-config.ts.`);
31
+ }
32
+ const replacement = `${entries.join("\n")}\n${marker}`;
33
+ return source.replace(marker, () => replacement);
34
+ }
35
+ function appendInventorySectionEntries(source, options) {
36
+ let nextSource = source;
37
+ for (const section of [BLOCK_INVENTORY_SECTION, ...INVENTORY_SECTIONS]) {
38
+ if (!section.append) {
39
+ continue;
40
+ }
41
+ nextSource = appendEntriesAtMarker(nextSource, section.append.marker, options[section.append.optionKey] ?? []);
42
+ }
43
+ return nextSource;
44
+ }
45
+ function ensureInterfaceField(source, interfaceName, fieldName, fieldSource) {
46
+ const interfacePattern = new RegExp(`(export\\s+interface\\s+${escapeRegex(interfaceName)}\\s*\\{\\r?\\n)([\\s\\S]*?)(\\r?\\n\\})`, "u");
47
+ return source.replace(interfacePattern, (match, start, body, end) => {
48
+ if (new RegExp(`^[ \t]*${escapeRegex(fieldName)}\\??:`, "mu").test(body)) {
49
+ return match;
50
+ }
51
+ const lineEnding = start.endsWith("\r\n") ? "\r\n" : "\n";
52
+ const formattedFieldSource = `${fieldSource
53
+ .replace(/\r?\n$/u, "")
54
+ .split("\n")
55
+ .join(lineEnding)}${lineEnding}`;
56
+ const memberPattern = /^[ \t]*([A-Za-z_$][\w$]*)\??:/gmu;
57
+ for (const member of body.matchAll(memberPattern)) {
58
+ const memberIndex = member.index;
59
+ const memberName = member[1];
60
+ if (memberIndex === undefined || !memberName) {
61
+ continue;
62
+ }
63
+ if (memberName.localeCompare(fieldName) > 0) {
64
+ return `${start}${body.slice(0, memberIndex)}${formattedFieldSource}${body.slice(memberIndex)}${end}`;
65
+ }
66
+ }
67
+ return `${start}${body}${body.length > 0 && !body.endsWith(lineEnding) ? lineEnding : ""}${formattedFieldSource}${end}`;
68
+ });
69
+ }
70
+ function normalizeInterfaceFieldBlock(source, interfaceName, fieldName, fieldSource, requiredFragments) {
71
+ const interfacePattern = new RegExp(`(export\\s+interface\\s+${escapeRegex(interfaceName)}\\s*\\{\\r?\\n)([\\s\\S]*?)(\\r?\\n\\})`, "u");
72
+ return source.replace(interfacePattern, (match, start, body, end) => {
73
+ const fieldPattern = new RegExp(`(^([ \\t]*)${escapeRegex(fieldName)}\\??:\\s*\\{[ \\t]*\\r?\\n)([\\s\\S]*?)(^\\2\\};\\r?\\n?)`, "mu");
74
+ const fieldMatch = fieldPattern.exec(body);
75
+ if (!fieldMatch) {
76
+ return match;
77
+ }
78
+ const existingFieldSource = fieldMatch[0];
79
+ if (requiredFragments.every((fragment) => existingFieldSource.includes(fragment))) {
80
+ return match;
81
+ }
82
+ const lineEnding = start.endsWith("\r\n") ? "\r\n" : "\n";
83
+ const formattedFieldSource = `${fieldSource
84
+ .replace(/\r?\n$/u, "")
85
+ .split("\n")
86
+ .join(lineEnding)}${lineEnding}`;
87
+ return `${start}${body.slice(0, fieldMatch.index)}${formattedFieldSource}${body.slice(fieldMatch.index + existingFieldSource.length)}${end}`;
88
+ });
89
+ }
90
+ /**
91
+ * Update `scripts/block-config.ts` source text with additional inventory entries.
92
+ *
93
+ * Missing inventory sections for variations, patterns, binding sources, REST
94
+ * resources, workflow abilities, AI features, editor plugins, block styles, and
95
+ * block transforms are created
96
+ * automatically before new entries are appended at their marker comments.
97
+ * When provided, `transformSource` runs before any entries are inserted.
98
+ *
99
+ * @param source Existing `scripts/block-config.ts` source.
100
+ * @param options Entry lists plus an optional source transformer.
101
+ * @returns Updated source text with all requested inventory entries appended.
102
+ */
103
+ export function updateWorkspaceInventorySource(source, options = {}) {
104
+ let nextSource = ensureWorkspaceInventorySections(source);
105
+ if (options.transformSource) {
106
+ nextSource = options.transformSource(nextSource);
107
+ }
108
+ nextSource = appendInventorySectionEntries(nextSource, options);
109
+ nextSource = ensureInterfaceField(nextSource, "WorkspaceBindingSourceConfig", "attribute", "\tattribute?: string;");
110
+ nextSource = ensureInterfaceField(nextSource, "WorkspaceBindingSourceConfig", "block", "\tblock?: string;");
111
+ nextSource = ensureInterfaceField(nextSource, "WorkspaceAbilityConfig", "compatibility", WORKSPACE_COMPATIBILITY_CONFIG_FIELD);
112
+ nextSource = normalizeInterfaceFieldBlock(nextSource, "WorkspaceAbilityConfig", "compatibility", WORKSPACE_COMPATIBILITY_CONFIG_FIELD, ["optionalFeatureIds: string[];", "requiredFeatureIds: string[];"]);
113
+ nextSource = ensureInterfaceField(nextSource, "WorkspaceAiFeatureConfig", "compatibility", WORKSPACE_COMPATIBILITY_CONFIG_FIELD);
114
+ nextSource = normalizeInterfaceFieldBlock(nextSource, "WorkspaceAiFeatureConfig", "compatibility", WORKSPACE_COMPATIBILITY_CONFIG_FIELD, ["optionalFeatureIds: string[];", "requiredFeatureIds: string[];"]);
115
+ return nextSource;
116
+ }
117
+ /**
118
+ * Append new entries to the canonical workspace inventory file on disk.
119
+ *
120
+ * @param projectDir Workspace root directory.
121
+ * @param options Entry lists and optional source transform passed through to
122
+ * `updateWorkspaceInventorySource`.
123
+ * @returns Resolves once `scripts/block-config.ts` has been updated if needed.
124
+ */
125
+ export async function appendWorkspaceInventoryEntries(projectDir, options) {
126
+ const blockConfigPath = path.join(projectDir, "scripts", "block-config.ts");
127
+ const source = await readFile(blockConfigPath, "utf8");
128
+ const nextSource = updateWorkspaceInventorySource(source, options);
129
+ if (nextSource !== source) {
130
+ await writeFile(blockConfigPath, nextSource, "utf8");
131
+ }
132
+ }
@@ -0,0 +1,52 @@
1
+ import type { WorkspaceInventory, WorkspaceInventoryAppendOptionKey, WorkspaceInventoryEntriesKey, WorkspaceInventorySectionFlagKey } from "./workspace-inventory-types.js";
2
+ type InventoryEntryFieldValidationContext = {
3
+ elementIndex: number;
4
+ entryName: string;
5
+ key: string;
6
+ };
7
+ type InventoryEntryFieldDescriptor = {
8
+ key: string;
9
+ kind?: "string" | "stringArray";
10
+ required?: boolean;
11
+ validate?: (value: string | string[] | undefined, context: InventoryEntryFieldValidationContext) => void;
12
+ };
13
+ type InventoryEntryParserDescriptor = {
14
+ entryName: string;
15
+ fields: readonly InventoryEntryFieldDescriptor[];
16
+ };
17
+ export type InventorySectionDescriptor = {
18
+ /** Optional marker metadata used when appending generated entries. */
19
+ append?: {
20
+ marker: string;
21
+ optionKey: WorkspaceInventoryAppendOptionKey;
22
+ };
23
+ /** Optional exported interface that backs the inventory section entries. */
24
+ interface?: {
25
+ name: string;
26
+ section: string;
27
+ };
28
+ /** Optional parser metadata for descriptor-driven inventory reads. */
29
+ parse?: {
30
+ entriesKey: WorkspaceInventoryEntriesKey;
31
+ entry: InventoryEntryParserDescriptor;
32
+ exportName?: string;
33
+ hasSectionKey?: WorkspaceInventorySectionFlagKey;
34
+ required?: boolean;
35
+ };
36
+ /** Optional exported const array that stores the inventory section entries. */
37
+ value?: {
38
+ name: string;
39
+ section: string;
40
+ };
41
+ };
42
+ export declare const BLOCK_INVENTORY_SECTION: InventorySectionDescriptor;
43
+ export declare const INVENTORY_SECTIONS: readonly InventorySectionDescriptor[];
44
+ /**
45
+ * Parse workspace inventory entries from the source of `scripts/block-config.ts`.
46
+ *
47
+ * @param source Raw TypeScript source from `scripts/block-config.ts`.
48
+ * @returns Parsed inventory sections without the resolved `blockConfigPath`.
49
+ * @throws {Error} When `BLOCKS` is missing or any inventory entry is malformed.
50
+ */
51
+ export declare function parseWorkspaceInventorySource(source: string): Omit<WorkspaceInventory, "blockConfigPath">;
52
+ export {};