@wp-typia/project-tools 0.16.14 → 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 (31) hide show
  1. package/dist/runtime/block-generator-service-spec.d.ts +6 -0
  2. package/dist/runtime/block-generator-service-spec.js +27 -0
  3. package/dist/runtime/built-in-block-code-artifacts.js +14 -1
  4. package/dist/runtime/built-in-block-code-templates/query-loop.d.ts +1 -0
  5. package/dist/runtime/built-in-block-code-templates/query-loop.js +70 -0
  6. package/dist/runtime/built-in-block-code-templates.d.ts +1 -0
  7. package/dist/runtime/built-in-block-code-templates.js +1 -0
  8. package/dist/runtime/built-in-block-non-ts-artifacts.js +2 -0
  9. package/dist/runtime/cli-help.js +1 -0
  10. package/dist/runtime/cli-scaffold.d.ts +2 -1
  11. package/dist/runtime/cli-scaffold.js +2 -1
  12. package/dist/runtime/local-dev-presets.js +21 -11
  13. package/dist/runtime/scaffold-answer-resolution.d.ts +1 -1
  14. package/dist/runtime/scaffold-answer-resolution.js +26 -1
  15. package/dist/runtime/scaffold-documents.js +8 -9
  16. package/dist/runtime/scaffold-onboarding.js +12 -0
  17. package/dist/runtime/scaffold-template-variables.js +6 -0
  18. package/dist/runtime/scaffold.d.ts +8 -0
  19. package/dist/runtime/template-defaults.d.ts +7 -0
  20. package/dist/runtime/template-defaults.js +4 -0
  21. package/dist/runtime/template-registry.d.ts +1 -1
  22. package/dist/runtime/template-registry.js +14 -1
  23. package/package.json +1 -1
  24. package/templates/query-loop/inc/query-runtime.php.mustache +85 -0
  25. package/templates/query-loop/package.json.mustache +32 -0
  26. package/templates/query-loop/src/patterns/grid.php.mustache +49 -0
  27. package/templates/query-loop/src/patterns/list.php.mustache +48 -0
  28. package/templates/query-loop/src/query-extension.ts.mustache +41 -0
  29. package/templates/query-loop/src/validator-toolkit.ts.mustache +1 -0
  30. package/templates/query-loop/webpack.config.js.mustache +16 -0
  31. package/templates/query-loop/{{slugKebabCase}}.php.mustache +84 -0
