@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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getWorkspaceBlockSelectOptions } from "./workspace-inventory.js";
|
|
1
2
|
/**
|
|
2
3
|
* Supported top-level `wp-typia add` kinds exposed by the canonical CLI.
|
|
3
4
|
*/
|
|
@@ -8,6 +9,15 @@ export type AddKindId = (typeof ADD_KIND_IDS)[number];
|
|
|
8
9
|
*/
|
|
9
10
|
export declare const ADD_BLOCK_TEMPLATE_IDS: readonly ["basic", "interactivity", "persistence", "compound"];
|
|
10
11
|
export type AddBlockTemplateId = (typeof ADD_BLOCK_TEMPLATE_IDS)[number];
|
|
12
|
+
interface RunAddVariationCommandOptions {
|
|
13
|
+
blockName: string;
|
|
14
|
+
cwd?: string;
|
|
15
|
+
variationName: string;
|
|
16
|
+
}
|
|
17
|
+
interface RunAddPatternCommandOptions {
|
|
18
|
+
cwd?: string;
|
|
19
|
+
patternName: string;
|
|
20
|
+
}
|
|
11
21
|
interface RunAddBlockCommandOptions {
|
|
12
22
|
blockName: string;
|
|
13
23
|
cwd?: string;
|
|
@@ -32,7 +42,43 @@ export declare function runAddBlockCommand({ blockName, cwd, dataStorageMode, pe
|
|
|
32
42
|
templateId: AddBlockTemplateId;
|
|
33
43
|
}>;
|
|
34
44
|
/**
|
|
35
|
-
*
|
|
45
|
+
* Add one variation entry to an existing workspace block.
|
|
46
|
+
*
|
|
47
|
+
* @param options Command options for the variation scaffold workflow.
|
|
48
|
+
* @param options.blockName Target workspace block slug that will own the variation.
|
|
49
|
+
* @param options.cwd Working directory used to resolve the nearest official workspace.
|
|
50
|
+
* Defaults to `process.cwd()`.
|
|
51
|
+
* @param options.variationName Human-entered variation name that will be normalized
|
|
52
|
+
* and validated before files are written.
|
|
53
|
+
* @returns A promise that resolves with the normalized `blockSlug`,
|
|
54
|
+
* `variationSlug`, and owning `projectDir` after the variation files and
|
|
55
|
+
* inventory entry have been written successfully.
|
|
56
|
+
* @throws {Error} When the command is run outside an official workspace, when
|
|
57
|
+
* the target block is unknown, when the variation slug is invalid, or when a
|
|
58
|
+
* conflicting file or inventory entry already exists.
|
|
36
59
|
*/
|
|
37
|
-
export declare function
|
|
38
|
-
|
|
60
|
+
export declare function runAddVariationCommand({ blockName, cwd, variationName, }: RunAddVariationCommandOptions): Promise<{
|
|
61
|
+
blockSlug: string;
|
|
62
|
+
projectDir: string;
|
|
63
|
+
variationSlug: string;
|
|
64
|
+
}>;
|
|
65
|
+
/**
|
|
66
|
+
* Add one PHP block pattern shell to an official workspace project.
|
|
67
|
+
*
|
|
68
|
+
* @param options Command options for the pattern scaffold workflow.
|
|
69
|
+
* @param options.cwd Working directory used to resolve the nearest official workspace.
|
|
70
|
+
* Defaults to `process.cwd()`.
|
|
71
|
+
* @param options.patternName Human-entered pattern name that will be normalized
|
|
72
|
+
* and validated before files are written.
|
|
73
|
+
* @returns A promise that resolves with the normalized `patternSlug` and
|
|
74
|
+
* owning `projectDir` after the pattern file and inventory entry have been
|
|
75
|
+
* written successfully.
|
|
76
|
+
* @throws {Error} When the command is run outside an official workspace, when
|
|
77
|
+
* the pattern slug is invalid, or when a conflicting file or inventory entry
|
|
78
|
+
* already exists.
|
|
79
|
+
*/
|
|
80
|
+
export declare function runAddPatternCommand({ cwd, patternName, }: RunAddPatternCommandOptions): Promise<{
|
|
81
|
+
patternSlug: string;
|
|
82
|
+
projectDir: string;
|
|
83
|
+
}>;
|
|
84
|
+
export { getWorkspaceBlockSelectOptions };
|
package/dist/runtime/cli-add.js
CHANGED
|
@@ -8,7 +8,9 @@ import { snapshotProjectVersion } from "./migrations.js";
|
|
|
8
8
|
import { getDefaultAnswers, scaffoldProject } from "./scaffold.js";
|
|
9
9
|
import { SHARED_WORKSPACE_TEMPLATE_ROOT, } from "./template-registry.js";
|
|
10
10
|
import { copyInterpolatedDirectory } from "./template-render.js";
|
|
11
|
-
import { toKebabCase, toSnakeCase, } from "./string-case.js";
|
|
11
|
+
import { toKebabCase, toTitleCase, toSnakeCase, } from "./string-case.js";
|
|
12
|
+
import { appendWorkspaceInventoryEntries, getWorkspaceBlockSelectOptions, readWorkspaceInventory, } from "./workspace-inventory.js";
|
|
13
|
+
import { resolveWorkspaceProject, WORKSPACE_TEMPLATE_PACKAGE, } from "./workspace-project.js";
|
|
12
14
|
/**
|
|
13
15
|
* Supported top-level `wp-typia add` kinds exposed by the canonical CLI.
|
|
14
16
|
*/
|
|
@@ -22,26 +24,28 @@ export const ADD_BLOCK_TEMPLATE_IDS = [
|
|
|
22
24
|
"persistence",
|
|
23
25
|
"compound",
|
|
24
26
|
];
|
|
25
|
-
const WORKSPACE_TEMPLATE_PACKAGE = "@wp-typia/create-workspace-template";
|
|
26
|
-
const BLOCK_CONFIG_ENTRY_MARKER = "\t// wp-typia add block entries";
|
|
27
27
|
const COLLECTION_IMPORT_LINE = "import '../../collection';";
|
|
28
|
-
const EMPTY_BLOCKS_ARRAY = `${BLOCK_CONFIG_ENTRY_MARKER}\n];`;
|
|
29
28
|
const REST_MANIFEST_IMPORT_PATTERN = /import\s*\{[^}]*\bdefineEndpointManifest\b[^}]*\}\s*from\s*["']@wp-typia\/block-runtime\/metadata-core["'];?/m;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
case "npm":
|
|
35
|
-
case "pnpm":
|
|
36
|
-
case "yarn":
|
|
37
|
-
return packageManagerId;
|
|
38
|
-
default:
|
|
39
|
-
return "bun";
|
|
40
|
-
}
|
|
41
|
-
}
|
|
29
|
+
const VARIATIONS_IMPORT_LINE = "import { registerWorkspaceVariations } from './variations';";
|
|
30
|
+
const VARIATIONS_CALL_LINE = "registerWorkspaceVariations();";
|
|
31
|
+
const PATTERN_BOOTSTRAP_CATEGORY = "register_block_pattern_category";
|
|
32
|
+
const WORKSPACE_GENERATED_SLUG_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
42
33
|
function normalizeBlockSlug(input) {
|
|
43
34
|
return toKebabCase(input);
|
|
44
35
|
}
|
|
36
|
+
function assertValidGeneratedSlug(label, slug, usage) {
|
|
37
|
+
if (!slug) {
|
|
38
|
+
throw new Error(`${label} is required. Use \`${usage}\`.`);
|
|
39
|
+
}
|
|
40
|
+
if (!WORKSPACE_GENERATED_SLUG_PATTERN.test(slug)) {
|
|
41
|
+
throw new Error(`${label} must start with a letter and contain only lowercase letters, numbers, and hyphens.`);
|
|
42
|
+
}
|
|
43
|
+
return slug;
|
|
44
|
+
}
|
|
45
|
+
function getWorkspaceBootstrapPath(workspace) {
|
|
46
|
+
const workspaceBaseName = workspace.packageName.split("/").pop() ?? workspace.packageName;
|
|
47
|
+
return path.join(workspace.projectDir, `${workspaceBaseName}.php`);
|
|
48
|
+
}
|
|
45
49
|
function buildWorkspacePhpPrefix(workspacePhpPrefix, slug) {
|
|
46
50
|
return toSnakeCase(`${workspacePhpPrefix}_${slug}`);
|
|
47
51
|
}
|
|
@@ -263,6 +267,205 @@ async function addCollectionImportsForTemplate(projectDir, templateId, variables
|
|
|
263
267
|
}
|
|
264
268
|
await ensureCollectionImport(path.join(projectDir, "src", "blocks", variables.slugKebabCase, "index.tsx"));
|
|
265
269
|
}
|
|
270
|
+
function buildVariationConfigEntry(blockSlug, variationSlug) {
|
|
271
|
+
return [
|
|
272
|
+
"\t{",
|
|
273
|
+
`\t\tblock: ${quoteTsString(blockSlug)},`,
|
|
274
|
+
`\t\tfile: ${quoteTsString(`src/blocks/${blockSlug}/variations/${variationSlug}.ts`)},`,
|
|
275
|
+
`\t\tslug: ${quoteTsString(variationSlug)},`,
|
|
276
|
+
"\t},",
|
|
277
|
+
].join("\n");
|
|
278
|
+
}
|
|
279
|
+
function buildPatternConfigEntry(patternSlug) {
|
|
280
|
+
return [
|
|
281
|
+
"\t{",
|
|
282
|
+
`\t\tfile: ${quoteTsString(`src/patterns/${patternSlug}.php`)},`,
|
|
283
|
+
`\t\tslug: ${quoteTsString(patternSlug)},`,
|
|
284
|
+
"\t},",
|
|
285
|
+
].join("\n");
|
|
286
|
+
}
|
|
287
|
+
function buildVariationConstName(variationSlug) {
|
|
288
|
+
const identifierSegments = toKebabCase(variationSlug)
|
|
289
|
+
.split("-")
|
|
290
|
+
.filter(Boolean);
|
|
291
|
+
return `workspaceVariation_${identifierSegments.join("_")}`;
|
|
292
|
+
}
|
|
293
|
+
function getVariationConstBindings(variationSlugs) {
|
|
294
|
+
const seenConstNames = new Map();
|
|
295
|
+
return variationSlugs.map((variationSlug) => {
|
|
296
|
+
const constName = buildVariationConstName(variationSlug);
|
|
297
|
+
const previousSlug = seenConstNames.get(constName);
|
|
298
|
+
if (previousSlug && previousSlug !== variationSlug) {
|
|
299
|
+
throw new Error(`Variation slugs "${previousSlug}" and "${variationSlug}" generate the same registry identifier "${constName}". Rename one of the variations.`);
|
|
300
|
+
}
|
|
301
|
+
seenConstNames.set(constName, variationSlug);
|
|
302
|
+
return { constName, variationSlug };
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
function buildVariationSource(variationSlug, textDomain) {
|
|
306
|
+
const variationTitle = toTitleCase(variationSlug);
|
|
307
|
+
const variationConstName = buildVariationConstName(variationSlug);
|
|
308
|
+
return `import type { BlockVariation } from '@wordpress/blocks';
|
|
309
|
+
import { __ } from '@wordpress/i18n';
|
|
310
|
+
|
|
311
|
+
export const ${variationConstName} = {
|
|
312
|
+
\tname: ${quoteTsString(variationSlug)},
|
|
313
|
+
\ttitle: __( ${quoteTsString(variationTitle)}, ${quoteTsString(textDomain)} ),
|
|
314
|
+
\tdescription: __(
|
|
315
|
+
\t\t${quoteTsString(`A starter variation for ${variationTitle}.`)},
|
|
316
|
+
\t\t${quoteTsString(textDomain)},
|
|
317
|
+
\t),
|
|
318
|
+
\tattributes: {},
|
|
319
|
+
\tscope: ['inserter'],
|
|
320
|
+
} satisfies BlockVariation;
|
|
321
|
+
`;
|
|
322
|
+
}
|
|
323
|
+
function buildVariationIndexSource(variationSlugs) {
|
|
324
|
+
const variationBindings = getVariationConstBindings(variationSlugs);
|
|
325
|
+
const importLines = variationBindings
|
|
326
|
+
.map(({ constName, variationSlug }) => {
|
|
327
|
+
return `import { ${constName} } from './${variationSlug}';`;
|
|
328
|
+
})
|
|
329
|
+
.join("\n");
|
|
330
|
+
const variationConstNames = variationBindings
|
|
331
|
+
.map(({ constName }) => constName)
|
|
332
|
+
.join(",\n\t\t");
|
|
333
|
+
return `import { registerBlockVariation } from '@wordpress/blocks';
|
|
334
|
+
import metadata from '../block.json';
|
|
335
|
+
${importLines ? `\n${importLines}` : ""}
|
|
336
|
+
|
|
337
|
+
const WORKSPACE_VARIATIONS = [
|
|
338
|
+
\t${variationConstNames}
|
|
339
|
+
\t// wp-typia add variation entries
|
|
340
|
+
];
|
|
341
|
+
|
|
342
|
+
export function registerWorkspaceVariations() {
|
|
343
|
+
\tfor (const variation of WORKSPACE_VARIATIONS) {
|
|
344
|
+
\t\tregisterBlockVariation(metadata.name, variation);
|
|
345
|
+
\t}
|
|
346
|
+
}
|
|
347
|
+
`;
|
|
348
|
+
}
|
|
349
|
+
function buildPatternSource(patternSlug, namespace, textDomain) {
|
|
350
|
+
const patternTitle = toTitleCase(patternSlug);
|
|
351
|
+
return `<?php
|
|
352
|
+
if ( ! defined( 'ABSPATH' ) ) {
|
|
353
|
+
\treturn;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
register_block_pattern(
|
|
357
|
+
\t'${namespace}/${patternSlug}',
|
|
358
|
+
\tarray(
|
|
359
|
+
\t\t'title' => __( ${JSON.stringify(patternTitle)}, '${textDomain}' ),
|
|
360
|
+
\t\t'description' => __( ${JSON.stringify(`A starter pattern for ${patternTitle}.`)}, '${textDomain}' ),
|
|
361
|
+
\t\t'categories' => array( '${namespace}' ),
|
|
362
|
+
\t\t'content' => '<!-- wp:paragraph --><p>' . esc_html__( 'Describe this pattern here.', '${textDomain}' ) . '</p><!-- /wp:paragraph -->',
|
|
363
|
+
\t)
|
|
364
|
+
);
|
|
365
|
+
`;
|
|
366
|
+
}
|
|
367
|
+
async function ensureVariationRegistrationHook(blockIndexPath) {
|
|
368
|
+
await patchFile(blockIndexPath, (source) => {
|
|
369
|
+
let nextSource = source;
|
|
370
|
+
if (!nextSource.includes(VARIATIONS_IMPORT_LINE)) {
|
|
371
|
+
nextSource = `${VARIATIONS_IMPORT_LINE}\n${nextSource}`;
|
|
372
|
+
}
|
|
373
|
+
if (!nextSource.includes(VARIATIONS_CALL_LINE)) {
|
|
374
|
+
const callInsertionPatterns = [
|
|
375
|
+
/(registerBlockType<[\s\S]*?\);\s*)/u,
|
|
376
|
+
/(registerBlockType\([\s\S]*?\);\s*)/u,
|
|
377
|
+
];
|
|
378
|
+
let inserted = false;
|
|
379
|
+
for (const pattern of callInsertionPatterns) {
|
|
380
|
+
const candidate = nextSource.replace(pattern, (match) => `${match}\n${VARIATIONS_CALL_LINE}\n`);
|
|
381
|
+
if (candidate !== nextSource) {
|
|
382
|
+
nextSource = candidate;
|
|
383
|
+
inserted = true;
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
if (!inserted) {
|
|
388
|
+
nextSource = `${nextSource.trimEnd()}\n\n${VARIATIONS_CALL_LINE}\n`;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if (!nextSource.includes(VARIATIONS_CALL_LINE)) {
|
|
392
|
+
throw new Error(`Unable to inject ${VARIATIONS_CALL_LINE} into ${path.basename(blockIndexPath)}.`);
|
|
393
|
+
}
|
|
394
|
+
return nextSource;
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
async function writeVariationRegistry(projectDir, blockSlug, variationSlug) {
|
|
398
|
+
const variationsDir = path.join(projectDir, "src", "blocks", blockSlug, "variations");
|
|
399
|
+
const variationsIndexPath = path.join(variationsDir, "index.ts");
|
|
400
|
+
await fsp.mkdir(variationsDir, { recursive: true });
|
|
401
|
+
const existingVariationSlugs = fs.existsSync(variationsDir)
|
|
402
|
+
? fs
|
|
403
|
+
.readdirSync(variationsDir)
|
|
404
|
+
.filter((entry) => entry.endsWith(".ts") && entry !== "index.ts")
|
|
405
|
+
.map((entry) => entry.replace(/\.ts$/u, ""))
|
|
406
|
+
: [];
|
|
407
|
+
const nextVariationSlugs = Array.from(new Set([...existingVariationSlugs, variationSlug])).sort();
|
|
408
|
+
await fsp.writeFile(variationsIndexPath, buildVariationIndexSource(nextVariationSlugs), "utf8");
|
|
409
|
+
}
|
|
410
|
+
async function ensurePatternBootstrapAnchors(workspace) {
|
|
411
|
+
const workspaceBaseName = workspace.packageName.split("/").pop() ?? workspace.packageName;
|
|
412
|
+
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
413
|
+
await patchFile(bootstrapPath, (source) => {
|
|
414
|
+
let nextSource = source;
|
|
415
|
+
const patternCategoryFunctionName = `${workspace.workspace.phpPrefix}_register_pattern_category`;
|
|
416
|
+
const patternRegistrationFunctionName = `${workspace.workspace.phpPrefix}_register_patterns`;
|
|
417
|
+
const patternCategoryHook = `add_action( 'init', '${patternCategoryFunctionName}' );`;
|
|
418
|
+
const patternRegistrationHook = `add_action( 'init', '${patternRegistrationFunctionName}', 20 );`;
|
|
419
|
+
const patternFunctions = `
|
|
420
|
+
|
|
421
|
+
function ${patternCategoryFunctionName}() {
|
|
422
|
+
\tif ( function_exists( 'register_block_pattern_category' ) ) {
|
|
423
|
+
\t\tregister_block_pattern_category(
|
|
424
|
+
\t\t\t'${workspace.workspace.namespace}',
|
|
425
|
+
\t\t\tarray(
|
|
426
|
+
\t\t\t\t'label' => __( ${JSON.stringify(`${toTitleCase(workspaceBaseName)} Patterns`)}, '${workspace.workspace.textDomain}' ),
|
|
427
|
+
\t\t\t)
|
|
428
|
+
\t\t);
|
|
429
|
+
\t}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function ${patternRegistrationFunctionName}() {
|
|
433
|
+
\tforeach ( glob( __DIR__ . '/src/patterns/*.php' ) ?: array() as $pattern_module ) {
|
|
434
|
+
\t\trequire $pattern_module;
|
|
435
|
+
\t}
|
|
436
|
+
}
|
|
437
|
+
`;
|
|
438
|
+
if (!nextSource.includes(PATTERN_BOOTSTRAP_CATEGORY)) {
|
|
439
|
+
const insertionAnchors = [
|
|
440
|
+
/add_action\(\s*["']init["']\s*,\s*["'][^"']+_load_textdomain["']\s*\);\s*\n/u,
|
|
441
|
+
/\?>\s*$/u,
|
|
442
|
+
];
|
|
443
|
+
let inserted = false;
|
|
444
|
+
for (const anchor of insertionAnchors) {
|
|
445
|
+
const candidate = nextSource.replace(anchor, (match) => `${patternFunctions}\n${match}`);
|
|
446
|
+
if (candidate !== nextSource) {
|
|
447
|
+
nextSource = candidate;
|
|
448
|
+
inserted = true;
|
|
449
|
+
break;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
if (!inserted) {
|
|
453
|
+
nextSource = `${nextSource.trimEnd()}\n${patternFunctions}\n`;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
if (!nextSource.includes(patternCategoryFunctionName) ||
|
|
457
|
+
!nextSource.includes(patternRegistrationFunctionName)) {
|
|
458
|
+
throw new Error(`Unable to inject pattern bootstrap functions into ${path.basename(bootstrapPath)}.`);
|
|
459
|
+
}
|
|
460
|
+
if (!nextSource.includes(patternCategoryHook)) {
|
|
461
|
+
nextSource = `${nextSource.trimEnd()}\n${patternCategoryHook}\n`;
|
|
462
|
+
}
|
|
463
|
+
if (!nextSource.includes(patternRegistrationHook)) {
|
|
464
|
+
nextSource = `${nextSource.trimEnd()}\n${patternRegistrationHook}\n`;
|
|
465
|
+
}
|
|
466
|
+
return nextSource;
|
|
467
|
+
});
|
|
468
|
+
}
|
|
266
469
|
function ensureBlockConfigCanAddRestManifests(source) {
|
|
267
470
|
const importLine = "import { defineEndpointManifest } from '@wp-typia/block-runtime/metadata-core';";
|
|
268
471
|
if (REST_MANIFEST_IMPORT_PATTERN.test(source)) {
|
|
@@ -271,21 +474,18 @@ function ensureBlockConfigCanAddRestManifests(source) {
|
|
|
271
474
|
return `${importLine}\n\n${source}`;
|
|
272
475
|
}
|
|
273
476
|
async function appendBlockConfigEntries(projectDir, entries, needsRestManifestImport) {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
if (needsRestManifestImport) {
|
|
278
|
-
nextSource = ensureBlockConfigCanAddRestManifests(nextSource);
|
|
279
|
-
}
|
|
280
|
-
if (nextSource.includes(BLOCK_CONFIG_ENTRY_MARKER)) {
|
|
281
|
-
return nextSource.replace(BLOCK_CONFIG_ENTRY_MARKER, `${entries.join("\n")}\n${BLOCK_CONFIG_ENTRY_MARKER}`);
|
|
282
|
-
}
|
|
283
|
-
if (nextSource.includes(EMPTY_BLOCKS_ARRAY)) {
|
|
284
|
-
return nextSource.replace(EMPTY_BLOCKS_ARRAY, `${entries.join("\n")}\n];`);
|
|
285
|
-
}
|
|
286
|
-
return nextSource.replace("];", `${entries.join("\n")}\n];`);
|
|
477
|
+
await appendWorkspaceInventoryEntries(projectDir, {
|
|
478
|
+
blockEntries: entries,
|
|
479
|
+
transformSource: needsRestManifestImport ? ensureBlockConfigCanAddRestManifests : undefined,
|
|
287
480
|
});
|
|
288
481
|
}
|
|
482
|
+
async function snapshotWorkspaceFiles(filePaths) {
|
|
483
|
+
const uniquePaths = Array.from(new Set(filePaths));
|
|
484
|
+
return Promise.all(uniquePaths.map(async (filePath) => ({
|
|
485
|
+
filePath,
|
|
486
|
+
source: await readOptionalFile(filePath),
|
|
487
|
+
})));
|
|
488
|
+
}
|
|
289
489
|
async function renderWorkspacePersistenceServerModule(projectDir, variables) {
|
|
290
490
|
const targetDir = path.join(projectDir, "src", "blocks", variables.slugKebabCase);
|
|
291
491
|
const templateDir = buildServerTemplateRoot(variables.persistencePolicy);
|
|
@@ -387,39 +587,6 @@ async function syncWorkspaceAddedBlockArtifacts(projectDir, templateId, variable
|
|
|
387
587
|
await syncWorkspacePersistenceArtifacts(projectDir, variables);
|
|
388
588
|
}
|
|
389
589
|
}
|
|
390
|
-
function resolveWorkspaceProject(startDir) {
|
|
391
|
-
let currentDir = path.resolve(startDir);
|
|
392
|
-
while (true) {
|
|
393
|
-
const packageJsonPath = path.join(currentDir, "package.json");
|
|
394
|
-
if (fs.existsSync(packageJsonPath)) {
|
|
395
|
-
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
396
|
-
if (packageJson.wpTypia?.projectType === "workspace" &&
|
|
397
|
-
packageJson.wpTypia?.templatePackage === WORKSPACE_TEMPLATE_PACKAGE &&
|
|
398
|
-
typeof packageJson.wpTypia.namespace === "string" &&
|
|
399
|
-
typeof packageJson.wpTypia.textDomain === "string" &&
|
|
400
|
-
typeof packageJson.wpTypia.phpPrefix === "string") {
|
|
401
|
-
return {
|
|
402
|
-
author: typeof packageJson.author === "string" ? packageJson.author : "Your Name",
|
|
403
|
-
packageManager: parsePackageManagerId(packageJson.packageManager),
|
|
404
|
-
projectDir: currentDir,
|
|
405
|
-
workspace: {
|
|
406
|
-
namespace: packageJson.wpTypia.namespace,
|
|
407
|
-
phpPrefix: packageJson.wpTypia.phpPrefix,
|
|
408
|
-
projectType: "workspace",
|
|
409
|
-
templatePackage: WORKSPACE_TEMPLATE_PACKAGE,
|
|
410
|
-
textDomain: packageJson.wpTypia.textDomain,
|
|
411
|
-
},
|
|
412
|
-
};
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
const parentDir = path.dirname(currentDir);
|
|
416
|
-
if (parentDir === currentDir) {
|
|
417
|
-
break;
|
|
418
|
-
}
|
|
419
|
-
currentDir = parentDir;
|
|
420
|
-
}
|
|
421
|
-
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.`);
|
|
422
|
-
}
|
|
423
590
|
function assertPersistenceFlagsAllowed(templateId, options) {
|
|
424
591
|
const hasPersistenceFlags = typeof options.dataStorageMode === "string" ||
|
|
425
592
|
typeof options.persistencePolicy === "string";
|
|
@@ -447,12 +614,13 @@ function assertPersistenceFlagsAllowed(templateId, options) {
|
|
|
447
614
|
export function formatAddHelpText() {
|
|
448
615
|
return `Usage:
|
|
449
616
|
wp-typia add block <name> --template <${ADD_BLOCK_TEMPLATE_IDS.join("|")}> [--data-storage <post-meta|custom-table>] [--persistence-policy <authenticated|public>]
|
|
450
|
-
wp-typia add variation
|
|
451
|
-
wp-typia add pattern
|
|
617
|
+
wp-typia add variation <name> --block <block-slug>
|
|
618
|
+
wp-typia add pattern <name>
|
|
452
619
|
|
|
453
620
|
Notes:
|
|
454
|
-
\`wp-typia add
|
|
455
|
-
\`
|
|
621
|
+
\`wp-typia add\` runs only inside official ${WORKSPACE_TEMPLATE_PACKAGE} workspaces.
|
|
622
|
+
\`add variation\` targets an existing block slug from \`scripts/block-config.ts\`.
|
|
623
|
+
\`add pattern\` scaffolds a namespaced PHP pattern shell under \`src/patterns/\`.`;
|
|
456
624
|
}
|
|
457
625
|
/**
|
|
458
626
|
* Seeds an empty official workspace migration project before any blocks are added.
|
|
@@ -467,15 +635,16 @@ export async function seedWorkspaceMigrationProject(projectDir, currentMigration
|
|
|
467
635
|
ensureMigrationDirectories(projectDir, []);
|
|
468
636
|
writeInitialMigrationScaffold(projectDir, currentMigrationVersion, []);
|
|
469
637
|
}
|
|
470
|
-
async function rollbackWorkspaceMutation(
|
|
638
|
+
async function rollbackWorkspaceMutation(snapshot) {
|
|
471
639
|
for (const targetPath of snapshot.targetPaths) {
|
|
472
640
|
await fsp.rm(targetPath, { force: true, recursive: true });
|
|
473
641
|
}
|
|
474
642
|
for (const snapshotDir of snapshot.snapshotDirs) {
|
|
475
643
|
await fsp.rm(snapshotDir, { force: true, recursive: true });
|
|
476
644
|
}
|
|
477
|
-
|
|
478
|
-
|
|
645
|
+
for (const { filePath, source } of snapshot.fileSources) {
|
|
646
|
+
await restoreOptionalFile(filePath, source);
|
|
647
|
+
}
|
|
479
648
|
}
|
|
480
649
|
/**
|
|
481
650
|
* Adds one built-in block slice to an official workspace project.
|
|
@@ -496,9 +665,10 @@ export async function runAddBlockCommand({ blockName, cwd = process.cwd(), dataS
|
|
|
496
665
|
try {
|
|
497
666
|
tempRoot = await fsp.mkdtemp(path.join(os.tmpdir(), "wp-typia-add-block-"));
|
|
498
667
|
const tempProjectDir = path.join(tempRoot, normalizedSlug);
|
|
668
|
+
const blockConfigPath = path.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
669
|
+
const migrationConfigPath = path.join(workspace.projectDir, "src", "migrations", "config.ts");
|
|
499
670
|
const blockPhpPrefix = buildWorkspacePhpPrefix(workspace.workspace.phpPrefix, normalizedSlug);
|
|
500
|
-
const
|
|
501
|
-
const migrationConfigSource = await readOptionalFile(path.join(workspace.projectDir, "src", "migrations", "config.ts"));
|
|
671
|
+
const migrationConfigSource = await readOptionalFile(migrationConfigPath);
|
|
502
672
|
const migrationConfig = migrationConfigSource === null ? null : parseMigrationConfig(migrationConfigSource);
|
|
503
673
|
const result = await scaffoldProject({
|
|
504
674
|
answers: {
|
|
@@ -520,8 +690,7 @@ export async function runAddBlockCommand({ blockName, cwd = process.cwd(), dataS
|
|
|
520
690
|
});
|
|
521
691
|
assertBlockTargetsDoNotExist(workspace.projectDir, resolvedTemplateId, result.variables);
|
|
522
692
|
const mutationSnapshot = {
|
|
523
|
-
|
|
524
|
-
migrationConfigSource,
|
|
693
|
+
fileSources: await snapshotWorkspaceFiles([blockConfigPath, migrationConfigPath]),
|
|
525
694
|
snapshotDirs: migrationConfig === null
|
|
526
695
|
? []
|
|
527
696
|
: buildMigrationBlocks(resolvedTemplateId, result.variables).map((block) => path.join(workspace.projectDir, ...migrationConfig.snapshotDir.split("/"), migrationConfig.currentMigrationVersion, block.key)),
|
|
@@ -542,7 +711,7 @@ export async function runAddBlockCommand({ blockName, cwd = process.cwd(), dataS
|
|
|
542
711
|
};
|
|
543
712
|
}
|
|
544
713
|
catch (error) {
|
|
545
|
-
await rollbackWorkspaceMutation(
|
|
714
|
+
await rollbackWorkspaceMutation(mutationSnapshot);
|
|
546
715
|
throw error;
|
|
547
716
|
}
|
|
548
717
|
}
|
|
@@ -552,10 +721,130 @@ export async function runAddBlockCommand({ blockName, cwd = process.cwd(), dataS
|
|
|
552
721
|
}
|
|
553
722
|
}
|
|
554
723
|
}
|
|
724
|
+
function resolveWorkspaceBlock(inventory, blockSlug) {
|
|
725
|
+
const block = inventory.blocks.find((entry) => entry.slug === blockSlug);
|
|
726
|
+
if (!block) {
|
|
727
|
+
throw new Error(`Unknown workspace block "${blockSlug}". Choose one of: ${inventory.blocks.map((entry) => entry.slug).join(", ")}`);
|
|
728
|
+
}
|
|
729
|
+
return block;
|
|
730
|
+
}
|
|
731
|
+
function assertVariationDoesNotExist(projectDir, blockSlug, variationSlug, inventory) {
|
|
732
|
+
const variationPath = path.join(projectDir, "src", "blocks", blockSlug, "variations", `${variationSlug}.ts`);
|
|
733
|
+
if (fs.existsSync(variationPath)) {
|
|
734
|
+
throw new Error(`A variation already exists at ${path.relative(projectDir, variationPath)}. Choose a different name.`);
|
|
735
|
+
}
|
|
736
|
+
if (inventory.variations.some((entry) => entry.block === blockSlug && entry.slug === variationSlug)) {
|
|
737
|
+
throw new Error(`A variation inventory entry already exists for ${blockSlug}/${variationSlug}. Choose a different name.`);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
function assertPatternDoesNotExist(projectDir, patternSlug, inventory) {
|
|
741
|
+
const patternPath = path.join(projectDir, "src", "patterns", `${patternSlug}.php`);
|
|
742
|
+
if (fs.existsSync(patternPath)) {
|
|
743
|
+
throw new Error(`A pattern already exists at ${path.relative(projectDir, patternPath)}. Choose a different name.`);
|
|
744
|
+
}
|
|
745
|
+
if (inventory.patterns.some((entry) => entry.slug === patternSlug)) {
|
|
746
|
+
throw new Error(`A pattern inventory entry already exists for ${patternSlug}. Choose a different name.`);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* Add one variation entry to an existing workspace block.
|
|
751
|
+
*
|
|
752
|
+
* @param options Command options for the variation scaffold workflow.
|
|
753
|
+
* @param options.blockName Target workspace block slug that will own the variation.
|
|
754
|
+
* @param options.cwd Working directory used to resolve the nearest official workspace.
|
|
755
|
+
* Defaults to `process.cwd()`.
|
|
756
|
+
* @param options.variationName Human-entered variation name that will be normalized
|
|
757
|
+
* and validated before files are written.
|
|
758
|
+
* @returns A promise that resolves with the normalized `blockSlug`,
|
|
759
|
+
* `variationSlug`, and owning `projectDir` after the variation files and
|
|
760
|
+
* inventory entry have been written successfully.
|
|
761
|
+
* @throws {Error} When the command is run outside an official workspace, when
|
|
762
|
+
* the target block is unknown, when the variation slug is invalid, or when a
|
|
763
|
+
* conflicting file or inventory entry already exists.
|
|
764
|
+
*/
|
|
765
|
+
export async function runAddVariationCommand({ blockName, cwd = process.cwd(), variationName, }) {
|
|
766
|
+
const workspace = resolveWorkspaceProject(cwd);
|
|
767
|
+
const blockSlug = normalizeBlockSlug(blockName);
|
|
768
|
+
const variationSlug = assertValidGeneratedSlug("Variation name", normalizeBlockSlug(variationName), "wp-typia add variation <name> --block <block-slug>");
|
|
769
|
+
const inventory = readWorkspaceInventory(workspace.projectDir);
|
|
770
|
+
resolveWorkspaceBlock(inventory, blockSlug);
|
|
771
|
+
assertVariationDoesNotExist(workspace.projectDir, blockSlug, variationSlug, inventory);
|
|
772
|
+
const blockConfigPath = path.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
773
|
+
const blockIndexPath = path.join(workspace.projectDir, "src", "blocks", blockSlug, "index.tsx");
|
|
774
|
+
const variationsDir = path.join(workspace.projectDir, "src", "blocks", blockSlug, "variations");
|
|
775
|
+
const variationFilePath = path.join(variationsDir, `${variationSlug}.ts`);
|
|
776
|
+
const variationsIndexPath = path.join(variationsDir, "index.ts");
|
|
777
|
+
const mutationSnapshot = {
|
|
778
|
+
fileSources: await snapshotWorkspaceFiles([
|
|
779
|
+
blockConfigPath,
|
|
780
|
+
blockIndexPath,
|
|
781
|
+
variationsIndexPath,
|
|
782
|
+
]),
|
|
783
|
+
snapshotDirs: [],
|
|
784
|
+
targetPaths: [variationFilePath],
|
|
785
|
+
};
|
|
786
|
+
try {
|
|
787
|
+
await fsp.mkdir(variationsDir, { recursive: true });
|
|
788
|
+
await fsp.writeFile(variationFilePath, buildVariationSource(variationSlug, workspace.workspace.textDomain), "utf8");
|
|
789
|
+
await writeVariationRegistry(workspace.projectDir, blockSlug, variationSlug);
|
|
790
|
+
await ensureVariationRegistrationHook(blockIndexPath);
|
|
791
|
+
await appendWorkspaceInventoryEntries(workspace.projectDir, {
|
|
792
|
+
variationEntries: [buildVariationConfigEntry(blockSlug, variationSlug)],
|
|
793
|
+
});
|
|
794
|
+
return {
|
|
795
|
+
blockSlug,
|
|
796
|
+
projectDir: workspace.projectDir,
|
|
797
|
+
variationSlug,
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
catch (error) {
|
|
801
|
+
await rollbackWorkspaceMutation(mutationSnapshot);
|
|
802
|
+
throw error;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
555
805
|
/**
|
|
556
|
-
*
|
|
806
|
+
* Add one PHP block pattern shell to an official workspace project.
|
|
807
|
+
*
|
|
808
|
+
* @param options Command options for the pattern scaffold workflow.
|
|
809
|
+
* @param options.cwd Working directory used to resolve the nearest official workspace.
|
|
810
|
+
* Defaults to `process.cwd()`.
|
|
811
|
+
* @param options.patternName Human-entered pattern name that will be normalized
|
|
812
|
+
* and validated before files are written.
|
|
813
|
+
* @returns A promise that resolves with the normalized `patternSlug` and
|
|
814
|
+
* owning `projectDir` after the pattern file and inventory entry have been
|
|
815
|
+
* written successfully.
|
|
816
|
+
* @throws {Error} When the command is run outside an official workspace, when
|
|
817
|
+
* the pattern slug is invalid, or when a conflicting file or inventory entry
|
|
818
|
+
* already exists.
|
|
557
819
|
*/
|
|
558
|
-
export function
|
|
559
|
-
const
|
|
560
|
-
|
|
820
|
+
export async function runAddPatternCommand({ cwd = process.cwd(), patternName, }) {
|
|
821
|
+
const workspace = resolveWorkspaceProject(cwd);
|
|
822
|
+
const patternSlug = assertValidGeneratedSlug("Pattern name", normalizeBlockSlug(patternName), "wp-typia add pattern <name>");
|
|
823
|
+
const inventory = readWorkspaceInventory(workspace.projectDir);
|
|
824
|
+
assertPatternDoesNotExist(workspace.projectDir, patternSlug, inventory);
|
|
825
|
+
const blockConfigPath = path.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
826
|
+
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
827
|
+
const patternFilePath = path.join(workspace.projectDir, "src", "patterns", `${patternSlug}.php`);
|
|
828
|
+
const mutationSnapshot = {
|
|
829
|
+
fileSources: await snapshotWorkspaceFiles([blockConfigPath, bootstrapPath]),
|
|
830
|
+
snapshotDirs: [],
|
|
831
|
+
targetPaths: [patternFilePath],
|
|
832
|
+
};
|
|
833
|
+
try {
|
|
834
|
+
await fsp.mkdir(path.dirname(patternFilePath), { recursive: true });
|
|
835
|
+
await ensurePatternBootstrapAnchors(workspace);
|
|
836
|
+
await fsp.writeFile(patternFilePath, buildPatternSource(patternSlug, workspace.workspace.namespace, workspace.workspace.textDomain), "utf8");
|
|
837
|
+
await appendWorkspaceInventoryEntries(workspace.projectDir, {
|
|
838
|
+
patternEntries: [buildPatternConfigEntry(patternSlug)],
|
|
839
|
+
});
|
|
840
|
+
return {
|
|
841
|
+
patternSlug,
|
|
842
|
+
projectDir: workspace.projectDir,
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
catch (error) {
|
|
846
|
+
await rollbackWorkspaceMutation(mutationSnapshot);
|
|
847
|
+
throw error;
|
|
848
|
+
}
|
|
561
849
|
}
|
|
850
|
+
export { getWorkspaceBlockSelectOptions };
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* `packages/wp-typia`.
|
|
7
7
|
*
|
|
8
8
|
* Import `formatAddHelpText`, `runAddBlockCommand`,
|
|
9
|
-
* `
|
|
9
|
+
* `runAddVariationCommand`, `runAddPatternCommand`,
|
|
10
|
+
* `getWorkspaceBlockSelectOptions`, and `seedWorkspaceMigrationProject` for
|
|
10
11
|
* explicit `wp-typia add` flows,
|
|
11
12
|
* `getDoctorChecks`, `runDoctor`, and `DoctorCheck` for diagnostics,
|
|
12
13
|
* `formatHelpText` for top-level CLI usage output, scaffold helpers such as
|
|
@@ -18,7 +19,7 @@
|
|
|
18
19
|
* template inspection flows.
|
|
19
20
|
*/
|
|
20
21
|
export { getDoctorChecks, runDoctor, type DoctorCheck } from "./cli-doctor.js";
|
|
21
|
-
export {
|
|
22
|
+
export { formatAddHelpText, getWorkspaceBlockSelectOptions, runAddBlockCommand, runAddPatternCommand, runAddVariationCommand, seedWorkspaceMigrationProject, } from "./cli-add.js";
|
|
22
23
|
export { formatHelpText } from "./cli-help.js";
|
|
23
24
|
export { getNextSteps, getOptionalOnboarding, runScaffoldFlow, } from "./cli-scaffold.js";
|
|
24
25
|
export { createReadlinePrompt, type ReadlinePrompt } from "./cli-prompt.js";
|
package/dist/runtime/cli-core.js
CHANGED
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
* `packages/wp-typia`.
|
|
7
7
|
*
|
|
8
8
|
* Import `formatAddHelpText`, `runAddBlockCommand`,
|
|
9
|
-
* `
|
|
9
|
+
* `runAddVariationCommand`, `runAddPatternCommand`,
|
|
10
|
+
* `getWorkspaceBlockSelectOptions`, and `seedWorkspaceMigrationProject` for
|
|
10
11
|
* explicit `wp-typia add` flows,
|
|
11
12
|
* `getDoctorChecks`, `runDoctor`, and `DoctorCheck` for diagnostics,
|
|
12
13
|
* `formatHelpText` for top-level CLI usage output, scaffold helpers such as
|
|
@@ -18,7 +19,7 @@
|
|
|
18
19
|
* template inspection flows.
|
|
19
20
|
*/
|
|
20
21
|
export { getDoctorChecks, runDoctor } from "./cli-doctor.js";
|
|
21
|
-
export {
|
|
22
|
+
export { formatAddHelpText, getWorkspaceBlockSelectOptions, runAddBlockCommand, runAddPatternCommand, runAddVariationCommand, seedWorkspaceMigrationProject, } from "./cli-add.js";
|
|
22
23
|
export { formatHelpText } from "./cli-help.js";
|
|
23
24
|
export { getNextSteps, getOptionalOnboarding, runScaffoldFlow, } from "./cli-scaffold.js";
|
|
24
25
|
export { createReadlinePrompt } from "./cli-prompt.js";
|