@wp-typia/project-tools 0.22.10 → 0.23.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. package/dist/runtime/ai-feature-artifacts.js +4 -1
  2. package/dist/runtime/block-generator-service-spec.js +2 -1
  3. package/dist/runtime/cli-add-block-json.js +5 -1
  4. package/dist/runtime/cli-add-collision.d.ts +25 -0
  5. package/dist/runtime/cli-add-collision.js +76 -0
  6. package/dist/runtime/cli-add-help.js +12 -2
  7. package/dist/runtime/cli-add-kind-ids.d.ts +1 -1
  8. package/dist/runtime/cli-add-kind-ids.js +3 -0
  9. package/dist/runtime/cli-add-types.d.ts +129 -0
  10. package/dist/runtime/cli-add-types.js +26 -0
  11. package/dist/runtime/cli-add-validation.d.ts +97 -1
  12. package/dist/runtime/cli-add-validation.js +313 -1
  13. package/dist/runtime/cli-add-workspace-ability-scaffold.js +4 -1
  14. package/dist/runtime/cli-add-workspace-admin-view-scaffold.js +79 -20
  15. package/dist/runtime/cli-add-workspace-admin-view-source.js +11 -2
  16. package/dist/runtime/cli-add-workspace-admin-view-templates-core-data.d.ts +34 -0
  17. package/dist/runtime/cli-add-workspace-admin-view-templates-core-data.js +483 -0
  18. package/dist/runtime/cli-add-workspace-admin-view-templates-default.d.ts +30 -0
  19. package/dist/runtime/cli-add-workspace-admin-view-templates-default.js +310 -0
  20. package/dist/runtime/cli-add-workspace-admin-view-templates-rest.d.ts +25 -0
  21. package/dist/runtime/cli-add-workspace-admin-view-templates-rest.js +124 -0
  22. package/dist/runtime/cli-add-workspace-admin-view-templates-settings.d.ts +34 -0
  23. package/dist/runtime/cli-add-workspace-admin-view-templates-settings.js +370 -0
  24. package/dist/runtime/cli-add-workspace-admin-view-templates-shared.d.ts +49 -0
  25. package/dist/runtime/cli-add-workspace-admin-view-templates-shared.js +259 -0
  26. package/dist/runtime/cli-add-workspace-admin-view-templates.d.ts +19 -10
  27. package/dist/runtime/cli-add-workspace-admin-view-templates.js +31 -971
  28. package/dist/runtime/cli-add-workspace-admin-view-types.d.ts +21 -0
  29. package/dist/runtime/cli-add-workspace-admin-view-types.js +22 -0
  30. package/dist/runtime/cli-add-workspace-ai-anchors.js +125 -32
  31. package/dist/runtime/cli-add-workspace-ai-source-emitters.js +17 -1
  32. package/dist/runtime/cli-add-workspace-contract-source-emitters.d.ts +15 -0
  33. package/dist/runtime/cli-add-workspace-contract-source-emitters.js +42 -0
  34. package/dist/runtime/cli-add-workspace-contract.d.ts +15 -0
  35. package/dist/runtime/cli-add-workspace-contract.js +65 -0
  36. package/dist/runtime/cli-add-workspace-integration-env.d.ts +26 -0
  37. package/dist/runtime/cli-add-workspace-integration-env.js +428 -0
  38. package/dist/runtime/cli-add-workspace-post-meta-anchors.d.ts +23 -0
  39. package/dist/runtime/cli-add-workspace-post-meta-anchors.js +244 -0
  40. package/dist/runtime/cli-add-workspace-post-meta-source-emitters.d.ts +63 -0
  41. package/dist/runtime/cli-add-workspace-post-meta-source-emitters.js +179 -0
  42. package/dist/runtime/cli-add-workspace-post-meta.d.ts +15 -0
  43. package/dist/runtime/cli-add-workspace-post-meta.js +107 -0
  44. package/dist/runtime/cli-add-workspace-rest-anchors.d.ts +9 -0
  45. package/dist/runtime/cli-add-workspace-rest-anchors.js +326 -21
  46. package/dist/runtime/cli-add-workspace-rest-generated.d.ts +9 -0
  47. package/dist/runtime/cli-add-workspace-rest-generated.js +158 -0
  48. package/dist/runtime/cli-add-workspace-rest-manual.d.ts +8 -0
  49. package/dist/runtime/cli-add-workspace-rest-manual.js +279 -0
  50. package/dist/runtime/cli-add-workspace-rest-php-templates.d.ts +24 -0
  51. package/dist/runtime/cli-add-workspace-rest-php-templates.js +678 -0
  52. package/dist/runtime/cli-add-workspace-rest-source-emitters.d.ts +98 -2
  53. package/dist/runtime/cli-add-workspace-rest-source-emitters.js +323 -29
  54. package/dist/runtime/cli-add-workspace-rest-types.d.ts +108 -0
  55. package/dist/runtime/cli-add-workspace-rest-types.js +1 -0
  56. package/dist/runtime/cli-add-workspace-rest.d.ts +3 -7
  57. package/dist/runtime/cli-add-workspace-rest.js +34 -481
  58. package/dist/runtime/cli-add-workspace.d.ts +15 -0
  59. package/dist/runtime/cli-add-workspace.js +15 -0
  60. package/dist/runtime/cli-add.d.ts +1 -1
  61. package/dist/runtime/cli-add.js +1 -1
  62. package/dist/runtime/cli-core.d.ts +3 -2
  63. package/dist/runtime/cli-core.js +3 -2
  64. package/dist/runtime/cli-diagnostics.d.ts +3 -1
  65. package/dist/runtime/cli-diagnostics.js +17 -5
  66. package/dist/runtime/cli-doctor-environment.js +1 -3
  67. package/dist/runtime/cli-doctor-workspace-bindings.js +4 -1
  68. package/dist/runtime/cli-doctor-workspace-block-addons.d.ts +12 -0
  69. package/dist/runtime/cli-doctor-workspace-block-addons.js +134 -0
  70. package/dist/runtime/cli-doctor-workspace-block-iframe.d.ts +9 -0
  71. package/dist/runtime/cli-doctor-workspace-block-iframe.js +228 -0
  72. package/dist/runtime/cli-doctor-workspace-block-metadata.d.ts +11 -0
  73. package/dist/runtime/cli-doctor-workspace-block-metadata.js +111 -0
  74. package/dist/runtime/cli-doctor-workspace-blocks.js +6 -424
  75. package/dist/runtime/cli-doctor-workspace-features-abilities.d.ts +11 -0
  76. package/dist/runtime/cli-doctor-workspace-features-abilities.js +112 -0
  77. package/dist/runtime/cli-doctor-workspace-features-admin-views.d.ts +11 -0
  78. package/dist/runtime/cli-doctor-workspace-features-admin-views.js +128 -0
  79. package/dist/runtime/cli-doctor-workspace-features-ai.d.ts +11 -0
  80. package/dist/runtime/cli-doctor-workspace-features-ai.js +57 -0
  81. package/dist/runtime/cli-doctor-workspace-features-editor-plugins.d.ts +11 -0
  82. package/dist/runtime/cli-doctor-workspace-features-editor-plugins.js +80 -0
  83. package/dist/runtime/cli-doctor-workspace-features-post-meta.d.ts +11 -0
  84. package/dist/runtime/cli-doctor-workspace-features-post-meta.js +77 -0
  85. package/dist/runtime/cli-doctor-workspace-features-rest.d.ts +11 -0
  86. package/dist/runtime/cli-doctor-workspace-features-rest.js +120 -0
  87. package/dist/runtime/cli-doctor-workspace-features.js +14 -369
  88. package/dist/runtime/cli-doctor-workspace-package.d.ts +25 -3
  89. package/dist/runtime/cli-doctor-workspace-package.js +35 -13
  90. package/dist/runtime/cli-doctor-workspace-shared.d.ts +2 -0
  91. package/dist/runtime/cli-doctor-workspace-shared.js +2 -0
  92. package/dist/runtime/cli-doctor-workspace.js +8 -3
  93. package/dist/runtime/cli-doctor.d.ts +52 -3
  94. package/dist/runtime/cli-doctor.js +79 -8
  95. package/dist/runtime/cli-help.js +10 -0
  96. package/dist/runtime/cli-init-package-json.js +4 -2
  97. package/dist/runtime/cli-init-templates.js +11 -1
  98. package/dist/runtime/cli-prompt.d.ts +16 -2
  99. package/dist/runtime/cli-prompt.js +29 -12
  100. package/dist/runtime/cli-scaffold.d.ts +2 -1
  101. package/dist/runtime/cli-scaffold.js +19 -10
  102. package/dist/runtime/contract-artifacts.d.ts +14 -0
  103. package/dist/runtime/contract-artifacts.js +15 -0
  104. package/dist/runtime/external-template-guards.js +4 -6
  105. package/dist/runtime/index.d.ts +2 -2
  106. package/dist/runtime/index.js +1 -1
  107. package/dist/runtime/json-utils.d.ts +62 -4
  108. package/dist/runtime/json-utils.js +78 -4
  109. package/dist/runtime/local-dev-presets.js +4 -1
  110. package/dist/runtime/migration-ui-capability.js +4 -1
  111. package/dist/runtime/migration-utils.js +4 -1
  112. package/dist/runtime/package-managers.js +6 -1
  113. package/dist/runtime/package-versions.js +6 -1
  114. package/dist/runtime/rest-resource-artifacts.d.ts +57 -1
  115. package/dist/runtime/rest-resource-artifacts.js +97 -1
  116. package/dist/runtime/scaffold-bootstrap.js +7 -2
  117. package/dist/runtime/scaffold-package-manager-files.js +5 -1
  118. package/dist/runtime/scaffold-repository-reference.js +4 -2
  119. package/dist/runtime/scaffold-template-variables.js +2 -1
  120. package/dist/runtime/scaffold.d.ts +18 -1
  121. package/dist/runtime/scaffold.js +55 -2
  122. package/dist/runtime/temp-roots.js +4 -1
  123. package/dist/runtime/template-layers.js +4 -1
  124. package/dist/runtime/template-registry.js +9 -3
  125. package/dist/runtime/template-render.d.ts +1 -1
  126. package/dist/runtime/template-render.js +1 -1
  127. package/dist/runtime/template-source-cache-markers.d.ts +37 -0
  128. package/dist/runtime/template-source-cache-markers.js +125 -0
  129. package/dist/runtime/template-source-cache.d.ts +1 -4
  130. package/dist/runtime/template-source-cache.js +16 -122
  131. package/dist/runtime/template-source-contracts.d.ts +2 -0
  132. package/dist/runtime/template-source-external.d.ts +4 -2
  133. package/dist/runtime/template-source-external.js +4 -2
  134. package/dist/runtime/template-source-normalization.js +2 -1
  135. package/dist/runtime/template-source-remote.d.ts +8 -4
  136. package/dist/runtime/template-source-remote.js +26 -9
  137. package/dist/runtime/template-source-seeds.js +10 -3
  138. package/dist/runtime/workspace-inventory-mutations.js +54 -4
  139. package/dist/runtime/workspace-inventory-parser-entries.d.ts +17 -0
  140. package/dist/runtime/workspace-inventory-parser-entries.js +157 -0
  141. package/dist/runtime/workspace-inventory-parser-validation.d.ts +104 -0
  142. package/dist/runtime/workspace-inventory-parser-validation.js +34 -0
  143. package/dist/runtime/workspace-inventory-parser.d.ts +3 -44
  144. package/dist/runtime/workspace-inventory-parser.js +7 -464
  145. package/dist/runtime/workspace-inventory-read.d.ts +9 -2
  146. package/dist/runtime/workspace-inventory-read.js +9 -2
  147. package/dist/runtime/workspace-inventory-section-descriptors.d.ts +19 -0
  148. package/dist/runtime/workspace-inventory-section-descriptors.js +435 -0
  149. package/dist/runtime/workspace-inventory-templates.d.ts +16 -1
  150. package/dist/runtime/workspace-inventory-templates.js +75 -4
  151. package/dist/runtime/workspace-inventory-types.d.ts +52 -2
  152. package/dist/runtime/workspace-inventory.d.ts +2 -2
  153. package/dist/runtime/workspace-inventory.js +1 -1
  154. package/dist/runtime/workspace-project.js +4 -6
  155. package/package.json +2 -2
