@wp-typia/project-tools 0.16.13 → 0.17.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.
Files changed (38) hide show
  1. package/README.md +0 -1
  2. package/dist/runtime/block-generator-service-spec.d.ts +6 -0
  3. package/dist/runtime/block-generator-service-spec.js +27 -0
  4. package/dist/runtime/built-in-block-code-artifacts.js +14 -1
  5. package/dist/runtime/built-in-block-code-templates/query-loop.d.ts +1 -0
  6. package/dist/runtime/built-in-block-code-templates/query-loop.js +70 -0
  7. package/dist/runtime/built-in-block-code-templates.d.ts +1 -0
  8. package/dist/runtime/built-in-block-code-templates.js +1 -0
  9. package/dist/runtime/built-in-block-non-ts-artifacts.js +2 -0
  10. package/dist/runtime/cli-help.js +1 -0
  11. package/dist/runtime/cli-prompt.js +78 -19
  12. package/dist/runtime/cli-scaffold.d.ts +2 -1
  13. package/dist/runtime/cli-scaffold.js +2 -1
  14. package/dist/runtime/local-dev-presets.js +21 -11
  15. package/dist/runtime/scaffold-answer-resolution.d.ts +37 -0
  16. package/dist/runtime/scaffold-answer-resolution.js +163 -0
  17. package/dist/runtime/scaffold-apply-utils.d.ts +1 -16
  18. package/dist/runtime/scaffold-apply-utils.js +4 -128
  19. package/dist/runtime/scaffold-documents.d.ts +34 -0
  20. package/dist/runtime/scaffold-documents.js +143 -0
  21. package/dist/runtime/scaffold-onboarding.js +12 -0
  22. package/dist/runtime/scaffold-template-variables.d.ts +9 -0
  23. package/dist/runtime/scaffold-template-variables.js +117 -0
  24. package/dist/runtime/scaffold.d.ts +19 -9
  25. package/dist/runtime/scaffold.js +6 -202
  26. package/dist/runtime/template-defaults.d.ts +7 -0
  27. package/dist/runtime/template-defaults.js +4 -0
  28. package/dist/runtime/template-registry.d.ts +1 -1
  29. package/dist/runtime/template-registry.js +14 -1
  30. package/package.json +3 -3
  31. package/templates/query-loop/inc/query-runtime.php.mustache +85 -0
  32. package/templates/query-loop/package.json.mustache +32 -0
  33. package/templates/query-loop/src/patterns/grid.php.mustache +49 -0
  34. package/templates/query-loop/src/patterns/list.php.mustache +48 -0
  35. package/templates/query-loop/src/query-extension.ts.mustache +41 -0
  36. package/templates/query-loop/src/validator-toolkit.ts.mustache +1 -0
  37. package/templates/query-loop/webpack.config.js.mustache +16 -0
  38. package/templates/query-loop/{{slugKebabCase}}.php.mustache +84 -0