@@ -27,6 +27,11 @@ export interface BlockSpec {
27
27
  project: {
28
28
  author: string;
29
29
  };
30
+ queryLoop: {
31
+ allowedControls: readonly string[];
32
+ enabled: boolean;
33
+ postType: string;
34
+ };
30
35
  runtime: {
31
36
  withMigrationUi: boolean;
32
37
  withTestPreset: boolean;
@@ -66,6 +71,7 @@ export interface PlanBlockInput {
66
71
  templateId: BuiltInTemplateId;
67
72
  variant?: string;
68
73
  withMigrationUi?: boolean;
74
+ queryPostType?: string;
69
75
  withTestPreset?: boolean;
70
76
  withWpEnv?: boolean;
71
77
  }
@@ -24,6 +24,15 @@ function getBuiltInPersistenceSpec({ templateId, dataStorageMode, persistencePol
24
24
  enabled: false,
25
25
  };
26
26
  }
27
+ const DEFAULT_QUERY_LOOP_ALLOWED_CONTROLS = [
28
+ "inherit",
29
+ "postType",
30
+ "order",
31
+ "sticky",
32
+ "taxQuery",
33
+ "author",
34
+ "search",
35
+ ];
27
36
  export function createBuiltInBlockSpec({ answers, dataStorageMode, persistencePolicy, templateId, withMigrationUi = false, withTestPreset = false, withWpEnv = false, }) {
28
37
  const template = getTemplateById(templateId);
29
38
  const metadataDefaults = getBuiltInTemplateMetadataDefaults(templateId);
@@ -52,6 +61,17 @@ export function createBuiltInBlockSpec({ answers, dataStorageMode, persistencePo
52
61
  project: {
53
62
  author: answers.author.trim(),
54
63
  },
64
+ queryLoop: templateId === "query-loop"
65
+ ? {
66
+ allowedControls: DEFAULT_QUERY_LOOP_ALLOWED_CONTROLS,
67
+ enabled: true,
68
+ postType: (answers.queryPostType ?? "post").trim() || "post",
69
+ }
70
+ : {
71
+ allowedControls: [],
72
+ enabled: false,
73
+ postType: "post",
74
+ },
55
75
  runtime: {
56
76
  withMigrationUi,
57
77
  withTestPreset,
@@ -82,6 +102,7 @@ export function buildTemplateVariablesFromBlockSpec(spec) {
82
102
  const persistencePolicy = persistenceEnabled
83
103
  ? spec.persistence.persistencePolicy
84
104
  : "authenticated";
105
+ const queryVariationNamespace = `${namespace}/${slug}`;
85
106
  return {
86
107
  apiClientPackageVersion,
87
108
  author: spec.project.author,
@@ -101,7 +122,13 @@ export function buildTemplateVariablesFromBlockSpec(spec) {
101
122
  dashCase: slug,
102
123
  dataStorageMode,
103
124
  description: spec.metadata.description,
125
+ descriptionJson: JSON.stringify(spec.metadata.description),
104
126
  frontendCssClassName: buildFrontendCssClassName(cssClassName),
127
+ queryAllowedControlsJson: JSON.stringify(spec.queryLoop.enabled ? spec.queryLoop.allowedControls : [], null, 2),
128
+ queryPostType: spec.queryLoop.enabled ? spec.queryLoop.postType : "post",
129
+ queryPostTypeJson: JSON.stringify(spec.queryLoop.enabled ? spec.queryLoop.postType : "post"),
130
+ queryVariationNamespace,
131
+ queryVariationNamespaceJson: JSON.stringify(queryVariationNamespace),
105
132
  isAuthenticatedPersistencePolicy: persistencePolicy === "authenticated" ? "true" : "false",
106
133
  isPublicPersistencePolicy: persistencePolicy === "public" ? "true" : "false",
107
134
  bootstrapCredentialDeclarations: persistencePolicy === "public"
@@ -1,5 +1,5 @@
1
1
  import { buildBuiltInNonTsArtifacts } from "./built-in-block-non-ts-artifacts.js";
2
- import { BASIC_EDIT_TEMPLATE, BASIC_INDEX_TEMPLATE, BASIC_SAVE_TEMPLATE, BASIC_VALIDATORS_TEMPLATE, BLOCK_METADATA_WRAPPER_TEMPLATE, COMPOUND_CHILD_EDIT_TEMPLATE, COMPOUND_CHILD_INDEX_TEMPLATE, COMPOUND_CHILD_SAVE_TEMPLATE, COMPOUND_CHILD_VALIDATORS_TEMPLATE, COMPOUND_CHILDREN_TEMPLATE, COMPOUND_LOCAL_HOOKS_TEMPLATE, COMPOUND_PARENT_EDIT_TEMPLATE, COMPOUND_PARENT_INDEX_TEMPLATE, COMPOUND_PARENT_SAVE_TEMPLATE, COMPOUND_PARENT_VALIDATORS_TEMPLATE, COMPOUND_PERSISTENCE_PARENT_EDIT_TEMPLATE, COMPOUND_PERSISTENCE_PARENT_INTERACTIVITY_TEMPLATE, COMPOUND_PERSISTENCE_PARENT_SAVE_TEMPLATE, COMPOUND_PERSISTENCE_PARENT_VALIDATORS_TEMPLATE, INTERACTIVITY_EDIT_TEMPLATE, INTERACTIVITY_INDEX_TEMPLATE, INTERACTIVITY_SAVE_TEMPLATE, INTERACTIVITY_SCRIPT_TEMPLATE, INTERACTIVITY_VALIDATORS_TEMPLATE, MANIFEST_DEFAULTS_DOCUMENT_WRAPPER_TEMPLATE, MANIFEST_DOCUMENT_WRAPPER_TEMPLATE, PERSISTENCE_EDIT_TEMPLATE, PERSISTENCE_INDEX_TEMPLATE, PERSISTENCE_INTERACTIVITY_TEMPLATE, PERSISTENCE_SAVE_TEMPLATE, PERSISTENCE_VALIDATORS_TEMPLATE, SHARED_HOOKS_TEMPLATE, } from "./built-in-block-code-templates.js";
2
+ import { BASIC_EDIT_TEMPLATE, BASIC_INDEX_TEMPLATE, BASIC_SAVE_TEMPLATE, BASIC_VALIDATORS_TEMPLATE, BLOCK_METADATA_WRAPPER_TEMPLATE, COMPOUND_CHILD_EDIT_TEMPLATE, COMPOUND_CHILD_INDEX_TEMPLATE, COMPOUND_CHILD_SAVE_TEMPLATE, COMPOUND_CHILD_VALIDATORS_TEMPLATE, COMPOUND_CHILDREN_TEMPLATE, COMPOUND_LOCAL_HOOKS_TEMPLATE, COMPOUND_PARENT_EDIT_TEMPLATE, COMPOUND_PARENT_INDEX_TEMPLATE, COMPOUND_PARENT_SAVE_TEMPLATE, COMPOUND_PARENT_VALIDATORS_TEMPLATE, COMPOUND_PERSISTENCE_PARENT_EDIT_TEMPLATE, COMPOUND_PERSISTENCE_PARENT_INTERACTIVITY_TEMPLATE, COMPOUND_PERSISTENCE_PARENT_SAVE_TEMPLATE, COMPOUND_PERSISTENCE_PARENT_VALIDATORS_TEMPLATE, INTERACTIVITY_EDIT_TEMPLATE, INTERACTIVITY_INDEX_TEMPLATE, INTERACTIVITY_SAVE_TEMPLATE, INTERACTIVITY_SCRIPT_TEMPLATE, INTERACTIVITY_VALIDATORS_TEMPLATE, MANIFEST_DEFAULTS_DOCUMENT_WRAPPER_TEMPLATE, MANIFEST_DOCUMENT_WRAPPER_TEMPLATE, PERSISTENCE_EDIT_TEMPLATE, PERSISTENCE_INDEX_TEMPLATE, PERSISTENCE_INTERACTIVITY_TEMPLATE, PERSISTENCE_SAVE_TEMPLATE, PERSISTENCE_VALIDATORS_TEMPLATE, QUERY_LOOP_INDEX_TEMPLATE, SHARED_HOOKS_TEMPLATE, } from "./built-in-block-code-templates.js";
3
3
  import { renderMustacheTemplateString } from "./template-render.js";
4
4
  function renderCodeTemplate(template, variables) {
5
5
  const rendered = renderMustacheTemplateString(template, variables);
@@ -162,6 +162,17 @@ function buildPersistenceCodeArtifacts(variables) {
162
162
  ...buildBuiltInNonTsArtifacts({ templateId: "persistence", variables }),
163
163
  ]);
164
164
  }
165
+ function buildQueryLoopCodeArtifacts(variables) {
166
+ return ensureUniqueArtifactPaths([
167
+ ...createCodeArtifacts([
168
+ {
169
+ relativePath: "src/index.ts",
170
+ template: QUERY_LOOP_INDEX_TEMPLATE,
171
+ },
172
+ ], variables),
173
+ ...buildBuiltInNonTsArtifacts({ templateId: "query-loop", variables }),
174
+ ]);
175
+ }
165
176
  /**
166
177
  * Build the emitter-owned scaffold files for a built-in template family.
167
178
  *
@@ -179,6 +190,8 @@ export function buildBuiltInCodeArtifacts({ templateId, variables, }) {
179
190
  return buildPersistenceCodeArtifacts(variables);
180
191
  case "compound":
181
192
  return buildCompoundCodeArtifacts(variables);
193
+ case "query-loop":
194
+ return buildQueryLoopCodeArtifacts(variables);
182
195
  default: {
183
196
  const unhandledTemplateId = templateId;
184
197
  throw new Error(`Unhandled built-in template id: ${unhandledTemplateId}`);
@@ -0,0 +1 @@
1
+ export declare const QUERY_LOOP_INDEX_TEMPLATE = "import { registerBlockVariation } from '@wordpress/blocks';\nimport type { BlockVariation } from '@wp-typia/block-types/blocks/registration';\nimport { __ } from '@wordpress/i18n';\nimport {\n getQueryLoopCustomAllowedControls,\n getQueryLoopCustomQuerySeed,\n registerQueryLoopEditorExtensions,\n} from './query-extension';\n\ntype QueryLoopVariationAttributes = {\n namespace?: string;\n query?: {\n inherit?: boolean;\n order?: 'asc' | 'desc';\n orderBy?: string;\n perPage?: number;\n postType?: string;\n wpTypiaVariation?: string;\n [key: string]: unknown;\n };\n};\n\ntype QueryLoopVariation = BlockVariation<QueryLoopVariationAttributes> & {\n allowedControls: string[];\n};\n\nconst VARIATION_NAME = {{queryVariationNamespaceJson}};\nconst DEFAULT_ALLOWED_CONTROLS = {{queryAllowedControlsJson}};\nconst customQuerySeed = getQueryLoopCustomQuerySeed();\nconst allowedControls = Array.from(\n new Set([...DEFAULT_ALLOWED_CONTROLS, ...getQueryLoopCustomAllowedControls()]),\n);\n\nconst queryLoopVariation = {\n name: VARIATION_NAME,\n title: __({{titleJson}}, '{{textDomain}}'),\n description: __({{descriptionJson}}, '{{textDomain}}'),\n scope: ['inserter'],\n isActive: ['namespace'],\n attributes: {\n namespace: VARIATION_NAME,\n query: {\n inherit: false,\n order: 'desc',\n orderBy: 'date',\n perPage: 6,\n postType: {{queryPostTypeJson}},\n ...customQuerySeed,\n wpTypiaVariation: VARIATION_NAME,\n },\n },\n allowedControls,\n innerBlocks: [\n [\n 'core/post-template',\n {},\n [\n ['core/post-featured-image'],\n ['core/post-title', { isLink: true }],\n ['core/post-excerpt'],\n ],\n ],\n ['core/query-pagination'],\n ['core/query-no-results'],\n ],\n} satisfies QueryLoopVariation;\n\nregisterBlockVariation('core/query', queryLoopVariation);\nregisterQueryLoopEditorExtensions({ variationName: VARIATION_NAME });\n";
@@ -0,0 +1,70 @@
1
+ export const QUERY_LOOP_INDEX_TEMPLATE = `import { registerBlockVariation } from '@wordpress/blocks';
2
+ import type { BlockVariation } from '@wp-typia/block-types/blocks/registration';
3
+ import { __ } from '@wordpress/i18n';
4
+ import {
5
+ getQueryLoopCustomAllowedControls,
6
+ getQueryLoopCustomQuerySeed,
7
+ registerQueryLoopEditorExtensions,
8
+ } from './query-extension';
9
+
10
+ type QueryLoopVariationAttributes = {
11
+ namespace?: string;
12
+ query?: {
13
+ inherit?: boolean;
14
+ order?: 'asc' | 'desc';
15
+ orderBy?: string;
16
+ perPage?: number;
17
+ postType?: string;
18
+ wpTypiaVariation?: string;
19
+ [key: string]: unknown;
20
+ };
21
+ };
22
+
23
+ type QueryLoopVariation = BlockVariation<QueryLoopVariationAttributes> & {
24
+ allowedControls: string[];
25
+ };
26
+
27
+ const VARIATION_NAME = {{queryVariationNamespaceJson}};
28
+ const DEFAULT_ALLOWED_CONTROLS = {{queryAllowedControlsJson}};
29
+ const customQuerySeed = getQueryLoopCustomQuerySeed();
30
+ const allowedControls = Array.from(
31
+ new Set([...DEFAULT_ALLOWED_CONTROLS, ...getQueryLoopCustomAllowedControls()]),
32
+ );
33
+
34
+ const queryLoopVariation = {
35
+ name: VARIATION_NAME,
36
+ title: __({{titleJson}}, '{{textDomain}}'),
37
+ description: __({{descriptionJson}}, '{{textDomain}}'),
38
+ scope: ['inserter'],
39
+ isActive: ['namespace'],
40
+ attributes: {
41
+ namespace: VARIATION_NAME,
42
+ query: {
43
+ inherit: false,
44
+ order: 'desc',
45
+ orderBy: 'date',
46
+ perPage: 6,
47
+ postType: {{queryPostTypeJson}},
48
+ ...customQuerySeed,
49
+ wpTypiaVariation: VARIATION_NAME,
50
+ },
51
+ },
52
+ allowedControls,
53
+ innerBlocks: [
54
+ [
55
+ 'core/post-template',
56
+ {},
57
+ [
58
+ ['core/post-featured-image'],
59
+ ['core/post-title', { isLink: true }],
60
+ ['core/post-excerpt'],
61
+ ],
62
+ ],
63
+ ['core/query-pagination'],
64
+ ['core/query-no-results'],
65
+ ],
66
+ } satisfies QueryLoopVariation;
67
+
68
+ registerBlockVariation('core/query', queryLoopVariation);
69
+ registerQueryLoopEditorExtensions({ variationName: VARIATION_NAME });
70
+ `;
@@ -6,4 +6,5 @@ export { BLOCK_METADATA_WRAPPER_TEMPLATE, MANIFEST_DEFAULTS_DOCUMENT_WRAPPER_TEM
6
6
  export { BASIC_EDIT_TEMPLATE, BASIC_INDEX_TEMPLATE, BASIC_SAVE_TEMPLATE, BASIC_VALIDATORS_TEMPLATE, } from "./built-in-block-code-templates/basic.js";
7
7
  export { INTERACTIVITY_EDIT_TEMPLATE, INTERACTIVITY_INDEX_TEMPLATE, INTERACTIVITY_SAVE_TEMPLATE, INTERACTIVITY_SCRIPT_TEMPLATE, INTERACTIVITY_VALIDATORS_TEMPLATE, } from "./built-in-block-code-templates/interactivity.js";
8
8
  export { PERSISTENCE_EDIT_TEMPLATE, PERSISTENCE_INDEX_TEMPLATE, PERSISTENCE_INTERACTIVITY_TEMPLATE, PERSISTENCE_SAVE_TEMPLATE, PERSISTENCE_VALIDATORS_TEMPLATE, } from "./built-in-block-code-templates/persistence.js";
9
+ export { QUERY_LOOP_INDEX_TEMPLATE, } from "./built-in-block-code-templates/query-loop.js";
9
10
  export { COMPOUND_CHILD_EDIT_TEMPLATE, COMPOUND_CHILD_INDEX_TEMPLATE, COMPOUND_CHILDREN_TEMPLATE, COMPOUND_CHILD_SAVE_TEMPLATE, COMPOUND_CHILD_VALIDATORS_TEMPLATE, COMPOUND_LOCAL_HOOKS_TEMPLATE, COMPOUND_PARENT_EDIT_TEMPLATE, COMPOUND_PARENT_INDEX_TEMPLATE, COMPOUND_PARENT_SAVE_TEMPLATE, COMPOUND_PARENT_VALIDATORS_TEMPLATE, COMPOUND_PERSISTENCE_PARENT_EDIT_TEMPLATE, COMPOUND_PERSISTENCE_PARENT_INTERACTIVITY_TEMPLATE, COMPOUND_PERSISTENCE_PARENT_SAVE_TEMPLATE, COMPOUND_PERSISTENCE_PARENT_VALIDATORS_TEMPLATE, } from "./built-in-block-code-templates/compound.js";
@@ -6,4 +6,5 @@ export { BLOCK_METADATA_WRAPPER_TEMPLATE, MANIFEST_DEFAULTS_DOCUMENT_WRAPPER_TEM
6
6
  export { BASIC_EDIT_TEMPLATE, BASIC_INDEX_TEMPLATE, BASIC_SAVE_TEMPLATE, BASIC_VALIDATORS_TEMPLATE, } from "./built-in-block-code-templates/basic.js";
7
7
  export { INTERACTIVITY_EDIT_TEMPLATE, INTERACTIVITY_INDEX_TEMPLATE, INTERACTIVITY_SAVE_TEMPLATE, INTERACTIVITY_SCRIPT_TEMPLATE, INTERACTIVITY_VALIDATORS_TEMPLATE, } from "./built-in-block-code-templates/interactivity.js";
8
8
  export { PERSISTENCE_EDIT_TEMPLATE, PERSISTENCE_INDEX_TEMPLATE, PERSISTENCE_INTERACTIVITY_TEMPLATE, PERSISTENCE_SAVE_TEMPLATE, PERSISTENCE_VALIDATORS_TEMPLATE, } from "./built-in-block-code-templates/persistence.js";
9
+ export { QUERY_LOOP_INDEX_TEMPLATE, } from "./built-in-block-code-templates/query-loop.js";
9
10
  export { COMPOUND_CHILD_EDIT_TEMPLATE, COMPOUND_CHILD_INDEX_TEMPLATE, COMPOUND_CHILDREN_TEMPLATE, COMPOUND_CHILD_SAVE_TEMPLATE, COMPOUND_CHILD_VALIDATORS_TEMPLATE, COMPOUND_LOCAL_HOOKS_TEMPLATE, COMPOUND_PARENT_EDIT_TEMPLATE, COMPOUND_PARENT_INDEX_TEMPLATE, COMPOUND_PARENT_SAVE_TEMPLATE, COMPOUND_PARENT_VALIDATORS_TEMPLATE, COMPOUND_PERSISTENCE_PARENT_EDIT_TEMPLATE, COMPOUND_PERSISTENCE_PARENT_INTERACTIVITY_TEMPLATE, COMPOUND_PERSISTENCE_PARENT_SAVE_TEMPLATE, COMPOUND_PERSISTENCE_PARENT_VALIDATORS_TEMPLATE, } from "./built-in-block-code-templates/compound.js";
@@ -555,6 +555,8 @@ export function buildBuiltInNonTsArtifacts({ templateId, variables, }) {
555
555
  return buildPersistenceArtifacts(variables);
556
556
  case "compound":
557
557
  return buildCompoundArtifacts(variables);
558
+ case "query-loop":
559
+ return [];
558
560
  default: {
559
561
  const unhandledTemplateId = templateId;
560
562
  throw new Error(`Unhandled built-in template id: ${unhandledTemplateId}`);
@@ -11,6 +11,7 @@ import { TEMPLATE_IDS } from "./template-registry.js";
11
11
  export function formatHelpText() {
12
12
  return `Usage:
13
13
  wp-typia create <project-dir> [--template <basic|interactivity>] [--external-layer-source <./path|github:owner/repo/path[#ref]|npm-package>] [--external-layer-id <layer-id>] [--namespace <value>] [--text-domain <value>] [--php-prefix <value>] [--with-migration-ui] [--with-wp-env] [--with-test-preset] [--yes] [--no-install] [--package-manager <id>]
14
+ wp-typia create <project-dir> [--template query-loop] [--external-layer-source <./path|github:owner/repo/path[#ref]|npm-package>] [--external-layer-id <layer-id>] [--query-post-type <post-type>] [--namespace <value>] [--text-domain <value>] [--php-prefix <value>] [--with-migration-ui] [--with-wp-env] [--with-test-preset] [--yes] [--no-install] [--package-manager <id>]
14
15
  wp-typia create <project-dir> [--template <./path|github:owner/repo/path[#ref]>] [--variant <name>] [--namespace <value>] [--text-domain <value>] [--php-prefix <value>] [--with-wp-env] [--with-test-preset] [--yes] [--no-install] [--package-manager <id>]
15
16
  wp-typia create <project-dir> [--template <npm-package>] [--variant <name>] [--namespace <value>] [--text-domain <value>] [--php-prefix <value>] [--with-wp-env] [--with-test-preset] [--yes] [--no-install] [--package-manager <id>]
16
17
  wp-typia create <project-dir> [--template persistence] [--external-layer-source <./path|github:owner/repo/path[#ref]|npm-package>] [--external-layer-id <layer-id>] [--data-storage <post-meta|custom-table>] [--persistence-policy <authenticated|public>] [--namespace <value>] [--text-domain <value>] [--php-prefix <value>] [--with-migration-ui] [--with-wp-env] [--with-test-preset] [--yes] [--no-install] [--package-manager <id>]
@@ -34,6 +34,7 @@ interface RunScaffoldFlowOptions {
34
34
  phpPrefix?: string;
35
35
  projectInput: string;
36
36
  promptText?: Parameters<typeof collectScaffoldAnswers>[0]["promptText"];
37
+ queryPostType?: string;
37
38
  selectDataStorage?: () => Promise<DataStorageMode>;
38
39
  selectExternalLayerId?: (options: ExternalLayerSelectionOption[]) => Promise<string>;
39
40
  selectPackageManager?: () => Promise<PackageManagerId>;
@@ -73,7 +74,7 @@ export declare function getOptionalOnboarding({ availableScripts, packageManager
73
74
  * project.
74
75
  * @returns The scaffold result together with next-step guidance.
75
76
  */
76
- export declare function runScaffoldFlow({ projectInput, cwd, templateId, dataStorageMode, externalLayerId, externalLayerSource, persistencePolicy, packageManager, namespace, textDomain, phpPrefix, yes, noInstall, isInteractive, allowExistingDir, selectTemplate, selectDataStorage, selectExternalLayerId, selectPersistencePolicy, selectPackageManager, promptText, installDependencies, variant, selectWithTestPreset, selectWithWpEnv, selectWithMigrationUi, withMigrationUi, withTestPreset, withWpEnv, }: RunScaffoldFlowOptions): Promise<{
77
+ export declare function runScaffoldFlow({ projectInput, cwd, templateId, dataStorageMode, externalLayerId, externalLayerSource, persistencePolicy, packageManager, namespace, textDomain, phpPrefix, queryPostType, yes, noInstall, isInteractive, allowExistingDir, selectTemplate, selectDataStorage, selectExternalLayerId, selectPersistencePolicy, selectPackageManager, promptText, installDependencies, variant, selectWithTestPreset, selectWithWpEnv, selectWithMigrationUi, withMigrationUi, withTestPreset, withWpEnv, }: RunScaffoldFlowOptions): Promise<{
77
78
  optionalOnboarding: OptionalOnboardingGuidance;
78
79
  projectDir: string;
79
80
  projectInput: string;
@@ -135,7 +135,7 @@ export function getOptionalOnboarding({ availableScripts, packageManager, templa
135
135
  * project.
136
136
  * @returns The scaffold result together with next-step guidance.
137
137
  */
138
- export async function runScaffoldFlow({ projectInput, cwd = process.cwd(), templateId, dataStorageMode, externalLayerId, externalLayerSource, persistencePolicy, packageManager, namespace, textDomain, phpPrefix, yes = false, noInstall = false, isInteractive = false, allowExistingDir = false, selectTemplate, selectDataStorage, selectExternalLayerId, selectPersistencePolicy, selectPackageManager, promptText, installDependencies = undefined, variant, selectWithTestPreset, selectWithWpEnv, selectWithMigrationUi, withMigrationUi, withTestPreset, withWpEnv, }) {
138
+ export async function runScaffoldFlow({ projectInput, cwd = process.cwd(), templateId, dataStorageMode, externalLayerId, externalLayerSource, persistencePolicy, packageManager, namespace, textDomain, phpPrefix, queryPostType, yes = false, noInstall = false, isInteractive = false, allowExistingDir = false, selectTemplate, selectDataStorage, selectExternalLayerId, selectPersistencePolicy, selectPackageManager, promptText, installDependencies = undefined, variant, selectWithTestPreset, selectWithWpEnv, selectWithMigrationUi, withMigrationUi, withTestPreset, withWpEnv, }) {
139
139
  const normalizedExternalLayerId = typeof externalLayerId === "string" && externalLayerId.trim().length > 0
140
140
  ? externalLayerId.trim()
141
141
  : undefined;
@@ -228,6 +228,7 @@ export async function runScaffoldFlow({ projectInput, cwd = process.cwd(), templ
228
228
  persistencePolicy: resolvedPersistencePolicy,
229
229
  phpPrefix,
230
230
  projectName,
231
+ queryPostType,
231
232
  templateId: resolvedTemplateId,
232
233
  textDomain,
233
234
  yes,
@@ -13,6 +13,12 @@ import { copyInterpolatedDirectory } from "./template-render.js";
13
13
  function templateHasPersistenceSync(templateId, compoundPersistenceEnabled) {
14
14
  return templateId === "persistence" || (templateId === "compound" && compoundPersistenceEnabled);
15
15
  }
16
+ function templateSupportsGeneratedSyncWatchers(templateId) {
17
+ return (templateId === "basic" ||
18
+ templateId === "interactivity" ||
19
+ templateId === "persistence" ||
20
+ templateId === "compound");
21
+ }
16
22
  function getWatchSyncTypesScript(packageManager, templateId) {
17
23
  if (templateId === "compound") {
18
24
  return `chokidar "src/blocks/**/types.ts" "scripts/block-config.ts" --debounce 200 -c "${formatRunScript(packageManager, "sync-types")}"`;
@@ -80,11 +86,16 @@ export async function applyLocalDevPresetFiles({ projectDir, variables, withTest
80
86
  */
81
87
  export async function applyGeneratedProjectDxPackageJson({ compoundPersistenceEnabled = false, packageManager, projectDir, templateId, withTestPreset = false, withWpEnv = false, }) {
82
88
  const hasPersistenceSync = templateHasPersistenceSync(templateId, compoundPersistenceEnabled);
89
+ const supportsGeneratedSyncWatchers = templateSupportsGeneratedSyncWatchers(templateId);
83
90
  await mutatePackageJson(projectDir, (packageJson) => {
84
91
  packageJson.devDependencies = {
85
92
  ...(packageJson.devDependencies ?? {}),
86
- "chokidar-cli": "^3.0.0",
87
- concurrently: "^9.0.1",
93
+ ...(supportsGeneratedSyncWatchers
94
+ ? {
95
+ "chokidar-cli": "^3.0.0",
96
+ concurrently: "^9.0.1",
97
+ }
98
+ : {}),
88
99
  };
89
100
  if (withWpEnv || withTestPreset) {
90
101
  packageJson.devDependencies["@wordpress/env"] = "^11.2.0";
@@ -95,12 +106,16 @@ export async function applyGeneratedProjectDxPackageJson({ compoundPersistenceEn
95
106
  const scripts = {
96
107
  ...(packageJson.scripts ?? {}),
97
108
  };
98
- scripts["start:editor"] = "wp-scripts start --experimental-modules";
99
- scripts["watch:sync-types"] = getWatchSyncTypesScript(packageManager, templateId);
109
+ if (supportsGeneratedSyncWatchers) {
110
+ scripts["start:editor"] = "wp-scripts start --experimental-modules";
111
+ scripts["watch:sync-types"] = getWatchSyncTypesScript(packageManager, templateId);
112
+ }
100
113
  if (hasPersistenceSync) {
101
114
  scripts["watch:sync-rest"] = getWatchSyncRestScript(packageManager, templateId);
102
115
  }
103
- scripts.dev = getDevScript(packageManager, compoundPersistenceEnabled, templateId);
116
+ if (supportsGeneratedSyncWatchers) {
117
+ scripts.dev = getDevScript(packageManager, compoundPersistenceEnabled, templateId);
118
+ }
104
119
  if (withWpEnv) {
105
120
  scripts["wp-env:start"] = "wp-env start";
106
121
  scripts["wp-env:stop"] = "wp-env stop";
@@ -123,10 +138,5 @@ export async function applyGeneratedProjectDxPackageJson({ compoundPersistenceEn
123
138
  * scaffolded template.
124
139
  */
125
140
  export function getPrimaryDevelopmentScript(templateId) {
126
- return templateId === "basic" ||
127
- templateId === "interactivity" ||
128
- templateId === "persistence" ||
129
- templateId === "compound"
130
- ? "dev"
131
- : "start";
141
+ return templateSupportsGeneratedSyncWatchers(templateId) ? "dev" : "start";
132
142
  }
@@ -34,4 +34,4 @@ export declare function resolvePackageManagerId({ packageManager, yes, isInterac
34
34
  * @param options Answer collection inputs including prompt callbacks and explicit overrides.
35
35
  * @returns The normalized scaffold answers used for rendering and file generation.
36
36
  */
37
- export declare function collectScaffoldAnswers({ projectName, templateId, yes, dataStorageMode, namespace, persistencePolicy, phpPrefix, promptText, textDomain, }: CollectScaffoldAnswersOptions): Promise<ScaffoldAnswers>;
37
+ export declare function collectScaffoldAnswers({ projectName, templateId, yes, dataStorageMode, namespace, persistencePolicy, phpPrefix, promptText, queryPostType, textDomain, }: CollectScaffoldAnswersOptions): Promise<ScaffoldAnswers>;
@@ -37,11 +37,32 @@ export function getDefaultAnswers(projectName, templateId) {
37
37
  namespace: slugDefault,
38
38
  persistencePolicy: templateId === 'persistence' ? 'authenticated' : undefined,
39
39
  phpPrefix: toSnakeCase(slugDefault),
40
+ queryPostType: templateId === 'query-loop' ? 'post' : undefined,
40
41
  slug: slugDefault,
41
42
  textDomain: slugDefault,
42
43
  title: toTitleCase(slugDefault),
43
44
  };
44
45
  }
46
+ function validateQueryPostType(value) {
47
+ const normalizedValue = value.trim().toLowerCase();
48
+ if (normalizedValue.length === 0) {
49
+ return 'Query post type is required.';
50
+ }
51
+ if (!/^[a-z0-9_-]{1,20}$/u.test(normalizedValue)) {
52
+ return 'Query post type must be lowercase, 1-20 chars, and only a-z, 0-9, "_" or "-".';
53
+ }
54
+ return true;
55
+ }
56
+ function normalizeQueryPostType(value) {
57
+ if (typeof value !== 'string') {
58
+ return undefined;
59
+ }
60
+ const normalizedValue = value.trim().toLowerCase();
61
+ if (validateQueryPostType(normalizedValue) !== true) {
62
+ throw new Error('Query post type must be lowercase, 1-20 chars, and only a-z, 0-9, "_" or "-".');
63
+ }
64
+ return normalizedValue;
65
+ }
45
66
  function normalizeTemplateSelection(templateId) {
46
67
  return templateId === WORKSPACE_TEMPLATE_ALIAS
47
68
  ? OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE
@@ -96,7 +117,7 @@ export async function resolvePackageManagerId({ packageManager, yes = false, isI
96
117
  * @param options Answer collection inputs including prompt callbacks and explicit overrides.
97
118
  * @returns The normalized scaffold answers used for rendering and file generation.
98
119
  */
99
- export async function collectScaffoldAnswers({ projectName, templateId, yes = false, dataStorageMode, namespace, persistencePolicy, phpPrefix, promptText, textDomain, }) {
120
+ export async function collectScaffoldAnswers({ projectName, templateId, yes = false, dataStorageMode, namespace, persistencePolicy, phpPrefix, promptText, queryPostType, textDomain, }) {
100
121
  const defaults = getDefaultAnswers(projectName, templateId);
101
122
  if (yes) {
102
123
  const identifiers = resolveScaffoldIdentifiers({
@@ -111,6 +132,7 @@ export async function collectScaffoldAnswers({ projectName, templateId, yes = fa
111
132
  namespace: identifiers.namespace,
112
133
  persistencePolicy: persistencePolicy ?? defaults.persistencePolicy,
113
134
  phpPrefix: identifiers.phpPrefix,
135
+ queryPostType: normalizeQueryPostType(queryPostType ?? defaults.queryPostType),
114
136
  textDomain: identifiers.textDomain,
115
137
  };
116
138
  }
@@ -130,6 +152,9 @@ export async function collectScaffoldAnswers({ projectName, templateId, yes = fa
130
152
  namespace: identifiers.namespace,
131
153
  persistencePolicy: persistencePolicy ?? defaults.persistencePolicy,
132
154
  phpPrefix: identifiers.phpPrefix,
155
+ queryPostType: templateId === 'query-loop'
156
+ ? normalizeQueryPostType(await promptText('Query post type', queryPostType ?? defaults.queryPostType ?? 'post', validateQueryPostType))
157
+ : normalizeQueryPostType(queryPostType ?? defaults.queryPostType),
133
158
  slug: identifiers.slug,
134
159
  textDomain: identifiers.textDomain,
135
160
  title: await promptText('Block title', toTitleCase(identifiers.slug)),
@@ -37,6 +37,13 @@ export function buildReadme(templateId, variables, packageManager, { withMigrati
37
37
  const migrationSection = withMigrationUi
38
38
  ? `## Migration UI\n\nThis scaffold already includes an initialized migration workspace at \`v1\`, generated deprecated/runtime artifacts, and an editor-embedded migration dashboard. Migration versions are schema lineage labels and are separate from your package or plugin release version. Use the existing CLI commands to snapshot, diff, scaffold, verify, and fuzz future schema changes.\n\n\`\`\`bash\n${formatRunScript(packageManager, 'migration:doctor')}\n${formatRunScript(packageManager, 'migration:verify')}\n${formatRunScript(packageManager, 'migration:fuzz')}\n\`\`\`\n\nRun \`migration:init\` only when retrofitting migration support into an older project that was not scaffolded with \`--with-migration-ui\`.`
39
39
  : '';
40
+ const advancedSyncSection = optionalOnboardingSteps.length > 0
41
+ ? `## Advanced Sync\n\n\`\`\`bash\n${optionalOnboardingSteps.join('\n')}\n\`\`\`\n\n${getOptionalOnboardingNote(packageManager, templateId, {
42
+ compoundPersistenceEnabled,
43
+ })}`
44
+ : `## Artifact Refresh\n\n${getOptionalOnboardingNote(packageManager, templateId, {
45
+ compoundPersistenceEnabled,
46
+ })}`;
40
47
  return `# ${variables.title}
41
48
 
42
49
  ${variables.description}
@@ -63,15 +70,7 @@ ${formatRunScript(packageManager, 'build')}
63
70
  ${formatRunScript(packageManager, 'typecheck')}
64
71
  \`\`\`
65
72
 
66
- ## Advanced Sync
67
-
68
- \`\`\`bash
69
- ${optionalOnboardingSteps.join('\n')}
70
- \`\`\`
71
-
72
- ${getOptionalOnboardingNote(packageManager, templateId, {
73
- compoundPersistenceEnabled,
74
- })}
73
+ ${advancedSyncSection}
75
74
 
76
75
  ## Before First Commit
77
76
 
@@ -13,6 +13,9 @@ function templateHasPersistenceSync(templateId, { compoundPersistenceEnabled = f
13
13
  * Returns the optional sync script names to suggest for a template.
14
14
  */
15
15
  export function getOptionalSyncScriptNames(templateId, options = {}) {
16
+ if (templateId === "query-loop") {
17
+ return [];
18
+ }
16
19
  const availableScripts = new Set(options.availableScripts ?? []);
17
20
  if (availableScripts.has("sync")) {
18
21
  return ["sync"];
@@ -37,6 +40,9 @@ export function getOptionalOnboardingSteps(packageManager, templateId, options =
37
40
  * Returns the quick-start note explaining the scaffold's primary local loop.
38
41
  */
39
42
  export function getQuickStartWorkflowNote(packageManager, templateId = "basic", options = {}) {
43
+ if (templateId === "query-loop") {
44
+ return `${formatRunScript(packageManager, "start")} runs the editor build/watch loop that registers your Query Loop variation in the block editor. This scaffold is editor-facing by design: update \`src/index.ts\` when you want to change the variation namespace, default query, allowed controls, or the minimal inline starter layout, update \`src/patterns/*.php\` when you want richer connected layout presets in the inserter, use \`src/query-extension.ts\` when the variation needs custom query params or optional editor-side hooks, and mirror frontend/editor preview query mapping in \`inc/query-runtime.php\`.`;
45
+ }
40
46
  const developmentScript = getPrimaryDevelopmentScript(templateId);
41
47
  const devCommand = formatRunScript(packageManager, developmentScript);
42
48
  const startCommand = formatRunScript(packageManager, "start");
@@ -55,6 +61,9 @@ export function getQuickStartWorkflowNote(packageManager, templateId = "basic",
55
61
  * Returns the onboarding note explaining when manual sync is optional.
56
62
  */
57
63
  export function getOptionalOnboardingNote(packageManager, templateId = "basic", options = {}) {
64
+ if (templateId === "query-loop") {
65
+ return `This scaffold does not generate \`block.json\` or Typia manifests. Edit \`src/index.ts\` to change the variation contract, edit \`src/patterns/*.php\` when you want richer connected layouts beyond the inline fallback, edit \`src/query-extension.ts\` when you need variation-specific query params or custom editor hooks, and edit \`inc/query-runtime.php\` when those params need frontend or editor preview parity. Then rerun ${formatRunScript(packageManager, "build")}, ${formatRunScript(packageManager, "start")}, or ${formatRunScript(packageManager, "typecheck")} as needed.`;
66
+ }
58
67
  const optionalSyncScripts = getOptionalSyncScriptNames(templateId, options);
59
68
  const hasUnifiedSync = optionalSyncScripts.includes("sync");
60
69
  const syncSteps = optionalSyncScripts.map((scriptName) => formatRunScript(packageManager, scriptName));
@@ -100,6 +109,9 @@ export function getInitialCommitNote() {
100
109
  * Returns source-of-truth guidance for generated artifacts by template mode.
101
110
  */
102
111
  export function getTemplateSourceOfTruthNote(templateId, { compoundPersistenceEnabled = false } = {}) {
112
+ if (templateId === "query-loop") {
113
+ return "`src/index.ts` remains the source of truth for the Query Loop variation name, default query attributes, `allowedControls`, and the minimal inline starter `innerBlocks`. Use `src/patterns/*.php` for richer connected layout presets that stay tied to the same variation namespace, use `src/query-extension.ts` for custom query seed values or optional editor-only hook registration, and use `inc/query-runtime.php` to keep frontend and editor preview query mapping aligned for those custom keys. The generated plugin bootstrap should stay focused on script registration, pattern loading, and explicit runtime glue for the variation.";
114
+ }
103
115
  if (templateId === "compound") {
104
116
  const compoundBase = "`src/blocks/*/types.ts` files remain the source of truth for each block's `block.json`, `typia.manifest.json`, and `typia-validator.php`. Fresh scaffolds include starter `typia.manifest.json` files so editor imports resolve before the first sync.";
105
117
  if (compoundPersistenceEnabled) {
@@ -73,7 +73,13 @@ export function getTemplateVariables(templateId, answers) {
73
73
  dataStorageMode,
74
74
  dashCase: slug,
75
75
  description,
76
+ descriptionJson: JSON.stringify(description),
76
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}`),
77
83
  isAuthenticatedPersistencePolicy: persistencePolicy === 'authenticated' ? 'true' : 'false',
78
84
  isPublicPersistencePolicy: persistencePolicy === 'public' ? 'true' : 'false',
79
85
  bootstrapCredentialDeclarations: persistencePolicy === 'public'
@@ -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;
@@ -106,6 +113,7 @@ export interface CollectScaffoldAnswersOptions {
106
113
  projectName: string;
107
114
  promptText?: (message: string, defaultValue: string, validate?: (value: string) => true | string) => Promise<string>;
108
115
  persistencePolicy?: PersistencePolicy;
116
+ queryPostType?: string;
109
117
  textDomain?: string;
110
118
  templateId: string;
111
119
  withTestPreset?: boolean;
@@ -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.14",
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",
@@ -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
+ }
@@ -0,0 +1,49 @@
1
+ <?php
2
+ if ( ! defined( 'ABSPATH' ) ) {
3
+ exit;
4
+ }
5
+
6
+ $content = sprintf(
7
+ <<<'HTML'
8
+ <!-- wp:query {"namespace":"{{namespace}}/{{slug}}","query":{"inherit":false,"postType":"{{queryPostType}}","perPage":6,"order":"desc","orderBy":"date","wpTypiaVariation":"{{namespace}}/{{slug}}"},"displayLayout":{"type":"grid","columns":3}} -->
9
+ <div class="wp-block-query">
10
+ <!-- wp:post-template -->
11
+ <!-- wp:group {"style":{"spacing":{"blockGap":"0.75rem","padding":{"top":"1rem","right":"1rem","bottom":"1rem","left":"1rem"}}},"layout":{"type":"constrained"}} -->
12
+ <div class="wp-block-group" style="padding-top:1rem;padding-right:1rem;padding-bottom:1rem;padding-left:1rem">
13
+ <!-- wp:post-featured-image /-->
14
+ <!-- wp:post-title {"isLink":true} /-->
15
+ <!-- wp:post-excerpt /-->
16
+ </div>
17
+ <!-- /wp:group -->
18
+ <!-- /wp:post-template -->
19
+
20
+ <!-- wp:query-pagination -->
21
+ <!-- wp:query-pagination-previous /-->
22
+ <!-- wp:query-pagination-numbers /-->
23
+ <!-- wp:query-pagination-next /-->
24
+ <!-- /wp:query-pagination -->
25
+
26
+ <!-- wp:query-no-results -->
27
+ <!-- wp:paragraph -->
28
+ <p>%s</p>
29
+ <!-- /wp:paragraph -->
30
+ <!-- /wp:query-no-results -->
31
+ </div>
32
+ <!-- /wp:query -->
33
+ HTML,
34
+ esc_html__( 'No items matched this preset yet.', '{{textDomain}}' )
35
+ );
36
+
37
+ register_block_pattern(
38
+ '{{namespace}}/{{slug}}-grid',
39
+ array(
40
+ 'title' => __( '{{title}} Grid', '{{textDomain}}' ),
41
+ 'description' => __( 'A connected grid layout preset for the {{title}} Query Loop variation.', '{{textDomain}}' ),
42
+ 'categories' => array( '{{slug}}-query-loop' ),
43
+ 'blockTypes' => array( 'core/query' ),
44
+ 'postTypes' => array( '{{queryPostType}}' ),
45
+ 'inserter' => true,
46
+ 'viewportWidth' => 1280,
47
+ 'content' => $content,
48
+ )
49
+ );
@@ -0,0 +1,48 @@
1
+ <?php
2
+ if ( ! defined( 'ABSPATH' ) ) {
3
+ exit;
4
+ }
5
+
6
+ $content = sprintf(
7
+ <<<'HTML'
8
+ <!-- wp:query {"namespace":"{{namespace}}/{{slug}}","query":{"inherit":false,"postType":"{{queryPostType}}","perPage":4,"order":"desc","orderBy":"date","wpTypiaVariation":"{{namespace}}/{{slug}}"}} -->
9
+ <div class="wp-block-query">
10
+ <!-- wp:post-template -->
11
+ <!-- wp:group {"style":{"spacing":{"blockGap":"1rem","padding":{"top":"1.25rem","bottom":"1.25rem"}}},"layout":{"type":"constrained"}} -->
12
+ <div class="wp-block-group" style="padding-top:1.25rem;padding-bottom:1.25rem">
13
+ <!-- wp:post-title {"isLink":true} /-->
14
+ <!-- wp:post-date /-->
15
+ <!-- wp:post-excerpt {"moreText":"Continue reading"} /-->
16
+ </div>
17
+ <!-- /wp:group -->
18
+ <!-- /wp:post-template -->
19
+
20
+ <!-- wp:query-pagination -->
21
+ <!-- wp:query-pagination-previous /-->
22
+ <!-- wp:query-pagination-next /-->
23
+ <!-- /wp:query-pagination -->
24
+
25
+ <!-- wp:query-no-results -->
26
+ <!-- wp:paragraph -->
27
+ <p>%s</p>
28
+ <!-- /wp:paragraph -->
29
+ <!-- /wp:query-no-results -->
30
+ </div>
31
+ <!-- /wp:query -->
32
+ HTML,
33
+ esc_html__( 'No items matched this preset yet.', '{{textDomain}}' )
34
+ );
35
+
36
+ register_block_pattern(
37
+ '{{namespace}}/{{slug}}-list',
38
+ array(
39
+ 'title' => __( '{{title}} List', '{{textDomain}}' ),
40
+ 'description' => __( 'A connected list layout preset for the {{title}} Query Loop variation.', '{{textDomain}}' ),
41
+ 'categories' => array( '{{slug}}-query-loop' ),
42
+ 'blockTypes' => array( 'core/query' ),
43
+ 'postTypes' => array( '{{queryPostType}}' ),
44
+ 'inserter' => true,
45
+ 'viewportWidth' => 1280,
46
+ 'content' => $content,
47
+ )
48
+ );
@@ -0,0 +1,41 @@
1
+ export type QueryLoopEditorExtensionContext = {
2
+ variationName: string;
3
+ };
4
+
5
+ export type QueryLoopCustomQuerySeed = Record<string, unknown>;
6
+
7
+ /**
8
+ * Extend the generated Query Loop variation with variation-specific query params.
9
+ *
10
+ * Add custom keys here only when the variation should always seed them. Runtime
11
+ * parity hooks in `inc/query-runtime.php` can read the same keys later from the
12
+ * Query Loop `query` object.
13
+ */
14
+ export function getQueryLoopCustomQuerySeed(): QueryLoopCustomQuerySeed {
15
+ return {};
16
+ }
17
+
18
+ /**
19
+ * Add extra inspector controls by key when your custom query seed needs them.
20
+ *
21
+ * Keep the generated defaults in `src/index.ts` and return only the additional
22
+ * control ids that your variation-specific query params rely on.
23
+ */
24
+ export function getQueryLoopCustomAllowedControls(): string[] {
25
+ return [];
26
+ }
27
+
28
+ /**
29
+ * Register optional editor-only extension hooks for custom Query Loop params.
30
+ *
31
+ * This stays a no-op by default. When you need custom inspector UI, add your
32
+ * `editor.BlockEdit` filters or other editor hooks here so `src/index.ts` can
33
+ * keep variation registration focused on identity and default query values. If
34
+ * those hooks introduce frontend-affecting query params, mirror them in
35
+ * `inc/query-runtime.php` so editor preview and frontend rendering stay aligned.
36
+ */
37
+ export function registerQueryLoopEditorExtensions(
38
+ _context: QueryLoopEditorExtensionContext,
39
+ ): void {
40
+ // Add optional editor hooks here when this variation introduces custom query UI.
41
+ }
@@ -0,0 +1,16 @@
1
+ const path = require("node:path");
2
+ const defaultConfig = require("@wordpress/scripts/config/webpack.config");
3
+
4
+ const queryLoopEntry = {
5
+ index: path.resolve(process.cwd(), "src/index.ts"),
6
+ };
7
+
8
+ module.exports = Array.isArray(defaultConfig)
9
+ ? defaultConfig.map((config) => ({
10
+ ...config,
11
+ entry: config?.output?.module ? config.entry : queryLoopEntry,
12
+ }))
13
+ : {
14
+ ...defaultConfig,
15
+ entry: queryLoopEntry,
16
+ };
@@ -0,0 +1,84 @@
1
+ <?php
2
+ /**
3
+ * Plugin Name: {{title}}
4
+ * Description: {{description}}
5
+ * Version: 0.1.0
6
+ * Requires at least: 6.7
7
+ * Tested up to: 6.9
8
+ * Requires PHP: 8.0
9
+ * Author: {{author}}
10
+ * License: GPL-2.0-or-later
11
+ * License URI: https://www.gnu.org/licenses/gpl-2.0.html
12
+ * Text Domain: {{textDomain}}
13
+ * Domain Path: /languages
14
+ */
15
+
16
+ if ( ! defined( 'ABSPATH' ) ) {
17
+ exit;
18
+ }
19
+
20
+ require_once __DIR__ . '/inc/query-runtime.php';
21
+
22
+ function {{phpPrefix}}_load_textdomain() {
23
+ load_plugin_textdomain(
24
+ '{{textDomain}}',
25
+ false,
26
+ dirname( plugin_basename( __FILE__ ) ) . '/languages'
27
+ );
28
+ }
29
+
30
+ function {{phpPrefix}}_register_query_loop_pattern_category() {
31
+ if ( ! function_exists( 'register_block_pattern_category' ) ) {
32
+ return;
33
+ }
34
+
35
+ register_block_pattern_category(
36
+ '{{slug}}-query-loop',
37
+ array(
38
+ 'label' => __( '{{title}} Query Loop', '{{textDomain}}' ),
39
+ )
40
+ );
41
+ }
42
+
43
+ function {{phpPrefix}}_register_query_loop_patterns() {
44
+ if ( ! function_exists( 'register_block_pattern' ) ) {
45
+ return;
46
+ }
47
+
48
+ foreach ( glob( __DIR__ . '/src/patterns/*.php' ) ?: array() as $pattern_module ) {
49
+ require $pattern_module;
50
+ }
51
+ }
52
+
53
+ function {{phpPrefix}}_register_query_loop_variation_script() {
54
+ $script_path = __DIR__ . '/build/index.js';
55
+ $script_asset_path = __DIR__ . '/build/index.asset.php';
56
+
57
+ if ( ! file_exists( $script_path ) || ! file_exists( $script_asset_path ) ) {
58
+ return;
59
+ }
60
+
61
+ $asset = require $script_asset_path;
62
+ $handle = '{{phpPrefix}}_query_loop_variation';
63
+
64
+ wp_register_script(
65
+ $handle,
66
+ plugins_url( 'build/index.js', __FILE__ ),
67
+ is_array( $asset ) && isset( $asset['dependencies'] ) ? $asset['dependencies'] : array(),
68
+ is_array( $asset ) && isset( $asset['version'] ) ? $asset['version'] : filemtime( $script_path ),
69
+ true
70
+ );
71
+ wp_set_script_translations( $handle, '{{textDomain}}', __DIR__ . '/languages' );
72
+ }
73
+
74
+ function {{phpPrefix}}_enqueue_query_loop_variation_script() {
75
+ {{phpPrefix}}_register_query_loop_variation_script();
76
+ wp_enqueue_script( '{{phpPrefix}}_query_loop_variation' );
77
+ }
78
+
79
+ add_action( 'init', '{{phpPrefix}}_load_textdomain' );
80
+ add_action( 'init', '{{phpPrefix}}_register_query_loop_pattern_category' );
81
+ add_action( 'init', '{{phpPrefix}}_register_query_loop_patterns', 20 );
82
+ add_action( 'enqueue_block_editor_assets', '{{phpPrefix}}_enqueue_query_loop_variation_script' );
83
+ add_filter( 'query_loop_block_query_vars', '{{phpPrefix}}_filter_query_loop_block_query_vars', 10, 3 );
84
+ add_filter( 'rest_{{queryPostType}}_query', '{{phpPrefix}}_filter_query_loop_editor_preview_query_vars', 10, 2 );