@wp-typia/project-tools 0.19.0 → 0.19.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/runtime/block-generator-service-spec.js +206 -2
  2. package/dist/runtime/built-in-block-artifacts.js +3 -1
  3. package/dist/runtime/built-in-block-code-artifacts.js +3 -1
  4. package/dist/runtime/built-in-block-non-ts-artifacts.js +1 -1039
  5. package/dist/runtime/built-in-block-non-ts-family-artifacts.d.ts +30 -0
  6. package/dist/runtime/built-in-block-non-ts-family-artifacts.js +1035 -0
  7. package/dist/runtime/built-in-block-non-ts-render-utils.d.ts +27 -0
  8. package/dist/runtime/built-in-block-non-ts-render-utils.js +51 -0
  9. package/dist/runtime/cli-add-block.js +11 -5
  10. package/dist/runtime/cli-add-workspace-rest-anchors.d.ts +3 -0
  11. package/dist/runtime/cli-add-workspace-rest-anchors.js +188 -0
  12. package/dist/runtime/cli-add-workspace-rest-source-emitters.d.ts +7 -0
  13. package/dist/runtime/cli-add-workspace-rest-source-emitters.js +379 -0
  14. package/dist/runtime/cli-add-workspace-rest.js +5 -564
  15. package/dist/runtime/cli-diagnostics.d.ts +26 -0
  16. package/dist/runtime/cli-diagnostics.js +107 -0
  17. package/dist/runtime/cli-doctor-workspace.js +4 -4
  18. package/dist/runtime/cli-scaffold.js +3 -3
  19. package/dist/runtime/index.d.ts +3 -1
  20. package/dist/runtime/index.js +2 -1
  21. package/dist/runtime/scaffold-bootstrap.js +5 -1
  22. package/dist/runtime/scaffold-documents.js +11 -8
  23. package/dist/runtime/scaffold-template-variable-groups.d.ts +154 -0
  24. package/dist/runtime/scaffold-template-variable-groups.js +13 -0
  25. package/dist/runtime/scaffold-template-variables.js +58 -1
  26. package/dist/runtime/scaffold.d.ts +5 -1
  27. package/dist/runtime/scaffold.js +5 -1
  28. package/dist/runtime/temp-roots.d.ts +44 -0
  29. package/dist/runtime/temp-roots.js +129 -0
  30. package/dist/runtime/template-builtins.js +4 -6
  31. package/dist/runtime/template-registry.d.ts +8 -0
  32. package/dist/runtime/template-registry.js +34 -1
  33. package/dist/runtime/template-source-external.d.ts +1 -0
  34. package/dist/runtime/template-source-external.js +4 -7
  35. package/dist/runtime/template-source-remote.js +44 -23
  36. package/dist/runtime/template-source-seeds.js +3 -9
  37. package/dist/runtime/template-source.d.ts +2 -3
  38. package/dist/runtime/template-source.js +8 -2
  39. package/package.json +6 -1
