@wp-typia/project-tools 0.11.1 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/runtime/cli-add.d.ts +49 -3
- package/dist/runtime/cli-add.js +367 -78
- package/dist/runtime/cli-core.d.ts +3 -2
- package/dist/runtime/cli-core.js +3 -2
- package/dist/runtime/cli-doctor.js +202 -0
- package/dist/runtime/cli-help.js +6 -3
- package/dist/runtime/index.d.ts +12 -1
- package/dist/runtime/index.js +12 -1
- package/dist/runtime/migrations.d.ts +5 -5
- package/dist/runtime/migrations.js +45 -0
- package/dist/runtime/package-managers.js +1 -1
- package/dist/runtime/workspace-inventory.d.ts +84 -0
- package/dist/runtime/workspace-inventory.js +270 -0
- package/dist/runtime/workspace-project.d.ts +66 -0
- package/dist/runtime/workspace-project.js +152 -0
- package/package.json +2 -2
- package/templates/_shared/base/package.json.mustache +1 -1
- package/templates/_shared/compound/core/package.json.mustache +1 -1
- package/templates/_shared/compound/persistence/package.json.mustache +1 -1
- package/templates/_shared/persistence/core/package.json.mustache +1 -1
- package/templates/interactivity/package.json.mustache +1 -1
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
4
|
+
import ts from "typescript";
|
|
5
|
+
export const BLOCK_CONFIG_ENTRY_MARKER = "\t// wp-typia add block entries";
|
|
6
|
+
export const VARIATION_CONFIG_ENTRY_MARKER = "\t// wp-typia add variation entries";
|
|
7
|
+
export const PATTERN_CONFIG_ENTRY_MARKER = "\t// wp-typia add pattern entries";
|
|
8
|
+
const VARIATIONS_SECTION = `
|
|
9
|
+
|
|
10
|
+
export interface WorkspaceVariationConfig {
|
|
11
|
+
\tblock: string;
|
|
12
|
+
\tfile: string;
|
|
13
|
+
\tslug: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const VARIATIONS: WorkspaceVariationConfig[] = [
|
|
17
|
+
\t// wp-typia add variation entries
|
|
18
|
+
];
|
|
19
|
+
`;
|
|
20
|
+
const PATTERNS_SECTION = `
|
|
21
|
+
|
|
22
|
+
export interface WorkspacePatternConfig {
|
|
23
|
+
\tfile: string;
|
|
24
|
+
\tslug: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const PATTERNS: WorkspacePatternConfig[] = [
|
|
28
|
+
\t// wp-typia add pattern entries
|
|
29
|
+
];
|
|
30
|
+
`;
|
|
31
|
+
function getPropertyNameText(name) {
|
|
32
|
+
if (ts.isIdentifier(name) || ts.isStringLiteral(name)) {
|
|
33
|
+
return name.text;
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
function findExportedArrayLiteral(sourceFile, exportName) {
|
|
38
|
+
for (const statement of sourceFile.statements) {
|
|
39
|
+
if (!ts.isVariableStatement(statement)) {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (!statement.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
46
|
+
if (!ts.isIdentifier(declaration.name) || declaration.name.text !== exportName) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (declaration.initializer && ts.isArrayLiteralExpression(declaration.initializer)) {
|
|
50
|
+
return {
|
|
51
|
+
array: declaration.initializer,
|
|
52
|
+
found: true,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
array: null,
|
|
57
|
+
found: true,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
array: null,
|
|
63
|
+
found: false,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function getOptionalStringProperty(entryName, elementIndex, objectLiteral, key) {
|
|
67
|
+
for (const property of objectLiteral.properties) {
|
|
68
|
+
if (!ts.isPropertyAssignment(property)) {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const propertyName = getPropertyNameText(property.name);
|
|
72
|
+
if (propertyName !== key) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (ts.isStringLiteralLike(property.initializer)) {
|
|
76
|
+
return property.initializer.text;
|
|
77
|
+
}
|
|
78
|
+
throw new Error(`${entryName}[${elementIndex}] must use a string literal for "${key}" in scripts/block-config.ts.`);
|
|
79
|
+
}
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
function getRequiredStringProperty(entryName, elementIndex, objectLiteral, key) {
|
|
83
|
+
const value = getOptionalStringProperty(entryName, elementIndex, objectLiteral, key);
|
|
84
|
+
if (!value) {
|
|
85
|
+
throw new Error(`${entryName}[${elementIndex}] is missing required "${key}" in scripts/block-config.ts.`);
|
|
86
|
+
}
|
|
87
|
+
return value;
|
|
88
|
+
}
|
|
89
|
+
function parseBlockEntries(arrayLiteral) {
|
|
90
|
+
return arrayLiteral.elements.map((element, elementIndex) => {
|
|
91
|
+
if (!ts.isObjectLiteralExpression(element)) {
|
|
92
|
+
throw new Error(`BLOCKS[${elementIndex}] must be an object literal in scripts/block-config.ts.`);
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
apiTypesFile: getOptionalStringProperty("BLOCKS", elementIndex, element, "apiTypesFile"),
|
|
96
|
+
attributeTypeName: getOptionalStringProperty("BLOCKS", elementIndex, element, "attributeTypeName"),
|
|
97
|
+
openApiFile: getOptionalStringProperty("BLOCKS", elementIndex, element, "openApiFile"),
|
|
98
|
+
slug: getRequiredStringProperty("BLOCKS", elementIndex, element, "slug"),
|
|
99
|
+
typesFile: getRequiredStringProperty("BLOCKS", elementIndex, element, "typesFile"),
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
function parseVariationEntries(arrayLiteral) {
|
|
104
|
+
return arrayLiteral.elements.map((element, elementIndex) => {
|
|
105
|
+
if (!ts.isObjectLiteralExpression(element)) {
|
|
106
|
+
throw new Error(`VARIATIONS[${elementIndex}] must be an object literal in scripts/block-config.ts.`);
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
block: getRequiredStringProperty("VARIATIONS", elementIndex, element, "block"),
|
|
110
|
+
file: getRequiredStringProperty("VARIATIONS", elementIndex, element, "file"),
|
|
111
|
+
slug: getRequiredStringProperty("VARIATIONS", elementIndex, element, "slug"),
|
|
112
|
+
};
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
function parsePatternEntries(arrayLiteral) {
|
|
116
|
+
return arrayLiteral.elements.map((element, elementIndex) => {
|
|
117
|
+
if (!ts.isObjectLiteralExpression(element)) {
|
|
118
|
+
throw new Error(`PATTERNS[${elementIndex}] must be an object literal in scripts/block-config.ts.`);
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
file: getRequiredStringProperty("PATTERNS", elementIndex, element, "file"),
|
|
122
|
+
slug: getRequiredStringProperty("PATTERNS", elementIndex, element, "slug"),
|
|
123
|
+
};
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Parse workspace inventory entries from the source of `scripts/block-config.ts`.
|
|
128
|
+
*
|
|
129
|
+
* @param source Raw TypeScript source from `scripts/block-config.ts`.
|
|
130
|
+
* @returns Parsed inventory sections without the resolved `blockConfigPath`.
|
|
131
|
+
* @throws {Error} When `BLOCKS` is missing or any inventory entry is malformed.
|
|
132
|
+
*/
|
|
133
|
+
export function parseWorkspaceInventorySource(source) {
|
|
134
|
+
const sourceFile = ts.createSourceFile("block-config.ts", source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
135
|
+
const blockArray = findExportedArrayLiteral(sourceFile, "BLOCKS");
|
|
136
|
+
if (!blockArray.found || !blockArray.array) {
|
|
137
|
+
throw new Error("scripts/block-config.ts must export a BLOCKS array.");
|
|
138
|
+
}
|
|
139
|
+
const variationArray = findExportedArrayLiteral(sourceFile, "VARIATIONS");
|
|
140
|
+
const patternArray = findExportedArrayLiteral(sourceFile, "PATTERNS");
|
|
141
|
+
if (variationArray.found && !variationArray.array) {
|
|
142
|
+
throw new Error("scripts/block-config.ts must export VARIATIONS as an array literal.");
|
|
143
|
+
}
|
|
144
|
+
if (patternArray.found && !patternArray.array) {
|
|
145
|
+
throw new Error("scripts/block-config.ts must export PATTERNS as an array literal.");
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
blocks: parseBlockEntries(blockArray.array),
|
|
149
|
+
hasPatternsSection: patternArray.found,
|
|
150
|
+
hasVariationsSection: variationArray.found,
|
|
151
|
+
patterns: patternArray.array ? parsePatternEntries(patternArray.array) : [],
|
|
152
|
+
source,
|
|
153
|
+
variations: variationArray.array ? parseVariationEntries(variationArray.array) : [],
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Read and parse the canonical workspace inventory file.
|
|
158
|
+
*
|
|
159
|
+
* @param projectDir Workspace root directory.
|
|
160
|
+
* @returns Parsed `WorkspaceInventory` including the resolved `blockConfigPath`.
|
|
161
|
+
* @throws {Error} When `scripts/block-config.ts` is missing or invalid.
|
|
162
|
+
*/
|
|
163
|
+
export function readWorkspaceInventory(projectDir) {
|
|
164
|
+
const blockConfigPath = path.join(projectDir, "scripts", "block-config.ts");
|
|
165
|
+
let source;
|
|
166
|
+
try {
|
|
167
|
+
source = fs.readFileSync(blockConfigPath, "utf8");
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
if (typeof error === "object" &&
|
|
171
|
+
error !== null &&
|
|
172
|
+
"code" in error &&
|
|
173
|
+
error.code === "ENOENT") {
|
|
174
|
+
throw new Error(`Workspace inventory file is missing at ${blockConfigPath}. Expected scripts/block-config.ts to exist.`);
|
|
175
|
+
}
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
blockConfigPath,
|
|
180
|
+
...parseWorkspaceInventorySource(source),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Return select options for the current workspace block inventory.
|
|
185
|
+
*
|
|
186
|
+
* The `description` field mirrors `block.typesFile`, while `name` and `value`
|
|
187
|
+
* both map to the block slug for use in interactive add flows.
|
|
188
|
+
*
|
|
189
|
+
* @param projectDir Workspace root directory.
|
|
190
|
+
* @returns Block options for variation-target selection.
|
|
191
|
+
*/
|
|
192
|
+
export function getWorkspaceBlockSelectOptions(projectDir) {
|
|
193
|
+
return readWorkspaceInventory(projectDir).blocks.map((block) => ({
|
|
194
|
+
description: block.typesFile,
|
|
195
|
+
name: block.slug,
|
|
196
|
+
value: block.slug,
|
|
197
|
+
}));
|
|
198
|
+
}
|
|
199
|
+
function ensureWorkspaceInventorySections(source) {
|
|
200
|
+
let nextSource = source.trimEnd();
|
|
201
|
+
if (!/export\s+interface\s+WorkspaceVariationConfig\b/u.test(nextSource)) {
|
|
202
|
+
nextSource += VARIATIONS_SECTION;
|
|
203
|
+
}
|
|
204
|
+
else if (!/export\s+const\s+VARIATIONS\b/u.test(nextSource)) {
|
|
205
|
+
nextSource += `
|
|
206
|
+
|
|
207
|
+
export const VARIATIONS: WorkspaceVariationConfig[] = [
|
|
208
|
+
\t// wp-typia add variation entries
|
|
209
|
+
];
|
|
210
|
+
`;
|
|
211
|
+
}
|
|
212
|
+
if (!/export\s+interface\s+WorkspacePatternConfig\b/u.test(nextSource)) {
|
|
213
|
+
nextSource += PATTERNS_SECTION;
|
|
214
|
+
}
|
|
215
|
+
else if (!/export\s+const\s+PATTERNS\b/u.test(nextSource)) {
|
|
216
|
+
nextSource += `
|
|
217
|
+
|
|
218
|
+
export const PATTERNS: WorkspacePatternConfig[] = [
|
|
219
|
+
\t// wp-typia add pattern entries
|
|
220
|
+
];
|
|
221
|
+
`;
|
|
222
|
+
}
|
|
223
|
+
return `${nextSource}\n`;
|
|
224
|
+
}
|
|
225
|
+
function appendEntriesAtMarker(source, marker, entries) {
|
|
226
|
+
if (entries.length === 0) {
|
|
227
|
+
return source;
|
|
228
|
+
}
|
|
229
|
+
if (!source.includes(marker)) {
|
|
230
|
+
throw new Error(`Workspace inventory marker "${marker}" is missing in scripts/block-config.ts.`);
|
|
231
|
+
}
|
|
232
|
+
return source.replace(marker, `${entries.join("\n")}\n${marker}`);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Update `scripts/block-config.ts` source text with additional inventory entries.
|
|
236
|
+
*
|
|
237
|
+
* Missing `VARIATIONS` and `PATTERNS` sections are created automatically before
|
|
238
|
+
* new entries are appended at their marker comments. When provided,
|
|
239
|
+
* `transformSource` runs before any entries are inserted.
|
|
240
|
+
*
|
|
241
|
+
* @param source Existing `scripts/block-config.ts` source.
|
|
242
|
+
* @param options Entry lists plus an optional source transformer.
|
|
243
|
+
* @returns Updated source text with all requested inventory entries appended.
|
|
244
|
+
*/
|
|
245
|
+
export function updateWorkspaceInventorySource(source, { blockEntries = [], patternEntries = [], variationEntries = [], transformSource, } = {}) {
|
|
246
|
+
let nextSource = ensureWorkspaceInventorySections(source);
|
|
247
|
+
if (transformSource) {
|
|
248
|
+
nextSource = transformSource(nextSource);
|
|
249
|
+
}
|
|
250
|
+
nextSource = appendEntriesAtMarker(nextSource, BLOCK_CONFIG_ENTRY_MARKER, blockEntries);
|
|
251
|
+
nextSource = appendEntriesAtMarker(nextSource, VARIATION_CONFIG_ENTRY_MARKER, variationEntries);
|
|
252
|
+
nextSource = appendEntriesAtMarker(nextSource, PATTERN_CONFIG_ENTRY_MARKER, patternEntries);
|
|
253
|
+
return nextSource;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Append new entries to the canonical workspace inventory file on disk.
|
|
257
|
+
*
|
|
258
|
+
* @param projectDir Workspace root directory.
|
|
259
|
+
* @param options Entry lists and optional source transform passed through to
|
|
260
|
+
* `updateWorkspaceInventorySource`.
|
|
261
|
+
* @returns Resolves once `scripts/block-config.ts` has been updated if needed.
|
|
262
|
+
*/
|
|
263
|
+
export async function appendWorkspaceInventoryEntries(projectDir, options) {
|
|
264
|
+
const blockConfigPath = path.join(projectDir, "scripts", "block-config.ts");
|
|
265
|
+
const source = await readFile(blockConfigPath, "utf8");
|
|
266
|
+
const nextSource = updateWorkspaceInventorySource(source, options);
|
|
267
|
+
if (nextSource !== source) {
|
|
268
|
+
await writeFile(blockConfigPath, nextSource, "utf8");
|
|
269
|
+
}
|
|
270
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { PackageManagerId } from "./package-managers.js";
|
|
2
|
+
export declare const WORKSPACE_TEMPLATE_PACKAGE = "@wp-typia/create-workspace-template";
|
|
3
|
+
export interface WorkspacePackageJson {
|
|
4
|
+
author?: string;
|
|
5
|
+
name?: string;
|
|
6
|
+
packageManager?: string;
|
|
7
|
+
scripts?: Record<string, string>;
|
|
8
|
+
wpTypia?: {
|
|
9
|
+
namespace?: string;
|
|
10
|
+
phpPrefix?: string;
|
|
11
|
+
projectType?: string;
|
|
12
|
+
templatePackage?: string;
|
|
13
|
+
textDomain?: string;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export interface WorkspaceProject {
|
|
17
|
+
author: string;
|
|
18
|
+
packageManager: PackageManagerId;
|
|
19
|
+
packageName: string;
|
|
20
|
+
projectDir: string;
|
|
21
|
+
workspace: Required<NonNullable<WorkspacePackageJson["wpTypia"]>>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Parse a workspace package manifest from a project directory or `package.json` path.
|
|
25
|
+
*
|
|
26
|
+
* @param projectDirOrManifestPath Absolute or relative project directory, or a direct
|
|
27
|
+
* path to `package.json`.
|
|
28
|
+
* @returns The parsed workspace package manifest.
|
|
29
|
+
* @throws {Error} When the manifest cannot be parsed.
|
|
30
|
+
*/
|
|
31
|
+
export declare function parseWorkspacePackageJson(projectDirOrManifestPath: string): WorkspacePackageJson;
|
|
32
|
+
/**
|
|
33
|
+
* Explain why a nearby wp-typia workspace cannot be resolved from `startDir`.
|
|
34
|
+
*
|
|
35
|
+
* @param startDir Directory to begin walking upward from.
|
|
36
|
+
* @returns A human-readable validation error when a candidate workspace package
|
|
37
|
+
* manifest is found but its `wpTypia` metadata is invalid, or `null` when no
|
|
38
|
+
* invalid workspace candidate is discovered.
|
|
39
|
+
* @throws {Error} When a discovered `package.json` cannot be parsed.
|
|
40
|
+
*/
|
|
41
|
+
export declare function getInvalidWorkspaceProjectReason(startDir: string): string | null;
|
|
42
|
+
/**
|
|
43
|
+
* Parse a package-manager identifier from a `packageManager` field.
|
|
44
|
+
*
|
|
45
|
+
* @param packageManagerField Raw package-manager field such as `bun@1.3.11`.
|
|
46
|
+
* @returns A normalized `PackageManagerId`, defaulting to `"bun"` when the
|
|
47
|
+
* field is missing or unsupported.
|
|
48
|
+
*/
|
|
49
|
+
export declare function parseWorkspacePackageManagerId(packageManagerField: string | undefined): PackageManagerId;
|
|
50
|
+
/**
|
|
51
|
+
* Try to resolve the nearest official wp-typia workspace from `startDir`.
|
|
52
|
+
*
|
|
53
|
+
* @param startDir Directory to begin walking upward from.
|
|
54
|
+
* @returns The resolved `WorkspaceProject`, or `null` when no
|
|
55
|
+
* `WORKSPACE_TEMPLATE_PACKAGE` workspace is found.
|
|
56
|
+
* @throws {Error} When a discovered `package.json` cannot be parsed.
|
|
57
|
+
*/
|
|
58
|
+
export declare function tryResolveWorkspaceProject(startDir: string): WorkspaceProject | null;
|
|
59
|
+
/**
|
|
60
|
+
* Resolve the nearest official wp-typia workspace from `startDir`.
|
|
61
|
+
*
|
|
62
|
+
* @param startDir Directory to begin walking upward from.
|
|
63
|
+
* @returns The resolved `WorkspaceProject`.
|
|
64
|
+
* @throws {Error} When no `WORKSPACE_TEMPLATE_PACKAGE` workspace can be found.
|
|
65
|
+
*/
|
|
66
|
+
export declare function resolveWorkspaceProject(startDir: string): WorkspaceProject;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export const WORKSPACE_TEMPLATE_PACKAGE = "@wp-typia/create-workspace-template";
|
|
4
|
+
function hasNonEmptyString(value) {
|
|
5
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Parse a workspace package manifest from a project directory or `package.json` path.
|
|
9
|
+
*
|
|
10
|
+
* @param projectDirOrManifestPath Absolute or relative project directory, or a direct
|
|
11
|
+
* path to `package.json`.
|
|
12
|
+
* @returns The parsed workspace package manifest.
|
|
13
|
+
* @throws {Error} When the manifest cannot be parsed.
|
|
14
|
+
*/
|
|
15
|
+
export function parseWorkspacePackageJson(projectDirOrManifestPath) {
|
|
16
|
+
const packageJsonPath = path.basename(projectDirOrManifestPath) === "package.json"
|
|
17
|
+
? projectDirOrManifestPath
|
|
18
|
+
: path.join(projectDirOrManifestPath, "package.json");
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
throw new Error(`Failed to parse workspace package manifest at ${packageJsonPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function getWorkspaceMetadataIssues(packageJson) {
|
|
27
|
+
if (!packageJson.wpTypia) {
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
const issues = [];
|
|
31
|
+
if (packageJson.wpTypia.projectType !== "workspace") {
|
|
32
|
+
issues.push('wpTypia.projectType must be "workspace"');
|
|
33
|
+
}
|
|
34
|
+
if (packageJson.wpTypia.templatePackage !== WORKSPACE_TEMPLATE_PACKAGE) {
|
|
35
|
+
issues.push(`wpTypia.templatePackage must be "${WORKSPACE_TEMPLATE_PACKAGE}"`);
|
|
36
|
+
}
|
|
37
|
+
if (!hasNonEmptyString(packageJson.wpTypia.namespace)) {
|
|
38
|
+
issues.push("wpTypia.namespace must be a non-empty string");
|
|
39
|
+
}
|
|
40
|
+
if (!hasNonEmptyString(packageJson.wpTypia.textDomain)) {
|
|
41
|
+
issues.push("wpTypia.textDomain must be a non-empty string");
|
|
42
|
+
}
|
|
43
|
+
if (!hasNonEmptyString(packageJson.wpTypia.phpPrefix)) {
|
|
44
|
+
issues.push("wpTypia.phpPrefix must be a non-empty string");
|
|
45
|
+
}
|
|
46
|
+
return issues;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Explain why a nearby wp-typia workspace cannot be resolved from `startDir`.
|
|
50
|
+
*
|
|
51
|
+
* @param startDir Directory to begin walking upward from.
|
|
52
|
+
* @returns A human-readable validation error when a candidate workspace package
|
|
53
|
+
* manifest is found but its `wpTypia` metadata is invalid, or `null` when no
|
|
54
|
+
* invalid workspace candidate is discovered.
|
|
55
|
+
* @throws {Error} When a discovered `package.json` cannot be parsed.
|
|
56
|
+
*/
|
|
57
|
+
export function getInvalidWorkspaceProjectReason(startDir) {
|
|
58
|
+
let currentDir = path.resolve(startDir);
|
|
59
|
+
while (true) {
|
|
60
|
+
const packageJsonPath = path.join(currentDir, "package.json");
|
|
61
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
62
|
+
const packageJson = parseWorkspacePackageJson(packageJsonPath);
|
|
63
|
+
const issues = getWorkspaceMetadataIssues(packageJson);
|
|
64
|
+
if (issues.length > 0) {
|
|
65
|
+
return `Invalid wp-typia workspace metadata at ${packageJsonPath}: ${issues.join("; ")}`;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const parentDir = path.dirname(currentDir);
|
|
69
|
+
if (parentDir === currentDir) {
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
currentDir = parentDir;
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Parse a package-manager identifier from a `packageManager` field.
|
|
78
|
+
*
|
|
79
|
+
* @param packageManagerField Raw package-manager field such as `bun@1.3.11`.
|
|
80
|
+
* @returns A normalized `PackageManagerId`, defaulting to `"bun"` when the
|
|
81
|
+
* field is missing or unsupported.
|
|
82
|
+
*/
|
|
83
|
+
export function parseWorkspacePackageManagerId(packageManagerField) {
|
|
84
|
+
const packageManagerId = packageManagerField?.split("@", 1)[0];
|
|
85
|
+
switch (packageManagerId) {
|
|
86
|
+
case "bun":
|
|
87
|
+
case "npm":
|
|
88
|
+
case "pnpm":
|
|
89
|
+
case "yarn":
|
|
90
|
+
return packageManagerId;
|
|
91
|
+
default:
|
|
92
|
+
return "bun";
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Try to resolve the nearest official wp-typia workspace from `startDir`.
|
|
97
|
+
*
|
|
98
|
+
* @param startDir Directory to begin walking upward from.
|
|
99
|
+
* @returns The resolved `WorkspaceProject`, or `null` when no
|
|
100
|
+
* `WORKSPACE_TEMPLATE_PACKAGE` workspace is found.
|
|
101
|
+
* @throws {Error} When a discovered `package.json` cannot be parsed.
|
|
102
|
+
*/
|
|
103
|
+
export function tryResolveWorkspaceProject(startDir) {
|
|
104
|
+
let currentDir = path.resolve(startDir);
|
|
105
|
+
while (true) {
|
|
106
|
+
const packageJsonPath = path.join(currentDir, "package.json");
|
|
107
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
108
|
+
const packageJson = parseWorkspacePackageJson(packageJsonPath);
|
|
109
|
+
if (packageJson.wpTypia?.projectType === "workspace" &&
|
|
110
|
+
packageJson.wpTypia?.templatePackage === WORKSPACE_TEMPLATE_PACKAGE &&
|
|
111
|
+
hasNonEmptyString(packageJson.wpTypia.namespace) &&
|
|
112
|
+
hasNonEmptyString(packageJson.wpTypia.textDomain) &&
|
|
113
|
+
hasNonEmptyString(packageJson.wpTypia.phpPrefix)) {
|
|
114
|
+
return {
|
|
115
|
+
author: typeof packageJson.author === "string" ? packageJson.author : "Your Name",
|
|
116
|
+
packageManager: parseWorkspacePackageManagerId(packageJson.packageManager),
|
|
117
|
+
packageName: typeof packageJson.name === "string"
|
|
118
|
+
? packageJson.name
|
|
119
|
+
: path.basename(currentDir),
|
|
120
|
+
projectDir: currentDir,
|
|
121
|
+
workspace: {
|
|
122
|
+
namespace: packageJson.wpTypia.namespace,
|
|
123
|
+
phpPrefix: packageJson.wpTypia.phpPrefix,
|
|
124
|
+
projectType: "workspace",
|
|
125
|
+
templatePackage: WORKSPACE_TEMPLATE_PACKAGE,
|
|
126
|
+
textDomain: packageJson.wpTypia.textDomain,
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const parentDir = path.dirname(currentDir);
|
|
132
|
+
if (parentDir === currentDir) {
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
currentDir = parentDir;
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Resolve the nearest official wp-typia workspace from `startDir`.
|
|
141
|
+
*
|
|
142
|
+
* @param startDir Directory to begin walking upward from.
|
|
143
|
+
* @returns The resolved `WorkspaceProject`.
|
|
144
|
+
* @throws {Error} When no `WORKSPACE_TEMPLATE_PACKAGE` workspace can be found.
|
|
145
|
+
*/
|
|
146
|
+
export function resolveWorkspaceProject(startDir) {
|
|
147
|
+
const workspace = tryResolveWorkspaceProject(startDir);
|
|
148
|
+
if (workspace) {
|
|
149
|
+
return workspace;
|
|
150
|
+
}
|
|
151
|
+
throw new Error(`This command must run inside a ${WORKSPACE_TEMPLATE_PACKAGE} project. Create one with \`wp-typia create my-plugin --template ${WORKSPACE_TEMPLATE_PACKAGE}\` first.`);
|
|
152
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wp-typia/project-tools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Project orchestration and programmatic tooling for wp-typia",
|
|
5
5
|
"packageManager": "bun@1.3.11",
|
|
6
6
|
"type": "module",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
64
|
"@wp-typia/api-client": "^0.4.0",
|
|
65
|
-
"@wp-typia/block-runtime": "^0.
|
|
65
|
+
"@wp-typia/block-runtime": "^0.4.0",
|
|
66
66
|
"@wp-typia/rest": "^0.3.1",
|
|
67
67
|
"@wp-typia/block-types": "^0.2.0",
|
|
68
68
|
"mustache": "^4.2.0",
|