@@ -0,0 +1,117 @@
1
+ import { buildTemplateVariablesFromBlockSpec, createBuiltInBlockSpec, } from './block-generator-service.js';
2
+ import { getPackageVersions } from './package-versions.js';
3
+ import { buildBlockCssClassName, buildFrontendCssClassName, resolveScaffoldIdentifiers, } from './scaffold-identifiers.js';
4
+ import { BUILTIN_BLOCK_METADATA_VERSION, COMPOUND_CHILD_BLOCK_METADATA_DEFAULTS, getBuiltInTemplateMetadataDefaults, } from './template-defaults.js';
5
+ import { getTemplateById, isBuiltInTemplateId, } from './template-registry.js';
6
+ import { toPascalCase, toSnakeCase, } from './string-case.js';
7
+ /**
8
+ * Build the normalized template variables used by scaffold rendering.
9
+ *
10
+ * @param templateId Selected scaffold template identifier.
11
+ * @param answers Normalized scaffold answers collected from defaults, flags, and prompts.
12
+ * @returns Template variables ready for file interpolation and generated artifacts.
13
+ */
14
+ export function getTemplateVariables(templateId, answers) {
15
+ if (isBuiltInTemplateId(templateId)) {
16
+ return buildTemplateVariablesFromBlockSpec(createBuiltInBlockSpec({
17
+ answers,
18
+ dataStorageMode: answers.dataStorageMode,
19
+ persistencePolicy: answers.persistencePolicy,
20
+ templateId,
21
+ }));
22
+ }
23
+ const { apiClientPackageVersion, blockRuntimePackageVersion, blockTypesPackageVersion, projectToolsPackageVersion, restPackageVersion, } = getPackageVersions();
24
+ const template = isBuiltInTemplateId(templateId) ? getTemplateById(templateId) : null;
25
+ const metadataDefaults = isBuiltInTemplateId(templateId)
26
+ ? getBuiltInTemplateMetadataDefaults(templateId)
27
+ : null;
28
+ const identifiers = resolveScaffoldIdentifiers({
29
+ namespace: answers.namespace,
30
+ phpPrefix: answers.phpPrefix,
31
+ slug: answers.slug,
32
+ textDomain: answers.textDomain,
33
+ });
34
+ const slug = identifiers.slug;
35
+ const slugSnakeCase = toSnakeCase(slug);
36
+ const pascalCase = toPascalCase(slug);
37
+ const title = answers.title.trim();
38
+ const namespace = identifiers.namespace;
39
+ const description = answers.description.trim();
40
+ const textDomain = identifiers.textDomain;
41
+ const phpPrefix = identifiers.phpPrefix;
42
+ const phpPrefixUpper = phpPrefix.toUpperCase();
43
+ const compoundChildTitle = `${title} Item`;
44
+ const cssClassName = buildBlockCssClassName(namespace, slug);
45
+ const compoundChildCssClassName = buildBlockCssClassName(namespace, `${slug}-item`);
46
+ const compoundPersistenceEnabled = templateId === 'persistence'
47
+ ? true
48
+ : templateId === 'compound'
49
+ ? Boolean(answers.dataStorageMode || answers.persistencePolicy)
50
+ : false;
51
+ const dataStorageMode = templateId === 'persistence' || compoundPersistenceEnabled
52
+ ? answers.dataStorageMode ?? 'custom-table'
53
+ : 'custom-table';
54
+ const persistencePolicy = templateId === 'persistence' || compoundPersistenceEnabled
55
+ ? answers.persistencePolicy ?? 'authenticated'
56
+ : 'authenticated';
57
+ return {
58
+ apiClientPackageVersion,
59
+ author: answers.author.trim(),
60
+ blockRuntimePackageVersion,
61
+ blockMetadataVersion: BUILTIN_BLOCK_METADATA_VERSION,
62
+ blockTypesPackageVersion,
63
+ category: metadataDefaults?.category ?? template?.defaultCategory ?? 'widgets',
64
+ icon: metadataDefaults?.icon ?? 'smiley',
65
+ compoundChildTitle,
66
+ compoundChildCategory: COMPOUND_CHILD_BLOCK_METADATA_DEFAULTS.category,
67
+ compoundChildCssClassName,
68
+ compoundChildIcon: COMPOUND_CHILD_BLOCK_METADATA_DEFAULTS.icon,
69
+ compoundChildTitleJson: JSON.stringify(compoundChildTitle),
70
+ compoundPersistenceEnabled: compoundPersistenceEnabled ? 'true' : 'false',
71
+ projectToolsPackageVersion,
72
+ cssClassName,
73
+ dataStorageMode,
74
+ dashCase: slug,
75
+ description,
76
+ descriptionJson: JSON.stringify(description),
77
+ frontendCssClassName: buildFrontendCssClassName(cssClassName),
78
+ queryAllowedControlsJson: JSON.stringify([], null, 2),
79
+ queryPostType: answers.queryPostType?.trim() || 'post',
80
+ queryPostTypeJson: JSON.stringify(answers.queryPostType?.trim() || 'post'),
81
+ queryVariationNamespace: `${namespace}/${slug}`,
82
+ queryVariationNamespaceJson: JSON.stringify(`${namespace}/${slug}`),
83
+ isAuthenticatedPersistencePolicy: persistencePolicy === 'authenticated' ? 'true' : 'false',
84
+ isPublicPersistencePolicy: persistencePolicy === 'public' ? 'true' : 'false',
85
+ bootstrapCredentialDeclarations: persistencePolicy === 'public'
86
+ ? "publicWriteExpiresAt?: number & tags.Type< 'uint32' >;\n\tpublicWriteToken?: string & tags.MinLength< 1 > & tags.MaxLength< 512 >;"
87
+ : "restNonce?: string & tags.MinLength< 1 > & tags.MaxLength< 128 >;",
88
+ persistencePolicyDescriptionJson: JSON.stringify(persistencePolicy === 'authenticated'
89
+ ? 'Writes require a logged-in user and a valid REST nonce.'
90
+ : 'Anonymous writes use signed short-lived public tokens, per-request ids, and coarse rate limiting.'),
91
+ keyword: slug.replace(/-/g, ' '),
92
+ namespace,
93
+ needsMigration: '{{needsMigration}}',
94
+ pascalCase,
95
+ phpPrefix,
96
+ phpPrefixUpper,
97
+ restPackageVersion,
98
+ publicWriteRequestIdDeclaration: persistencePolicy === 'public'
99
+ ? "publicWriteRequestId: string & tags.MinLength< 1 > & tags.MaxLength< 128 >;"
100
+ : "publicWriteRequestId?: string & tags.MinLength< 1 > & tags.MaxLength< 128 >;",
101
+ restWriteAuthIntent: persistencePolicy === 'public'
102
+ ? 'public-write-protected'
103
+ : 'authenticated',
104
+ restWriteAuthMechanism: persistencePolicy === 'public' ? 'public-signed-token' : 'rest-nonce',
105
+ restWriteAuthMode: persistencePolicy === 'public' ? 'public-signed-token' : 'authenticated-rest-nonce',
106
+ slug,
107
+ slugCamelCase: pascalCase.charAt(0).toLowerCase() + pascalCase.slice(1),
108
+ slugKebabCase: slug,
109
+ slugSnakeCase,
110
+ textDomain,
111
+ textdomain: textDomain,
112
+ title,
113
+ titleJson: JSON.stringify(title),
114
+ titleCase: pascalCase,
115
+ persistencePolicy,
116
+ };
117
+ }
@@ -15,6 +15,7 @@ export interface ScaffoldAnswers {
15
15
  persistencePolicy?: PersistencePolicy;
16
16
  /** Snake_case PHP symbol prefix used for generated functions, constants, and keys. */
17
17
  phpPrefix?: string;
18
+ queryPostType?: string;
18
19
  slug: string;
19
20
  /** Kebab-case WordPress text domain used in block metadata and i18n strings. */
20
21
  textDomain?: string;
@@ -46,6 +47,7 @@ export interface ScaffoldTemplateVariables extends Record<string, string> {
46
47
  dashCase: string;
47
48
  dataStorageMode: DataStorageMode;
48
49
  description: string;
50
+ descriptionJson: string;
49
51
  frontendCssClassName: string;
50
52
  keyword: string;
51
53
  namespace: string;
@@ -53,6 +55,11 @@ export interface ScaffoldTemplateVariables extends Record<string, string> {
53
55
  pascalCase: string;
54
56
  phpPrefix: string;
55
57
  phpPrefixUpper: string;
58
+ queryAllowedControlsJson: string;
59
+ queryPostTypeJson: string;
60
+ queryPostType: string;
61
+ queryVariationNamespace: string;
62
+ queryVariationNamespaceJson: string;
56
63
  isAuthenticatedPersistencePolicy: "false" | "true";
57
64
  isPublicPersistencePolicy: "false" | "true";
58
65
  bootstrapCredentialDeclarations: string;
@@ -81,25 +88,32 @@ export interface ScaffoldTemplateVariables extends Record<string, string> {
81
88
  * can surface custom ids. Downstream code uses `isBuiltInTemplateId()` to
82
89
  * distinguish built-in templates from custom sources.
83
90
  */
84
- interface ResolveTemplateOptions {
91
+ export interface ResolveTemplateOptions {
85
92
  isInteractive?: boolean;
86
93
  selectTemplate?: () => Promise<string>;
87
94
  templateId?: string;
88
95
  yes?: boolean;
89
96
  }
90
- interface ResolvePackageManagerOptions {
97
+ /**
98
+ * Options for resolving the package manager in CLI and interactive scaffold flows.
99
+ */
100
+ export interface ResolvePackageManagerOptions {
91
101
  isInteractive?: boolean;
92
102
  packageManager?: string;
93
103
  selectPackageManager?: () => Promise<PackageManagerId>;
94
104
  yes?: boolean;
95
105
  }
96
- interface CollectScaffoldAnswersOptions {
106
+ /**
107
+ * Options for collecting scaffold answers from defaults, overrides, and prompts.
108
+ */
109
+ export interface CollectScaffoldAnswersOptions {
97
110
  dataStorageMode?: DataStorageMode;
98
111
  namespace?: string;
99
112
  phpPrefix?: string;
100
113
  projectName: string;
101
114
  promptText?: (message: string, defaultValue: string, validate?: (value: string) => true | string) => Promise<string>;
102
115
  persistencePolicy?: PersistencePolicy;
116
+ queryPostType?: string;
103
117
  textDomain?: string;
104
118
  templateId: string;
105
119
  withTestPreset?: boolean;
@@ -139,12 +153,8 @@ export interface ScaffoldProjectResult {
139
153
  warnings: string[];
140
154
  }
141
155
  export { buildBlockCssClassName } from "./scaffold-identifiers.js";
156
+ export { collectScaffoldAnswers, detectAuthor, getDefaultAnswers, resolvePackageManagerId, resolveTemplateId, } from "./scaffold-answer-resolution.js";
157
+ export { getTemplateVariables } from "./scaffold-template-variables.js";
142
158
  export declare function isDataStorageMode(value: string): value is DataStorageMode;
143
159
  export declare function isPersistencePolicy(value: string): value is PersistencePolicy;
144
- export declare function detectAuthor(): string;
145
- export declare function getDefaultAnswers(projectName: string, templateId: string): ScaffoldAnswers;
146
- export declare function resolveTemplateId({ templateId, yes, isInteractive, selectTemplate, }: ResolveTemplateOptions): Promise<string>;
147
- export declare function resolvePackageManagerId({ packageManager, yes, isInteractive, selectPackageManager, }: ResolvePackageManagerOptions): Promise<PackageManagerId>;
148
- export declare function collectScaffoldAnswers({ projectName, templateId, yes, dataStorageMode, namespace, persistencePolicy, phpPrefix, promptText, textDomain, }: CollectScaffoldAnswersOptions): Promise<ScaffoldAnswers>;
149
- export declare function getTemplateVariables(templateId: string, answers: ScaffoldAnswers): ScaffoldTemplateVariables;
150
160
  export declare function scaffoldProject({ projectDir, templateId, answers, dataStorageMode, persistencePolicy, packageManager, externalLayerId, externalLayerSource, externalLayerSourceLabel, repositoryReference, cwd, allowExistingDir, noInstall, installDependencies, variant, withMigrationUi, withTestPreset, withWpEnv, }: ScaffoldProjectOptions): Promise<ScaffoldProjectResult>;
@@ -1,230 +1,34 @@
1
1
  import fs from "node:fs";
2
2
  import { promises as fsp } from "node:fs";
3
3
  import path from "node:path";
4
- import { execSync } from "node:child_process";
5
- import { PACKAGE_MANAGER_IDS, getPackageManager, } from "./package-managers.js";
4
+ import { getPackageManager } from "./package-managers.js";
6
5
  import { buildGitignore, buildReadme, mergeTextLines, replaceTextRecursively, } from "./scaffold-apply-utils.js";
7
- import { buildBlockCssClassName, buildFrontendCssClassName, normalizeBlockSlug, resolveScaffoldIdentifiers, validateBlockSlug, validateNamespace, } from "./scaffold-identifiers.js";
8
6
  import { applyGeneratedProjectDxPackageJson, applyLocalDevPresetFiles, } from "./local-dev-presets.js";
9
7
  import { applyMigrationUiCapability } from "./migration-ui-capability.js";
10
- import { getPackageVersions } from "./package-versions.js";
11
8
  import { applyWorkspaceMigrationCapability, ensureScaffoldDirectory, isOfficialWorkspaceProject, seedBuiltInPersistenceArtifacts, writeStarterManifestFiles, } from "./scaffold-bootstrap.js";
12
9
  import { defaultInstallDependencies, normalizePackageJson, normalizePackageManagerFiles, removeUnexpectedLockfiles, } from "./scaffold-package-manager-files.js";
13
- import { toPascalCase, toSnakeCase, toTitleCase, } from "./string-case.js";
14
- import { BUILTIN_BLOCK_METADATA_VERSION, COMPOUND_CHILD_BLOCK_METADATA_DEFAULTS, getBuiltInTemplateMetadataDefaults, getRemovedBuiltInTemplateMessage, isRemovedBuiltInTemplateId, } from "./template-defaults.js";
15
10
  import { copyInterpolatedDirectory } from "./template-render.js";
16
- import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, TEMPLATE_IDS, getTemplateById, isBuiltInTemplateId, } from "./template-registry.js";
11
+ import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, isBuiltInTemplateId, } from "./template-registry.js";
17
12
  import { resolveTemplateSource } from "./template-source.js";
18
- import { BlockGeneratorService, buildTemplateVariablesFromBlockSpec, createBuiltInBlockSpec, } from "./block-generator-service.js";
13
+ import { BlockGeneratorService, } from "./block-generator-service.js";
14
+ import { getTemplateVariables } from "./scaffold-template-variables.js";
19
15
  const WORKSPACE_TEMPLATE_ALIAS = "workspace";
20
16
  export const DATA_STORAGE_MODES = ["post-meta", "custom-table"];
21
17
  export const PERSISTENCE_POLICIES = ["authenticated", "public"];
22
18
  export { buildBlockCssClassName } from "./scaffold-identifiers.js";
19
+ export { collectScaffoldAnswers, detectAuthor, getDefaultAnswers, resolvePackageManagerId, resolveTemplateId, } from "./scaffold-answer-resolution.js";
20
+ export { getTemplateVariables } from "./scaffold-template-variables.js";
23
21
  export function isDataStorageMode(value) {
24
22
  return DATA_STORAGE_MODES.includes(value);
25
23
  }
26
24
  export function isPersistencePolicy(value) {
27
25
  return PERSISTENCE_POLICIES.includes(value);
28
26
  }
29
- export function detectAuthor() {
30
- try {
31
- return (execSync("git config user.name", {
32
- encoding: "utf8",
33
- stdio: ["ignore", "pipe", "ignore"],
34
- }).trim() || "Your Name");
35
- }
36
- catch {
37
- return "Your Name";
38
- }
39
- }
40
- export function getDefaultAnswers(projectName, templateId) {
41
- const template = isBuiltInTemplateId(templateId) ? getTemplateById(templateId) : null;
42
- const slugDefault = normalizeBlockSlug(projectName) || "my-wp-typia-block";
43
- return {
44
- author: detectAuthor(),
45
- dataStorageMode: templateId === "persistence" ? "custom-table" : undefined,
46
- description: template?.description ?? "A WordPress block scaffolded from a remote template",
47
- namespace: slugDefault,
48
- persistencePolicy: templateId === "persistence" ? "authenticated" : undefined,
49
- phpPrefix: toSnakeCase(slugDefault),
50
- slug: slugDefault,
51
- textDomain: slugDefault,
52
- title: toTitleCase(slugDefault),
53
- };
54
- }
55
27
  function normalizeTemplateSelection(templateId) {
56
28
  return templateId === WORKSPACE_TEMPLATE_ALIAS
57
29
  ? OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE
58
30
  : templateId;
59
31
  }
60
- export async function resolveTemplateId({ templateId, yes = false, isInteractive = false, selectTemplate, }) {
61
- if (templateId) {
62
- const normalizedTemplateId = normalizeTemplateSelection(templateId);
63
- if (isRemovedBuiltInTemplateId(templateId)) {
64
- throw new Error(getRemovedBuiltInTemplateMessage(templateId));
65
- }
66
- if (isBuiltInTemplateId(normalizedTemplateId)) {
67
- return getTemplateById(normalizedTemplateId).id;
68
- }
69
- return normalizedTemplateId;
70
- }
71
- if (yes) {
72
- return "basic";
73
- }
74
- if (!isInteractive || !selectTemplate) {
75
- throw new Error(`Template is required in non-interactive mode. Use --template <${TEMPLATE_IDS.join("|")}|./path|github:owner/repo/path[#ref]|npm-package>.`);
76
- }
77
- return normalizeTemplateSelection(await selectTemplate());
78
- }
79
- export async function resolvePackageManagerId({ packageManager, yes = false, isInteractive = false, selectPackageManager, }) {
80
- if (packageManager) {
81
- return getPackageManager(packageManager).id;
82
- }
83
- if (yes) {
84
- return "npm";
85
- }
86
- if (!isInteractive || !selectPackageManager) {
87
- throw new Error(`Package manager is required in non-interactive mode. Use --package-manager <${PACKAGE_MANAGER_IDS.join("|")}>.`);
88
- }
89
- return selectPackageManager();
90
- }
91
- export async function collectScaffoldAnswers({ projectName, templateId, yes = false, dataStorageMode, namespace, persistencePolicy, phpPrefix, promptText, textDomain, }) {
92
- const defaults = getDefaultAnswers(projectName, templateId);
93
- if (yes) {
94
- const identifiers = resolveScaffoldIdentifiers({
95
- namespace: namespace ?? defaults.namespace,
96
- phpPrefix,
97
- slug: defaults.slug,
98
- textDomain,
99
- });
100
- return {
101
- ...defaults,
102
- dataStorageMode: dataStorageMode ?? defaults.dataStorageMode,
103
- namespace: identifiers.namespace,
104
- persistencePolicy: persistencePolicy ?? defaults.persistencePolicy,
105
- phpPrefix: identifiers.phpPrefix,
106
- textDomain: identifiers.textDomain,
107
- };
108
- }
109
- if (!promptText) {
110
- throw new Error("Interactive answers require a promptText callback.");
111
- }
112
- const identifiers = resolveScaffoldIdentifiers({
113
- namespace: namespace ?? (await promptText("Namespace", defaults.namespace, validateNamespace)),
114
- phpPrefix,
115
- slug: await promptText("Block slug", defaults.slug, validateBlockSlug),
116
- textDomain,
117
- });
118
- return {
119
- author: await promptText("Author", defaults.author),
120
- dataStorageMode: dataStorageMode ?? defaults.dataStorageMode,
121
- description: await promptText("Description", defaults.description),
122
- namespace: identifiers.namespace,
123
- persistencePolicy: persistencePolicy ?? defaults.persistencePolicy,
124
- phpPrefix: identifiers.phpPrefix,
125
- slug: identifiers.slug,
126
- textDomain: identifiers.textDomain,
127
- title: await promptText("Block title", toTitleCase(identifiers.slug)),
128
- };
129
- }
130
- export function getTemplateVariables(templateId, answers) {
131
- if (isBuiltInTemplateId(templateId)) {
132
- return buildTemplateVariablesFromBlockSpec(createBuiltInBlockSpec({
133
- answers,
134
- dataStorageMode: answers.dataStorageMode,
135
- persistencePolicy: answers.persistencePolicy,
136
- templateId,
137
- }));
138
- }
139
- const { apiClientPackageVersion, blockRuntimePackageVersion, blockTypesPackageVersion, projectToolsPackageVersion, restPackageVersion, } = getPackageVersions();
140
- const template = isBuiltInTemplateId(templateId) ? getTemplateById(templateId) : null;
141
- const metadataDefaults = isBuiltInTemplateId(templateId)
142
- ? getBuiltInTemplateMetadataDefaults(templateId)
143
- : null;
144
- const identifiers = resolveScaffoldIdentifiers({
145
- namespace: answers.namespace,
146
- phpPrefix: answers.phpPrefix,
147
- slug: answers.slug,
148
- textDomain: answers.textDomain,
149
- });
150
- const slug = identifiers.slug;
151
- const slugSnakeCase = toSnakeCase(slug);
152
- const pascalCase = toPascalCase(slug);
153
- const title = answers.title.trim();
154
- const namespace = identifiers.namespace;
155
- const description = answers.description.trim();
156
- const textDomain = identifiers.textDomain;
157
- const phpPrefix = identifiers.phpPrefix;
158
- const phpPrefixUpper = phpPrefix.toUpperCase();
159
- const compoundChildTitle = `${title} Item`;
160
- const cssClassName = buildBlockCssClassName(namespace, slug);
161
- const compoundChildCssClassName = buildBlockCssClassName(namespace, `${slug}-item`);
162
- const compoundPersistenceEnabled = templateId === "persistence"
163
- ? true
164
- : templateId === "compound"
165
- ? Boolean(answers.dataStorageMode || answers.persistencePolicy)
166
- : false;
167
- const dataStorageMode = templateId === "persistence" || compoundPersistenceEnabled
168
- ? answers.dataStorageMode ?? "custom-table"
169
- : "custom-table";
170
- const persistencePolicy = templateId === "persistence" || compoundPersistenceEnabled
171
- ? answers.persistencePolicy ?? "authenticated"
172
- : "authenticated";
173
- return {
174
- apiClientPackageVersion,
175
- author: answers.author.trim(),
176
- blockRuntimePackageVersion,
177
- blockMetadataVersion: BUILTIN_BLOCK_METADATA_VERSION,
178
- blockTypesPackageVersion,
179
- category: metadataDefaults?.category ?? template?.defaultCategory ?? "widgets",
180
- icon: metadataDefaults?.icon ?? "smiley",
181
- compoundChildTitle,
182
- compoundChildCategory: COMPOUND_CHILD_BLOCK_METADATA_DEFAULTS.category,
183
- compoundChildCssClassName,
184
- compoundChildIcon: COMPOUND_CHILD_BLOCK_METADATA_DEFAULTS.icon,
185
- compoundChildTitleJson: JSON.stringify(compoundChildTitle),
186
- compoundPersistenceEnabled: compoundPersistenceEnabled ? "true" : "false",
187
- projectToolsPackageVersion,
188
- cssClassName,
189
- dataStorageMode,
190
- dashCase: slug,
191
- description,
192
- frontendCssClassName: buildFrontendCssClassName(cssClassName),
193
- isAuthenticatedPersistencePolicy: persistencePolicy === "authenticated" ? "true" : "false",
194
- isPublicPersistencePolicy: persistencePolicy === "public" ? "true" : "false",
195
- bootstrapCredentialDeclarations: persistencePolicy === "public"
196
- ? "publicWriteExpiresAt?: number & tags.Type< 'uint32' >;\n\tpublicWriteToken?: string & tags.MinLength< 1 > & tags.MaxLength< 512 >;"
197
- : "restNonce?: string & tags.MinLength< 1 > & tags.MaxLength< 128 >;",
198
- persistencePolicyDescriptionJson: JSON.stringify(persistencePolicy === "authenticated"
199
- ? "Writes require a logged-in user and a valid REST nonce."
200
- : "Anonymous writes use signed short-lived public tokens, per-request ids, and coarse rate limiting."),
201
- keyword: slug.replace(/-/g, " "),
202
- namespace,
203
- needsMigration: "{{needsMigration}}",
204
- pascalCase,
205
- phpPrefix,
206
- phpPrefixUpper,
207
- restPackageVersion,
208
- publicWriteRequestIdDeclaration: persistencePolicy === "public"
209
- ? "publicWriteRequestId: string & tags.MinLength< 1 > & tags.MaxLength< 128 >;"
210
- : "publicWriteRequestId?: string & tags.MinLength< 1 > & tags.MaxLength< 128 >;",
211
- restWriteAuthIntent: persistencePolicy === "public"
212
- ? "public-write-protected"
213
- : "authenticated",
214
- restWriteAuthMechanism: persistencePolicy === "public" ? "public-signed-token" : "rest-nonce",
215
- restWriteAuthMode: persistencePolicy === "public" ? "public-signed-token" : "authenticated-rest-nonce",
216
- slug,
217
- slugCamelCase: pascalCase.charAt(0).toLowerCase() + pascalCase.slice(1),
218
- slugKebabCase: slug,
219
- slugSnakeCase,
220
- textDomain,
221
- textdomain: textDomain,
222
- title,
223
- titleJson: JSON.stringify(title),
224
- titleCase: pascalCase,
225
- persistencePolicy,
226
- };
227
- }
228
32
  export async function scaffoldProject({ projectDir, templateId, answers, dataStorageMode, persistencePolicy, packageManager, externalLayerId, externalLayerSource, externalLayerSourceLabel, repositoryReference, cwd = process.cwd(), allowExistingDir = false, noInstall = false, installDependencies = undefined, variant, withMigrationUi = false, withTestPreset = false, withWpEnv = false, }) {
229
33
  const resolvedTemplateId = normalizeTemplateSelection(templateId);
230
34
  const resolvedPackageManager = getPackageManager(packageManager).id;
@@ -22,6 +22,10 @@ export declare const BUILTIN_TEMPLATE_METADATA_DEFAULTS: Readonly<{
22
22
  category: "widgets";
23
23
  icon: "screenoptions";
24
24
  }>;
25
+ "query-loop": Readonly<{
26
+ category: "widgets";
27
+ icon: "query-pagination";
28
+ }>;
25
29
  }>;
26
30
  /**
27
31
  * Shared hidden child block metadata defaults for compound scaffolds.
@@ -56,6 +60,9 @@ export declare function getBuiltInTemplateMetadataDefaults(templateId: keyof typ
56
60
  }> | Readonly<{
57
61
  category: "widgets";
58
62
  icon: "screenoptions";
63
+ }> | Readonly<{
64
+ category: "widgets";
65
+ icon: "query-pagination";
59
66
  }>;
60
67
  /**
61
68
  * Checks whether a template id points at a removed built-in scaffold.
@@ -22,6 +22,10 @@ export const BUILTIN_TEMPLATE_METADATA_DEFAULTS = Object.freeze({
22
22
  category: "widgets",
23
23
  icon: "screenoptions",
24
24
  }),
25
+ "query-loop": Object.freeze({
26
+ category: "widgets",
27
+ icon: "query-pagination",
28
+ }),
25
29
  });
26
30
  /**
27
31
  * Shared hidden child block metadata defaults for compound scaffolds.
@@ -14,7 +14,7 @@ export declare const SHARED_MIGRATION_UI_TEMPLATE_ROOT: string;
14
14
  export declare const SHARED_WORKSPACE_TEMPLATE_ROOT: string;
15
15
  export declare const SHARED_TEST_PRESET_TEMPLATE_ROOT: string;
16
16
  export declare const SHARED_WP_ENV_PRESET_TEMPLATE_ROOT: string;
17
- export declare const BUILTIN_TEMPLATE_IDS: readonly ["basic", "interactivity", "persistence", "compound"];
17
+ export declare const BUILTIN_TEMPLATE_IDS: readonly ["basic", "interactivity", "persistence", "compound", "query-loop"];
18
18
  export declare const OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE = "@wp-typia/create-workspace-template";
19
19
  export type BuiltInTemplateId = (typeof BUILTIN_TEMPLATE_IDS)[number];
20
20
  export type PersistencePolicy = "authenticated" | "public";
@@ -40,7 +40,13 @@ export const SHARED_MIGRATION_UI_TEMPLATE_ROOT = path.join(SHARED_TEMPLATE_ROOT,
40
40
  export const SHARED_WORKSPACE_TEMPLATE_ROOT = path.join(SHARED_TEMPLATE_ROOT, "workspace");
41
41
  export const SHARED_TEST_PRESET_TEMPLATE_ROOT = path.join(SHARED_PRESET_TEMPLATE_ROOT, "test-preset");
42
42
  export const SHARED_WP_ENV_PRESET_TEMPLATE_ROOT = path.join(SHARED_PRESET_TEMPLATE_ROOT, "wp-env");
43
- export const BUILTIN_TEMPLATE_IDS = ["basic", "interactivity", "persistence", "compound"];
43
+ export const BUILTIN_TEMPLATE_IDS = [
44
+ "basic",
45
+ "interactivity",
46
+ "persistence",
47
+ "compound",
48
+ "query-loop",
49
+ ];
44
50
  export const OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE = "@wp-typia/create-workspace-template";
45
51
  export const TEMPLATE_REGISTRY = Object.freeze([
46
52
  {
@@ -71,6 +77,13 @@ export const TEMPLATE_REGISTRY = Object.freeze([
71
77
  features: ["InnerBlocks", "Hidden child blocks", "Optional persistence layer"],
72
78
  templateDir: path.join(TEMPLATE_ROOT, "compound"),
73
79
  },
80
+ {
81
+ id: "query-loop",
82
+ description: "A Query Loop block variation scaffold with stable namespace-based identity, inline starter layout, connected pattern presets, custom query seams, and runtime parity hooks",
83
+ defaultCategory: getBuiltInTemplateMetadataDefaults("query-loop").category,
84
+ features: ["core/query variation", "Default innerBlocks", "Connected patterns", "Custom query hooks", "Runtime parity hooks", "Allowed controls"],
85
+ templateDir: path.join(TEMPLATE_ROOT, "query-loop"),
86
+ },
74
87
  {
75
88
  id: OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE,
76
89
  description: "The official empty workspace template that powers `wp-typia add ...` workflows",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wp-typia/project-tools",
3
- "version": "0.16.13",
3
+ "version": "0.17.0",
4
4
  "description": "Project orchestration and programmatic tooling for wp-typia",
5
5
  "packageManager": "bun@1.3.11",
6
6
  "type": "module",
@@ -120,8 +120,8 @@
120
120
  },
121
121
  "dependencies": {
122
122
  "@wp-typia/api-client": "^0.4.5",
123
- "@wp-typia/block-runtime": "^0.4.9",
124
- "@wp-typia/rest": "^0.3.9",
123
+ "@wp-typia/block-runtime": "^0.4.10",
124
+ "@wp-typia/rest": "^0.3.10",
125
125
  "@wp-typia/block-types": "^0.2.4",
126
126
  "mustache": "^4.2.0",
127
127
  "npm-package-arg": "^13.0.0",
@@ -0,0 +1,85 @@
1
+ <?php
2
+ if ( ! defined( 'ABSPATH' ) ) {
3
+ exit;
4
+ }
5
+
6
+ function {{phpPrefix}}_get_query_loop_variation_name() {
7
+ return '{{namespace}}/{{slug}}';
8
+ }
9
+
10
+ function {{phpPrefix}}_get_query_loop_variation_marker_key() {
11
+ return 'wpTypiaVariation';
12
+ }
13
+
14
+ function {{phpPrefix}}_extract_query_loop_request_query( WP_REST_Request $request ) {
15
+ $params = $request->get_params();
16
+
17
+ if ( isset( $params['query'] ) && is_array( $params['query'] ) ) {
18
+ return $params['query'];
19
+ }
20
+
21
+ return is_array( $params ) ? $params : array();
22
+ }
23
+
24
+ function {{phpPrefix}}_map_query_loop_custom_query_vars( array $query_vars, array $source_query ) {
25
+ unset( $query_vars[ {{phpPrefix}}_get_query_loop_variation_marker_key() ] );
26
+
27
+ $allowed_custom_query_values = array(
28
+ // 'featuredOnly' => isset( $source_query['featuredOnly'] ) ? (bool) $source_query['featuredOnly'] : null,
29
+ // 'eventStatus' => isset( $source_query['eventStatus'] ) ? sanitize_text_field( $source_query['eventStatus'] ) : null,
30
+ );
31
+
32
+ // Mirror only known custom query keys from `src/query-extension.ts` here when
33
+ // they should affect frontend rendering or editor preview requests. Sanitize
34
+ // and cast values before copying them into WP_Query vars; do not merge the
35
+ // raw source query wholesale.
36
+ foreach ( $allowed_custom_query_values as $key => $value ) {
37
+ if ( null === $value ) {
38
+ continue;
39
+ }
40
+
41
+ $query_vars[ $key ] = $value;
42
+ }
43
+
44
+ return $query_vars;
45
+ }
46
+
47
+ function {{phpPrefix}}_filter_query_loop_block_query_vars( $query_vars, $block, $page ) {
48
+ if ( ! is_array( $query_vars ) ) {
49
+ return $query_vars;
50
+ }
51
+
52
+ $attrs = is_array( $block ) && isset( $block['attrs'] ) && is_array( $block['attrs'] )
53
+ ? $block['attrs']
54
+ : array();
55
+ $namespace = isset( $attrs['namespace'] ) && is_string( $attrs['namespace'] )
56
+ ? $attrs['namespace']
57
+ : null;
58
+ $source_query = isset( $attrs['query'] ) && is_array( $attrs['query'] )
59
+ ? $attrs['query']
60
+ : array();
61
+
62
+ if ( {{phpPrefix}}_get_query_loop_variation_name() !== $namespace ) {
63
+ return $query_vars;
64
+ }
65
+
66
+ return {{phpPrefix}}_map_query_loop_custom_query_vars( $query_vars, $source_query );
67
+ }
68
+
69
+ function {{phpPrefix}}_filter_query_loop_editor_preview_query_vars( $query_vars, $request ) {
70
+ if ( ! is_array( $query_vars ) || ! $request instanceof WP_REST_Request ) {
71
+ return $query_vars;
72
+ }
73
+
74
+ $source_query = {{phpPrefix}}_extract_query_loop_request_query( $request );
75
+ $marker_key = {{phpPrefix}}_get_query_loop_variation_marker_key();
76
+ $variation_name = isset( $source_query[ $marker_key ] ) && is_string( $source_query[ $marker_key ] )
77
+ ? $source_query[ $marker_key ]
78
+ : null;
79
+
80
+ if ( {{phpPrefix}}_get_query_loop_variation_name() !== $variation_name ) {
81
+ return $query_vars;
82
+ }
83
+
84
+ return {{phpPrefix}}_map_query_loop_custom_query_vars( $query_vars, $source_query );
85
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "{{slug}}",
3
+ "version": "0.1.0",
4
+ "packageManager": "bun@1.3.11",
5
+ "description": "{{description}}",
6
+ "author": "{{author}}",
7
+ "license": "GPL-2.0-or-later",
8
+ "main": "build/index.js",
9
+ "scripts": {
10
+ "build": "wp-scripts build --experimental-modules",
11
+ "start": "wp-scripts start --experimental-modules",
12
+ "dev": "wp-scripts start --experimental-modules",
13
+ "lint:js": "wp-scripts lint-js",
14
+ "lint": "bun run lint:js",
15
+ "format": "wp-scripts format",
16
+ "typecheck": "tsc --noEmit"
17
+ },
18
+ "devDependencies": {
19
+ "@wp-typia/block-types": "{{blockTypesPackageVersion}}",
20
+ "@types/wordpress__block-editor": "^11.5.17",
21
+ "@types/wordpress__blocks": "^12.5.18",
22
+ "@wordpress/browserslist-config": "^6.42.0",
23
+ "@wordpress/scripts": "^30.22.0",
24
+ "eslint-plugin-jsx-a11y": "^6.10.2",
25
+ "prettier": "3.8.2",
26
+ "typescript": "^5.9.2"
27
+ },
28
+ "dependencies": {
29
+ "@wordpress/blocks": "^15.2.0",
30
+ "@wordpress/i18n": "^6.2.0"
31
+ }
32
+ }