@@ -0,0 +1,27 @@
1
+ import type { BuiltInCodeArtifact } from "./built-in-block-code-artifacts.js";
2
+ import type { ScaffoldTemplateVariables } from "./scaffold.js";
3
+ /**
4
+ * Renders a non-TypeScript artifact and normalizes the output to a trailing newline.
5
+ *
6
+ * @param relativePath Relative output path for the generated artifact.
7
+ * @param template Mustache template source to render.
8
+ * @param view Template view data passed to the Mustache renderer.
9
+ * @returns A built-in code artifact with normalized source text.
10
+ */
11
+ export declare function renderArtifact(relativePath: string, template: string, view: Record<string, unknown>): BuiltInCodeArtifact;
12
+ /**
13
+ * Builds a PHP entrypoint for a specific alternate render target.
14
+ *
15
+ * @param relativePath Relative output path for the generated PHP file.
16
+ * @param target Alternate render target identifier to dispatch.
17
+ * @param variables Scaffold template variables used to render the entrypoint.
18
+ * @returns A built-in code artifact for the requested alternate render target.
19
+ */
20
+ export declare function buildAlternateRenderEntryArtifact(relativePath: string, target: "email" | "mjml" | "plain-text" | "web", variables: ScaffoldTemplateVariables): BuiltInCodeArtifact;
21
+ /**
22
+ * Escapes a string for safe embedding in a PHP single-quoted literal.
23
+ *
24
+ * @param value Source string to escape.
25
+ * @returns A PHP single-quoted string literal.
26
+ */
27
+ export declare function toPhpSingleQuotedString(value: string): string;
@@ -0,0 +1,51 @@
1
+ import { renderMustacheTemplateString } from "./template-render.js";
2
+ /**
3
+ * Renders a non-TypeScript artifact and normalizes the output to a trailing newline.
4
+ *
5
+ * @param relativePath Relative output path for the generated artifact.
6
+ * @param template Mustache template source to render.
7
+ * @param view Template view data passed to the Mustache renderer.
8
+ * @returns A built-in code artifact with normalized source text.
9
+ */
10
+ export function renderArtifact(relativePath, template, view) {
11
+ const source = renderMustacheTemplateString(template, view);
12
+ return {
13
+ relativePath,
14
+ source: source.endsWith("\n") ? source : `${source}\n`,
15
+ };
16
+ }
17
+ /**
18
+ * Builds a PHP entrypoint for a specific alternate render target.
19
+ *
20
+ * @param relativePath Relative output path for the generated PHP file.
21
+ * @param target Alternate render target identifier to dispatch.
22
+ * @param variables Scaffold template variables used to render the entrypoint.
23
+ * @returns A built-in code artifact for the requested alternate render target.
24
+ */
25
+ export function buildAlternateRenderEntryArtifact(relativePath, target, variables) {
26
+ const template = `<?php
27
+ /**
28
+ * Alternate ${target} render entry for {{title}}.
29
+ *
30
+ * @package {{pascalCase}}
31
+ */
32
+
33
+ if ( ! defined( 'ABSPATH' ) ) {
34
+ \texit;
35
+ }
36
+
37
+ require_once __DIR__ . '/render-targets.php';
38
+
39
+ return {{phpPrefix}}_{{slugSnakeCase}}_render_target( '${target}', $attributes, $content ?? '', $block ?? null );
40
+ `;
41
+ return renderArtifact(relativePath, template, variables);
42
+ }
43
+ /**
44
+ * Escapes a string for safe embedding in a PHP single-quoted literal.
45
+ *
46
+ * @param value Source string to escape.
47
+ * @returns A PHP single-quoted string literal.
48
+ */
49
+ export function toPhpSingleQuotedString(value) {
50
+ return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
51
+ }
@@ -1,6 +1,5 @@
1
1
  import fs from "node:fs";
2
2
  import { promises as fsp } from "node:fs";
3
- import os from "node:os";
4
3
  import path from "node:path";
5
4
  import { syncBlockMetadata, } from "@wp-typia/block-runtime/metadata-core";
6
5
  import { ensureMigrationDirectories, parseMigrationConfig, writeInitialMigrationScaffold, writeMigrationConfig, } from "./migration-project.js";
@@ -9,6 +8,7 @@ import { snapshotProjectVersion } from "./migrations.js";
9
8
  import { getDefaultAnswers, scaffoldProject } from "./scaffold.js";
10
9
  import { copyInterpolatedDirectory, listInterpolatedDirectoryOutputs, } from "./template-render.js";
11
10
  import { appendWorkspaceInventoryEntries, } from "./workspace-inventory.js";
11
+ import { createManagedTempRoot } from "./temp-roots.js";
12
12
  import { resolveWorkspaceProject, } from "./workspace-project.js";
13
13
  import { ADD_BLOCK_TEMPLATE_IDS, buildWorkspacePhpPrefix, isAddBlockTemplateId, patchFile, readOptionalFile, rollbackWorkspaceMutation, snapshotWorkspaceFiles, } from "./cli-add-shared.js";
14
14
  import { resolveNonEmptyNormalizedBlockSlug, } from "./scaffold-identifiers.js";
@@ -310,6 +310,7 @@ export async function runAddBlockCommand({ alternateRenderTargets, blockName, cw
310
310
  selectExternalLayerId,
311
311
  });
312
312
  let tempRoot = "";
