@wp-typia/project-tools 0.24.11 → 0.24.12
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/add/cli-add-block-config.js +7 -15
- package/dist/runtime/add/cli-add-block-package-json.d.ts +5 -0
- package/dist/runtime/add/cli-add-block-package-json.js +32 -0
- package/dist/runtime/add/cli-add-block.js +17 -4
- package/dist/runtime/add/cli-add-workspace-admin-view-scaffold.js +1 -5
- package/dist/runtime/add/persistence-rest-artifacts.d.ts +32 -7
- package/dist/runtime/add/persistence-rest-artifacts.js +41 -15
- package/dist/runtime/cli/cli-diagnostics.d.ts +9 -0
- package/dist/runtime/cli/cli-diagnostics.js +8 -0
- package/dist/runtime/cli/cli-init-package-json.js +5 -1
- package/dist/runtime/doctor/cli-doctor-standalone-control-flow.d.ts +38 -0
- package/dist/runtime/doctor/cli-doctor-standalone-control-flow.js +365 -0
- package/dist/runtime/doctor/cli-doctor-standalone-rest.d.ts +21 -0
- package/dist/runtime/doctor/cli-doctor-standalone-rest.js +1373 -0
- package/dist/runtime/doctor/cli-doctor-standalone.d.ts +32 -0
- package/dist/runtime/doctor/cli-doctor-standalone.js +2613 -0
- package/dist/runtime/doctor/cli-doctor-workspace.d.ts +5 -3
- package/dist/runtime/doctor/cli-doctor-workspace.js +27 -3
- package/dist/runtime/doctor/cli-doctor.d.ts +4 -2
- package/dist/runtime/doctor/cli-doctor.js +4 -2
- package/dist/runtime/shared/json-utils.d.ts +7 -0
- package/dist/runtime/shared/json-utils.js +10 -0
- package/dist/runtime/shared/package-json-types.d.ts +1 -1
- package/dist/runtime/shared/package-versions.d.ts +2 -0
- package/dist/runtime/shared/package-versions.js +2 -0
- package/dist/runtime/shared/php-utils.d.ts +39 -2
- package/dist/runtime/shared/php-utils.js +205 -6
- package/dist/runtime/templates/block-generator-service-core.d.ts +1 -1
- package/dist/runtime/templates/block-generator-service-core.js +9 -3
- package/dist/runtime/templates/block-generator-service-spec.d.ts +5 -0
- package/dist/runtime/templates/built-in-block-artifact-documents.d.ts +1 -1
- package/dist/runtime/templates/built-in-block-artifact-documents.js +1 -1
- package/dist/runtime/templates/built-in-block-artifact-types.js +2 -2
- package/dist/runtime/templates/built-in-block-artifacts.d.ts +1 -1
- package/dist/runtime/templates/built-in-block-artifacts.js +16 -12
- package/dist/runtime/templates/built-in-block-attribute-emitters.d.ts +5 -0
- package/dist/runtime/templates/built-in-block-attribute-emitters.js +113 -8
- package/dist/runtime/templates/built-in-block-attribute-specs.js +4 -9
- package/dist/runtime/templates/built-in-block-code-artifacts.js +6 -1
- package/dist/runtime/templates/built-in-block-code-templates/compound-persistence.d.ts +2 -2
- package/dist/runtime/templates/built-in-block-code-templates/compound-persistence.js +24 -1
- package/dist/runtime/templates/built-in-block-code-templates/persistence.d.ts +2 -2
- package/dist/runtime/templates/built-in-block-code-templates/persistence.js +22 -1
- package/dist/runtime/templates/built-in-block-non-ts-compound-templates.d.ts +2 -2
- package/dist/runtime/templates/built-in-block-non-ts-compound-templates.js +6 -2
- package/dist/runtime/templates/built-in-block-non-ts-persistence-templates.d.ts +2 -2
- package/dist/runtime/templates/built-in-block-non-ts-persistence-templates.js +6 -2
- package/dist/runtime/templates/cli-scaffold-emission.d.ts +7 -1
- package/dist/runtime/templates/cli-scaffold-emission.js +10 -1
- package/dist/runtime/templates/cli-templates.d.ts +47 -0
- package/dist/runtime/templates/cli-templates.js +50 -1
- package/dist/runtime/templates/local-dev-presets.js +1 -1
- package/dist/runtime/templates/scaffold-apply-utils.d.ts +4 -8
- package/dist/runtime/templates/scaffold-apply-utils.js +44 -28
- package/dist/runtime/templates/scaffold-compiler-artifacts.d.ts +17 -0
- package/dist/runtime/templates/scaffold-compiler-artifacts.js +81 -0
- package/dist/runtime/templates/scaffold-onboarding.d.ts +5 -0
- package/dist/runtime/templates/scaffold-onboarding.js +13 -2
- package/dist/runtime/templates/scaffold-template-variable-groups.d.ts +4 -0
- package/dist/runtime/templates/scaffold-template-variable-groups.js +7 -0
- package/dist/runtime/templates/scaffold.d.ts +3 -1
- package/dist/runtime/templates/scaffold.js +2 -1
- package/dist/runtime/templates/starter-manifests.js +1 -1
- package/package.json +3 -3
- package/templates/_shared/compound/core/scripts/add-compound-child.ts.mustache +1 -1
- package/templates/_shared/compound/persistence/package.json.mustache +1 -0
- package/templates/_shared/persistence/core/package.json.mustache +1 -0
|
@@ -7,9 +7,11 @@ export interface WorkspaceDoctorChecksOptions {
|
|
|
7
7
|
/**
|
|
8
8
|
* Collect workspace-scoped doctor checks for the given working directory.
|
|
9
9
|
*
|
|
10
|
-
* When the directory is not an official workspace, the function
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* When the directory is not an official workspace, the function runs supported
|
|
11
|
+
* standalone single-block diagnostics when that project surface is detected.
|
|
12
|
+
* Otherwise it returns a "Doctor scope" row explaining that only environment
|
|
13
|
+
* checks ran, plus a failing workspace metadata row when a nearby candidate
|
|
14
|
+
* workspace is invalid.
|
|
13
15
|
* When workspace resolution or metadata parsing throws, the corresponding
|
|
14
16
|
* failing rows are returned early and the remaining checks are skipped.
|
|
15
17
|
* When an official workspace is detected, a passing "Doctor scope" row is
|
|
@@ -4,6 +4,7 @@ import { getWorkspaceFeatureDoctorChecks, } from "./cli-doctor-workspace-feature
|
|
|
4
4
|
import { getWorkspaceWordPressVersionDoctorChecks, } from "./cli-doctor-wordpress-version.js";
|
|
5
5
|
import { getMigrationWorkspaceHintCheck, getWorkspacePackageMetadataCheck, prepareWorkspacePackageDoctorSnapshot, } from "./cli-doctor-workspace-package.js";
|
|
6
6
|
import { createDoctorCheck, createDoctorScopeCheck, } from "./cli-doctor-workspace-shared.js";
|
|
7
|
+
import { getStandaloneScaffoldDoctorChecks, tryResolveStandaloneScaffoldProject, } from "./cli-doctor-standalone.js";
|
|
7
8
|
import { readWorkspaceInventoryAsync, } from "../workspace/workspace-inventory.js";
|
|
8
9
|
import { getInvalidWorkspaceProjectReason, parseWorkspacePackageJson, tryResolveWorkspaceProject, } from "../workspace/workspace-project.js";
|
|
9
10
|
function formatWorkspaceInventorySummary(inventory) {
|
|
@@ -25,9 +26,11 @@ function formatWorkspaceInventorySummary(inventory) {
|
|
|
25
26
|
/**
|
|
26
27
|
* Collect workspace-scoped doctor checks for the given working directory.
|
|
27
28
|
*
|
|
28
|
-
* When the directory is not an official workspace, the function
|
|
29
|
-
*
|
|
30
|
-
*
|
|
29
|
+
* When the directory is not an official workspace, the function runs supported
|
|
30
|
+
* standalone single-block diagnostics when that project surface is detected.
|
|
31
|
+
* Otherwise it returns a "Doctor scope" row explaining that only environment
|
|
32
|
+
* checks ran, plus a failing workspace metadata row when a nearby candidate
|
|
33
|
+
* workspace is invalid.
|
|
31
34
|
* When workspace resolution or metadata parsing throws, the corresponding
|
|
32
35
|
* failing rows are returned early and the remaining checks are skipped.
|
|
33
36
|
* When an official workspace is detected, a passing "Doctor scope" row is
|
|
@@ -42,6 +45,20 @@ function formatWorkspaceInventorySummary(inventory) {
|
|
|
42
45
|
*/
|
|
43
46
|
export async function getWorkspaceDoctorChecks(cwd, options = {}) {
|
|
44
47
|
const checks = [];
|
|
48
|
+
let standaloneProject = null;
|
|
49
|
+
let standaloneDiscoveryError = null;
|
|
50
|
+
try {
|
|
51
|
+
// The nearest standalone package boundary wins over unrelated ancestor
|
|
52
|
+
// workspace metadata. Official workspace manifests are rejected by the
|
|
53
|
+
// standalone detector and continue through workspace discovery below.
|
|
54
|
+
standaloneProject = tryResolveStandaloneScaffoldProject(cwd);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
standaloneDiscoveryError = error;
|
|
58
|
+
}
|
|
59
|
+
if (standaloneProject) {
|
|
60
|
+
return getStandaloneScaffoldDoctorChecks(standaloneProject);
|
|
61
|
+
}
|
|
45
62
|
let workspace = null;
|
|
46
63
|
let invalidWorkspaceReason = null;
|
|
47
64
|
try {
|
|
@@ -61,6 +78,13 @@ export async function getWorkspaceDoctorChecks(cwd, options = {}) {
|
|
|
61
78
|
checks.push(createDoctorCheck("Workspace package metadata", "fail", invalidWorkspaceReason));
|
|
62
79
|
}
|
|
63
80
|
else {
|
|
81
|
+
if (standaloneDiscoveryError) {
|
|
82
|
+
checks.push(createDoctorScopeCheck("fail", "Scope: blocked before standalone scaffold checks. Environment checks ran, but standalone discovery could not continue. Fix the nearest package metadata and rerun `wp-typia doctor`."));
|
|
83
|
+
checks.push(createDoctorCheck("Standalone package metadata", "fail", standaloneDiscoveryError instanceof Error
|
|
84
|
+
? standaloneDiscoveryError.message
|
|
85
|
+
: String(standaloneDiscoveryError)));
|
|
86
|
+
return checks;
|
|
87
|
+
}
|
|
64
88
|
checks.push(createDoctorScopeCheck("pass", "Scope: environment-only. No official wp-typia workspace root was detected, so this run only covered environment readiness. Re-run `wp-typia doctor` from a workspace root if you expected package metadata, inventory, or generated artifact checks."));
|
|
65
89
|
}
|
|
66
90
|
return checks;
|
|
@@ -56,8 +56,10 @@ export interface GetDoctorChecksOptions {
|
|
|
56
56
|
*
|
|
57
57
|
* The returned array concatenates environment checks (command availability,
|
|
58
58
|
* directory writability, and built-in template assets) followed by
|
|
59
|
-
*
|
|
60
|
-
* patterns, bindings, and optional migration
|
|
59
|
+
* project checks in display order. Official workspaces cover package metadata,
|
|
60
|
+
* inventory, blocks, variations, patterns, bindings, and optional migration
|
|
61
|
+
* hints. Supported standalone scaffolds cover package/bootstrap integrity,
|
|
62
|
+
* source layout, dependencies, and canonical generated-artifact freshness.
|
|
61
63
|
*
|
|
62
64
|
* @param cwd Working directory to validate for writability.
|
|
63
65
|
* @param options Optional feature gates for additional doctor rows.
|
|
@@ -42,8 +42,10 @@ function toFailureSummary(check, severity) {
|
|
|
42
42
|
*
|
|
43
43
|
* The returned array concatenates environment checks (command availability,
|
|
44
44
|
* directory writability, and built-in template assets) followed by
|
|
45
|
-
*
|
|
46
|
-
* patterns, bindings, and optional migration
|
|
45
|
+
* project checks in display order. Official workspaces cover package metadata,
|
|
46
|
+
* inventory, blocks, variations, patterns, bindings, and optional migration
|
|
47
|
+
* hints. Supported standalone scaffolds cover package/bootstrap integrity,
|
|
48
|
+
* source layout, dependencies, and canonical generated-artifact freshness.
|
|
47
49
|
*
|
|
48
50
|
* @param cwd Working directory to validate for writability.
|
|
49
51
|
* @param options Optional feature gates for additional doctor rows.
|
|
@@ -24,6 +24,13 @@
|
|
|
24
24
|
* @returns A deep-cloned copy created with `JSON.parse(JSON.stringify(...))`.
|
|
25
25
|
*/
|
|
26
26
|
export declare function cloneJsonValue<T>(value: T): T;
|
|
27
|
+
/**
|
|
28
|
+
* Detect the indentation used by a formatted JSON document.
|
|
29
|
+
*
|
|
30
|
+
* @param source Raw JSON source text.
|
|
31
|
+
* @returns The first property indentation, or two spaces for compact/empty JSON.
|
|
32
|
+
*/
|
|
33
|
+
export declare function detectJsonIndent(source: string): string | number;
|
|
27
34
|
/**
|
|
28
35
|
* Optional metadata used to enrich JSON parse errors.
|
|
29
36
|
*/
|
|
@@ -28,6 +28,16 @@ import { promises as fsp } from "node:fs";
|
|
|
28
28
|
export function cloneJsonValue(value) {
|
|
29
29
|
return JSON.parse(JSON.stringify(value));
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Detect the indentation used by a formatted JSON document.
|
|
33
|
+
*
|
|
34
|
+
* @param source Raw JSON source text.
|
|
35
|
+
* @returns The first property indentation, or two spaces for compact/empty JSON.
|
|
36
|
+
*/
|
|
37
|
+
export function detectJsonIndent(source) {
|
|
38
|
+
const indentMatch = /\n([ \t]+)"/u.exec(source);
|
|
39
|
+
return indentMatch?.[1] ?? 2;
|
|
40
|
+
}
|
|
31
41
|
function formatJsonParseTarget({ context, filePath }) {
|
|
32
42
|
const operation = context?.trim() || "JSON";
|
|
33
43
|
return filePath ? `${operation} at ${filePath}` : operation;
|
|
@@ -19,6 +19,8 @@ export interface PackageVersions {
|
|
|
19
19
|
* not drift across runtime modules.
|
|
20
20
|
*/
|
|
21
21
|
export declare const DEFAULT_WORDPRESS_ABILITIES_VERSION = "^0.10.0";
|
|
22
|
+
export declare const DEFAULT_WORDPRESS_BLOCKS_TYPES_VERSION = "^12.5.18";
|
|
23
|
+
export declare const DEFAULT_WORDPRESS_BLOCKS_VERSION = "~15.19.0";
|
|
22
24
|
export declare const DEFAULT_WORDPRESS_CORE_ABILITIES_VERSION = "^0.9.0";
|
|
23
25
|
export declare const DEFAULT_WORDPRESS_CORE_DATA_VERSION = "~7.46.0";
|
|
24
26
|
export declare const DEFAULT_WORDPRESS_DATA_VERSION = "~10.46.0";
|
|
@@ -17,6 +17,8 @@ const DEFAULT_TYPIA_UNPLUGIN_PACKAGE_VERSION = '^12.0.1';
|
|
|
17
17
|
* not drift across runtime modules.
|
|
18
18
|
*/
|
|
19
19
|
export const DEFAULT_WORDPRESS_ABILITIES_VERSION = '^0.10.0';
|
|
20
|
+
export const DEFAULT_WORDPRESS_BLOCKS_TYPES_VERSION = '^12.5.18';
|
|
21
|
+
export const DEFAULT_WORDPRESS_BLOCKS_VERSION = '~15.19.0';
|
|
20
22
|
export const DEFAULT_WORDPRESS_CORE_ABILITIES_VERSION = '^0.9.0';
|
|
21
23
|
export const DEFAULT_WORDPRESS_CORE_DATA_VERSION = '~7.46.0';
|
|
22
24
|
export const DEFAULT_WORDPRESS_DATA_VERSION = '~10.46.0';
|
|
@@ -3,7 +3,12 @@ export type PhpFunctionRange = {
|
|
|
3
3
|
source: string;
|
|
4
4
|
start: number;
|
|
5
5
|
};
|
|
6
|
-
|
|
6
|
+
/** Options controlling whether PHP call helpers scan snippets or full PHP files. */
|
|
7
|
+
export type PhpFunctionCallScanOptions = {
|
|
8
|
+
/** Ignore text outside explicit PHP open/close tag regions. */
|
|
9
|
+
requirePhpOpenTag?: boolean;
|
|
10
|
+
};
|
|
11
|
+
export type PhpFunctionRangeOptions = PhpFunctionCallScanOptions & {
|
|
7
12
|
includeTrailingNewlines?: boolean;
|
|
8
13
|
};
|
|
9
14
|
export type ReplacePhpFunctionDefinitionOptions = PhpFunctionRangeOptions & {
|
|
@@ -17,9 +22,38 @@ export declare function hasPhpFunctionDefinition(source: string, functionName: s
|
|
|
17
22
|
*
|
|
18
23
|
* @param source PHP source to scan.
|
|
19
24
|
* @param functionName Literal PHP function identifier to find.
|
|
25
|
+
* @param options Scanner options for full files that require explicit PHP tags.
|
|
20
26
|
* @returns Whether `source` contains a code-mode call to `functionName`.
|
|
21
27
|
*/
|
|
22
|
-
export declare function hasPhpFunctionCall(source: string, functionName: string): boolean;
|
|
28
|
+
export declare function hasPhpFunctionCall(source: string, functionName: string, options?: PhpFunctionCallScanOptions): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Find the exclusive end offset of one PHP function call at a known offset.
|
|
31
|
+
*
|
|
32
|
+
* Parentheses inside strings, comments, heredoc, and nowdoc blocks are ignored
|
|
33
|
+
* by the same scanner used by {@link hasPhpFunctionCall}.
|
|
34
|
+
*
|
|
35
|
+
* @param source PHP source containing the call.
|
|
36
|
+
* @param functionOffset Offset where the function identifier starts.
|
|
37
|
+
* @param functionName Literal PHP function identifier expected at the offset.
|
|
38
|
+
* @param options Scanner options for full files that require explicit PHP tags.
|
|
39
|
+
* @returns The offset after the matching closing parenthesis, or `null`.
|
|
40
|
+
*/
|
|
41
|
+
export declare function findPhpFunctionCallEnd(source: string, functionOffset: number, functionName: string, options?: PhpFunctionCallScanOptions): number | null;
|
|
42
|
+
type PhpStringArgumentMatcher = string | ((value: string) => boolean);
|
|
43
|
+
/**
|
|
44
|
+
* Detect a PHP function call whose leading arguments are literal strings that
|
|
45
|
+
* satisfy the supplied matchers.
|
|
46
|
+
*
|
|
47
|
+
* Each matcher applies to the corresponding argument in the same code-mode
|
|
48
|
+
* function call. Additional arguments after the matched prefix are allowed.
|
|
49
|
+
*
|
|
50
|
+
* @param source PHP source to scan.
|
|
51
|
+
* @param functionName Literal PHP function identifier to find.
|
|
52
|
+
* @param argumentMatchers Matchers for the leading literal string arguments.
|
|
53
|
+
* @param options Scanner options for full files that require explicit PHP tags.
|
|
54
|
+
* @returns Whether one code-mode call satisfies every supplied matcher.
|
|
55
|
+
*/
|
|
56
|
+
export declare function hasPhpFunctionCallWithStringArguments(source: string, functionName: string, argumentMatchers: readonly PhpStringArgumentMatcher[], options?: PhpFunctionCallScanOptions): boolean;
|
|
23
57
|
/**
|
|
24
58
|
* Detect a PHP function call whose first argument is a variable previously
|
|
25
59
|
* assigned from a literal string prefix in executable PHP code.
|
|
@@ -46,6 +80,8 @@ export declare function hasPhpFunctionCallWithStringArgumentPrefix(source: strin
|
|
|
46
80
|
* global function call consumes the variable.
|
|
47
81
|
*/
|
|
48
82
|
export declare function hasPhpCodeStringLiteralPrefix(source: string, literalPrefix: string): boolean;
|
|
83
|
+
/** Return the PHP code-block brace depth at an executable source offset. */
|
|
84
|
+
export declare function getPhpCodeBraceDepth(source: string, offset: number, options?: PhpFunctionCallScanOptions): number | null;
|
|
49
85
|
/**
|
|
50
86
|
* Locate a PHP function body without counting braces in non-code regions.
|
|
51
87
|
*
|
|
@@ -56,3 +92,4 @@ export declare function hasPhpCodeStringLiteralPrefix(source: string, literalPre
|
|
|
56
92
|
*/
|
|
57
93
|
export declare function findPhpFunctionRange(source: string, functionName: string, options?: PhpFunctionRangeOptions): PhpFunctionRange | null;
|
|
58
94
|
export declare function replacePhpFunctionDefinition(source: string, functionName: string, replacement: string, options?: ReplacePhpFunctionDefinitionOptions): string | null;
|
|
95
|
+
export {};
|
|
@@ -202,17 +202,43 @@ function getPhpFunctionCallFirstArgumentStart(source, index, functionName) {
|
|
|
202
202
|
}
|
|
203
203
|
return skipPhpCallTrivia(source, callStart + 1);
|
|
204
204
|
}
|
|
205
|
-
function createPhpScannerState() {
|
|
205
|
+
function createPhpScannerState(options = {}) {
|
|
206
|
+
const requirePhpOpenTag = options.requirePhpOpenTag === true;
|
|
206
207
|
return {
|
|
207
208
|
heredocDelimiter: "",
|
|
209
|
+
inPhp: !requirePhpOpenTag,
|
|
208
210
|
interpolationComment: "",
|
|
209
211
|
interpolationDepth: 0,
|
|
210
212
|
interpolationQuote: "",
|
|
211
213
|
mode: "code",
|
|
214
|
+
requirePhpOpenTag,
|
|
212
215
|
};
|
|
213
216
|
}
|
|
217
|
+
function getPhpOpenTagLength(source, index) {
|
|
218
|
+
if (source.startsWith("<?=", index)) {
|
|
219
|
+
return 3;
|
|
220
|
+
}
|
|
221
|
+
if (source.slice(index, index + 5).toLowerCase() === "<?php") {
|
|
222
|
+
const nextCharacter = source[index + 5];
|
|
223
|
+
return nextCharacter === undefined || isPhpWhitespace(nextCharacter) ? 5 : 0;
|
|
224
|
+
}
|
|
225
|
+
return 0;
|
|
226
|
+
}
|
|
214
227
|
function advancePhpScanner(source, index, state) {
|
|
215
228
|
const character = source[index];
|
|
229
|
+
if (state.requirePhpOpenTag && !state.inPhp) {
|
|
230
|
+
const openTagLength = getPhpOpenTagLength(source, index);
|
|
231
|
+
if (openTagLength > 0) {
|
|
232
|
+
state.inPhp = true;
|
|
233
|
+
state.mode = "code";
|
|
234
|
+
return {
|
|
235
|
+
ambiguous: false,
|
|
236
|
+
inCode: false,
|
|
237
|
+
index: index + openTagLength,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
return { ambiguous: false, inCode: false, index: index + 1 };
|
|
241
|
+
}
|
|
216
242
|
if (state.mode === "heredoc") {
|
|
217
243
|
const closingEnd = findPhpHeredocClosingEnd(source, index, state.heredocDelimiter);
|
|
218
244
|
if (closingEnd !== null) {
|
|
@@ -300,6 +326,13 @@ function advancePhpScanner(source, index, state) {
|
|
|
300
326
|
return { ambiguous: false, inCode: false, index: index + 1 };
|
|
301
327
|
}
|
|
302
328
|
if (state.mode === "line-comment") {
|
|
329
|
+
if (state.requirePhpOpenTag &&
|
|
330
|
+
character === "?" &&
|
|
331
|
+
source[index + 1] === ">") {
|
|
332
|
+
state.inPhp = false;
|
|
333
|
+
state.mode = "code";
|
|
334
|
+
return { ambiguous: false, inCode: false, index: index + 2 };
|
|
335
|
+
}
|
|
303
336
|
if (character === "\r" || character === "\n") {
|
|
304
337
|
state.mode = "code";
|
|
305
338
|
}
|
|
@@ -312,6 +345,13 @@ function advancePhpScanner(source, index, state) {
|
|
|
312
345
|
}
|
|
313
346
|
return { ambiguous: false, inCode: false, index: index + 1 };
|
|
314
347
|
}
|
|
348
|
+
if (state.requirePhpOpenTag &&
|
|
349
|
+
character === "?" &&
|
|
350
|
+
source[index + 1] === ">") {
|
|
351
|
+
state.inPhp = false;
|
|
352
|
+
state.mode = "code";
|
|
353
|
+
return { ambiguous: false, inCode: false, index: index + 2 };
|
|
354
|
+
}
|
|
315
355
|
if (character === "'") {
|
|
316
356
|
state.mode = "single-quoted";
|
|
317
357
|
return { ambiguous: false, inCode: false, index: index + 1 };
|
|
@@ -351,10 +391,11 @@ function advancePhpScanner(source, index, state) {
|
|
|
351
391
|
*
|
|
352
392
|
* @param source PHP source to scan.
|
|
353
393
|
* @param functionName Literal PHP function identifier to find.
|
|
394
|
+
* @param options Scanner options for full files that require explicit PHP tags.
|
|
354
395
|
* @returns Whether `source` contains a code-mode call to `functionName`.
|
|
355
396
|
*/
|
|
356
|
-
export function hasPhpFunctionCall(source, functionName) {
|
|
357
|
-
const scanner = createPhpScannerState();
|
|
397
|
+
export function hasPhpFunctionCall(source, functionName, options = {}) {
|
|
398
|
+
const scanner = createPhpScannerState(options);
|
|
358
399
|
let index = 0;
|
|
359
400
|
while (index < source.length) {
|
|
360
401
|
const scan = advancePhpScanner(source, index, scanner);
|
|
@@ -372,6 +413,125 @@ export function hasPhpFunctionCall(source, functionName) {
|
|
|
372
413
|
}
|
|
373
414
|
return false;
|
|
374
415
|
}
|
|
416
|
+
/**
|
|
417
|
+
* Find the exclusive end offset of one PHP function call at a known offset.
|
|
418
|
+
*
|
|
419
|
+
* Parentheses inside strings, comments, heredoc, and nowdoc blocks are ignored
|
|
420
|
+
* by the same scanner used by {@link hasPhpFunctionCall}.
|
|
421
|
+
*
|
|
422
|
+
* @param source PHP source containing the call.
|
|
423
|
+
* @param functionOffset Offset where the function identifier starts.
|
|
424
|
+
* @param functionName Literal PHP function identifier expected at the offset.
|
|
425
|
+
* @param options Scanner options for full files that require explicit PHP tags.
|
|
426
|
+
* @returns The offset after the matching closing parenthesis, or `null`.
|
|
427
|
+
*/
|
|
428
|
+
export function findPhpFunctionCallEnd(source, functionOffset, functionName, options = {}) {
|
|
429
|
+
if (!matchesPhpFunctionCallAt(source, functionOffset, functionName)) {
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
const openParenthesis = skipPhpCallTrivia(source, functionOffset + functionName.length);
|
|
433
|
+
if (openParenthesis === null || source[openParenthesis] !== "(") {
|
|
434
|
+
return null;
|
|
435
|
+
}
|
|
436
|
+
const scanner = createPhpScannerState(options);
|
|
437
|
+
let depth = 0;
|
|
438
|
+
let index = 0;
|
|
439
|
+
while (index < source.length) {
|
|
440
|
+
const scan = advancePhpScanner(source, index, scanner);
|
|
441
|
+
if (scan.ambiguous) {
|
|
442
|
+
return null;
|
|
443
|
+
}
|
|
444
|
+
if (!scan.inCode) {
|
|
445
|
+
index = scan.index;
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
if (index < openParenthesis) {
|
|
449
|
+
index += 1;
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
if (source[index] === "(") {
|
|
453
|
+
depth += 1;
|
|
454
|
+
}
|
|
455
|
+
else if (source[index] === ")") {
|
|
456
|
+
depth -= 1;
|
|
457
|
+
if (depth === 0) {
|
|
458
|
+
return index + 1;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
index += 1;
|
|
462
|
+
}
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
function matchesPhpStringArgument(matcher, value) {
|
|
466
|
+
return typeof matcher === "string" ? value === matcher : matcher(value);
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Detect a PHP function call whose leading arguments are literal strings that
|
|
470
|
+
* satisfy the supplied matchers.
|
|
471
|
+
*
|
|
472
|
+
* Each matcher applies to the corresponding argument in the same code-mode
|
|
473
|
+
* function call. Additional arguments after the matched prefix are allowed.
|
|
474
|
+
*
|
|
475
|
+
* @param source PHP source to scan.
|
|
476
|
+
* @param functionName Literal PHP function identifier to find.
|
|
477
|
+
* @param argumentMatchers Matchers for the leading literal string arguments.
|
|
478
|
+
* @param options Scanner options for full files that require explicit PHP tags.
|
|
479
|
+
* @returns Whether one code-mode call satisfies every supplied matcher.
|
|
480
|
+
*/
|
|
481
|
+
export function hasPhpFunctionCallWithStringArguments(source, functionName, argumentMatchers, options = {}) {
|
|
482
|
+
if (argumentMatchers.length === 0) {
|
|
483
|
+
return hasPhpFunctionCall(source, functionName, options);
|
|
484
|
+
}
|
|
485
|
+
const scanner = createPhpScannerState(options);
|
|
486
|
+
let index = 0;
|
|
487
|
+
while (index < source.length) {
|
|
488
|
+
const scan = advancePhpScanner(source, index, scanner);
|
|
489
|
+
if (scan.ambiguous) {
|
|
490
|
+
return false;
|
|
491
|
+
}
|
|
492
|
+
if (!scan.inCode) {
|
|
493
|
+
index = scan.index;
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
if (!matchesPhpFunctionCallAt(source, index, functionName)) {
|
|
497
|
+
index += 1;
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
let argumentStart = getPhpFunctionCallFirstArgumentStart(source, index, functionName);
|
|
501
|
+
let argumentsMatch = argumentStart !== null;
|
|
502
|
+
for (const [argumentIndex, matcher] of argumentMatchers.entries()) {
|
|
503
|
+
if (!argumentsMatch || argumentStart === null) {
|
|
504
|
+
argumentsMatch = false;
|
|
505
|
+
break;
|
|
506
|
+
}
|
|
507
|
+
const argument = parsePhpQuotedStringLiteralAt(source, argumentStart);
|
|
508
|
+
if (!argument ||
|
|
509
|
+
!matchesPhpStringArgument(matcher, argument.value)) {
|
|
510
|
+
argumentsMatch = false;
|
|
511
|
+
break;
|
|
512
|
+
}
|
|
513
|
+
const argumentEnd = skipPhpCallTrivia(source, argument.end);
|
|
514
|
+
const nextToken = argumentEnd === null ? undefined : source[argumentEnd];
|
|
515
|
+
const isLastMatcher = argumentIndex === argumentMatchers.length - 1;
|
|
516
|
+
if (isLastMatcher) {
|
|
517
|
+
argumentsMatch =
|
|
518
|
+
argumentEnd !== null && (nextToken === "," || nextToken === ")");
|
|
519
|
+
break;
|
|
520
|
+
}
|
|
521
|
+
if (argumentEnd === null || nextToken !== ",") {
|
|
522
|
+
argumentsMatch = false;
|
|
523
|
+
break;
|
|
524
|
+
}
|
|
525
|
+
argumentStart = skipPhpCallTrivia(source, argumentEnd + 1);
|
|
526
|
+
argumentsMatch = argumentStart !== null;
|
|
527
|
+
}
|
|
528
|
+
if (argumentsMatch) {
|
|
529
|
+
return true;
|
|
530
|
+
}
|
|
531
|
+
index += functionName.length;
|
|
532
|
+
}
|
|
533
|
+
return false;
|
|
534
|
+
}
|
|
375
535
|
function isPhpAssignmentOperatorAt(source, index) {
|
|
376
536
|
if (index === null || source[index] !== "=") {
|
|
377
537
|
return false;
|
|
@@ -577,6 +737,36 @@ function hasPhpFunctionCallWithStringArgumentMatching(source, functionName, matc
|
|
|
577
737
|
}
|
|
578
738
|
return false;
|
|
579
739
|
}
|
|
740
|
+
/** Return the PHP code-block brace depth at an executable source offset. */
|
|
741
|
+
export function getPhpCodeBraceDepth(source, offset, options = {}) {
|
|
742
|
+
const scanner = createPhpScannerState(options);
|
|
743
|
+
let depth = 0;
|
|
744
|
+
let index = 0;
|
|
745
|
+
while (index < source.length && index <= offset) {
|
|
746
|
+
const scan = advancePhpScanner(source, index, scanner);
|
|
747
|
+
if (index === offset) {
|
|
748
|
+
return !scan.ambiguous && scan.inCode ? depth : null;
|
|
749
|
+
}
|
|
750
|
+
if (scan.ambiguous) {
|
|
751
|
+
return null;
|
|
752
|
+
}
|
|
753
|
+
if (!scan.inCode) {
|
|
754
|
+
index = scan.index;
|
|
755
|
+
continue;
|
|
756
|
+
}
|
|
757
|
+
if (source[index] === "{") {
|
|
758
|
+
depth += 1;
|
|
759
|
+
}
|
|
760
|
+
else if (source[index] === "}") {
|
|
761
|
+
depth = Math.max(0, depth - 1);
|
|
762
|
+
}
|
|
763
|
+
index += 1;
|
|
764
|
+
}
|
|
765
|
+
return null;
|
|
766
|
+
}
|
|
767
|
+
function isPhpCodeOffset(source, offset, options) {
|
|
768
|
+
return getPhpCodeBraceDepth(source, offset, options) !== null;
|
|
769
|
+
}
|
|
580
770
|
/**
|
|
581
771
|
* Locate a PHP function body without counting braces in non-code regions.
|
|
582
772
|
*
|
|
@@ -586,8 +776,14 @@ function hasPhpFunctionCallWithStringArgumentMatching(source, functionName, matc
|
|
|
586
776
|
* @returns The matched {@link PhpFunctionRange}, or `null` when no safe range exists.
|
|
587
777
|
*/
|
|
588
778
|
export function findPhpFunctionRange(source, functionName, options = {}) {
|
|
589
|
-
const signaturePattern = new RegExp(`function\\s
|
|
590
|
-
|
|
779
|
+
const signaturePattern = new RegExp(`function\\s+&?\\s*${escapeRegex(functionName)}\\s*\\([^)]*\\)\\s*(?::\\s*[^{};]+)?\\s*\\{`, "gu");
|
|
780
|
+
let signatureMatch = null;
|
|
781
|
+
for (let candidate = signaturePattern.exec(source); candidate; candidate = signaturePattern.exec(source)) {
|
|
782
|
+
if (isPhpCodeOffset(source, candidate.index, options)) {
|
|
783
|
+
signatureMatch = candidate;
|
|
784
|
+
break;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
591
787
|
if (!signatureMatch) {
|
|
592
788
|
return null;
|
|
593
789
|
}
|
|
@@ -598,7 +794,10 @@ export function findPhpFunctionRange(source, functionName, options = {}) {
|
|
|
598
794
|
}
|
|
599
795
|
const openBraceIndex = functionStart + openBraceOffset;
|
|
600
796
|
let depth = 0;
|
|
601
|
-
const scanner = createPhpScannerState();
|
|
797
|
+
const scanner = createPhpScannerState(options);
|
|
798
|
+
// The signature was already verified at a PHP code offset, and the body scan
|
|
799
|
+
// starts at its opening brace rather than at the beginning of the source.
|
|
800
|
+
scanner.inPhp = true;
|
|
602
801
|
let index = openBraceIndex;
|
|
603
802
|
while (index < source.length) {
|
|
604
803
|
const scan = advancePhpScanner(source, index, scanner);
|
|
@@ -4,5 +4,5 @@ export declare class BlockGeneratorService {
|
|
|
4
4
|
plan({ allowExistingDir, alternateRenderTargets, answers, cwd, dataStorageMode, externalLayerId, externalLayerSource, externalLayerSourceLabel, noInstall, packageManager, persistencePolicy, projectDir, repositoryReference, templateId, variant, withMigrationUi, withTestPreset, withWpEnv, wpVersion, }: PlanBlockInput): Promise<PlanBlockResult>;
|
|
5
5
|
validate({ plan }: ValidateBlockInput): Promise<ValidateBlockResult>;
|
|
6
6
|
render({ validated }: RenderBlockInput): Promise<RenderBlockResult>;
|
|
7
|
-
apply({ rendered, installDependencies, onProgress, }: ApplyBlockInput): Promise<ScaffoldProjectResult>;
|
|
7
|
+
apply({ rendered, installDependencies, onProgress, seedCompilerArtifacts, }: ApplyBlockInput): Promise<ScaffoldProjectResult>;
|
|
8
8
|
}
|
|
@@ -7,6 +7,7 @@ import { resolveTemplateSeed, parseTemplateLocator } from "./template-source.js"
|
|
|
7
7
|
import { assertExternalTemplateLayersDoNotWriteProtectedOutputs, resolveExternalTemplateLayers, } from "./template-layers.js";
|
|
8
8
|
import { getBuiltInTemplateOverlayDir, getBuiltInTemplateSharedLayerDirs, resolveBuiltInTemplateSource, resolveBuiltInTemplateSourceFromLayerDirs, } from "./template-builtins.js";
|
|
9
9
|
import { buildTemplateVariablesFromBlockSpec, createBuiltInBlockSpec, } from "./block-generator-service-spec.js";
|
|
10
|
+
import { collectBuiltInCompilerArtifactPaths } from "./scaffold-compiler-artifacts.js";
|
|
10
11
|
import { assertBuiltInTemplateVariantAllowed, assertExternalLayerCompositionOptions, } from "../cli/cli-validation.js";
|
|
11
12
|
const renderedArtifactCache = new WeakMap();
|
|
12
13
|
function createVariablesFingerprint(variables) {
|
|
@@ -32,6 +33,9 @@ function buildProtectedTemplateOutputPaths({ codeArtifacts, spec, variables, art
|
|
|
32
33
|
protectedOutputs.add(`${artifact.relativeDir}/block.json`);
|
|
33
34
|
protectedOutputs.add(`${artifact.relativeDir}/types.ts`);
|
|
34
35
|
}
|
|
36
|
+
for (const artifactPath of collectBuiltInCompilerArtifactPaths(spec.template.family, variables)) {
|
|
37
|
+
protectedOutputs.add(artifactPath);
|
|
38
|
+
}
|
|
35
39
|
for (const manifest of getStarterManifestFiles(spec.template.family, variables)) {
|
|
36
40
|
protectedOutputs.add(manifest.relativePath);
|
|
37
41
|
}
|
|
@@ -227,7 +231,7 @@ export class BlockGeneratorService {
|
|
|
227
231
|
});
|
|
228
232
|
return rendered;
|
|
229
233
|
}
|
|
230
|
-
async apply({ rendered, installDependencies, onProgress, }) {
|
|
234
|
+
async apply({ rendered, installDependencies, onProgress, seedCompilerArtifacts = true, }) {
|
|
231
235
|
const cachedArtifacts = renderedArtifactCache.get(rendered);
|
|
232
236
|
const currentVariablesFingerprint = createVariablesFingerprint(rendered.variables);
|
|
233
237
|
const artifacts = cachedArtifacts &&
|
|
@@ -244,8 +248,9 @@ export class BlockGeneratorService {
|
|
|
244
248
|
templateId: rendered.spec.template.family,
|
|
245
249
|
variables: rendered.variables,
|
|
246
250
|
});
|
|
251
|
+
let applyWarnings = [];
|
|
247
252
|
try {
|
|
248
|
-
await applyBuiltInScaffoldProjectFiles({
|
|
253
|
+
applyWarnings = await applyBuiltInScaffoldProjectFiles({
|
|
249
254
|
allowExistingDir: rendered.target.allowExistingDir,
|
|
250
255
|
artifacts,
|
|
251
256
|
codeArtifacts,
|
|
@@ -257,6 +262,7 @@ export class BlockGeneratorService {
|
|
|
257
262
|
repositoryReference: rendered.target.repositoryReference,
|
|
258
263
|
gitignoreContent: rendered.gitignoreContent,
|
|
259
264
|
readmeContent: rendered.readmeContent,
|
|
265
|
+
seedCompilerArtifacts,
|
|
260
266
|
templateDir: rendered.templateDir,
|
|
261
267
|
templateId: rendered.spec.template.family,
|
|
262
268
|
variables: rendered.variables,
|
|
@@ -274,7 +280,7 @@ export class BlockGeneratorService {
|
|
|
274
280
|
selectedVariant: rendered.selectedVariant,
|
|
275
281
|
templateId: rendered.spec.template.family,
|
|
276
282
|
variables: rendered.variables,
|
|
277
|
-
warnings: rendered.warnings,
|
|
283
|
+
warnings: [...rendered.warnings, ...applyWarnings],
|
|
278
284
|
};
|
|
279
285
|
}
|
|
280
286
|
}
|
|
@@ -116,6 +116,11 @@ export interface ApplyBlockInput {
|
|
|
116
116
|
rendered: RenderBlockResult;
|
|
117
117
|
installDependencies?: ((options: InstallDependenciesOptions) => Promise<void>) | undefined;
|
|
118
118
|
onProgress?: ((event: ScaffoldProgressEvent) => void | Promise<void>) | undefined;
|
|
119
|
+
/**
|
|
120
|
+
* Whether compiler-backed metadata and REST artifacts should be seeded.
|
|
121
|
+
* Dry-run previews disable this and report the expected paths separately.
|
|
122
|
+
*/
|
|
123
|
+
seedCompilerArtifacts?: boolean;
|
|
119
124
|
}
|
|
120
125
|
export declare function createBuiltInBlockSpec({ alternateRenderTargets, answers, dataStorageMode, persistencePolicy, templateId, withMigrationUi, withTestPreset, withWpEnv, wpVersion, }: Omit<PlanBlockInput, "allowExistingDir" | "cwd" | "noInstall" | "packageManager" | "projectDir" | "variant">): BlockSpec;
|
|
121
126
|
export declare function buildTemplateVariablesFromBlockSpec(spec: BlockSpec): ScaffoldTemplateVariables;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { DEFAULT_COMPOUND_CHILD_BODY_PLACEHOLDER, buildBlockJsonAttributes, buildManifestDocument, } from "./built-in-block-attribute-emitters.js";
|
|
1
|
+
export { DEFAULT_COMPOUND_CHILD_BODY_PLACEHOLDER, buildBlockJsonAttributes, buildBlockJsonExampleAttributes, buildManifestDocument, } from "./built-in-block-attribute-emitters.js";
|
|
2
2
|
export type { AttributeDescription, EmittedAttributeDefinition, } from "./built-in-block-attribute-emitters.js";
|
|
3
3
|
export { buildBasicAttributes, buildCompoundChildAttributes, buildCompoundParentAttributes, buildInteractivityAttributes, buildPersistenceAttributes, } from "./built-in-block-attribute-specs.js";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { DEFAULT_COMPOUND_CHILD_BODY_PLACEHOLDER, buildBlockJsonAttributes, buildManifestDocument, } from "./built-in-block-attribute-emitters.js";
|
|
1
|
+
export { DEFAULT_COMPOUND_CHILD_BODY_PLACEHOLDER, buildBlockJsonAttributes, buildBlockJsonExampleAttributes, buildManifestDocument, } from "./built-in-block-attribute-emitters.js";
|
|
2
2
|
export { buildBasicAttributes, buildCompoundChildAttributes, buildCompoundParentAttributes, buildInteractivityAttributes, buildPersistenceAttributes, } from "./built-in-block-attribute-specs.js";
|
|
@@ -5,13 +5,13 @@ const STANDARD_PREAMBLE_LINES = [
|
|
|
5
5
|
"\tTypiaValidationError,",
|
|
6
6
|
"\tValidationResult,",
|
|
7
7
|
'} from "@wp-typia/block-runtime/validation";',
|
|
8
|
-
'import { tags } from "typia";',
|
|
8
|
+
'import type { tags } from "@wp-typia/block-runtime/typia-tags";',
|
|
9
9
|
"",
|
|
10
10
|
'export type { TypiaValidationError, ValidationResult } from "@wp-typia/block-runtime/validation";',
|
|
11
11
|
];
|
|
12
12
|
const VALIDATION_ONLY_PREAMBLE_LINES = [
|
|
13
13
|
'import type { ValidationResult } from "@wp-typia/block-runtime/validation";',
|
|
14
|
-
'import { tags } from "typia";',
|
|
14
|
+
'import type { tags } from "@wp-typia/block-runtime/typia-tags";',
|
|
15
15
|
"",
|
|
16
16
|
'export type { ValidationResult } from "@wp-typia/block-runtime/validation";',
|
|
17
17
|
];
|
|
@@ -32,6 +32,6 @@ export declare function buildBuiltInBlockArtifacts({ templateId, variables, }: {
|
|
|
32
32
|
* Serializes a generated `block.json` document using scaffold formatting.
|
|
33
33
|
*
|
|
34
34
|
* @param document Structured `block.json` document.
|
|
35
|
-
* @returns Pretty-printed JSON
|
|
35
|
+
* @returns Pretty-printed JSON using tab indentation.
|
|
36
36
|
*/
|
|
37
37
|
export declare function stringifyBuiltInBlockJsonDocument(document: Record<string, unknown>): string;
|