@@ -2,8 +2,10 @@ 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.
5
+ * @deprecated Since 0.22.10. Use `getDefaultCategoryAsync()` from async
6
+ * template-source paths. Removal target: not currently scheduled; this sync
7
+ * compatibility helper remains available until release notes announce a
8
+ * versioned target.
7
9
  *
8
10
  * @param sourceDir Block source directory that may contain a block.json file.
9
11
  * @returns The declared block category, or "widgets" when detection fails.
@@ -20,8 +22,10 @@ export declare function getDefaultCategoryAsync(sourceDir: string): Promise<stri
20
22
  * Read `wpTypia.projectType` from a rendered or source template package
21
23
  * manifest and return it when present.
22
24
  *
23
- * @deprecated Use `getTemplateProjectTypeAsync()` from async template-source
24
- * paths. This synchronous helper remains only for compatibility callers.
25
+ * @deprecated Since 0.22.10. Use `getTemplateProjectTypeAsync()` from async
26
+ * template-source paths. Removal target: not currently scheduled; this sync
27
+ * compatibility helper remains available until release notes announce a
28
+ * versioned target.
25
29
  */
26
30
  export declare function getTemplateProjectType(sourceDir: string): string | null;
27
31
  /**
@@ -6,6 +6,7 @@ import { getBuiltInTemplateLayerDirs, isOmittableBuiltInTemplateLayerDir, } from
6
6
  import { copyRawDirectory } from './template-render.js';
7
7
  import { createManagedTempRoot } from './temp-roots.js';
8
8
  import { pathExists } from './fs-async.js';
9
+ import { readJsonFile, readJsonFileSync, safeJsonParse, } from './json-utils.js';
9
10
  async function cleanupSeedRootPair(cleanup, seedCleanup) {
10
11
  let cleanupError;
11
12
  try {
@@ -39,7 +40,9 @@ function readRemoteBlockJson(blockDir) {
39
40
  path.join(sourceRoot, 'block.json'),
40
41
  ]) {
41
42
  if (fs.existsSync(candidate)) {
42
- return JSON.parse(fs.readFileSync(candidate, 'utf8'));
43
+ return readJsonFileSync(candidate, {
44
+ context: 'remote block metadata',
45
+ });
43
46
  }
44
47
  }
45
48
  throw new Error(`Unable to locate block.json in ${blockDir}`);
@@ -51,7 +54,9 @@ async function readRemoteBlockJsonAsync(blockDir) {
51
54
  path.join(sourceRoot, 'block.json'),
52
55
  ]) {
53
56
  if (await pathExists(candidate)) {
54
- return JSON.parse(await fsp.readFile(candidate, 'utf8'));
57
+ return readJsonFile(candidate, {
58
+ context: 'remote block metadata',
59
+ });
55
60
  }
56
61
  }
57
62
  throw new Error(`Unable to locate block.json in ${blockDir}`);
@@ -59,8 +64,10 @@ async function readRemoteBlockJsonAsync(blockDir) {
59
64
  /**
60
65
  * Read a remote block source and return its default block category.
61
66
  *
62
- * @deprecated Use `getDefaultCategoryAsync()` from async template-source paths.
63
- * This synchronous helper remains only for compatibility callers.
67
+ * @deprecated Since 0.22.10. Use `getDefaultCategoryAsync()` from async
68
+ * template-source paths. Removal target: not currently scheduled; this sync
69
+ * compatibility helper remains available until release notes announce a
70
+ * versioned target.
64
71
  *
65
72
  * @param sourceDir Block source directory that may contain a block.json file.
66
73
  * @returns The declared block category, or "widgets" when detection fails.
@@ -103,7 +110,10 @@ function readTemplatePackageJson(sourceDir) {
103
110
  maxBytes: getExternalTemplatePackageJsonMaxBytes(),
104
111
  });
105
112
  return {
106
- packageJson: JSON.parse(fs.readFileSync(candidate, 'utf8')),
113
+ packageJson: safeJsonParse(fs.readFileSync(candidate, 'utf8'), {
114
+ context: 'template metadata file',
115
+ filePath: candidate,
116
+ }),
107
117
  sourcePath: candidate,
108
118
  };
109
119
  }
@@ -128,7 +138,10 @@ async function readTemplatePackageJsonAsync(sourceDir) {
128
138
  maxBytes: getExternalTemplatePackageJsonMaxBytes(),
129
139
  });
130
140
  return {
131
- packageJson: JSON.parse(await fsp.readFile(candidate, 'utf8')),
141
+ packageJson: safeJsonParse(await fsp.readFile(candidate, 'utf8'), {
142
+ context: 'template metadata file',
143
+ filePath: candidate,
144
+ }),
132
145
  sourcePath: candidate,
133
146
  };
134
147
  }
@@ -143,8 +156,10 @@ async function readTemplatePackageJsonAsync(sourceDir) {
143
156
  * Read `wpTypia.projectType` from a rendered or source template package
144
157
  * manifest and return it when present.
145
158
  *
146
- * @deprecated Use `getTemplateProjectTypeAsync()` from async template-source
147
- * paths. This synchronous helper remains only for compatibility callers.
159
+ * @deprecated Since 0.22.10. Use `getTemplateProjectTypeAsync()` from async
160
+ * template-source paths. Removal target: not currently scheduled; this sync
161
+ * compatibility helper remains available until release notes announce a
162
+ * versioned target.
148
163
  */
