@wp-typia/project-tools 0.22.9 → 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.
- package/dist/runtime/ai-artifacts.js +3 -4
- package/dist/runtime/ai-feature-artifacts.js +2 -4
- package/dist/runtime/cli-add-filesystem.js +2 -15
- package/dist/runtime/cli-add-types.js +3 -1
- package/dist/runtime/cli-doctor-workspace-shared.js +3 -0
- package/dist/runtime/cli-doctor-workspace.d.ts +1 -1
- package/dist/runtime/cli-doctor-workspace.js +8 -5
- package/dist/runtime/cli-doctor.js +1 -1
- package/dist/runtime/fs-async.d.ts +7 -0
- package/dist/runtime/fs-async.js +11 -2
- package/dist/runtime/migration-maintenance-verify.js +3 -0
- package/dist/runtime/migration-render-generated.js +4 -0
- package/dist/runtime/package-versions.js +3 -7
- package/dist/runtime/scaffold-repository-reference.js +3 -7
- package/dist/runtime/typia-llm.js +3 -4
- package/dist/runtime/workspace-inventory-mutations.d.ts +24 -0
- package/dist/runtime/workspace-inventory-mutations.js +132 -0
- package/dist/runtime/workspace-inventory-parser.d.ts +52 -0
- package/dist/runtime/workspace-inventory-parser.js +511 -0
- package/dist/runtime/workspace-inventory-read.d.ts +44 -0
- package/dist/runtime/workspace-inventory-read.js +91 -0
- package/dist/runtime/workspace-inventory-templates.d.ts +35 -0
- package/dist/runtime/workspace-inventory-templates.js +198 -0
- package/dist/runtime/workspace-inventory-types.d.ts +171 -0
- package/dist/runtime/workspace-inventory-types.js +1 -0
- package/dist/runtime/workspace-inventory.d.ts +5 -252
- package/dist/runtime/workspace-inventory.js +4 -928
- package/package.json +2 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { buildWordPressAiArtifacts, } from './wordpress-ai.js';
|
|
4
|
+
import { getOptionalNodeErrorCode, isFileNotFoundError, } from './fs-async.js';
|
|
4
5
|
export { buildWordPressAiArtifacts, buildWordPressAbilitiesDocument, projectWordPressAiSchema, } from './wordpress-ai.js';
|
|
5
6
|
function normalizeGeneratedArtifactContent(content) {
|
|
6
7
|
return content.replace(/\r\n?/g, '\n');
|
|
@@ -25,13 +26,11 @@ async function reconcileGeneratedAiArtifacts(artifacts, check) {
|
|
|
25
26
|
}
|
|
26
27
|
}
|
|
27
28
|
catch (error) {
|
|
28
|
-
|
|
29
|
-
? error.code
|
|
30
|
-
: undefined;
|
|
31
|
-
if (code === 'ENOENT') {
|
|
29
|
+
if (isFileNotFoundError(error)) {
|
|
32
30
|
issues.push(`- ${artifact.filePath} (missing)`);
|
|
33
31
|
continue;
|
|
34
32
|
}
|
|
33
|
+
const code = getOptionalNodeErrorCode(error);
|
|
35
34
|
issues.push(`- ${artifact.filePath} (unreadable: ${error instanceof Error ? error.message : code ?? 'unknown'})`);
|
|
36
35
|
}
|
|
37
36
|
}
|
|
@@ -2,6 +2,7 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { defineEndpointManifest, syncEndpointClient, syncRestOpenApi, syncTypeSchemas, } from "@wp-typia/block-runtime/metadata-core";
|
|
4
4
|
import { projectWordPressAiSchema } from "./ai-artifacts.js";
|
|
5
|
+
import { isFileNotFoundError } from "./fs-async.js";
|
|
5
6
|
function normalizeGeneratedArtifactContent(content) {
|
|
6
7
|
return content.replace(/\r\n?/g, "\n");
|
|
7
8
|
}
|
|
@@ -21,10 +22,7 @@ async function reconcileGeneratedArtifact(options) {
|
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
24
|
catch (error) {
|
|
24
|
-
|
|
25
|
-
? error.code
|
|
26
|
-
: undefined;
|
|
27
|
-
if (code === "ENOENT") {
|
|
25
|
+
if (isFileNotFoundError(error)) {
|
|
28
26
|
throw new Error(`Generated AI feature artifact is missing: ${options.label} (${options.filePath}).`);
|
|
29
27
|
}
|
|
30
28
|
throw error;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { promises as fsp } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { readOptionalUtf8File } from "./fs-async.js";
|
|
3
4
|
/**
|
|
4
5
|
* Resolve the primary PHP bootstrap file for an official workspace.
|
|
5
6
|
*
|
|
@@ -24,15 +25,7 @@ export async function patchFile(filePath, transform) {
|
|
|
24
25
|
* Read a file when it exists and otherwise return `null`.
|
|
25
26
|
*/
|
|
26
27
|
export async function readOptionalFile(filePath) {
|
|
27
|
-
|
|
28
|
-
return await fsp.readFile(filePath, "utf8");
|
|
29
|
-
}
|
|
30
|
-
catch (error) {
|
|
31
|
-
if (isFileNotFoundError(error)) {
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
throw error;
|
|
35
|
-
}
|
|
28
|
+
return readOptionalUtf8File(filePath);
|
|
36
29
|
}
|
|
37
30
|
/**
|
|
38
31
|
* Restore a file to its captured source, deleting it when the snapshot was `null`.
|
|
@@ -69,9 +62,3 @@ export async function rollbackWorkspaceMutation(snapshot) {
|
|
|
69
62
|
await restoreOptionalFile(filePath, source);
|
|
70
63
|
}
|
|
71
64
|
}
|
|
72
|
-
function isFileNotFoundError(error) {
|
|
73
|
-
return (typeof error === "object" &&
|
|
74
|
-
error !== null &&
|
|
75
|
-
"code" in error &&
|
|
76
|
-
error.code === "ENOENT");
|
|
77
|
-
}
|
|
@@ -55,5 +55,7 @@ export const ADD_BLOCK_TEMPLATE_IDS = [
|
|
|
55
55
|
* close-id threshold, otherwise `null`.
|
|
56
56
|
*/
|
|
57
57
|
export function suggestAddBlockTemplateId(templateId) {
|
|
58
|
-
return suggestCloseId(templateId, ADD_BLOCK_TEMPLATE_IDS
|
|
58
|
+
return suggestCloseId(templateId, ADD_BLOCK_TEMPLATE_IDS, {
|
|
59
|
+
maxDistance: 3,
|
|
60
|
+
});
|
|
59
61
|
}
|
|
@@ -90,6 +90,9 @@ export function resolveWorkspaceBootstrapPath(projectDir, packageName) {
|
|
|
90
90
|
* @returns A passing or failing `DoctorCheck` describing any missing files.
|
|
91
91
|
*/
|
|
92
92
|
export function checkExistingFiles(projectDir, label, filePaths) {
|
|
93
|
+
// Workspace category collectors remain synchronous pure mappers after the
|
|
94
|
+
// async inventory snapshot is loaded, so these small existence probes stay
|
|
95
|
+
// sync to preserve their current non-Promise APIs and output ordering.
|
|
93
96
|
const missing = filePaths
|
|
94
97
|
.filter((filePath) => typeof filePath === "string")
|
|
95
98
|
.filter((filePath) => !fs.existsSync(path.join(projectDir, filePath)));
|
|
@@ -15,4 +15,4 @@ import type { DoctorCheck } from "./cli-doctor.js";
|
|
|
15
15
|
* @param cwd Working directory expected to host an official workspace.
|
|
16
16
|
* @returns Ordered workspace check rows ready for CLI rendering.
|
|
17
17
|
*/
|
|
18
|
-
export declare function getWorkspaceDoctorChecks(cwd: string): DoctorCheck[]
|
|
18
|
+
export declare function getWorkspaceDoctorChecks(cwd: string): Promise<DoctorCheck[]>;
|
|
@@ -3,7 +3,7 @@ import { getWorkspaceBlockDoctorChecks, } from "./cli-doctor-workspace-blocks.js
|
|
|
3
3
|
import { getWorkspaceFeatureDoctorChecks, } from "./cli-doctor-workspace-features.js";
|
|
4
4
|
import { getMigrationWorkspaceHintCheck, getWorkspacePackageMetadataCheck, } from "./cli-doctor-workspace-package.js";
|
|
5
5
|
import { createDoctorCheck, createDoctorScopeCheck, } from "./cli-doctor-workspace-shared.js";
|
|
6
|
-
import {
|
|
6
|
+
import { readWorkspaceInventoryAsync, } from "./workspace-inventory.js";
|
|
7
7
|
import { getInvalidWorkspaceProjectReason, parseWorkspacePackageJson, tryResolveWorkspaceProject, } from "./workspace-project.js";
|
|
8
8
|
function formatWorkspaceInventorySummary(inventory) {
|
|
9
9
|
return [
|
|
@@ -36,11 +36,13 @@ function formatWorkspaceInventorySummary(inventory) {
|
|
|
36
36
|
* @param cwd Working directory expected to host an official workspace.
|
|
37
37
|
* @returns Ordered workspace check rows ready for CLI rendering.
|
|
38
38
|
*/
|
|
39
|
-
export function getWorkspaceDoctorChecks(cwd) {
|
|
39
|
+
export async function getWorkspaceDoctorChecks(cwd) {
|
|
40
40
|
const checks = [];
|
|
41
41
|
let workspace = null;
|
|
42
42
|
let invalidWorkspaceReason = null;
|
|
43
43
|
try {
|
|
44
|
+
// Workspace discovery and package parsing intentionally stay synchronous
|
|
45
|
+
// because they share compatibility helpers with sync add/migration callers.
|
|
44
46
|
invalidWorkspaceReason = getInvalidWorkspaceProjectReason(cwd);
|
|
45
47
|
workspace = tryResolveWorkspaceProject(cwd);
|
|
46
48
|
}
|
|
@@ -62,6 +64,9 @@ export function getWorkspaceDoctorChecks(cwd) {
|
|
|
62
64
|
checks.push(createDoctorScopeCheck("pass", `Scope: full workspace diagnostics for ${workspace.workspace.namespace}. Environment readiness checks ran and workspace-scoped diagnostics are enabled for the package metadata, inventory, source-tree drift, and any configured migration hint rows below.`));
|
|
63
65
|
let workspacePackageJson;
|
|
64
66
|
try {
|
|
67
|
+
// Keep package metadata parsing sync until workspace-project exposes an
|
|
68
|
+
// async companion; the surrounding doctor orchestration already awaits
|
|
69
|
+
// async inventory reads below.
|
|
65
70
|
workspacePackageJson = parseWorkspacePackageJson(workspace.projectDir);
|
|
66
71
|
}
|
|
67
72
|
catch (error) {
|
|
@@ -70,9 +75,7 @@ export function getWorkspaceDoctorChecks(cwd) {
|
|
|
70
75
|
}
|
|
71
76
|
checks.push(getWorkspacePackageMetadataCheck(workspace, workspacePackageJson));
|
|
72
77
|
try {
|
|
73
|
-
|
|
74
|
-
// snapshot without mixing async inventory reads into check aggregation.
|
|
75
|
-
const inventory = readWorkspaceInventory(workspace.projectDir);
|
|
78
|
+
const inventory = await readWorkspaceInventoryAsync(workspace.projectDir);
|
|
76
79
|
checks.push(createDoctorCheck("Workspace inventory", "pass", formatWorkspaceInventorySummary(inventory)));
|
|
77
80
|
checks.push(...getWorkspaceBlockDoctorChecks(workspace, inventory));
|
|
78
81
|
checks.push(...getWorkspaceBindingDoctorChecks(workspace, inventory));
|
|
@@ -15,7 +15,7 @@ import { getWorkspaceDoctorChecks } from "./cli-doctor-workspace.js";
|
|
|
15
15
|
export async function getDoctorChecks(cwd) {
|
|
16
16
|
return [
|
|
17
17
|
...(await getEnvironmentDoctorChecks(cwd)),
|
|
18
|
-
...getWorkspaceDoctorChecks(cwd),
|
|
18
|
+
...(await getWorkspaceDoctorChecks(cwd)),
|
|
19
19
|
];
|
|
20
20
|
}
|
|
21
21
|
/**
|
|
@@ -19,6 +19,13 @@ export declare function readOptionalUtf8File(filePath: string): Promise<string |
|
|
|
19
19
|
* @returns The string error code, or an empty string when unavailable.
|
|
20
20
|
*/
|
|
21
21
|
export declare function getNodeErrorCode(error: unknown): string;
|
|
22
|
+
/**
|
|
23
|
+
* Extract a Node.js error code from an unknown thrown value when available.
|
|
24
|
+
*
|
|
25
|
+
* @param error Unknown error value.
|
|
26
|
+
* @returns The string error code, or `undefined` when unavailable.
|
|
27
|
+
*/
|
|
28
|
+
export declare function getOptionalNodeErrorCode(error: unknown): string | undefined;
|
|
22
29
|
/**
|
|
23
30
|
* Return whether an unknown error represents a missing filesystem path.
|
|
24
31
|
*
|
package/dist/runtime/fs-async.js
CHANGED
|
@@ -38,9 +38,18 @@ export async function readOptionalUtf8File(filePath) {
|
|
|
38
38
|
* @returns The string error code, or an empty string when unavailable.
|
|
39
39
|
*/
|
|
40
40
|
export function getNodeErrorCode(error) {
|
|
41
|
+
return getOptionalNodeErrorCode(error) ?? "";
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Extract a Node.js error code from an unknown thrown value when available.
|
|
45
|
+
*
|
|
46
|
+
* @param error Unknown error value.
|
|
47
|
+
* @returns The string error code, or `undefined` when unavailable.
|
|
48
|
+
*/
|
|
49
|
+
export function getOptionalNodeErrorCode(error) {
|
|
41
50
|
return typeof error === "object" && error !== null && "code" in error
|
|
42
51
|
? String(error.code)
|
|
43
|
-
:
|
|
52
|
+
: undefined;
|
|
44
53
|
}
|
|
45
54
|
/**
|
|
46
55
|
* Return whether an unknown error represents a missing filesystem path.
|
|
@@ -49,5 +58,5 @@ export function getNodeErrorCode(error) {
|
|
|
49
58
|
* @returns `true` when the error has Node.js code `ENOENT`.
|
|
50
59
|
*/
|
|
51
60
|
export function isFileNotFoundError(error) {
|
|
52
|
-
return
|
|
61
|
+
return getOptionalNodeErrorCode(error) === "ENOENT";
|
|
53
62
|
}
|
|
@@ -59,6 +59,9 @@ export function verifyProjectMigrations(projectDir, { all = false, fromMigration
|
|
|
59
59
|
return { verifiedVersions: targetVersions };
|
|
60
60
|
}
|
|
61
61
|
function recordWorkspaceMigrationTargetAlignment(projectDir, state, recordCheck) {
|
|
62
|
+
// `migrate doctor` is still a synchronous maintenance command: it shares
|
|
63
|
+
// sync migration project loading, generated artifact comparisons, and
|
|
64
|
+
// execFileSync verification with the rest of migration maintenance.
|
|
62
65
|
let invalidWorkspaceReason = null;
|
|
63
66
|
let workspace;
|
|
64
67
|
try {
|
|
@@ -217,6 +217,10 @@ export function renderPhpMigrationRegistryFile(state, entries) {
|
|
|
217
217
|
return `<?php
|
|
218
218
|
declare(strict_types=1);
|
|
219
219
|
|
|
220
|
+
if ( ! defined( 'ABSPATH' ) ) {
|
|
221
|
+
\texit;
|
|
222
|
+
}
|
|
223
|
+
|
|
220
224
|
/**
|
|
221
225
|
* Generated from advanced migration snapshots. Do not edit manually.
|
|
222
226
|
*/
|
|
@@ -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
|
const DEFAULT_VERSION_RANGE = '^0.0.0';
|
|
@@ -21,11 +22,6 @@ export const DEFAULT_WORDPRESS_DATA_VERSION = '^9.28.0';
|
|
|
21
22
|
export const DEFAULT_WORDPRESS_DATAVIEWS_VERSION = '^14.1.0';
|
|
22
23
|
export const DEFAULT_WP_TYPIA_DATAVIEWS_VERSION = '^0.1.1';
|
|
23
24
|
let cachedPackageVersions = null;
|
|
24
|
-
function getErrorCode(error) {
|
|
25
|
-
return typeof error === 'object' && error !== null && 'code' in error
|
|
26
|
-
? String(error.code)
|
|
27
|
-
: undefined;
|
|
28
|
-
}
|
|
29
25
|
function normalizeVersionRange(value) {
|
|
30
26
|
const trimmed = value?.trim();
|
|
31
27
|
if (!trimmed) {
|
|
@@ -82,7 +78,7 @@ function resolvePackageManifestLocation(packageJsonPath) {
|
|
|
82
78
|
};
|
|
83
79
|
}
|
|
84
80
|
catch (error) {
|
|
85
|
-
if (
|
|
81
|
+
if (getOptionalNodeErrorCode(error) === 'ENOENT') {
|
|
86
82
|
return {
|
|
87
83
|
cacheKey: `missing-file:${packageJsonPath}`,
|
|
88
84
|
packageJsonPath: null,
|
|
@@ -114,7 +110,7 @@ function resolveInstalledPackageManifestLocation(packageName) {
|
|
|
114
110
|
return resolvePackageManifestLocation(require.resolve(`${packageName}/package.json`));
|
|
115
111
|
}
|
|
116
112
|
catch (error) {
|
|
117
|
-
if (
|
|
113
|
+
if (getOptionalNodeErrorCode(error) === 'MODULE_NOT_FOUND') {
|
|
118
114
|
return {
|
|
119
115
|
cacheKey: `missing-module:${packageName}`,
|
|
120
116
|
packageJsonPath: null,
|
|
@@ -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 (
|
|
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 (
|
|
28
|
+
if (getOptionalNodeErrorCode(error) === "MODULE_NOT_FOUND") {
|
|
33
29
|
return null;
|
|
34
30
|
}
|
|
35
31
|
throw error;
|
|
@@ -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
|
-
|
|
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 {};
|