313
+ let cleanupTempRoot;
313
314
  try {
314
315
  const normalizedSlug = resolveNonEmptyNormalizedBlockSlug({
315
316
  input: blockName,
@@ -317,7 +318,10 @@ export async function runAddBlockCommand({ alternateRenderTargets, blockName, cw
317
318
  usage: "wp-typia add block <name> --template <family>",
318
319
  });
319
320
  const defaults = getDefaultAnswers(normalizedSlug, resolvedTemplateId);
320
- tempRoot = await fsp.mkdtemp(path.join(os.tmpdir(), "wp-typia-add-block-"));
321
+ ({
322
+ path: tempRoot,
323
+ cleanup: cleanupTempRoot,
324
+ } = await createManagedTempRoot("wp-typia-add-block-"));
321
325
  const tempProjectDir = path.join(tempRoot, normalizedSlug);
322
326
  const blockConfigPath = path.join(workspace.projectDir, "scripts", "block-config.ts");
323
327
  const migrationConfigPath = path.join(workspace.projectDir, "src", "migrations", "config.ts");
@@ -397,9 +401,11 @@ export async function runAddBlockCommand({ alternateRenderTargets, blockName, cw
397
401
  }
398
402
  }
399
403
  finally {
400
- await resolvedExternalLayerSelection.cleanup?.();
401
- if (tempRoot) {
402
- await fsp.rm(tempRoot, { force: true, recursive: true });
404
+ try {
405
+ await resolvedExternalLayerSelection.cleanup?.();
406
+ }
407
+ finally {
408
+ await cleanupTempRoot?.();
403
409
  }
404
410
  }
405
411
  }
@@ -0,0 +1,3 @@
1
+ import type { WorkspaceProject } from "./workspace-project.js";
2
+ export declare function ensureRestResourceBootstrapAnchors(workspace: WorkspaceProject): Promise<void>;
3
+ export declare function ensureRestResourceSyncScriptAnchors(workspace: WorkspaceProject): Promise<void>;
@@ -0,0 +1,188 @@
1
+ import path from "node:path";
2
+ import { getWorkspaceBootstrapPath, patchFile, } from "./cli-add-shared.js";
3
+ const REST_RESOURCE_SERVER_GLOB = "/inc/rest/*.php";
4
+ function escapeRegex(value) {
5
+ return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
6
+ }
7
+ export async function ensureRestResourceBootstrapAnchors(workspace) {
8
+ const bootstrapPath = getWorkspaceBootstrapPath(workspace);
9
+ await patchFile(bootstrapPath, (source) => {
10
+ let nextSource = source;
11
+ const registerFunctionName = `${workspace.workspace.phpPrefix}_register_rest_resources`;
12
+ const registerHook = `add_action( 'init', '${registerFunctionName}', 20 );`;
13
+ const registerFunction = `
14
+
15
+ function ${registerFunctionName}() {
16
+ \tforeach ( glob( __DIR__ . '${REST_RESOURCE_SERVER_GLOB}' ) ?: array() as $rest_resource_module ) {
17
+ \t\trequire_once $rest_resource_module;
18
+ \t}
19
+ }
20
+ `;
21
+ const insertionAnchors = [
22
+ /add_action\(\s*["']init["']\s*,\s*["'][^"']+_load_textdomain["']\s*\);\s*\n/u,
23
+ /\?>\s*$/u,
24
+ ];
25
+ const hasPhpFunctionDefinition = (functionName) => new RegExp(`function\\s+${escapeRegex(functionName)}\\s*\\(`, "u").test(nextSource);
26
+ const insertPhpSnippet = (snippet) => {
27
+ for (const anchor of insertionAnchors) {
28
+ const candidate = nextSource.replace(anchor, (match) => `${snippet}\n${match}`);
29
+ if (candidate !== nextSource) {
30
+ nextSource = candidate;
31
+ return;
32
+ }
33
+ }
34
+ nextSource = `${nextSource.trimEnd()}\n${snippet}\n`;
35
+ };
36
+ const appendPhpSnippet = (snippet) => {
37
+ const closingTagPattern = /\?>\s*$/u;
38
+ if (closingTagPattern.test(nextSource)) {
39
+ nextSource = nextSource.replace(closingTagPattern, `${snippet}\n?>`);
40
+ return;
41
+ }
42
+ nextSource = `${nextSource.trimEnd()}\n${snippet}\n`;
43
+ };
44
+ if (!hasPhpFunctionDefinition(registerFunctionName)) {
45
+ insertPhpSnippet(registerFunction);
46
+ }
47
+ else if (!nextSource.includes(REST_RESOURCE_SERVER_GLOB)) {
48
+ throw new Error([
49
+ `Unable to patch ${path.basename(bootstrapPath)} in ensureRestResourceBootstrapAnchors.`,
50
+ `The existing ${registerFunctionName}() definition does not include ${REST_RESOURCE_SERVER_GLOB}.`,
51
+ "Restore the generated bootstrap shape or wire the REST resource loader manually before retrying.",
52
+ ].join(" "));
53
+ }
54
+ if (!nextSource.includes(registerHook)) {
55
+ appendPhpSnippet(registerHook);
56
+ }
57
+ return nextSource;
58
+ });
59
+ }
60
+ function assertSyncRestAnchor(nextSource, target, anchorDescription, hasAnchor, syncRestScriptPath) {
61
+ if (!nextSource.includes(target) && !hasAnchor) {
62
+ throw new Error([
63
+ `ensureRestResourceSyncScriptAnchors could not patch ${path.basename(syncRestScriptPath)}.`,
64
+ `Missing expected ${anchorDescription} anchor in scripts/sync-rest-contracts.ts.`,
65
+ "Restore the generated template or add the REST_RESOURCES wiring manually before retrying.",
66
+ ].join(" "));
67
+ }
68
+ }
69
+ function replaceRequiredSyncRestSource(nextSource, target, anchor, replacement, anchorDescription, syncRestScriptPath) {
70
+ if (nextSource.includes(target)) {
71
+ return nextSource;
72
+ }
73
+ const hasAnchor = typeof anchor === "string" ? nextSource.includes(anchor) : anchor.test(nextSource);
74
+ assertSyncRestAnchor(nextSource, target, anchorDescription, hasAnchor, syncRestScriptPath);
75
+ return nextSource.replace(anchor, replacement);
76
+ }
77
+ export async function ensureRestResourceSyncScriptAnchors(workspace) {
78
+ const syncRestScriptPath = path.join(workspace.projectDir, "scripts", "sync-rest-contracts.ts");
79
+ await patchFile(syncRestScriptPath, (source) => {
80
+ let nextSource = source;
81
+ const importAnchor = "import { BLOCKS, type WorkspaceBlockConfig } from './block-config';";
82
+ const helperInsertionAnchor = "async function assertTypeArtifactsCurrent";
83
+ const restBlocksAnchor = "const restBlocks = BLOCKS.filter( isRestEnabledBlock );";
84
+ const noResourcesPattern = /if \( restBlocks.length === 0 \) \{[\s\S]*?\n\t\treturn;\n\t\}/u;
85
+ const consoleLogPattern = /\n\tconsole\.log\(\n\t\toptions\.check/u;
86
+ nextSource = replaceRequiredSyncRestSource(nextSource, "REST_RESOURCES", importAnchor, [
87
+ "import {",
88
+ "\tBLOCKS,",
89
+ "\tREST_RESOURCES,",
90
+ "\ttype WorkspaceBlockConfig,",
91
+ "\ttype WorkspaceRestResourceConfig,",
92
+ "} from './block-config';",
93
+ ].join("\n"), "BLOCKS import", syncRestScriptPath);
94
+ nextSource = replaceRequiredSyncRestSource(nextSource, "function isWorkspaceRestResource(", helperInsertionAnchor, [
95
+ "function isWorkspaceRestResource(",
96
+ "\tresource: WorkspaceRestResourceConfig",
97
+ "): resource is WorkspaceRestResourceConfig & {",
98
+ "\tclientFile: string;",
99
+ "\topenApiFile: string;",
100
+ "\trestManifest: NonNullable< WorkspaceRestResourceConfig[ 'restManifest' ] >;",
101
+ "\ttypesFile: string;",
102
+ "\tvalidatorsFile: string;",
103
+ "} {",
104
+ "\treturn (",
105
+ "\t\ttypeof resource.clientFile === 'string' &&",
106
+ "\t\ttypeof resource.openApiFile === 'string' &&",
107
+ "\t\ttypeof resource.typesFile === 'string' &&",
108
+ "\t\ttypeof resource.validatorsFile === 'string' &&",
109
+ "\t\ttypeof resource.restManifest === 'object' &&",
110
+ "\t\tresource.restManifest !== null",
111
+ "\t);",
112
+ "}",
113
+ "",
114
+ "async function assertTypeArtifactsCurrent",
115
+ ].join("\n"), "type artifact assertion helper", syncRestScriptPath);
116
+ nextSource = replaceRequiredSyncRestSource(nextSource, "const restResources = REST_RESOURCES.filter( isWorkspaceRestResource );", restBlocksAnchor, [
117
+ "const restBlocks = BLOCKS.filter( isRestEnabledBlock );",
118
+ "const restResources = REST_RESOURCES.filter( isWorkspaceRestResource );",
119
+ ].join("\n"), "restBlocks filter", syncRestScriptPath);
120
+ nextSource = replaceRequiredSyncRestSource(nextSource, "restBlocks.length === 0 && restResources.length === 0", noResourcesPattern, [
121
+ "if ( restBlocks.length === 0 && restResources.length === 0 ) {",
122
+ "\t\tconsole.log(",
123
+ "\t\t\toptions.check",
124
+ "\t\t\t\t? 'ℹ️ No REST-enabled workspace blocks or plugin-level REST resources are registered yet. `sync-rest --check` is already clean.'",
125
+ "\t\t\t\t: 'ℹ️ No REST-enabled workspace blocks or plugin-level REST resources are registered yet.'",
126
+ "\t\t);",
127
+ "\t\treturn;",
128
+ "\t}",
129
+ ].join("\n"), "no-resources guard", syncRestScriptPath);
130
+ nextSource = replaceRequiredSyncRestSource(nextSource, "for ( const resource of restResources ) {", consoleLogPattern, [
131
+ "",
132
+ "\tfor ( const resource of restResources ) {",
133
+ "\t\tconst contracts = resource.restManifest.contracts;",
134
+ "",
135
+ "\t\tfor ( const [ baseName, contract ] of Object.entries( contracts ) ) {",
136
+ "\t\t\tawait syncTypeSchemas(",
137
+ "\t\t\t\t{",
138
+ "\t\t\t\t\tjsonSchemaFile: path.join(",
139
+ "\t\t\t\t\t\tpath.dirname( resource.typesFile ),",
140
+ "\t\t\t\t\t\t'api-schemas',",
141
+ "\t\t\t\t\t\t`${ baseName }.schema.json`",
142
+ "\t\t\t\t\t),",
143
+ "\t\t\t\t\topenApiFile: path.join(",
144
+ "\t\t\t\t\t\tpath.dirname( resource.typesFile ),",
145
+ "\t\t\t\t\t\t'api-schemas',",
146
+ "\t\t\t\t\t\t`${ baseName }.openapi.json`",
147
+ "\t\t\t\t\t),",
148
+ "\t\t\t\t\tsourceTypeName: contract.sourceTypeName,",
149
+ "\t\t\t\t\ttypesFile: resource.typesFile,",
150
+ "\t\t\t\t},",
151
+ "\t\t\t\t{",
152
+ "\t\t\t\t\tcheck: options.check,",
153
+ "\t\t\t\t}",
154
+ "\t\t\t);",
155
+ "\t\t}",
156
+ "",
157
+ "\t\tawait syncRestOpenApi(",
158
+ "\t\t\t{",
159
+ "\t\t\t\tmanifest: resource.restManifest,",
160
+ "\t\t\t\topenApiFile: resource.openApiFile,",
161
+ "\t\t\t\ttypesFile: resource.typesFile,",
162
+ "\t\t\t},",
163
+ "\t\t\t{",
164
+ "\t\t\t\tcheck: options.check,",
165
+ "\t\t\t}",
166
+ "\t\t);",
167
+ "",
168
+ "\t\tawait syncEndpointClient(",
169
+ "\t\t\t{",
170
+ "\t\t\t\tclientFile: resource.clientFile,",
171
+ "\t\t\t\tmanifest: resource.restManifest,",
172
+ "\t\t\t\ttypesFile: resource.typesFile,",
173
+ "\t\t\t\tvalidatorsFile: resource.validatorsFile,",
174
+ "\t\t\t},",
175
+ "\t\t\t{",
176
+ "\t\t\t\tcheck: options.check,",
177
+ "\t\t\t}",
178
+ "\t\t);",
179
+ "\t}",
180
+ "",
181
+ "\tconsole.log(",
182
+ "\t\toptions.check",
183
+ ].join("\n"), "success log insertion point", syncRestScriptPath);
184
+ nextSource = nextSource.replace("✅ REST contract schemas, portable API clients, and endpoint-aware OpenAPI documents are already up to date with the TypeScript types!", "✅ REST contract schemas, portable API clients, and endpoint-aware OpenAPI documents are already up to date for workspace blocks and plugin-level resources!");
185
+ nextSource = nextSource.replace("✅ REST contract schemas, portable API clients, and endpoint-aware OpenAPI documents generated from TypeScript types!", "✅ REST contract schemas, portable API clients, and endpoint-aware OpenAPI documents generated for workspace blocks and plugin-level resources!");
186
+ return nextSource;
187
+ });
188
+ }
@@ -0,0 +1,7 @@
1
+ import { type RestResourceMethodId } from "./cli-add-shared.js";
2
+ export declare function toPascalCaseFromSlug(slug: string): string;
3
+ export declare function buildRestResourceConfigEntry(restResourceSlug: string, namespace: string, methods: RestResourceMethodId[]): string;
4
+ export declare function buildRestResourceTypesSource(restResourceSlug: string, methods: RestResourceMethodId[]): string;
5
+ export declare function buildRestResourceValidatorsSource(restResourceSlug: string, methods: RestResourceMethodId[]): string;
6
+ export declare function buildRestResourceApiSource(restResourceSlug: string, methods: RestResourceMethodId[]): string;
7
+ export declare function buildRestResourceDataSource(restResourceSlug: string, methods: RestResourceMethodId[]): string;