@wp-typia/project-tools 0.16.8 → 0.16.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/README.md +14 -4
- package/dist/runtime/block-generator-service.d.ts +5 -1
- package/dist/runtime/block-generator-service.js +7 -3
- package/dist/runtime/built-in-block-artifacts.js +388 -572
- package/dist/runtime/built-in-block-code-artifacts.js +96 -46
- package/dist/runtime/built-in-block-code-templates.d.ts +36 -0
- package/dist/runtime/built-in-block-code-templates.js +2234 -0
- package/dist/runtime/cli-add-block.d.ts +2 -1
- package/dist/runtime/cli-add-block.js +163 -25
- package/dist/runtime/cli-add-shared.d.ts +7 -0
- package/dist/runtime/cli-add-shared.js +4 -6
- package/dist/runtime/cli-add-workspace.js +56 -17
- package/dist/runtime/cli-core.d.ts +4 -0
- package/dist/runtime/cli-core.js +3 -0
- package/dist/runtime/cli-diagnostics.d.ts +58 -0
- package/dist/runtime/cli-diagnostics.js +101 -0
- package/dist/runtime/cli-doctor.d.ts +2 -1
- package/dist/runtime/cli-doctor.js +16 -5
- package/dist/runtime/cli-help.js +4 -4
- package/dist/runtime/cli-scaffold.d.ts +5 -1
- package/dist/runtime/cli-scaffold.js +138 -111
- package/dist/runtime/external-layer-selection.d.ts +14 -0
- package/dist/runtime/external-layer-selection.js +35 -0
- package/dist/runtime/index.d.ts +2 -2
- package/dist/runtime/index.js +1 -1
- package/dist/runtime/migration-render.d.ts +23 -1
- package/dist/runtime/migration-render.js +58 -10
- package/dist/runtime/migration-ui-capability.js +17 -8
- package/dist/runtime/migration-utils.d.ts +7 -6
- package/dist/runtime/migration-utils.js +76 -73
- package/dist/runtime/migrations.js +2 -2
- package/dist/runtime/object-utils.d.ts +8 -1
- package/dist/runtime/object-utils.js +21 -1
- package/dist/runtime/scaffold-apply-utils.d.ts +14 -2
- package/dist/runtime/scaffold-apply-utils.js +19 -6
- package/dist/runtime/scaffold-repository-reference.d.ts +22 -0
- package/dist/runtime/scaffold-repository-reference.js +119 -0
- package/dist/runtime/scaffold.d.ts +5 -1
- package/dist/runtime/scaffold.js +15 -37
- package/dist/runtime/template-layers.d.ts +6 -0
- package/dist/runtime/template-layers.js +20 -7
- package/dist/runtime/template-render.d.ts +13 -2
- package/dist/runtime/template-render.js +102 -71
- package/dist/runtime/template-source.d.ts +6 -5
- package/dist/runtime/template-source.js +284 -217
- package/package.json +8 -3
- package/templates/_shared/base/src/validator-toolkit.ts.mustache +2 -2
- package/templates/_shared/compound/core/scripts/add-compound-child.ts.mustache +61 -16
- package/templates/_shared/migration-ui/common/src/migrations/helpers.ts +19 -47
- package/templates/_shared/migration-ui/common/src/migrations/index.ts +40 -11
|
@@ -29,8 +29,9 @@ export declare function seedWorkspaceMigrationProject(projectDir: string, curren
|
|
|
29
29
|
* with unsupported templates, the command runs outside an official workspace,
|
|
30
30
|
* or target block paths already exist.
|
|
31
31
|
*/
|
|
32
|
-
export declare function runAddBlockCommand({ blockName, cwd, dataStorageMode, persistencePolicy, templateId, }: RunAddBlockCommandOptions): Promise<{
|
|
32
|
+
export declare function runAddBlockCommand({ blockName, cwd, dataStorageMode, externalLayerId, externalLayerSource, persistencePolicy, selectExternalLayerId, templateId, }: RunAddBlockCommandOptions): Promise<{
|
|
33
33
|
blockSlugs: string[];
|
|
34
34
|
projectDir: string;
|
|
35
35
|
templateId: AddBlockTemplateId;
|
|
36
|
+
warnings: string[];
|
|
36
37
|
}>;
|
|
@@ -7,11 +7,14 @@ import { ensureMigrationDirectories, parseMigrationConfig, writeInitialMigration
|
|
|
7
7
|
import { syncPersistenceRestArtifacts, } from "./persistence-rest-artifacts.js";
|
|
8
8
|
import { snapshotProjectVersion } from "./migrations.js";
|
|
9
9
|
import { getDefaultAnswers, scaffoldProject } from "./scaffold.js";
|
|
10
|
-
import { copyInterpolatedDirectory, } from "./template-render.js";
|
|
10
|
+
import { copyInterpolatedDirectory, listInterpolatedDirectoryOutputs, } from "./template-render.js";
|
|
11
11
|
import { SHARED_WORKSPACE_TEMPLATE_ROOT, } from "./template-registry.js";
|
|
12
12
|
import { appendWorkspaceInventoryEntries, } from "./workspace-inventory.js";
|
|
13
13
|
import { resolveWorkspaceProject, } from "./workspace-project.js";
|
|
14
14
|
import { ADD_BLOCK_TEMPLATE_IDS, buildWorkspacePhpPrefix, isAddBlockTemplateId, normalizeBlockSlug, patchFile, quoteTsString, readOptionalFile, rollbackWorkspaceMutation, snapshotWorkspaceFiles, } from "./cli-add-shared.js";
|
|
15
|
+
import { parseTemplateLocator, resolveTemplateSeed, } from "./template-source.js";
|
|
16
|
+
import { resolveExternalTemplateLayers, } from "./template-layers.js";
|
|
17
|
+
import { resolveOptionalInteractiveExternalLayerId, } from "./external-layer-selection.js";
|
|
15
18
|
const COLLECTION_IMPORT_LINE = "import '../../collection';";
|
|
16
19
|
const REST_MANIFEST_IMPORT_PATTERN = /import\s*\{[^}]*\bdefineEndpointManifest\b[^}]*\}\s*from\s*["']@wp-typia\/block-runtime\/metadata-core["'];?/m;
|
|
17
20
|
function buildServerTemplateRoot(persistencePolicy) {
|
|
@@ -159,6 +162,9 @@ async function ensureCollectionImport(filePath) {
|
|
|
159
162
|
if (source.includes(COLLECTION_IMPORT_LINE)) {
|
|
160
163
|
return source;
|
|
161
164
|
}
|
|
165
|
+
if (source.includes("import metadata from './block-metadata';")) {
|
|
166
|
+
return source.replace("import metadata from './block-metadata';", `${COLLECTION_IMPORT_LINE}\nimport metadata from './block-metadata';`);
|
|
167
|
+
}
|
|
162
168
|
if (source.includes("import metadata from './block.json';")) {
|
|
163
169
|
return source.replace("import metadata from './block.json';", `${COLLECTION_IMPORT_LINE}\nimport metadata from './block.json';`);
|
|
164
170
|
}
|
|
@@ -198,6 +204,7 @@ async function renderWorkspacePersistenceServerModule(projectDir, variables) {
|
|
|
198
204
|
const COMPOUND_SHARED_SUPPORT_FILES = ["hooks.ts", "validator-toolkit.ts"];
|
|
199
205
|
const LEGACY_ASSERT_PATTERN = /assert:\s*typia\.createAssert</u;
|
|
200
206
|
const LEGACY_MANIFEST_PATTERN = /\r?\n[ \t]*manifest:\s*currentManifest,/u;
|
|
207
|
+
const LEGACY_VALIDATOR_MANIFEST_IMPORT_PATTERN = /^[\uFEFF \t]*import\s+currentManifest\s+from\s*["']\.\/typia\.manifest\.json["'];?$/u;
|
|
201
208
|
const LEGACY_TOOLKIT_CALL_PATTERN = /createTemplateValidatorToolkit<\s*(?<typeName>[A-Za-z0-9_]+)\s*>\s*\(\s*\{/u;
|
|
202
209
|
const LEGACY_VALIDATOR_TOOLKIT_IMPORT_PATTERN = /from\s*["']\.\.\/\.\.\/validator-toolkit["']/u;
|
|
203
210
|
const TYPIA_IMPORT_PATTERN = /^[\uFEFF \t]*import\s+typia\s+from\s*["']typia["'];?/mu;
|
|
@@ -211,6 +218,21 @@ const COMPATIBLE_COMPOUND_TOOLKIT_PATTERNS = [
|
|
|
211
218
|
/\bvalidate\s*:\s*ScaffoldValidatorToolkitOptions\s*<\s*T\s*>\s*\[\s*["']validate["']\s*\]/u,
|
|
212
219
|
/createTemplateValidatorToolkit\s*<\s*T\s+extends\s+object\s*>\s*\(\s*\{/u,
|
|
213
220
|
];
|
|
221
|
+
function normalizeExternalLayerOption(value) {
|
|
222
|
+
if (typeof value !== "string") {
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
const trimmed = value.trim();
|
|
226
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
227
|
+
}
|
|
228
|
+
function resolveExternalLayerSourceFromCaller(source, callerCwd) {
|
|
229
|
+
if (typeof source !== "string" ||
|
|
230
|
+
source.length === 0 ||
|
|
231
|
+
!(path.isAbsolute(source) || source.startsWith("./") || source.startsWith("../"))) {
|
|
232
|
+
return source;
|
|
233
|
+
}
|
|
234
|
+
return path.resolve(callerCwd, source);
|
|
235
|
+
}
|
|
214
236
|
function shouldRefreshCompoundValidatorToolkit(source) {
|
|
215
237
|
return (source === null ||
|
|
216
238
|
!COMPATIBLE_COMPOUND_TOOLKIT_PATTERNS.every((pattern) => pattern.test(source)));
|
|
@@ -223,6 +245,36 @@ function isLegacyCompoundValidatorSource(source) {
|
|
|
223
245
|
function hasTypiaImport(source) {
|
|
224
246
|
return TYPIA_IMPORT_PATTERN.test(source.replace(/\/\*[\s\S]*?\*\//gu, ""));
|
|
225
247
|
}
|
|
248
|
+
function replaceFirstNonCommentLine(source, pattern, replacement) {
|
|
249
|
+
const lineEnding = source.includes("\r\n") ? "\r\n" : "\n";
|
|
250
|
+
const lines = source.split(/\r?\n/);
|
|
251
|
+
let inBlockComment = false;
|
|
252
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
253
|
+
const line = lines[index] ?? "";
|
|
254
|
+
const trimmed = line.trimStart();
|
|
255
|
+
if (inBlockComment) {
|
|
256
|
+
if (trimmed.includes("*/")) {
|
|
257
|
+
inBlockComment = false;
|
|
258
|
+
}
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
if (trimmed.startsWith("//")) {
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
if (trimmed.startsWith("/*")) {
|
|
265
|
+
if (!trimmed.includes("*/")) {
|
|
266
|
+
inBlockComment = true;
|
|
267
|
+
}
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
if (!pattern.test(line)) {
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
lines[index] = replacement;
|
|
274
|
+
return lines.join(lineEnding);
|
|
275
|
+
}
|
|
276
|
+
return source;
|
|
277
|
+
}
|
|
226
278
|
function upgradeLegacyCompoundValidatorSource(source) {
|
|
227
279
|
const typeNameMatch = source.match(LEGACY_TOOLKIT_CALL_PATTERN);
|
|
228
280
|
const typeName = typeNameMatch?.groups?.typeName;
|
|
@@ -233,6 +285,7 @@ function upgradeLegacyCompoundValidatorSource(source) {
|
|
|
233
285
|
if (!hasTypiaImport(nextSource)) {
|
|
234
286
|
nextSource = `import typia from 'typia';\n${nextSource}`;
|
|
235
287
|
}
|
|
288
|
+
nextSource = replaceFirstNonCommentLine(nextSource, LEGACY_VALIDATOR_MANIFEST_IMPORT_PATTERN, "import currentManifest from './manifest-defaults-document';");
|
|
236
289
|
nextSource = nextSource.replace(LEGACY_TOOLKIT_CALL_PATTERN, [
|
|
237
290
|
`createTemplateValidatorToolkit< ${typeName} >( {`,
|
|
238
291
|
`\tassert: typia.createAssert< ${typeName} >(),`,
|
|
@@ -255,6 +308,26 @@ function upgradeLegacyCompoundValidatorSource(source) {
|
|
|
255
308
|
}
|
|
256
309
|
return replacedManifest;
|
|
257
310
|
}
|
|
311
|
+
function renderLegacyManifestDefaultsWrapperSource() {
|
|
312
|
+
return [
|
|
313
|
+
"import rawCurrentManifest from './typia.manifest.json';",
|
|
314
|
+
"import { defineManifestDefaultsDocument } from '@wp-typia/block-runtime/defaults';",
|
|
315
|
+
"",
|
|
316
|
+
"const currentManifest = defineManifestDefaultsDocument( rawCurrentManifest );",
|
|
317
|
+
"",
|
|
318
|
+
"export default currentManifest;",
|
|
319
|
+
"",
|
|
320
|
+
].join("\n");
|
|
321
|
+
}
|
|
322
|
+
async function ensureLegacyCompoundValidatorManifestDefaultsWrapper(validatorPath) {
|
|
323
|
+
const validatorDir = path.dirname(validatorPath);
|
|
324
|
+
const wrapperPath = path.join(validatorDir, "manifest-defaults-document.ts");
|
|
325
|
+
const manifestPath = path.join(validatorDir, "typia.manifest.json");
|
|
326
|
+
if (fs.existsSync(wrapperPath) || !fs.existsSync(manifestPath)) {
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
await fsp.writeFile(wrapperPath, renderLegacyManifestDefaultsWrapperSource(), "utf8");
|
|
330
|
+
}
|
|
258
331
|
async function collectLegacyCompoundValidatorPaths(projectDir) {
|
|
259
332
|
const blocksDir = path.join(projectDir, "src", "blocks");
|
|
260
333
|
if (!fs.existsSync(blocksDir)) {
|
|
@@ -290,6 +363,7 @@ async function ensureCompoundWorkspaceSupportFiles(projectDir, tempProjectDir, l
|
|
|
290
363
|
if (!isLegacyCompoundValidatorSource(currentSource)) {
|
|
291
364
|
continue;
|
|
292
365
|
}
|
|
366
|
+
await ensureLegacyCompoundValidatorManifestDefaultsWrapper(validatorPath);
|
|
293
367
|
await fsp.writeFile(validatorPath, upgradeLegacyCompoundValidatorSource(currentSource), "utf8");
|
|
294
368
|
}
|
|
295
369
|
}
|
|
@@ -308,6 +382,47 @@ async function copyScaffoldedBlockSlice(projectDir, templateId, tempProjectDir,
|
|
|
308
382
|
await renderWorkspacePersistenceServerModule(projectDir, variables);
|
|
309
383
|
}
|
|
310
384
|
}
|
|
385
|
+
function isSupportedAddBlockLayerOutput(options) {
|
|
386
|
+
const { relativePath, templateId, variables } = options;
|
|
387
|
+
if (templateId === "compound") {
|
|
388
|
+
return (relativePath === "src/hooks.ts" ||
|
|
389
|
+
relativePath === "src/validator-toolkit.ts" ||
|
|
390
|
+
relativePath.startsWith(`src/blocks/${variables.slugKebabCase}/`) ||
|
|
391
|
+
relativePath.startsWith(`src/blocks/${variables.slugKebabCase}-item/`));
|
|
392
|
+
}
|
|
393
|
+
return relativePath.startsWith("src/");
|
|
394
|
+
}
|
|
395
|
+
async function assertAddBlockSupportsExternalLayerOutputs(options) {
|
|
396
|
+
const { callerCwd, externalLayerId, externalLayerSource, templateId, variables, } = options;
|
|
397
|
+
if (!externalLayerSource) {
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
const layerSeed = await resolveTemplateSeed(parseTemplateLocator(externalLayerSource), callerCwd);
|
|
401
|
+
try {
|
|
402
|
+
const resolvedLayers = await resolveExternalTemplateLayers({
|
|
403
|
+
externalLayerId,
|
|
404
|
+
sourceRoot: layerSeed.rootDir,
|
|
405
|
+
});
|
|
406
|
+
for (const entry of resolvedLayers.entries) {
|
|
407
|
+
if (entry.kind !== "external") {
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
for (const relativePath of await listInterpolatedDirectoryOutputs(entry.dir, variables)) {
|
|
411
|
+
if (isSupportedAddBlockLayerOutput({
|
|
412
|
+
relativePath,
|
|
413
|
+
templateId,
|
|
414
|
+
variables,
|
|
415
|
+
})) {
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
throw new Error(`External layer "${entry.id}" writes workspace-level output "${relativePath}", which \`wp-typia add block\` cannot merge safely. Restrict the layer to block-local files or scaffold it through \`wp-typia create\` instead.`);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
finally {
|
|
423
|
+
await layerSeed.cleanup?.();
|
|
424
|
+
}
|
|
425
|
+
}
|
|
311
426
|
function collectWorkspaceBlockPaths(projectDir, templateId, variables) {
|
|
312
427
|
if (templateId === "compound") {
|
|
313
428
|
return [
|
|
@@ -433,20 +548,28 @@ export async function seedWorkspaceMigrationProject(projectDir, currentMigration
|
|
|
433
548
|
* with unsupported templates, the command runs outside an official workspace,
|
|
434
549
|
* or target block paths already exist.
|
|
435
550
|
*/
|
|
436
|
-
export async function runAddBlockCommand({ blockName, cwd = process.cwd(), dataStorageMode, persistencePolicy, templateId = "basic", }) {
|
|
551
|
+
export async function runAddBlockCommand({ blockName, cwd = process.cwd(), dataStorageMode, externalLayerId, externalLayerSource, persistencePolicy, selectExternalLayerId, templateId = "basic", }) {
|
|
437
552
|
if (!isAddBlockTemplateId(templateId)) {
|
|
438
553
|
throw new Error(`Unknown add-block template "${templateId}". Expected one of: ${ADD_BLOCK_TEMPLATE_IDS.join(", ")}`);
|
|
439
554
|
}
|
|
440
555
|
const resolvedTemplateId = templateId;
|
|
441
556
|
assertPersistenceFlagsAllowed(resolvedTemplateId, { dataStorageMode, persistencePolicy });
|
|
442
557
|
const workspace = resolveWorkspaceProject(cwd);
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
558
|
+
const normalizedExternalLayerId = normalizeExternalLayerOption(externalLayerId);
|
|
559
|
+
const normalizedExternalLayerSource = resolveExternalLayerSourceFromCaller(normalizeExternalLayerOption(externalLayerSource), cwd);
|
|
560
|
+
const resolvedExternalLayerSelection = await resolveOptionalInteractiveExternalLayerId({
|
|
561
|
+
callerCwd: cwd,
|
|
562
|
+
externalLayerId: normalizedExternalLayerId,
|
|
563
|
+
externalLayerSource: normalizedExternalLayerSource,
|
|
564
|
+
selectExternalLayerId,
|
|
565
|
+
});
|
|
448
566
|
let tempRoot = "";
|
|
449
567
|
try {
|
|
568
|
+
const normalizedSlug = normalizeBlockSlug(blockName);
|
|
569
|
+
if (!normalizedSlug) {
|
|
570
|
+
throw new Error("Block name is required. Use `wp-typia add block <name> --template <family>`.");
|
|
571
|
+
}
|
|
572
|
+
const defaults = getDefaultAnswers(normalizedSlug, resolvedTemplateId);
|
|
450
573
|
tempRoot = await fsp.mkdtemp(path.join(os.tmpdir(), "wp-typia-add-block-"));
|
|
451
574
|
const tempProjectDir = path.join(tempRoot, normalizedSlug);
|
|
452
575
|
const blockConfigPath = path.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
@@ -460,24 +583,37 @@ export async function runAddBlockCommand({ blockName, cwd = process.cwd(), dataS
|
|
|
460
583
|
const legacyCompoundValidatorPaths = resolvedTemplateId === "compound"
|
|
461
584
|
? await collectLegacyCompoundValidatorPaths(workspace.projectDir)
|
|
462
585
|
: [];
|
|
463
|
-
const result = await
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
586
|
+
const result = await (async () => {
|
|
587
|
+
const scaffoldResult = await scaffoldProject({
|
|
588
|
+
answers: {
|
|
589
|
+
...defaults,
|
|
590
|
+
author: workspace.author,
|
|
591
|
+
namespace: workspace.workspace.namespace,
|
|
592
|
+
phpPrefix: blockPhpPrefix,
|
|
593
|
+
slug: normalizedSlug,
|
|
594
|
+
textDomain: workspace.workspace.textDomain,
|
|
595
|
+
title: defaults.title,
|
|
596
|
+
},
|
|
597
|
+
cwd: workspace.projectDir,
|
|
598
|
+
dataStorageMode: dataStorageMode,
|
|
599
|
+
externalLayerId: resolvedExternalLayerSelection.externalLayerId,
|
|
600
|
+
externalLayerSource: resolvedExternalLayerSelection.externalLayerSource,
|
|
601
|
+
externalLayerSourceLabel: normalizedExternalLayerSource,
|
|
602
|
+
noInstall: true,
|
|
603
|
+
packageManager: workspace.packageManager,
|
|
604
|
+
persistencePolicy: persistencePolicy,
|
|
605
|
+
projectDir: tempProjectDir,
|
|
606
|
+
templateId: resolvedTemplateId,
|
|
607
|
+
});
|
|
608
|
+
await assertAddBlockSupportsExternalLayerOutputs({
|
|
609
|
+
callerCwd: cwd,
|
|
610
|
+
externalLayerId: resolvedExternalLayerSelection.externalLayerId,
|
|
611
|
+
externalLayerSource: resolvedExternalLayerSelection.externalLayerSource,
|
|
612
|
+
templateId: resolvedTemplateId,
|
|
613
|
+
variables: scaffoldResult.variables,
|
|
614
|
+
});
|
|
615
|
+
return scaffoldResult;
|
|
616
|
+
})();
|
|
481
617
|
assertBlockTargetsDoNotExist(workspace.projectDir, resolvedTemplateId, result.variables);
|
|
482
618
|
const mutationSnapshot = {
|
|
483
619
|
fileSources: await snapshotWorkspaceFiles([
|
|
@@ -503,6 +639,7 @@ export async function runAddBlockCommand({ blockName, cwd = process.cwd(), dataS
|
|
|
503
639
|
blockSlugs: collectWorkspaceBlockPaths(workspace.projectDir, resolvedTemplateId, result.variables).map((targetPath) => path.basename(targetPath)),
|
|
504
640
|
projectDir: workspace.projectDir,
|
|
505
641
|
templateId: resolvedTemplateId,
|
|
642
|
+
warnings: result.warnings,
|
|
506
643
|
};
|
|
507
644
|
}
|
|
508
645
|
catch (error) {
|
|
@@ -511,6 +648,7 @@ export async function runAddBlockCommand({ blockName, cwd = process.cwd(), dataS
|
|
|
511
648
|
}
|
|
512
649
|
}
|
|
513
650
|
finally {
|
|
651
|
+
await resolvedExternalLayerSelection.cleanup?.();
|
|
514
652
|
if (tempRoot) {
|
|
515
653
|
await fsp.rm(tempRoot, { force: true, recursive: true });
|
|
516
654
|
}
|
|
@@ -34,7 +34,14 @@ export interface RunAddBlockCommandOptions {
|
|
|
34
34
|
blockName: string;
|
|
35
35
|
cwd?: string;
|
|
36
36
|
dataStorageMode?: string;
|
|
37
|
+
externalLayerId?: string;
|
|
38
|
+
externalLayerSource?: string;
|
|
37
39
|
persistencePolicy?: string;
|
|
40
|
+
selectExternalLayerId?: (options: Array<{
|
|
41
|
+
description?: string;
|
|
42
|
+
extends: string[];
|
|
43
|
+
id: string;
|
|
44
|
+
}>) => Promise<string>;
|
|
38
45
|
templateId?: string;
|
|
39
46
|
}
|
|
40
47
|
export interface WorkspaceMutationSnapshot {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import { promises as fsp } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { parseScaffoldBlockMetadata } from "@wp-typia/block-runtime/blocks";
|
|
4
5
|
import { HOOKED_BLOCK_ANCHOR_PATTERN, HOOKED_BLOCK_POSITION_IDS, } from "./hooked-blocks.js";
|
|
5
6
|
import { toKebabCase, toSnakeCase, } from "./string-case.js";
|
|
6
7
|
import { WORKSPACE_TEMPLATE_PACKAGE, } from "./workspace-project.js";
|
|
@@ -127,18 +128,15 @@ export function readWorkspaceBlockJson(projectDir, blockSlug) {
|
|
|
127
128
|
}
|
|
128
129
|
let blockJson;
|
|
129
130
|
try {
|
|
130
|
-
blockJson = JSON.parse(fs.readFileSync(blockJsonPath, "utf8"));
|
|
131
|
+
blockJson = parseScaffoldBlockMetadata(JSON.parse(fs.readFileSync(blockJsonPath, "utf8")));
|
|
131
132
|
}
|
|
132
133
|
catch (error) {
|
|
133
134
|
throw new Error(error instanceof Error
|
|
134
135
|
? `Failed to parse ${path.relative(projectDir, blockJsonPath)}: ${error.message}`
|
|
135
136
|
: `Failed to parse ${path.relative(projectDir, blockJsonPath)}.`);
|
|
136
137
|
}
|
|
137
|
-
if (!blockJson || typeof blockJson !== "object" || Array.isArray(blockJson)) {
|
|
138
|
-
throw new Error(`${path.relative(projectDir, blockJsonPath)} must contain a JSON object.`);
|
|
139
|
-
}
|
|
140
138
|
return {
|
|
141
|
-
blockJson
|
|
139
|
+
blockJson,
|
|
142
140
|
blockJsonPath,
|
|
143
141
|
};
|
|
144
142
|
}
|
|
@@ -186,7 +184,7 @@ export function assertBindingSourceDoesNotExist(projectDir, bindingSourceSlug, i
|
|
|
186
184
|
*/
|
|
187
185
|
export function formatAddHelpText() {
|
|
188
186
|
return `Usage:
|
|
189
|
-
wp-typia add block <name> --template <${ADD_BLOCK_TEMPLATE_IDS.join("|")}> [--data-storage <post-meta|custom-table>] [--persistence-policy <authenticated|public>]
|
|
187
|
+
wp-typia add block <name> --template <${ADD_BLOCK_TEMPLATE_IDS.join("|")}> [--external-layer-source <./path|github:owner/repo/path[#ref]|npm-package>] [--external-layer-id <layer-id>] [--data-storage <post-meta|custom-table>] [--persistence-policy <authenticated|public>]
|
|
190
188
|
wp-typia add variation <name> --block <block-slug>
|
|
191
189
|
wp-typia add pattern <name>
|
|
192
190
|
wp-typia add binding-source <name>
|
|
@@ -14,6 +14,9 @@ const BINDING_SOURCE_EDITOR_ASSET = "build/bindings/index.asset.php";
|
|
|
14
14
|
function escapeRegex(value) {
|
|
15
15
|
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
16
16
|
}
|
|
17
|
+
function quotePhpString(value) {
|
|
18
|
+
return `'${value.replace(/\\/gu, "\\\\").replace(/'/gu, "\\'")}'`;
|
|
19
|
+
}
|
|
17
20
|
function buildVariationConfigEntry(blockSlug, variationSlug) {
|
|
18
21
|
return [
|
|
19
22
|
"\t{",
|
|
@@ -61,7 +64,7 @@ function getVariationConstBindings(variationSlugs) {
|
|
|
61
64
|
function buildVariationSource(variationSlug, textDomain) {
|
|
62
65
|
const variationTitle = toTitleCase(variationSlug);
|
|
63
66
|
const variationConstName = buildVariationConstName(variationSlug);
|
|
64
|
-
return `import type { BlockVariation } from '@
|
|
67
|
+
return `import type { BlockVariation } from '@wp-typia/block-types/blocks/registration';
|
|
65
68
|
import { __ } from '@wordpress/i18n';
|
|
66
69
|
|
|
67
70
|
export const ${variationConstName} = {
|
|
@@ -120,8 +123,12 @@ register_block_pattern(
|
|
|
120
123
|
);
|
|
121
124
|
`;
|
|
122
125
|
}
|
|
123
|
-
function buildBindingSourceServerSource(bindingSourceSlug, namespace, textDomain) {
|
|
126
|
+
function buildBindingSourceServerSource(bindingSourceSlug, phpPrefix, namespace, textDomain) {
|
|
124
127
|
const bindingSourceTitle = toTitleCase(bindingSourceSlug);
|
|
128
|
+
const bindingSourcePhpId = bindingSourceSlug.replace(/-/g, "_");
|
|
129
|
+
const bindingSourceValueFunctionName = `${phpPrefix}_${bindingSourcePhpId}_binding_source_values`;
|
|
130
|
+
const bindingSourceResolveFunctionName = `${phpPrefix}_${bindingSourcePhpId}_resolve_binding_source_value`;
|
|
131
|
+
const starterValue = `${bindingSourceTitle} starter value`;
|
|
125
132
|
return `<?php
|
|
126
133
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
127
134
|
\treturn;
|
|
@@ -131,29 +138,55 @@ if ( ! function_exists( 'register_block_bindings_source' ) ) {
|
|
|
131
138
|
\treturn;
|
|
132
139
|
}
|
|
133
140
|
|
|
141
|
+
if ( ! function_exists( '${bindingSourceValueFunctionName}' ) ) {
|
|
142
|
+
\tfunction ${bindingSourceValueFunctionName}() : array {
|
|
143
|
+
\t\treturn array(
|
|
144
|
+
\t\t\t${quotePhpString(bindingSourceSlug)} => ${quotePhpString(starterValue)},
|
|
145
|
+
\t\t);
|
|
146
|
+
\t}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if ( ! function_exists( '${bindingSourceResolveFunctionName}' ) ) {
|
|
150
|
+
\tfunction ${bindingSourceResolveFunctionName}( array $source_args ) : string {
|
|
151
|
+
\t\t$field = isset( $source_args['field'] ) && is_string( $source_args['field'] )
|
|
152
|
+
\t\t\t? $source_args['field']
|
|
153
|
+
\t\t\t: '${bindingSourceSlug}';
|
|
154
|
+
\t\t$binding_source_values = ${bindingSourceValueFunctionName}();
|
|
155
|
+
\t\t$value = $binding_source_values[ $field ] ?? '';
|
|
156
|
+
|
|
157
|
+
\t\treturn is_string( $value ) ? $value : '';
|
|
158
|
+
\t}
|
|
159
|
+
}
|
|
160
|
+
|
|
134
161
|
register_block_bindings_source(
|
|
135
|
-
\t
|
|
162
|
+
\t${quotePhpString(`${namespace}/${bindingSourceSlug}`)},
|
|
136
163
|
\tarray(
|
|
137
|
-
\t\t'label' => __( ${
|
|
138
|
-
\t\t'get_value_callback' =>
|
|
139
|
-
\t\t\t$field = isset( $source_args['field'] ) && is_string( $source_args['field'] )
|
|
140
|
-
\t\t\t\t? $source_args['field']
|
|
141
|
-
\t\t\t\t: '${bindingSourceSlug}';
|
|
142
|
-
|
|
143
|
-
\t\t\treturn sprintf(
|
|
144
|
-
\t\t\t\t__( 'Replace %s with real binding source data.', '${textDomain}' ),
|
|
145
|
-
\t\t\t\t$field
|
|
146
|
-
\t\t\t);
|
|
147
|
-
\t\t},
|
|
164
|
+
\t\t'label' => __( ${quotePhpString(bindingSourceTitle)}, ${quotePhpString(textDomain)} ),
|
|
165
|
+
\t\t'get_value_callback' => ${quotePhpString(bindingSourceResolveFunctionName)},
|
|
148
166
|
\t)
|
|
149
167
|
);
|
|
150
168
|
`;
|
|
151
169
|
}
|
|
152
170
|
function buildBindingSourceEditorSource(bindingSourceSlug, namespace, textDomain) {
|
|
153
171
|
const bindingSourceTitle = toTitleCase(bindingSourceSlug);
|
|
172
|
+
const starterValue = `${bindingSourceTitle} starter value`;
|
|
154
173
|
return `import { registerBlockBindingsSource } from '@wordpress/blocks';
|
|
155
174
|
import { __ } from '@wordpress/i18n';
|
|
156
175
|
|
|
176
|
+
interface BindingSourceRegistration {
|
|
177
|
+
\targs?: {
|
|
178
|
+
\t\tfield?: string;
|
|
179
|
+
\t};
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const BINDING_SOURCE_VALUES: Record<string, string> = {
|
|
183
|
+
\t${quoteTsString(bindingSourceSlug)}: ${quoteTsString(starterValue)},
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
function resolveBindingSourceValue( field: string ): string {
|
|
187
|
+
\treturn BINDING_SOURCE_VALUES[ field ] ?? '';
|
|
188
|
+
}
|
|
189
|
+
|
|
157
190
|
registerBlockBindingsSource( {
|
|
158
191
|
\tname: ${quoteTsString(`${namespace}/${bindingSourceSlug}`)},
|
|
159
192
|
\tlabel: __( ${quoteTsString(bindingSourceTitle)}, ${quoteTsString(textDomain)} ),
|
|
@@ -170,8 +203,14 @@ registerBlockBindingsSource( {
|
|
|
170
203
|
\t},
|
|
171
204
|
\tgetValues( { bindings } ) {
|
|
172
205
|
\t\tconst values: Record<string, string> = {};
|
|
173
|
-
\t\tfor ( const attributeName of Object.
|
|
174
|
-
\t\t\
|
|
206
|
+
\t\tfor ( const [ attributeName, binding ] of Object.entries(
|
|
207
|
+
\t\t\tbindings as Record<string, BindingSourceRegistration>
|
|
208
|
+
\t\t) ) {
|
|
209
|
+
\t\t\tconst field =
|
|
210
|
+
\t\t\t\ttypeof binding?.args?.field === 'string'
|
|
211
|
+
\t\t\t\t\t? binding.args.field
|
|
212
|
+
\t\t\t\t\t: ${quoteTsString(bindingSourceSlug)};
|
|
213
|
+
\t\t\tvalues[ attributeName ] = resolveBindingSourceValue( field );
|
|
175
214
|
\t\t}
|
|
176
215
|
\t\treturn values;
|
|
177
216
|
\t},
|
|
@@ -512,7 +551,7 @@ export async function runAddBindingSourceCommand({ bindingSourceName, cwd = proc
|
|
|
512
551
|
try {
|
|
513
552
|
await fsp.mkdir(bindingSourceDir, { recursive: true });
|
|
514
553
|
await ensureBindingSourceBootstrapAnchors(workspace);
|
|
515
|
-
await fsp.writeFile(serverFilePath, buildBindingSourceServerSource(bindingSourceSlug, workspace.workspace.namespace, workspace.workspace.textDomain), "utf8");
|
|
554
|
+
await fsp.writeFile(serverFilePath, buildBindingSourceServerSource(bindingSourceSlug, workspace.workspace.phpPrefix, workspace.workspace.namespace, workspace.workspace.textDomain), "utf8");
|
|
516
555
|
await fsp.writeFile(editorFilePath, buildBindingSourceEditorSource(bindingSourceSlug, workspace.workspace.namespace, workspace.workspace.textDomain), "utf8");
|
|
517
556
|
await writeBindingSourceRegistry(workspace.projectDir, bindingSourceSlug);
|
|
518
557
|
await appendWorkspaceInventoryEntries(workspace.projectDir, {
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
* `getWorkspaceBlockSelectOptions`, and `seedWorkspaceMigrationProject` for
|
|
13
13
|
* explicit `wp-typia add` flows,
|
|
14
14
|
* `getDoctorChecks`, `runDoctor`, and `DoctorCheck` for diagnostics,
|
|
15
|
+
* `createCliCommandError` and `formatCliDiagnosticError` for shared
|
|
16
|
+
* non-interactive failure rendering,
|
|
15
17
|
* `formatHelpText` for top-level CLI usage output, scaffold helpers such as
|
|
16
18
|
* `createReadlinePrompt`, `getNextSteps`, `getOptionalOnboarding`,
|
|
17
19
|
* `runScaffoldFlow`, and `ReadlinePrompt` for interactive project creation,
|
|
@@ -21,6 +23,8 @@
|
|
|
21
23
|
* template inspection flows.
|
|
22
24
|
*/
|
|
23
25
|
export { getDoctorChecks, runDoctor, type DoctorCheck } from "./cli-doctor.js";
|
|
26
|
+
export { createCliCommandError, CliDiagnosticError, formatCliDiagnosticError, formatDoctorCheckLine, formatDoctorSummaryLine, getDoctorFailureDetailLines, getFailingDoctorChecks, isCliDiagnosticError, } from "./cli-diagnostics.js";
|
|
27
|
+
export type { CliDiagnosticMessage } from "./cli-diagnostics.js";
|
|
24
28
|
export { formatAddHelpText, getWorkspaceBlockSelectOptions, runAddBindingSourceCommand, runAddBlockCommand, runAddHookedBlockCommand, runAddPatternCommand, runAddVariationCommand, seedWorkspaceMigrationProject, } from "./cli-add.js";
|
|
25
29
|
export { HOOKED_BLOCK_POSITION_IDS } from "./hooked-blocks.js";
|
|
26
30
|
export type { HookedBlockPositionId } from "./hooked-blocks.js";
|
package/dist/runtime/cli-core.js
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
* `getWorkspaceBlockSelectOptions`, and `seedWorkspaceMigrationProject` for
|
|
13
13
|
* explicit `wp-typia add` flows,
|
|
14
14
|
* `getDoctorChecks`, `runDoctor`, and `DoctorCheck` for diagnostics,
|
|
15
|
+
* `createCliCommandError` and `formatCliDiagnosticError` for shared
|
|
16
|
+
* non-interactive failure rendering,
|
|
15
17
|
* `formatHelpText` for top-level CLI usage output, scaffold helpers such as
|
|
16
18
|
* `createReadlinePrompt`, `getNextSteps`, `getOptionalOnboarding`,
|
|
17
19
|
* `runScaffoldFlow`, and `ReadlinePrompt` for interactive project creation,
|
|
@@ -21,6 +23,7 @@
|
|
|
21
23
|
* template inspection flows.
|
|
22
24
|
*/
|
|
23
25
|
export { getDoctorChecks, runDoctor } from "./cli-doctor.js";
|
|
26
|
+
export { createCliCommandError, CliDiagnosticError, formatCliDiagnosticError, formatDoctorCheckLine, formatDoctorSummaryLine, getDoctorFailureDetailLines, getFailingDoctorChecks, isCliDiagnosticError, } from "./cli-diagnostics.js";
|
|
24
27
|
export { formatAddHelpText, getWorkspaceBlockSelectOptions, runAddBindingSourceCommand, runAddBlockCommand, runAddHookedBlockCommand, runAddPatternCommand, runAddVariationCommand, seedWorkspaceMigrationProject, } from "./cli-add.js";
|
|
25
28
|
export { HOOKED_BLOCK_POSITION_IDS } from "./hooked-blocks.js";
|
|
26
29
|
export { formatHelpText } from "./cli-help.js";
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared human-readable diagnostics for non-interactive `wp-typia` CLI flows.
|
|
3
|
+
*/
|
|
4
|
+
export interface CliDiagnosticMessage {
|
|
5
|
+
command: string;
|
|
6
|
+
detailLines: string[];
|
|
7
|
+
summary: string;
|
|
8
|
+
}
|
|
9
|
+
type DoctorCheckLike = {
|
|
10
|
+
detail: string;
|
|
11
|
+
label: string;
|
|
12
|
+
status: "pass" | "fail";
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Structured CLI failure carrying a stable summary/detail layout.
|
|
16
|
+
*/
|
|
17
|
+
export declare class CliDiagnosticError extends Error {
|
|
18
|
+
readonly command: string;
|
|
19
|
+
readonly detailLines: string[];
|
|
20
|
+
readonly summary: string;
|
|
21
|
+
constructor(message: CliDiagnosticMessage, options?: ErrorOptions);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Narrow an unknown error to the shared CLI diagnostic error shape.
|
|
25
|
+
*/
|
|
26
|
+
export declare function isCliDiagnosticError(error: unknown): error is CliDiagnosticError;
|
|
27
|
+
/**
|
|
28
|
+
* Build a shared diagnostic error for one CLI command failure.
|
|
29
|
+
*/
|
|
30
|
+
export declare function createCliCommandError(options: {
|
|
31
|
+
command: string;
|
|
32
|
+
detailLines?: string[];
|
|
33
|
+
error?: unknown;
|
|
34
|
+
summary?: string;
|
|
35
|
+
}): CliDiagnosticError;
|
|
36
|
+
/**
|
|
37
|
+
* Render a CLI diagnostic block. Non-diagnostic errors fall back to their
|
|
38
|
+
* plain message so existing non-command failures keep working.
|
|
39
|
+
*/
|
|
40
|
+
export declare function formatCliDiagnosticError(error: unknown): string;
|
|
41
|
+
/**
|
|
42
|
+
* Format one human-readable doctor check row.
|
|
43
|
+
*/
|
|
44
|
+
export declare function formatDoctorCheckLine(check: DoctorCheckLike): string;
|
|
45
|
+
/**
|
|
46
|
+
* Return the failing doctor checks from one doctor run.
|
|
47
|
+
*/
|
|
48
|
+
export declare function getFailingDoctorChecks<TCheck extends DoctorCheckLike>(checks: readonly TCheck[]): TCheck[];
|
|
49
|
+
/**
|
|
50
|
+
* Format the final doctor summary row.
|
|
51
|
+
*/
|
|
52
|
+
export declare function formatDoctorSummaryLine(checks: readonly DoctorCheckLike[]): string;
|
|
53
|
+
/**
|
|
54
|
+
* Build detail lines for doctor failures so the non-interactive formatter can
|
|
55
|
+
* restate the failed checks after the streaming rows.
|
|
56
|
+
*/
|
|
57
|
+
export declare function getDoctorFailureDetailLines(checks: readonly DoctorCheckLike[]): string[];
|
|
58
|
+
export {};
|