149
164
  export function getTemplateProjectType(sourceDir) {
150
165
  const packageJsonEntry = readTemplatePackageJson(sourceDir);
@@ -323,7 +338,9 @@ async function rewriteBlockJsonImports(directory) {
323
338
  }
324
339
  async function patchRemotePackageJson(templateDir, needsInteractivity) {
325
340
  const packageJsonPath = path.join(templateDir, 'package.json.mustache');
326
- const packageJson = JSON.parse(await fsp.readFile(packageJsonPath, 'utf8'));
341
+ const packageJson = await readJsonFile(packageJsonPath, {
342
+ context: 'remote package template manifest',
343
+ });
327
344
  const existingDependencies = { ...(packageJson.dependencies ?? {}) };
328
345
  const existingDevDependencies = { ...(packageJson.devDependencies ?? {}) };
329
346
  delete existingDependencies['@wp-typia/project-tools'];
@@ -9,6 +9,7 @@ import { createExternalTemplateTimeoutError, fetchWithExternalTemplateTimeout, g
9
9
  import { findReusableExternalTemplateSourceCache, isExternalTemplateCacheEnabled, resolveExternalTemplateSourceCache, } from './template-source-cache.js';
10
10
  import { CLI_DIAGNOSTIC_CODES, createCliDiagnosticCodeError, } from './cli-diagnostics.js';
11
11
  import { pathExists } from './fs-async.js';
12
+ import { readJsonFile, readJsonFileSync } from './json-utils.js';
12
13
  import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, OFFICIAL_WORKSPACE_TEMPLATE_ALIAS, PROJECT_TOOLS_PACKAGE_ROOT, TEMPLATE_IDS, } from './template-registry.js';
13
14
  import { isPlainObject } from './object-utils.js';
14
15
  import { createManagedTempRoot } from './temp-roots.js';
@@ -206,7 +207,9 @@ function resolveInstalledNpmTemplateSource(locator, cwd) {
206
207
  if (!fs.existsSync(packageJsonPath)) {
207
208
  continue;
208
209
  }
209
- const manifest = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
210
+ const manifest = readJsonFileSync(packageJsonPath, {
211
+ context: 'workspace template package manifest',
212
+ });
210
213
  if (manifest.name === locator.name) {
211
214
  return {
212
215
  blockDir: packageDir,
@@ -259,7 +262,9 @@ export function isOfficialWorkspaceTemplateSeed(seed) {
259
262
  return false;
260
263
  }
261
264
  try {
262
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
265
+ const packageJson = readJsonFileSync(packageJsonPath, {
266
+ context: 'workspace template seed manifest',
267
+ });
263
268
  return packageJson.name === OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE;
264
269
  }
265
270
  catch {
@@ -278,7 +283,9 @@ export async function isOfficialWorkspaceTemplateSeedAsync(seed) {
278
283
  return false;
279
284
  }
280
285
  try {
281
- const packageJson = JSON.parse(await fsp.readFile(packageJsonPath, 'utf8'));
286
+ const packageJson = await readJsonFile(packageJsonPath, {
287
+ context: 'workspace template seed manifest',
288
+ });
282
289
  return packageJson.name === OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE;
283
290
  }
284
291
  catch {
@@ -1,13 +1,13 @@
1
1
  import path from "node:path";
2
2
  import { readFile, writeFile } from "node:fs/promises";
3
3
  import { escapeRegex } from "./php-utils.js";
4
- import { BLOCK_INVENTORY_SECTION, INVENTORY_SECTIONS, } from "./workspace-inventory-parser.js";
4
+ import { BLOCK_INVENTORY_SECTION, INVENTORY_SECTIONS, } from "./workspace-inventory-section-descriptors.js";
5
5
  import { WORKSPACE_COMPATIBILITY_CONFIG_FIELD } from "./workspace-inventory-templates.js";
6
6
  function ensureWorkspaceInventorySections(source) {
7
7
  let nextSource = source.trimEnd();
8
8
  for (const section of INVENTORY_SECTIONS) {
9
9
  if (section.interface &&
10
- !hasExportedInterface(nextSource, section.interface.name)) {
10
+ !hasExportedTypeDeclaration(nextSource, section.interface.name)) {
11
11
  nextSource += section.interface.section;
12
12
  }
13
13
  if (section.value && !hasExportedConst(nextSource, section.value.name)) {
@@ -16,8 +16,8 @@ function ensureWorkspaceInventorySections(source) {
16
16
  }
17
17
  return `${nextSource}\n`;
18
18
  }
19
- function hasExportedInterface(source, interfaceName) {
20
- return new RegExp(`export\\s+interface\\s+${escapeRegex(interfaceName)}\\b`, "u").test(source);
19
+ function hasExportedTypeDeclaration(source, interfaceName) {
20
+ return new RegExp(`export\\s+(?:interface|type)\\s+${escapeRegex(interfaceName)}\\b`, "u").test(source);
21
21
  }
22
22
  function hasExportedConst(source, constName) {
23
23
  return new RegExp(`export\\s+const\\s+${escapeRegex(constName)}\\b`, "u").test(source);
@@ -67,6 +67,36 @@ function ensureInterfaceField(source, interfaceName, fieldName, fieldSource) {
67
67
  return `${start}${body}${body.length > 0 && !body.endsWith(lineEnding) ? lineEnding : ""}${formattedFieldSource}${end}`;
68
68
  });
69
69
  }
70
+ function upsertInterfaceField(source, interfaceName, fieldName, fieldSource) {
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 lineEnding = start.endsWith("\r\n") ? "\r\n" : "\n";
74
+ const formattedFieldSource = `${fieldSource
75
+ .replace(/\r?\n$/u, "")
76
+ .split("\n")
77
+ .join(lineEnding)}${lineEnding}`;
78
+ const existingFieldPattern = new RegExp(`(^[ \\t]*${escapeRegex(fieldName)}\\??:\\s*[^;\\r\\n]+;?\\r?\\n?)`, "mu");
79
+ const existingFieldMatch = existingFieldPattern.exec(body);
80
+ if (existingFieldMatch?.[0]) {
81
+ if (existingFieldMatch[0].trim() === fieldSource.trim()) {
82
+ return match;
83
+ }
84
+ return `${start}${body.slice(0, existingFieldMatch.index)}${formattedFieldSource}${body.slice(existingFieldMatch.index + existingFieldMatch[0].length)}${end}`;
85
+ }
86
+ const memberPattern = /^[ \t]*([A-Za-z_$][\w$]*)\??:/gmu;
87
+ for (const member of body.matchAll(memberPattern)) {
88
+ const memberIndex = member.index;
89
+ const memberName = member[1];
90
+ if (memberIndex === undefined || !memberName) {
91
+ continue;
92
+ }
93
+ if (memberName.localeCompare(fieldName) > 0) {
94
+ return `${start}${body.slice(0, memberIndex)}${formattedFieldSource}${body.slice(memberIndex)}${end}`;
95
+ }
96
+ }
97
+ return `${start}${body}${body.length > 0 && !body.endsWith(lineEnding) ? lineEnding : ""}${formattedFieldSource}${end}`;
98
+ });
99
+ }
70
100
  function normalizeInterfaceFieldBlock(source, interfaceName, fieldName, fieldSource, requiredFragments) {
71
101
  const interfacePattern = new RegExp(`(export\\s+interface\\s+${escapeRegex(interfaceName)}\\s*\\{\\r?\\n)([\\s\\S]*?)(\\r?\\n\\})`, "u");
72
102
  return source.replace(interfacePattern, (match, start, body, end) => {
@@ -112,6 +142,26 @@ export function updateWorkspaceInventorySource(source, options = {}) {
112
142
  nextSource = normalizeInterfaceFieldBlock(nextSource, "WorkspaceAbilityConfig", "compatibility", WORKSPACE_COMPATIBILITY_CONFIG_FIELD, ["optionalFeatureIds: string[];", "requiredFeatureIds: string[];"]);
113
143
  nextSource = ensureInterfaceField(nextSource, "WorkspaceAiFeatureConfig", "compatibility", WORKSPACE_COMPATIBILITY_CONFIG_FIELD);
114
144
  nextSource = normalizeInterfaceFieldBlock(nextSource, "WorkspaceAiFeatureConfig", "compatibility", WORKSPACE_COMPATIBILITY_CONFIG_FIELD, ["optionalFeatureIds: string[];", "requiredFeatureIds: string[];"]);
145
+ for (const [fieldName, fieldSource] of [
146
+ ["auth", "\tauth?: 'authenticated' | 'public' | 'public-write-protected';"],
147
+ ["bodyTypeName", "\tbodyTypeName?: string;"],
148
+ ["controllerClass", "\tcontrollerClass?: string;"],
149
+ ["controllerExtends", "\tcontrollerExtends?: string;"],
150
+ ["dataFile", "\tdataFile?: string;"],
151
+ ["method", "\tmethod?: 'DELETE' | 'GET' | 'PATCH' | 'POST' | 'PUT';"],
152
+ ["mode", "\tmode?: 'generated' | 'manual';"],
153
+ ["pathPattern", "\tpathPattern?: string;"],
154
+ ["permissionCallback", "\tpermissionCallback?: string;"],
155
+ ["phpFile", "\tphpFile?: string;"],
156
+ ["queryTypeName", "\tqueryTypeName?: string;"],
157
+ ["responseTypeName", "\tresponseTypeName?: string;"],
158
+ ["routePattern", "\troutePattern?: string;"],
159
+ ["secretFieldName", "\tsecretFieldName?: string;"],
160
+ ["secretPreserveOnEmpty", "\tsecretPreserveOnEmpty?: boolean;"],
161
+ ["secretStateFieldName", "\tsecretStateFieldName?: string;"],
162
+ ]) {
163
+ nextSource = upsertInterfaceField(nextSource, "WorkspaceRestResourceConfig", fieldName, fieldSource);
164
+ }
115
165
  return nextSource;
116
166
  }
117
167
  /**
@@ -0,0 +1,17 @@
1
+ import ts from "typescript";
2
+ import { type InventorySectionDescriptor } from "./workspace-inventory-parser-validation.js";
3
+ /**
4
+ * Parse one descriptor-backed inventory section from a TypeScript source file.
5
+ *
6
+ * The generic `T` is the typed entry shape returned in `entries`. The
7
+ * `descriptor` supplies the exported array name, parser field metadata, and
8
+ * whether the section is required; this helper uses `findExportedArrayLiteral`
9
+ * and `parseInventoryEntries` to guarantee object-literal entries and literal
10
+ * field values. It returns parsed entries plus a `found` flag, and throws when
11
+ * required exports are missing, descriptors cannot resolve an export name, or
12
+ * exported values are not array literals.
13
+ */
14
+ export declare function parseInventorySection<T extends object>(sourceFile: ts.SourceFile, descriptor: InventorySectionDescriptor): {
15
+ entries: T[];
16
+ found: boolean;
17
+ };
@@ -0,0 +1,157 @@
1
+ import ts from "typescript";
2
+ import { getPropertyNameText } from "./ts-property-names.js";
3
+ import { assertParsedInventoryEntry, } from "./workspace-inventory-parser-validation.js";
4
+ function findExportedArrayLiteral(sourceFile, exportName) {
5
+ for (const statement of sourceFile.statements) {
6
+ if (!ts.isVariableStatement(statement)) {
7
+ continue;
8
+ }
9
+ if (!statement.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
10
+ continue;
11
+ }
12
+ for (const declaration of statement.declarationList.declarations) {
13
+ if (!ts.isIdentifier(declaration.name) ||
14
+ declaration.name.text !== exportName) {
15
+ continue;
16
+ }
17
+ if (declaration.initializer &&
18
+ ts.isArrayLiteralExpression(declaration.initializer)) {
19
+ return {
20
+ array: declaration.initializer,
21
+ found: true,
22
+ };
23
+ }
24
+ return {
25
+ array: null,
26
+ found: true,
27
+ };
28
+ }
29
+ }
30
+ return {
31
+ array: null,
32
+ found: false,
33
+ };
34
+ }
35
+ function getOptionalStringProperty(entryName, elementIndex, objectLiteral, key) {
36
+ for (const property of objectLiteral.properties) {
37
+ if (!ts.isPropertyAssignment(property)) {
38
+ continue;
39
+ }
40
+ const propertyName = getPropertyNameText(property.name);
41
+ if (propertyName !== key) {
42
+ continue;
43
+ }
44
+ if (ts.isStringLiteralLike(property.initializer)) {
45
+ return property.initializer.text;
46
+ }
47
+ throw new Error(`${entryName}[${elementIndex}] must use a string literal for "${key}" in scripts/block-config.ts.`);
48
+ }
49
+ return undefined;
50
+ }
51
+ function getOptionalStringArrayProperty(entryName, elementIndex, objectLiteral, key) {
52
+ for (const property of objectLiteral.properties) {
53
+ if (!ts.isPropertyAssignment(property)) {
54
+ continue;
55
+ }
56
+ const propertyName = getPropertyNameText(property.name);
57
+ if (propertyName !== key) {
58
+ continue;
59
+ }
60
+ if (!ts.isArrayLiteralExpression(property.initializer)) {
61
+ throw new Error(`${entryName}[${elementIndex}] must use an array literal for "${key}" in scripts/block-config.ts.`);
62
+ }
63
+ return property.initializer.elements.map((element, itemIndex) => {
64
+ if (!ts.isStringLiteralLike(element)) {
65
+ throw new Error(`${entryName}[${elementIndex}].${key}[${itemIndex}] must use a string literal in scripts/block-config.ts.`);
66
+ }
67
+ return element.text;
68
+ });
69
+ }
70
+ return undefined;
71
+ }
72
+ function getOptionalBooleanProperty(entryName, elementIndex, objectLiteral, key) {
73
+ for (const property of objectLiteral.properties) {
74
+ if (!ts.isPropertyAssignment(property)) {
75
+ continue;
76
+ }
77
+ const propertyName = getPropertyNameText(property.name);
78
+ if (propertyName !== key) {
79
+ continue;
80
+ }
81
+ if (property.initializer.kind === ts.SyntaxKind.TrueKeyword) {
82
+ return true;
83
+ }
84
+ if (property.initializer.kind === ts.SyntaxKind.FalseKeyword) {
85
+ return false;
86
+ }
87
+ throw new Error(`${entryName}[${elementIndex}] must use a boolean literal for "${key}" in scripts/block-config.ts.`);
88
+ }
89
+ return undefined;
90
+ }
91
+ function parseInventoryEntries(arrayLiteral, descriptor) {
92
+ return arrayLiteral.elements.map((element, elementIndex) => {
93
+ if (!ts.isObjectLiteralExpression(element)) {
94
+ throw new Error(`${descriptor.entryName}[${elementIndex}] must be an object literal in scripts/block-config.ts.`);
95
+ }
96
+ const entry = {};
97
+ for (const field of descriptor.fields) {
98
+ const kind = field.kind ?? "string";
99
+ const value = kind === "stringArray"
100
+ ? getOptionalStringArrayProperty(descriptor.entryName, elementIndex, element, field.key)
101
+ : kind === "boolean"
102
+ ? getOptionalBooleanProperty(descriptor.entryName, elementIndex, element, field.key)
103
+ : getOptionalStringProperty(descriptor.entryName, elementIndex, element, field.key);
104
+ field.validate?.(value, {
105
+ elementIndex,
106
+ entryName: descriptor.entryName,
107
+ key: field.key,
108
+ });
109
+ entry[field.key] = value;
110
+ }
111
+ assertParsedInventoryEntry(entry, descriptor, elementIndex);
112
+ return entry;
113
+ });
114
+ }
115
+ /**
116
+ * Parse one descriptor-backed inventory section from a TypeScript source file.
117
+ *
118
+ * The generic `T` is the typed entry shape returned in `entries`. The
119
+ * `descriptor` supplies the exported array name, parser field metadata, and
120
+ * whether the section is required; this helper uses `findExportedArrayLiteral`
121
+ * and `parseInventoryEntries` to guarantee object-literal entries and literal
122
+ * field values. It returns parsed entries plus a `found` flag, and throws when
123
+ * required exports are missing, descriptors cannot resolve an export name, or
124
+ * exported values are not array literals.
125
+ */
126
+ export function parseInventorySection(sourceFile, descriptor) {
127
+ if (!descriptor.parse) {
128
+ return {
129
+ entries: [],
130
+ found: false,
131
+ };
132
+ }
133
+ const exportName = descriptor.parse.exportName ?? descriptor.value?.name;
134
+ if (!exportName) {
135
+ throw new Error("Inventory parser descriptor is missing an export name.");
136
+ }
137
+ const exportedArray = findExportedArrayLiteral(sourceFile, exportName);
138
+ if (!exportedArray.found) {
139
+ if (descriptor.parse.required) {
140
+ throw new Error(`scripts/block-config.ts must export a ${exportName} array.`);
141
+ }
142
+ return {
143
+ entries: [],
144
+ found: false,
145
+ };
146
+ }
147
+ if (!exportedArray.array) {
148
+ if (descriptor.parse.required) {
149
+ throw new Error(`scripts/block-config.ts must export a ${exportName} array.`);
150
+ }
151
+ throw new Error(`scripts/block-config.ts must export ${exportName} as an array literal.`);
152
+ }
153
+ return {
154
+ entries: parseInventoryEntries(exportedArray.array, descriptor.parse.entry),
155
+ found: true,
156
+ };
157
+ }
@@ -0,0 +1,104 @@
1
+ import type { WorkspaceInventoryAppendOptionKey, WorkspaceInventoryEntriesKey, WorkspaceInventorySectionFlagKey } from "./workspace-inventory-types.js";
2
+ /**
3
+ * Literal value shape accepted by descriptor-driven inventory entry fields.
4
+ */
5
+ export type InventoryEntryFieldValue = string | string[] | boolean | undefined;
6
+ /**
7
+ * Context passed to custom field validators while parsing one inventory entry.
8
+ */
9
+ export type InventoryEntryFieldValidationContext = {
10
+ elementIndex: number;
11
+ entryName: string;
12
+ key: string;
13
+ };
14
+ type InventoryEntryFieldDescriptor = {
15
+ key: string;
16
+ kind?: "boolean" | "string" | "stringArray";
17
+ required?: boolean;
18
+ validate?: (value: InventoryEntryFieldValue, context: InventoryEntryFieldValidationContext) => void;
19
+ };
20
+ /**
21
+ * Runtime parser contract for one exported inventory array.
22
+ *
23
+ * `entryName` is used in diagnostics and `fields` defines the literal values,
24
+ * required flags, and custom validators accepted for each object entry.
25
+ */
26
+ export type InventoryEntryParserDescriptor = {
27
+ entryName: string;
28
+ fields: readonly InventoryEntryFieldDescriptor[];
29
+ };
30
+ type RequiredInventoryEntryKey<T extends object> = Extract<{
31
+ [Key in keyof T]-?: undefined extends T[Key] ? never : Key;
32
+ }[keyof T], string>;
33
+ type TypedInventoryEntryFieldDescriptor<T extends object, TKey extends Extract<keyof T, string> = Extract<keyof T, string>> = TKey extends Extract<keyof T, string> ? Omit<InventoryEntryFieldDescriptor, "key" | "required"> & {
34
+ key: TKey;
35
+ } & (TKey extends RequiredInventoryEntryKey<T> ? {
36
+ required: true;
37
+ } : {
38
+ required?: boolean;
39
+ }) : never;
40
+ type RequiredInventoryEntryFieldDescriptor<T extends object> = Omit<InventoryEntryFieldDescriptor, "key" | "required"> & {
41
+ key: RequiredInventoryEntryKey<T>;
42
+ required: true;
43
+ };
44
+ type MissingRequiredInventoryEntryKeys<T extends object, TFields extends readonly TypedInventoryEntryFieldDescriptor<T>[]> = Exclude<RequiredInventoryEntryKey<T>, TFields[number]["key"]>;
45
+ type RequiredInventoryEntryFieldsPresent<T extends object, TFields extends readonly TypedInventoryEntryFieldDescriptor<T>[]> = MissingRequiredInventoryEntryKeys<T, TFields> extends never ? unknown : {
46
+ missingRequiredInventoryEntryFields: MissingRequiredInventoryEntryKeys<T, TFields>;
47
+ };
48
+ type RequiredInventoryEntryFieldsMarkedRequired<T extends object, TFields extends readonly TypedInventoryEntryFieldDescriptor<T>[]> = Extract<TFields[number], {
49
+ key: RequiredInventoryEntryKey<T>;
50
+ }> extends RequiredInventoryEntryFieldDescriptor<T> ? unknown : {
51
+ requiredInventoryEntryFieldsMustSetRequiredTrue: RequiredInventoryEntryKey<T>;
52
+ };
53
+ /**
54
+ * Shared descriptor contract for generated workspace inventory sections.
55
+ *
56
+ * Callers rely on `append` markers when inserting generated entries, `parse`
57
+ * metadata when reading exported arrays, and `interface`/`value` sections when
58
+ * repairing older `scripts/block-config.ts` files.
59
+ */
60
+ export type InventorySectionDescriptor = {
61
+ /** Optional marker metadata used when appending generated entries. */
62
+ append?: {
63
+ marker: string;
64
+ optionKey: WorkspaceInventoryAppendOptionKey;
65
+ };
66
+ /** Optional exported interface that backs the inventory section entries. */
67
+ interface?: {
68
+ name: string;
69
+ section: string;
70
+ };
71
+ /** Optional parser metadata for descriptor-driven inventory reads. */
72
+ parse?: {
73
+ entriesKey: WorkspaceInventoryEntriesKey;
74
+ entry: InventoryEntryParserDescriptor;
75
+ exportName?: string;
76
+ hasSectionKey?: WorkspaceInventorySectionFlagKey;
77
+ required?: boolean;
78
+ };
79
+ /** Optional exported const array that stores the inventory section entries. */
80
+ value?: {
81
+ name: string;
82
+ section: string;
83
+ };
84
+ };
85
+ /**
86
+ * Define a typed inventory entry parser while enforcing required fields at
87
+ * compile time.
88
+ *
89
+ * The returned descriptor is consumed by `parseInventorySection` to extract
90
+ * literal field values and run runtime validators.
91
+ */
92
+ export declare function defineInventoryEntryParser<T extends object>(): <const TFields extends readonly TypedInventoryEntryFieldDescriptor<T>[]>(descriptor: {
93
+ entryName: string;
94
+ fields: TFields;
95
+ } & RequiredInventoryEntryFieldsPresent<T, TFields> & RequiredInventoryEntryFieldsMarkedRequired<T, TFields>) => InventoryEntryParserDescriptor;
96
+ /**
97
+ * Assert that a parsed inventory entry contains every required descriptor
98
+ * field.
99
+ *
100
+ * Missing `undefined` values and empty strings throw with diagnostics tied to
101
+ * the original exported inventory array name and element index.
102
+ */
103
+ export declare function assertParsedInventoryEntry<T extends object>(entry: Record<string, InventoryEntryFieldValue>, descriptor: InventoryEntryParserDescriptor, elementIndex: number): asserts entry is Record<string, InventoryEntryFieldValue> & T;
104
+ export {};
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Define a typed inventory entry parser while enforcing required fields at
3
+ * compile time.
4
+ *
5
+ * The returned descriptor is consumed by `parseInventorySection` to extract
6
+ * literal field values and run runtime validators.
7
+ */
8
+ export function defineInventoryEntryParser() {
9
+ return (descriptor) => descriptor;
10
+ }
11
+ function isMissingRequiredInventoryValue(value) {
12
+ return (value === undefined || (typeof value === "string" && value.length === 0));
13
+ }
14
+ function formatMissingRequiredInventoryFields(keys) {
15
+ return keys.length === 1
16
+ ? `required "${keys[0]}"`
17
+ : `required fields ${keys.map((key) => `"${key}"`).join(", ")}`;
18
+ }
19
+ /**
20
+ * Assert that a parsed inventory entry contains every required descriptor
21
+ * field.
22
+ *
23
+ * Missing `undefined` values and empty strings throw with diagnostics tied to
24
+ * the original exported inventory array name and element index.
25
+ */
26
+ export function assertParsedInventoryEntry(entry, descriptor, elementIndex) {
27
+ const missingRequiredKeys = descriptor.fields
28
+ .filter((field) => field.required === true &&
29
+ isMissingRequiredInventoryValue(entry[field.key]))
30
+ .map((field) => field.key);
31
+ if (missingRequiredKeys.length > 0) {
32
+ throw new Error(`${descriptor.entryName}[${elementIndex}] is missing ${formatMissingRequiredInventoryFields(missingRequiredKeys)} in scripts/block-config.ts.`);
33
+ }
34
+ }
@@ -1,46 +1,6 @@
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[];
1
+ import type { WorkspaceInventory } from "./workspace-inventory-types.js";
2
+ export { BLOCK_INVENTORY_SECTION, INVENTORY_SECTIONS, } from "./workspace-inventory-section-descriptors.js";
3
+ export type { InventorySectionDescriptor } from "./workspace-inventory-parser-validation.js";
44
4
  /**
45
5
  * Parse workspace inventory entries from the source of `scripts/block-config.ts`.
46
6
  *
@@ -49,4 +9,3 @@ export declare const INVENTORY_SECTIONS: readonly InventorySectionDescriptor[];
49
9
  * @throws {Error} When `BLOCKS` is missing or any inventory entry is malformed.
50
10
  */
51
11
  export declare function parseWorkspaceInventorySource(source: string): Omit<WorkspaceInventory, "blockConfigPath">;
52
- export {};