wp-typia 0.20.4 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/bin/argv-walker.d.ts +19 -0
- package/bin/argv-walker.js +53 -0
- package/bin/routing-metadata.generated.d.ts +1 -1
- package/bin/routing-metadata.generated.js +38 -38
- package/bin/wp-typia.js +75 -87
- package/dist-bunli/.bunli/commands.gen.js +4940 -1246
- package/dist-bunli/{cli-68145vb5.js → cli-9x01fjna.js} +346 -41
- package/dist-bunli/{cli-add-a27wjrk4.js → cli-add-93945fc2.js} +1900 -385
- package/dist-bunli/{cli-diagnostics-db6kxv83.js → cli-diagnostics-5dvztm7q.js} +8 -2
- package/dist-bunli/{cli-doctor-31djnnxs.js → cli-doctor-zsndr5sw.js} +492 -20
- package/dist-bunli/{cli-3w3qxq9w.js → cli-hx88xwr4.js} +257 -25
- package/dist-bunli/cli-init-6xxc0snz.js +844 -0
- package/dist-bunli/{cli-2rev5hqm.js → cli-jfj54qej.js} +1 -1
- package/dist-bunli/{cli-jcd4wgam.js → cli-p95wr1q8.js} +77 -5
- package/dist-bunli/{cli-c5021kqy.js → cli-pav309dt.js} +896 -286
- package/dist-bunli/{cli-scaffold-r0yxfhbq.js → cli-scaffold-d2vtf740.js} +6 -5
- package/dist-bunli/{cli-tesygdnr.js → cli-syg9qpxw.js} +22 -1
- package/dist-bunli/cli.js +31 -117
- package/dist-bunli/{command-list-kx7q3f18.js → command-list-p452y8td.js} +561 -319
- package/dist-bunli/{migrations-1p6mbkyw.js → migrations-aj1rv3h8.js} +2 -2
- package/dist-bunli/node-cli.js +1035 -555
- package/package.json +5 -3
- package/dist-bunli/cli-init-gdyp9enw.js +0 -341
|
@@ -20,13 +20,13 @@ import {
|
|
|
20
20
|
scaffoldProject,
|
|
21
21
|
syncPersistenceRestArtifacts,
|
|
22
22
|
updatePluginHeaderCompatibility
|
|
23
|
-
} from "./cli-
|
|
23
|
+
} from "./cli-pav309dt.js";
|
|
24
24
|
import {
|
|
25
25
|
getPackageVersions
|
|
26
|
-
} from "./cli-
|
|
26
|
+
} from "./cli-syg9qpxw.js";
|
|
27
27
|
import {
|
|
28
28
|
snapshotProjectVersion
|
|
29
|
-
} from "./cli-
|
|
29
|
+
} from "./cli-jfj54qej.js";
|
|
30
30
|
import {
|
|
31
31
|
ensureMigrationDirectories,
|
|
32
32
|
parseMigrationConfig,
|
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
} from "./cli-sj5mtyzj.js";
|
|
40
40
|
import"./cli-10pe4mf8.js";
|
|
41
41
|
import {
|
|
42
|
+
PROJECT_TOOLS_PACKAGE_ROOT,
|
|
42
43
|
SHARED_WORKSPACE_TEMPLATE_ROOT
|
|
43
44
|
} from "./cli-tke8twkn.js";
|
|
44
45
|
import {
|
|
@@ -47,6 +48,7 @@ import {
|
|
|
47
48
|
EDITOR_PLUGIN_SLOT_IDS,
|
|
48
49
|
appendWorkspaceInventoryEntries,
|
|
49
50
|
assertAbilityDoesNotExist,
|
|
51
|
+
assertAdminViewDoesNotExist,
|
|
50
52
|
assertAiFeatureDoesNotExist,
|
|
51
53
|
assertBindingSourceDoesNotExist,
|
|
52
54
|
assertEditorPluginDoesNotExist,
|
|
@@ -59,28 +61,37 @@ import {
|
|
|
59
61
|
assertValidRestResourceMethods,
|
|
60
62
|
assertVariationDoesNotExist,
|
|
61
63
|
buildWorkspacePhpPrefix,
|
|
64
|
+
escapeRegex,
|
|
65
|
+
findPhpFunctionRange,
|
|
62
66
|
formatAddHelpText,
|
|
63
67
|
getMutableBlockHooks,
|
|
64
68
|
getWorkspaceBlockSelectOptions,
|
|
65
69
|
getWorkspaceBootstrapPath,
|
|
70
|
+
hasPhpFunctionDefinition,
|
|
66
71
|
isAddBlockTemplateId,
|
|
67
72
|
normalizeBlockSlug,
|
|
68
73
|
patchFile,
|
|
74
|
+
quotePhpString,
|
|
69
75
|
quoteTsString,
|
|
70
76
|
readOptionalFile,
|
|
71
77
|
readWorkspaceBlockJson,
|
|
72
78
|
readWorkspaceInventory,
|
|
79
|
+
replacePhpFunctionDefinition,
|
|
80
|
+
require_typescript,
|
|
73
81
|
resolveNonEmptyNormalizedBlockSlug,
|
|
74
82
|
resolveRestResourceNamespace,
|
|
75
83
|
resolveWorkspaceBlock,
|
|
76
84
|
rollbackWorkspaceMutation,
|
|
77
85
|
snapshotWorkspaceFiles,
|
|
78
86
|
toKebabCase,
|
|
87
|
+
toPascalCase,
|
|
88
|
+
toSnakeCase,
|
|
79
89
|
toTitleCase
|
|
80
|
-
} from "./cli-
|
|
90
|
+
} from "./cli-hx88xwr4.js";
|
|
81
91
|
import {
|
|
82
92
|
createManagedTempRoot
|
|
83
93
|
} from "./cli-t73q5aqz.js";
|
|
94
|
+
import"./cli-p95wr1q8.js";
|
|
84
95
|
import {
|
|
85
96
|
resolveWorkspaceProject
|
|
86
97
|
} from "./cli-pd5pqgre.js";
|
|
@@ -641,7 +652,7 @@ async function runAddBlockCommand({
|
|
|
641
652
|
throw new Error("`wp-typia add block --template query-loop` is not supported. Query Loop is a create-time `core/query` variation scaffold, so use `wp-typia create <project-dir> --template query-loop` instead.");
|
|
642
653
|
}
|
|
643
654
|
if (!isAddBlockTemplateId(templateId)) {
|
|
644
|
-
throw new Error(`Unknown add-block template "${templateId}". Expected one of: ${ADD_BLOCK_TEMPLATE_IDS.join(", ")}
|
|
655
|
+
throw new Error(`Unknown add-block template "${templateId}". Expected one of: ${ADD_BLOCK_TEMPLATE_IDS.join(", ")}. Run \`wp-typia templates list\` to inspect available templates.`);
|
|
645
656
|
}
|
|
646
657
|
const resolvedTemplateId = templateId;
|
|
647
658
|
assertPersistenceFlagsAllowed(resolvedTemplateId, {
|
|
@@ -759,74 +770,894 @@ async function runAddBlockCommand({
|
|
|
759
770
|
}
|
|
760
771
|
}
|
|
761
772
|
// ../wp-typia-project-tools/src/runtime/cli-add-workspace.ts
|
|
762
|
-
import
|
|
763
|
-
import { promises as
|
|
764
|
-
import
|
|
773
|
+
import fs6 from "fs";
|
|
774
|
+
import { promises as fsp9 } from "fs";
|
|
775
|
+
import path13 from "path";
|
|
765
776
|
|
|
766
|
-
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-
|
|
777
|
+
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-admin-view.ts
|
|
767
778
|
import fs3 from "fs";
|
|
768
779
|
import { promises as fsp3 } from "fs";
|
|
780
|
+
import { createRequire } from "module";
|
|
769
781
|
import path4 from "path";
|
|
770
|
-
var
|
|
771
|
-
var
|
|
772
|
-
var
|
|
773
|
-
var
|
|
774
|
-
var
|
|
775
|
-
var
|
|
776
|
-
var
|
|
777
|
-
var
|
|
778
|
-
|
|
779
|
-
|
|
782
|
+
var ADMIN_VIEW_SOURCE_KIND = "rest-resource";
|
|
783
|
+
var ADMIN_VIEWS_SCRIPT = "build/admin-views/index.js";
|
|
784
|
+
var ADMIN_VIEWS_ASSET = "build/admin-views/index.asset.php";
|
|
785
|
+
var ADMIN_VIEWS_STYLE = "build/admin-views/style-index.css";
|
|
786
|
+
var ADMIN_VIEWS_STYLE_RTL = "build/admin-views/style-index-rtl.css";
|
|
787
|
+
var ADMIN_VIEWS_PHP_GLOB = "/inc/admin-views/*.php";
|
|
788
|
+
var DEFAULT_WP_TYPIA_DATAVIEWS_VERSION = "^0.1.0";
|
|
789
|
+
var DEFAULT_WORDPRESS_DATAVIEWS_VERSION = "^14.1.0";
|
|
790
|
+
var require2 = createRequire(import.meta.url);
|
|
791
|
+
function toCamelCase(input) {
|
|
792
|
+
const pascalCase = toPascalCase(input);
|
|
793
|
+
return `${pascalCase.charAt(0).toLowerCase()}${pascalCase.slice(1)}`;
|
|
794
|
+
}
|
|
795
|
+
function normalizeVersionRange(value, fallback) {
|
|
796
|
+
const trimmed = value?.trim();
|
|
797
|
+
if (!trimmed || trimmed.startsWith("workspace:")) {
|
|
798
|
+
return fallback;
|
|
799
|
+
}
|
|
800
|
+
return /^[~^<>=]/u.test(trimmed) ? trimmed : `^${trimmed}`;
|
|
801
|
+
}
|
|
802
|
+
function readPackageManifestVersion(packageJsonPath) {
|
|
803
|
+
try {
|
|
804
|
+
const packageJson = JSON.parse(fs3.readFileSync(packageJsonPath, "utf8"));
|
|
805
|
+
return packageJson.version;
|
|
806
|
+
} catch {
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
780
809
|
}
|
|
781
|
-
function
|
|
782
|
-
|
|
810
|
+
function detectJsonIndent(source) {
|
|
811
|
+
const indentMatch = /\n([ \t]+)"/u.exec(source);
|
|
812
|
+
return indentMatch?.[1] ?? 2;
|
|
783
813
|
}
|
|
784
|
-
function
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
814
|
+
function resolvePackageVersionRange(packageName, fallback, workspacePackageDirName) {
|
|
815
|
+
if (workspacePackageDirName) {
|
|
816
|
+
const workspaceVersion = readPackageManifestVersion(path4.join(PROJECT_TOOLS_PACKAGE_ROOT, "..", workspacePackageDirName, "package.json"));
|
|
817
|
+
if (workspaceVersion) {
|
|
818
|
+
return normalizeVersionRange(workspaceVersion, fallback);
|
|
819
|
+
}
|
|
789
820
|
}
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
return
|
|
821
|
+
try {
|
|
822
|
+
return normalizeVersionRange(readPackageManifestVersion(require2.resolve(`${packageName}/package.json`)), fallback);
|
|
823
|
+
} catch {
|
|
824
|
+
return fallback;
|
|
794
825
|
}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
826
|
+
}
|
|
827
|
+
function getAdminViewRelativeModuleSpecifier(adminViewSlug, workspaceFile) {
|
|
828
|
+
const adminViewDir = `src/admin-views/${adminViewSlug}`;
|
|
829
|
+
const normalizedFile = workspaceFile.replace(/\\/gu, "/");
|
|
830
|
+
const modulePath = normalizedFile.replace(/\.[cm]?[jt]sx?$/u, "");
|
|
831
|
+
const relativeModulePath = path4.posix.relative(adminViewDir, modulePath);
|
|
832
|
+
return relativeModulePath.startsWith(".") ? relativeModulePath : `./${relativeModulePath}`;
|
|
833
|
+
}
|
|
834
|
+
function parseAdminViewSource(source) {
|
|
835
|
+
const trimmed = source?.trim();
|
|
836
|
+
if (!trimmed) {
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
const [kind, slug, extra] = trimmed.split(":");
|
|
840
|
+
if (kind !== ADMIN_VIEW_SOURCE_KIND || !slug || extra !== undefined) {
|
|
841
|
+
throw new Error("Admin view source must use `rest-resource:<slug>` for now.");
|
|
842
|
+
}
|
|
843
|
+
return {
|
|
844
|
+
kind,
|
|
845
|
+
slug: assertValidGeneratedSlug("Admin view source slug", normalizeBlockSlug(slug), "wp-typia add admin-view <name> --source rest-resource:<slug>")
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
function resolveRestResourceSource(restResources, source) {
|
|
849
|
+
if (!source) {
|
|
850
|
+
return;
|
|
851
|
+
}
|
|
852
|
+
const restResource = restResources.find((entry) => entry.slug === source.slug);
|
|
853
|
+
if (!restResource) {
|
|
854
|
+
throw new Error(`Unknown REST resource source "${source.slug}". Choose one of: ${restResources.map((entry) => entry.slug).join(", ") || "<none>"}.`);
|
|
855
|
+
}
|
|
856
|
+
if (!restResource.methods.includes("list")) {
|
|
857
|
+
throw new Error(`REST resource source "${source.slug}" must include the list method for DataViews pagination.`);
|
|
858
|
+
}
|
|
859
|
+
return restResource;
|
|
860
|
+
}
|
|
861
|
+
function buildAdminViewConfigEntry(adminViewSlug, source) {
|
|
862
|
+
return [
|
|
863
|
+
"\t{",
|
|
864
|
+
` file: ${quoteTsString(`src/admin-views/${adminViewSlug}/index.tsx`)},`,
|
|
865
|
+
` phpFile: ${quoteTsString(`inc/admin-views/${adminViewSlug}.php`)},`,
|
|
866
|
+
` slug: ${quoteTsString(adminViewSlug)},`,
|
|
867
|
+
source ? ` source: ${quoteTsString(`${source.kind}:${source.slug}`)},` : null,
|
|
868
|
+
"\t},"
|
|
869
|
+
].filter((line) => typeof line === "string").join(`
|
|
870
|
+
`);
|
|
871
|
+
}
|
|
872
|
+
function buildAdminViewRegistrySource(adminViewSlugs) {
|
|
873
|
+
const importLines = adminViewSlugs.map((adminViewSlug) => `import './${adminViewSlug}';`).join(`
|
|
874
|
+
`);
|
|
875
|
+
return `${importLines}${importLines ? `
|
|
876
|
+
|
|
877
|
+
` : ""}// wp-typia add admin-view entries
|
|
878
|
+
`;
|
|
879
|
+
}
|
|
880
|
+
function buildAdminViewTypesSource(adminViewSlug, restResource) {
|
|
881
|
+
const pascalName = toPascalCase(adminViewSlug);
|
|
882
|
+
const itemTypeName = `${pascalName}AdminViewItem`;
|
|
883
|
+
const dataSetTypeName = `${pascalName}AdminViewDataSet`;
|
|
884
|
+
if (restResource) {
|
|
885
|
+
const restPascalName = toPascalCase(restResource.slug);
|
|
886
|
+
const restTypesModule = getAdminViewRelativeModuleSpecifier(adminViewSlug, restResource.typesFile);
|
|
887
|
+
return `import type { ${restPascalName}Record } from ${quoteTsString(restTypesModule)};
|
|
888
|
+
|
|
889
|
+
export type ${itemTypeName} = ${restPascalName}Record;
|
|
890
|
+
|
|
891
|
+
export interface ${dataSetTypeName} {
|
|
892
|
+
items: ${itemTypeName}[];
|
|
893
|
+
paginationInfo: {
|
|
894
|
+
totalItems: number;
|
|
895
|
+
totalPages: number;
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
`;
|
|
899
|
+
}
|
|
900
|
+
return `export type ${pascalName}AdminViewStatus = 'draft' | 'published';
|
|
901
|
+
|
|
902
|
+
export interface ${itemTypeName} {
|
|
903
|
+
id: number;
|
|
904
|
+
owner: string;
|
|
905
|
+
status: ${pascalName}AdminViewStatus;
|
|
906
|
+
title: string;
|
|
907
|
+
updatedAt: string;
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
export interface ${dataSetTypeName} {
|
|
911
|
+
items: ${itemTypeName}[];
|
|
912
|
+
paginationInfo: {
|
|
913
|
+
totalItems: number;
|
|
914
|
+
totalPages: number;
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
`;
|
|
918
|
+
}
|
|
919
|
+
function buildAdminViewConfigSource(adminViewSlug, textDomain, restResource) {
|
|
920
|
+
const pascalName = toPascalCase(adminViewSlug);
|
|
921
|
+
const camelName = toCamelCase(adminViewSlug);
|
|
922
|
+
const itemTypeName = `${pascalName}AdminViewItem`;
|
|
923
|
+
const dataViewsName = `${camelName}AdminDataViews`;
|
|
924
|
+
const defaultViewFields = restResource ? "['id']" : "['title', 'status', 'updatedAt']";
|
|
925
|
+
const searchEnabled = restResource ? "false" : "true";
|
|
926
|
+
const titleFieldSource = restResource ? "" : ` titleField: 'title',
|
|
927
|
+
`;
|
|
928
|
+
const defaultViewEnhancementsSource = restResource ? "" : ` sort: {
|
|
929
|
+
direction: 'desc',
|
|
930
|
+
field: 'updatedAt',
|
|
931
|
+
},
|
|
932
|
+
titleField: 'title',
|
|
933
|
+
`;
|
|
934
|
+
const additionalFieldsSource = restResource ? "\t\t// REST-backed screens start with the guaranteed ID column. Add project-owned fields here once they are declared on the REST record type." : ` owner: {
|
|
935
|
+
label: __( 'Owner', ${quoteTsString(textDomain)} ),
|
|
936
|
+
schema: { type: 'string' },
|
|
937
|
+
},
|
|
938
|
+
status: {
|
|
939
|
+
filterBy: { operators: ['isAny', 'isNone'] },
|
|
940
|
+
label: __( 'Status', ${quoteTsString(textDomain)} ),
|
|
941
|
+
schema: {
|
|
942
|
+
enum: ['draft', 'published'],
|
|
943
|
+
enumLabels: {
|
|
944
|
+
draft: __( 'Draft', ${quoteTsString(textDomain)} ),
|
|
945
|
+
published: __( 'Published', ${quoteTsString(textDomain)} ),
|
|
946
|
+
},
|
|
947
|
+
type: 'string',
|
|
948
|
+
},
|
|
949
|
+
},
|
|
950
|
+
title: {
|
|
951
|
+
enableGlobalSearch: true,
|
|
952
|
+
enableSorting: true,
|
|
953
|
+
label: __( 'Title', ${quoteTsString(textDomain)} ),
|
|
954
|
+
schema: { type: 'string' },
|
|
955
|
+
},
|
|
956
|
+
updatedAt: {
|
|
957
|
+
enableSorting: true,
|
|
958
|
+
label: __( 'Updated', ${quoteTsString(textDomain)} ),
|
|
959
|
+
schema: { format: 'date-time', type: 'string' },
|
|
960
|
+
type: 'datetime',
|
|
961
|
+
},`;
|
|
962
|
+
return `import { defineDataViews } from '@wp-typia/dataviews';
|
|
963
|
+
import { __ } from '@wordpress/i18n';
|
|
964
|
+
|
|
965
|
+
import type { ${itemTypeName} } from './types';
|
|
966
|
+
|
|
967
|
+
export const ${dataViewsName} = defineDataViews<${itemTypeName}>({
|
|
968
|
+
idField: 'id',
|
|
969
|
+
search: ${searchEnabled},
|
|
970
|
+
searchLabel: __( 'Search records', ${quoteTsString(textDomain)} ),
|
|
971
|
+
${titleFieldSource}
|
|
972
|
+
defaultView: {
|
|
973
|
+
fields: ${defaultViewFields},
|
|
974
|
+
page: 1,
|
|
975
|
+
perPage: 10,
|
|
976
|
+
${defaultViewEnhancementsSource}
|
|
977
|
+
type: 'table',
|
|
978
|
+
},
|
|
979
|
+
fields: {
|
|
980
|
+
id: {
|
|
981
|
+
enableHiding: false,
|
|
982
|
+
label: __( 'ID', ${quoteTsString(textDomain)} ),
|
|
983
|
+
readOnly: true,
|
|
984
|
+
schema: { type: 'integer' },
|
|
985
|
+
},
|
|
986
|
+
${additionalFieldsSource}
|
|
987
|
+
},
|
|
988
|
+
});
|
|
989
|
+
`;
|
|
990
|
+
}
|
|
991
|
+
function buildDefaultAdminViewDataSource(adminViewSlug) {
|
|
992
|
+
const pascalName = toPascalCase(adminViewSlug);
|
|
993
|
+
const camelName = toCamelCase(adminViewSlug);
|
|
994
|
+
const title = toTitleCase(adminViewSlug);
|
|
995
|
+
const itemTypeName = `${pascalName}AdminViewItem`;
|
|
996
|
+
const dataSetTypeName = `${pascalName}AdminViewDataSet`;
|
|
997
|
+
const queryTypeName = `${pascalName}AdminViewQuery`;
|
|
998
|
+
const dataViewsName = `${camelName}AdminDataViews`;
|
|
999
|
+
const fetchName = `fetch${pascalName}AdminViewData`;
|
|
1000
|
+
return `import type { DataViewsView } from '@wp-typia/dataviews';
|
|
1001
|
+
|
|
1002
|
+
import { ${dataViewsName} } from './config';
|
|
1003
|
+
import type { ${dataSetTypeName}, ${itemTypeName} } from './types';
|
|
1004
|
+
|
|
1005
|
+
export interface ${queryTypeName} {
|
|
1006
|
+
page?: number;
|
|
1007
|
+
perPage?: number;
|
|
1008
|
+
search?: string;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
const STARTER_ITEMS: ${itemTypeName}[] = [
|
|
1012
|
+
{
|
|
1013
|
+
id: 1,
|
|
1014
|
+
owner: 'Editorial',
|
|
1015
|
+
status: 'published',
|
|
1016
|
+
title: ${quoteTsString(`${title} launch checklist`)},
|
|
1017
|
+
updatedAt: '2026-04-01T10:30:00Z',
|
|
1018
|
+
},
|
|
1019
|
+
{
|
|
1020
|
+
id: 2,
|
|
1021
|
+
owner: 'Design',
|
|
1022
|
+
status: 'draft',
|
|
1023
|
+
title: ${quoteTsString(`${title} content refresh`)},
|
|
1024
|
+
updatedAt: '2026-04-03T14:15:00Z',
|
|
1025
|
+
},
|
|
1026
|
+
{
|
|
1027
|
+
id: 3,
|
|
1028
|
+
owner: 'Operations',
|
|
1029
|
+
status: 'published',
|
|
1030
|
+
title: ${quoteTsString(`${title} support handoff`)},
|
|
1031
|
+
updatedAt: '2026-04-08T08:45:00Z',
|
|
1032
|
+
},
|
|
1033
|
+
];
|
|
1034
|
+
|
|
1035
|
+
function matchesSearch(item: ${itemTypeName}, search: string | undefined): boolean {
|
|
1036
|
+
if (!search) {
|
|
1037
|
+
return true;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
const needle = search.toLowerCase();
|
|
1041
|
+
return [item.title, item.owner, item.status].some((value) =>
|
|
1042
|
+
value.toLowerCase().includes(needle),
|
|
1043
|
+
);
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
export async function ${fetchName}(
|
|
1047
|
+
view: DataViewsView<${itemTypeName}>,
|
|
1048
|
+
): Promise<${dataSetTypeName}> {
|
|
1049
|
+
const query = ${dataViewsName}.toQueryArgs<${queryTypeName}>(view, {
|
|
1050
|
+
perPageParam: 'perPage',
|
|
1051
|
+
});
|
|
1052
|
+
const requestedPage = query.page ?? 1;
|
|
1053
|
+
const page = requestedPage > 0 ? requestedPage : 1;
|
|
1054
|
+
const requestedPerPage = query.perPage ?? view.perPage ?? 10;
|
|
1055
|
+
const perPage = requestedPerPage > 0 ? requestedPerPage : 10;
|
|
1056
|
+
const filteredItems = STARTER_ITEMS.filter((item) =>
|
|
1057
|
+
matchesSearch(item, query.search),
|
|
1058
|
+
);
|
|
1059
|
+
const offset = (page - 1) * perPage;
|
|
1060
|
+
const items = filteredItems.slice(offset, offset + perPage);
|
|
1061
|
+
|
|
1062
|
+
return {
|
|
1063
|
+
items,
|
|
1064
|
+
paginationInfo: {
|
|
1065
|
+
totalItems: filteredItems.length,
|
|
1066
|
+
totalPages: Math.max(1, Math.ceil(filteredItems.length / perPage)),
|
|
1067
|
+
},
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
`;
|
|
1071
|
+
}
|
|
1072
|
+
function buildRestAdminViewDataSource(adminViewSlug, restResource) {
|
|
1073
|
+
const pascalName = toPascalCase(adminViewSlug);
|
|
1074
|
+
const restPascalName = toPascalCase(restResource.slug);
|
|
1075
|
+
const camelName = toCamelCase(adminViewSlug);
|
|
1076
|
+
const itemTypeName = `${pascalName}AdminViewItem`;
|
|
1077
|
+
const dataSetTypeName = `${pascalName}AdminViewDataSet`;
|
|
1078
|
+
const dataViewsName = `${camelName}AdminDataViews`;
|
|
1079
|
+
const fetchName = `fetch${pascalName}AdminViewData`;
|
|
1080
|
+
const restApiModule = getAdminViewRelativeModuleSpecifier(adminViewSlug, restResource.apiFile);
|
|
1081
|
+
const restTypesModule = getAdminViewRelativeModuleSpecifier(adminViewSlug, restResource.typesFile);
|
|
1082
|
+
return `import type { DataViewsView } from '@wp-typia/dataviews';
|
|
1083
|
+
|
|
1084
|
+
import { listResource } from ${quoteTsString(restApiModule)};
|
|
1085
|
+
import type { ${restPascalName}ListQuery } from ${quoteTsString(restTypesModule)};
|
|
1086
|
+
import { ${dataViewsName} } from './config';
|
|
1087
|
+
import type { ${dataSetTypeName}, ${itemTypeName} } from './types';
|
|
1088
|
+
|
|
1089
|
+
function resolveTotalPages(total: number, perPage: number | undefined): number {
|
|
1090
|
+
const resolvedPerPage = perPage && perPage > 0 ? perPage : 1;
|
|
1091
|
+
return Math.max(1, Math.ceil(total / resolvedPerPage));
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
export async function ${fetchName}(
|
|
1095
|
+
view: DataViewsView<${itemTypeName}>,
|
|
1096
|
+
): Promise<${dataSetTypeName}> {
|
|
1097
|
+
const query = ${dataViewsName}.toQueryArgs<${restPascalName}ListQuery>(view, {
|
|
1098
|
+
perPageParam: 'perPage',
|
|
1099
|
+
searchParam: false,
|
|
1100
|
+
});
|
|
1101
|
+
const result = await listResource({
|
|
1102
|
+
page: query.page,
|
|
1103
|
+
perPage: query.perPage,
|
|
1104
|
+
});
|
|
1105
|
+
if (!result.isValid || !result.data) {
|
|
1106
|
+
throw new Error('Unable to load REST resource records.');
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
const response = result.data;
|
|
1110
|
+
|
|
1111
|
+
return {
|
|
1112
|
+
items: response.items,
|
|
1113
|
+
paginationInfo: {
|
|
1114
|
+
totalItems: response.total,
|
|
1115
|
+
totalPages: resolveTotalPages(response.total, response.perPage ?? query.perPage),
|
|
1116
|
+
},
|
|
1117
|
+
};
|
|
1118
|
+
}
|
|
1119
|
+
`;
|
|
1120
|
+
}
|
|
1121
|
+
function buildAdminViewScreenSource(adminViewSlug, textDomain) {
|
|
1122
|
+
const pascalName = toPascalCase(adminViewSlug);
|
|
1123
|
+
const camelName = toCamelCase(adminViewSlug);
|
|
1124
|
+
const itemTypeName = `${pascalName}AdminViewItem`;
|
|
1125
|
+
const dataSetTypeName = `${pascalName}AdminViewDataSet`;
|
|
1126
|
+
const componentName = `${pascalName}AdminViewScreen`;
|
|
1127
|
+
const dataViewsName = `${camelName}AdminDataViews`;
|
|
1128
|
+
const fetchName = `fetch${pascalName}AdminViewData`;
|
|
1129
|
+
const title = toTitleCase(adminViewSlug);
|
|
1130
|
+
return `import type { DataViewsConfig, DataViewsView } from '@wp-typia/dataviews';
|
|
1131
|
+
import { Button, Notice, Spinner } from '@wordpress/components';
|
|
1132
|
+
import { useEffect, useState } from '@wordpress/element';
|
|
1133
|
+
import { __ } from '@wordpress/i18n';
|
|
1134
|
+
import { DataViews } from '@wordpress/dataviews/wp';
|
|
1135
|
+
|
|
1136
|
+
import { ${dataViewsName} } from './config';
|
|
1137
|
+
import { ${fetchName} } from './data';
|
|
1138
|
+
import type { ${dataSetTypeName}, ${itemTypeName} } from './types';
|
|
1139
|
+
|
|
1140
|
+
const TypedDataViews = DataViews as unknown as <TItem extends object>(
|
|
1141
|
+
props: DataViewsConfig<TItem>,
|
|
1142
|
+
) => ReturnType<typeof DataViews>;
|
|
1143
|
+
|
|
1144
|
+
const EMPTY_DATA_SET: ${dataSetTypeName} = {
|
|
1145
|
+
items: [],
|
|
1146
|
+
paginationInfo: {
|
|
1147
|
+
totalItems: 0,
|
|
1148
|
+
totalPages: 1,
|
|
1149
|
+
},
|
|
1150
|
+
};
|
|
1151
|
+
|
|
1152
|
+
export function ${componentName}() {
|
|
1153
|
+
const [view, setView] = useState<DataViewsView<${itemTypeName}>>(
|
|
1154
|
+
${dataViewsName}.defaultView,
|
|
1155
|
+
);
|
|
1156
|
+
const [dataSet, setDataSet] = useState<${dataSetTypeName}>(EMPTY_DATA_SET);
|
|
1157
|
+
const [error, setError] = useState<string | null>(null);
|
|
1158
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
1159
|
+
const [reloadToken, setReloadToken] = useState(0);
|
|
1160
|
+
|
|
1161
|
+
useEffect(() => {
|
|
1162
|
+
let isCurrentRequest = true;
|
|
1163
|
+
setIsLoading(true);
|
|
1164
|
+
setError(null);
|
|
1165
|
+
|
|
1166
|
+
void ${fetchName}(view)
|
|
1167
|
+
.then((nextDataSet) => {
|
|
1168
|
+
if (isCurrentRequest) {
|
|
1169
|
+
setDataSet(nextDataSet);
|
|
1170
|
+
}
|
|
1171
|
+
})
|
|
1172
|
+
.catch((nextError: unknown) => {
|
|
1173
|
+
if (isCurrentRequest) {
|
|
1174
|
+
setError(
|
|
1175
|
+
nextError instanceof Error
|
|
1176
|
+
? nextError.message
|
|
1177
|
+
: __( 'Unable to load records.', ${quoteTsString(textDomain)} ),
|
|
1178
|
+
);
|
|
1179
|
+
}
|
|
1180
|
+
})
|
|
1181
|
+
.finally(() => {
|
|
1182
|
+
if (isCurrentRequest) {
|
|
1183
|
+
setIsLoading(false);
|
|
1184
|
+
}
|
|
1185
|
+
});
|
|
1186
|
+
|
|
1187
|
+
return () => {
|
|
1188
|
+
isCurrentRequest = false;
|
|
1189
|
+
};
|
|
1190
|
+
}, [reloadToken, view]);
|
|
1191
|
+
|
|
1192
|
+
const config = ${dataViewsName}.createConfig({
|
|
1193
|
+
data: dataSet.items,
|
|
1194
|
+
isLoading,
|
|
1195
|
+
onChangeView: setView,
|
|
1196
|
+
paginationInfo: dataSet.paginationInfo,
|
|
1197
|
+
view,
|
|
1198
|
+
});
|
|
1199
|
+
|
|
1200
|
+
return (
|
|
1201
|
+
<div className="wp-typia-admin-view-screen">
|
|
1202
|
+
<header className="wp-typia-admin-view-screen__header">
|
|
1203
|
+
<div>
|
|
1204
|
+
<p className="wp-typia-admin-view-screen__eyebrow">
|
|
1205
|
+
{ __( 'DataViews admin screen', ${quoteTsString(textDomain)} ) }
|
|
1206
|
+
</p>
|
|
1207
|
+
<h1>{ __( ${quoteTsString(title)}, ${quoteTsString(textDomain)} ) }</h1>
|
|
1208
|
+
<p>
|
|
1209
|
+
{ __( 'Replace the fetcher in data.ts with your project data source when this screen graduates from scaffold to product UI.', ${quoteTsString(textDomain)} ) }
|
|
1210
|
+
</p>
|
|
1211
|
+
</div>
|
|
1212
|
+
<div className="wp-typia-admin-view-screen__actions">
|
|
1213
|
+
{ isLoading ? <Spinner /> : null }
|
|
1214
|
+
<Button
|
|
1215
|
+
isBusy={ isLoading }
|
|
1216
|
+
onClick={ () => setReloadToken((token) => token + 1) }
|
|
1217
|
+
variant="secondary"
|
|
1218
|
+
>
|
|
1219
|
+
{ __( 'Reload', ${quoteTsString(textDomain)} ) }
|
|
1220
|
+
</Button>
|
|
1221
|
+
</div>
|
|
1222
|
+
</header>
|
|
1223
|
+
{ error ? (
|
|
1224
|
+
<Notice isDismissible={ false } status="error">
|
|
1225
|
+
{ error }
|
|
1226
|
+
</Notice>
|
|
1227
|
+
) : null }
|
|
1228
|
+
<TypedDataViews<${itemTypeName}> { ...config } />
|
|
1229
|
+
</div>
|
|
1230
|
+
);
|
|
1231
|
+
}
|
|
1232
|
+
`;
|
|
1233
|
+
}
|
|
1234
|
+
function buildAdminViewEntrySource(adminViewSlug) {
|
|
1235
|
+
const pascalName = toPascalCase(adminViewSlug);
|
|
1236
|
+
const componentName = `${pascalName}AdminViewScreen`;
|
|
1237
|
+
const rootId = `wp-typia-admin-view-${adminViewSlug}`;
|
|
1238
|
+
return `import { createRoot } from '@wordpress/element';
|
|
1239
|
+
|
|
1240
|
+
import '@wordpress/dataviews/build-style/style.css';
|
|
1241
|
+
import { ${componentName} } from './Screen';
|
|
1242
|
+
import './style.scss';
|
|
1243
|
+
|
|
1244
|
+
const ROOT_ELEMENT_ID = ${quoteTsString(rootId)};
|
|
1245
|
+
|
|
1246
|
+
function mountAdminView() {
|
|
1247
|
+
const rootElement = document.getElementById(ROOT_ELEMENT_ID);
|
|
1248
|
+
if (!rootElement) {
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
createRoot(rootElement).render(<${componentName} />);
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
if (document.readyState === 'loading') {
|
|
1256
|
+
document.addEventListener('DOMContentLoaded', mountAdminView);
|
|
1257
|
+
} else {
|
|
1258
|
+
mountAdminView();
|
|
1259
|
+
}
|
|
1260
|
+
`;
|
|
1261
|
+
}
|
|
1262
|
+
function buildAdminViewStyleSource() {
|
|
1263
|
+
return `.wp-typia-admin-view-screen {
|
|
1264
|
+
box-sizing: border-box;
|
|
1265
|
+
max-width: 1180px;
|
|
1266
|
+
padding: 24px 24px 48px 0;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
.wp-typia-admin-view-screen__header {
|
|
1270
|
+
align-items: flex-start;
|
|
1271
|
+
display: flex;
|
|
1272
|
+
gap: 24px;
|
|
1273
|
+
justify-content: space-between;
|
|
1274
|
+
margin-bottom: 24px;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
.wp-typia-admin-view-screen__header h1 {
|
|
1278
|
+
font-size: 28px;
|
|
1279
|
+
line-height: 1.2;
|
|
1280
|
+
margin: 0 0 8px;
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
.wp-typia-admin-view-screen__header p {
|
|
1284
|
+
max-width: 680px;
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
.wp-typia-admin-view-screen__eyebrow {
|
|
1288
|
+
color: #3858e9;
|
|
1289
|
+
font-size: 11px;
|
|
1290
|
+
font-weight: 600;
|
|
1291
|
+
letter-spacing: 0.08em;
|
|
1292
|
+
margin: 0 0 8px;
|
|
1293
|
+
text-transform: uppercase;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
.wp-typia-admin-view-screen__actions {
|
|
1297
|
+
align-items: center;
|
|
1298
|
+
display: flex;
|
|
1299
|
+
gap: 12px;
|
|
1300
|
+
}
|
|
1301
|
+
`;
|
|
1302
|
+
}
|
|
1303
|
+
function buildAdminViewPhpSource(adminViewSlug, workspace) {
|
|
1304
|
+
const workspaceBaseName = workspace.packageName.split("/").pop() ?? workspace.packageName;
|
|
1305
|
+
const phpSlug = adminViewSlug.replace(/-/g, "_");
|
|
1306
|
+
const functionPrefix = `${workspace.workspace.phpPrefix}_${phpSlug}`;
|
|
1307
|
+
const menuSlugFunctionName = `${functionPrefix}_admin_view_menu_slug`;
|
|
1308
|
+
const renderFunctionName = `${functionPrefix}_render_admin_view`;
|
|
1309
|
+
const registerFunctionName = `${functionPrefix}_register_admin_view`;
|
|
1310
|
+
const enqueueFunctionName = `${functionPrefix}_enqueue_admin_view`;
|
|
1311
|
+
const hookGlobalName = `${functionPrefix}_admin_view_hook`;
|
|
1312
|
+
const rootId = `wp-typia-admin-view-${adminViewSlug}`;
|
|
1313
|
+
const title = toTitleCase(adminViewSlug);
|
|
1314
|
+
return `<?php
|
|
1315
|
+
if ( ! defined( 'ABSPATH' ) ) {
|
|
1316
|
+
return;
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
if ( ! function_exists( '${menuSlugFunctionName}' ) ) {
|
|
1320
|
+
function ${menuSlugFunctionName}() : string {
|
|
1321
|
+
return '${workspaceBaseName}-${adminViewSlug}';
|
|
1322
|
+
}
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
if ( ! function_exists( '${renderFunctionName}' ) ) {
|
|
1326
|
+
function ${renderFunctionName}() : void {
|
|
1327
|
+
?>
|
|
1328
|
+
<div class="wrap">
|
|
1329
|
+
<div id="${rootId}"></div>
|
|
1330
|
+
</div>
|
|
1331
|
+
<?php
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
if ( ! function_exists( '${registerFunctionName}' ) ) {
|
|
1336
|
+
function ${registerFunctionName}() : void {
|
|
1337
|
+
$GLOBALS['${hookGlobalName}'] = add_submenu_page(
|
|
1338
|
+
'tools.php',
|
|
1339
|
+
__( ${quotePhpString(title)}, ${quotePhpString(workspace.workspace.textDomain)} ),
|
|
1340
|
+
__( ${quotePhpString(title)}, ${quotePhpString(workspace.workspace.textDomain)} ),
|
|
1341
|
+
'edit_posts',
|
|
1342
|
+
${menuSlugFunctionName}(),
|
|
1343
|
+
'${renderFunctionName}'
|
|
1344
|
+
);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
if ( ! function_exists( '${enqueueFunctionName}' ) ) {
|
|
1349
|
+
function ${enqueueFunctionName}( string $hook_suffix ) : void {
|
|
1350
|
+
$page_hook = isset( $GLOBALS['${hookGlobalName}'] ) && is_string( $GLOBALS['${hookGlobalName}'] )
|
|
1351
|
+
? $GLOBALS['${hookGlobalName}']
|
|
1352
|
+
: '';
|
|
1353
|
+
|
|
1354
|
+
if ( $page_hook !== $hook_suffix ) {
|
|
1355
|
+
return;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
$plugin_file = dirname( __DIR__, 2 ) . '/${workspaceBaseName}.php';
|
|
1359
|
+
$script_path = dirname( __DIR__, 2 ) . '/${ADMIN_VIEWS_SCRIPT}';
|
|
1360
|
+
$asset_path = dirname( __DIR__, 2 ) . '/${ADMIN_VIEWS_ASSET}';
|
|
1361
|
+
$style_path = dirname( __DIR__, 2 ) . '/${ADMIN_VIEWS_STYLE}';
|
|
1362
|
+
$style_rtl_path = dirname( __DIR__, 2 ) . '/${ADMIN_VIEWS_STYLE_RTL}';
|
|
1363
|
+
|
|
1364
|
+
if ( ! file_exists( $script_path ) || ! file_exists( $asset_path ) ) {
|
|
1365
|
+
return;
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
$asset = require $asset_path;
|
|
1369
|
+
if ( ! is_array( $asset ) ) {
|
|
1370
|
+
$asset = array();
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
$dependencies = isset( $asset['dependencies'] ) && is_array( $asset['dependencies'] )
|
|
1374
|
+
? $asset['dependencies']
|
|
1375
|
+
: array();
|
|
1376
|
+
|
|
1377
|
+
wp_enqueue_script(
|
|
1378
|
+
'${workspaceBaseName}-${adminViewSlug}-admin-view',
|
|
1379
|
+
plugins_url( '${ADMIN_VIEWS_SCRIPT}', $plugin_file ),
|
|
1380
|
+
$dependencies,
|
|
1381
|
+
isset( $asset['version'] ) ? $asset['version'] : filemtime( $script_path ),
|
|
1382
|
+
true
|
|
1383
|
+
);
|
|
1384
|
+
|
|
1385
|
+
if ( file_exists( $style_path ) ) {
|
|
1386
|
+
wp_enqueue_style(
|
|
1387
|
+
'${workspaceBaseName}-${adminViewSlug}-admin-view',
|
|
1388
|
+
plugins_url( '${ADMIN_VIEWS_STYLE}', $plugin_file ),
|
|
1389
|
+
array( 'wp-components' ),
|
|
1390
|
+
isset( $asset['version'] ) ? $asset['version'] : filemtime( $style_path )
|
|
1391
|
+
);
|
|
1392
|
+
if ( file_exists( $style_rtl_path ) ) {
|
|
1393
|
+
wp_style_add_data( '${workspaceBaseName}-${adminViewSlug}-admin-view', 'rtl', 'replace' );
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
add_action( 'admin_menu', '${registerFunctionName}' );
|
|
1400
|
+
add_action( 'admin_enqueue_scripts', '${enqueueFunctionName}' );
|
|
1401
|
+
`;
|
|
1402
|
+
}
|
|
1403
|
+
async function ensureAdminViewPackageDependencies(workspace) {
|
|
1404
|
+
const packageJsonPath = path4.join(workspace.projectDir, "package.json");
|
|
1405
|
+
const wpTypiaDataViewsVersion = resolvePackageVersionRange("@wp-typia/dataviews", DEFAULT_WP_TYPIA_DATAVIEWS_VERSION, "wp-typia-dataviews");
|
|
1406
|
+
const wordpressDataViewsVersion = resolvePackageVersionRange("@wordpress/dataviews", DEFAULT_WORDPRESS_DATAVIEWS_VERSION);
|
|
1407
|
+
await patchFile(packageJsonPath, (source) => {
|
|
1408
|
+
const packageJson = JSON.parse(source);
|
|
1409
|
+
const nextDependencies = {
|
|
1410
|
+
...packageJson.dependencies ?? {},
|
|
1411
|
+
"@wordpress/dataviews": packageJson.dependencies?.["@wordpress/dataviews"] ?? wordpressDataViewsVersion
|
|
1412
|
+
};
|
|
1413
|
+
const nextDevDependencies = {
|
|
1414
|
+
...packageJson.devDependencies ?? {},
|
|
1415
|
+
"@wp-typia/dataviews": packageJson.devDependencies?.["@wp-typia/dataviews"] ?? wpTypiaDataViewsVersion
|
|
1416
|
+
};
|
|
1417
|
+
if (JSON.stringify(nextDependencies) === JSON.stringify(packageJson.dependencies ?? {}) && JSON.stringify(nextDevDependencies) === JSON.stringify(packageJson.devDependencies ?? {})) {
|
|
1418
|
+
return source;
|
|
804
1419
|
}
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
1420
|
+
packageJson.dependencies = nextDependencies;
|
|
1421
|
+
packageJson.devDependencies = nextDevDependencies;
|
|
1422
|
+
return `${JSON.stringify(packageJson, null, detectJsonIndent(source))}
|
|
1423
|
+
`;
|
|
1424
|
+
});
|
|
1425
|
+
}
|
|
1426
|
+
async function ensureAdminViewBootstrapAnchors(workspace) {
|
|
1427
|
+
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
1428
|
+
await patchFile(bootstrapPath, (source) => {
|
|
1429
|
+
let nextSource = source;
|
|
1430
|
+
const loadFunctionName = `${workspace.workspace.phpPrefix}_load_admin_views`;
|
|
1431
|
+
const loadHook = `add_action( 'plugins_loaded', '${loadFunctionName}' );`;
|
|
1432
|
+
const loadFunction = `
|
|
1433
|
+
|
|
1434
|
+
function ${loadFunctionName}() {
|
|
1435
|
+
foreach ( glob( __DIR__ . '${ADMIN_VIEWS_PHP_GLOB}' ) ?: array() as $admin_view_module ) {
|
|
1436
|
+
require_once $admin_view_module;
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
`;
|
|
1440
|
+
const insertionAnchors = [
|
|
1441
|
+
/add_action\(\s*["']init["']\s*,\s*["'][^"']+_load_textdomain["']\s*\);\s*\n/u,
|
|
1442
|
+
/\?>\s*$/u
|
|
1443
|
+
];
|
|
1444
|
+
const insertPhpSnippet = (snippet) => {
|
|
1445
|
+
for (const anchor of insertionAnchors) {
|
|
1446
|
+
const candidate = nextSource.replace(anchor, (match) => `${snippet}
|
|
1447
|
+
${match}`);
|
|
1448
|
+
if (candidate !== nextSource) {
|
|
1449
|
+
nextSource = candidate;
|
|
1450
|
+
return;
|
|
1451
|
+
}
|
|
810
1452
|
}
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
1453
|
+
nextSource = `${nextSource.trimEnd()}
|
|
1454
|
+
${snippet}
|
|
1455
|
+
`;
|
|
1456
|
+
};
|
|
1457
|
+
const appendPhpSnippet = (snippet) => {
|
|
1458
|
+
const closingTagPattern = /\?>\s*$/u;
|
|
1459
|
+
if (closingTagPattern.test(nextSource)) {
|
|
1460
|
+
nextSource = nextSource.replace(closingTagPattern, `${snippet}
|
|
1461
|
+
?>`);
|
|
1462
|
+
return;
|
|
1463
|
+
}
|
|
1464
|
+
nextSource = `${nextSource.trimEnd()}
|
|
1465
|
+
${snippet}
|
|
1466
|
+
`;
|
|
1467
|
+
};
|
|
1468
|
+
if (!hasPhpFunctionDefinition(nextSource, loadFunctionName)) {
|
|
1469
|
+
insertPhpSnippet(loadFunction);
|
|
1470
|
+
} else {
|
|
1471
|
+
const functionRange = findPhpFunctionRange(nextSource, loadFunctionName);
|
|
1472
|
+
const functionSource = functionRange ? nextSource.slice(functionRange.start, functionRange.end) : "";
|
|
1473
|
+
if (!functionSource.includes(ADMIN_VIEWS_PHP_GLOB)) {
|
|
1474
|
+
const replacedSource = replacePhpFunctionDefinition(nextSource, loadFunctionName, loadFunction);
|
|
1475
|
+
if (!replacedSource) {
|
|
1476
|
+
throw new Error(`Unable to repair ${path4.basename(bootstrapPath)} for ${loadFunctionName}.`);
|
|
1477
|
+
}
|
|
1478
|
+
nextSource = replacedSource;
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
if (!nextSource.includes(loadHook)) {
|
|
1482
|
+
appendPhpSnippet(loadHook);
|
|
1483
|
+
}
|
|
1484
|
+
return nextSource;
|
|
1485
|
+
});
|
|
1486
|
+
}
|
|
1487
|
+
async function ensureAdminViewBuildScriptAnchors(workspace) {
|
|
1488
|
+
const buildScriptPath = path4.join(workspace.projectDir, "scripts", "build-workspace.mjs");
|
|
1489
|
+
await patchFile(buildScriptPath, (source) => {
|
|
1490
|
+
if (/['"]src\/admin-views\/index\.(?:ts|js)['"]/u.test(source)) {
|
|
1491
|
+
return source;
|
|
1492
|
+
}
|
|
1493
|
+
const currentSharedEntriesPattern = /(\r?\n\s*['"]src\/editor-plugins\/index\.js['"])\s*,?/u;
|
|
1494
|
+
let nextSource = source.replace(currentSharedEntriesPattern, `$1,
|
|
1495
|
+
'src/admin-views/index.ts',
|
|
1496
|
+
'src/admin-views/index.js',`);
|
|
1497
|
+
if (nextSource !== source) {
|
|
1498
|
+
return nextSource;
|
|
1499
|
+
}
|
|
1500
|
+
const legacySharedEntriesPattern = /\[\s*['"]src\/bindings\/index\.ts['"]\s*,\s*['"]src\/bindings\/index\.js['"]\s*(?:,\s*)?\]/u;
|
|
1501
|
+
nextSource = source.replace(legacySharedEntriesPattern, `[
|
|
1502
|
+
'src/bindings/index.ts',
|
|
1503
|
+
'src/bindings/index.js',
|
|
1504
|
+
'src/editor-plugins/index.ts',
|
|
1505
|
+
'src/editor-plugins/index.js',
|
|
1506
|
+
'src/admin-views/index.ts',
|
|
1507
|
+
'src/admin-views/index.js',
|
|
1508
|
+
]`);
|
|
1509
|
+
if (nextSource !== source) {
|
|
1510
|
+
return nextSource;
|
|
1511
|
+
}
|
|
1512
|
+
throw new Error(`Unable to update ${path4.relative(workspace.projectDir, buildScriptPath)} for admin view shared entries.`);
|
|
1513
|
+
});
|
|
1514
|
+
}
|
|
1515
|
+
async function ensureAdminViewWebpackAnchors(workspace) {
|
|
1516
|
+
const webpackConfigPath = path4.join(workspace.projectDir, "webpack.config.js");
|
|
1517
|
+
await patchFile(webpackConfigPath, (source) => {
|
|
1518
|
+
if (/['"]admin-views\/index['"]/u.test(source)) {
|
|
1519
|
+
return source;
|
|
1520
|
+
}
|
|
1521
|
+
const editorPluginEntryPattern = /(\n\s*\[\s*['"]editor-plugins\/index['"][\s\S]*?['"]src\/editor-plugins\/index\.js['"][\s\S]*?\]\s*\])\s*,?/u;
|
|
1522
|
+
let nextSource = source.replace(editorPluginEntryPattern, `$1,
|
|
1523
|
+
[
|
|
1524
|
+
'admin-views/index',
|
|
1525
|
+
[ 'src/admin-views/index.ts', 'src/admin-views/index.js' ],
|
|
1526
|
+
],`);
|
|
1527
|
+
if (nextSource !== source) {
|
|
1528
|
+
return nextSource;
|
|
815
1529
|
}
|
|
1530
|
+
const legacySharedEntriesBlockPattern = /for\s*\(\s*const\s+relativePath\s+of\s+\[\s*['"]src\/bindings\/index\.ts['"]\s*,\s*['"]src\/bindings\/index\.js['"]\s*(?:,\s*)?\]\s*\)\s*\{[\s\S]*?entries\.push\(\s*\[\s*['"]bindings\/index['"]\s*,\s*entryPath\s*\]\s*\);\s*break;\s*\}/u;
|
|
1531
|
+
const nextSharedEntriesBlock = ` for ( const [ entryName, candidates ] of [
|
|
1532
|
+
[
|
|
1533
|
+
'bindings/index',
|
|
1534
|
+
[ 'src/bindings/index.ts', 'src/bindings/index.js' ],
|
|
1535
|
+
],
|
|
1536
|
+
[
|
|
1537
|
+
'editor-plugins/index',
|
|
1538
|
+
[ 'src/editor-plugins/index.ts', 'src/editor-plugins/index.js' ],
|
|
1539
|
+
],
|
|
1540
|
+
[
|
|
1541
|
+
'admin-views/index',
|
|
1542
|
+
[ 'src/admin-views/index.ts', 'src/admin-views/index.js' ],
|
|
1543
|
+
],
|
|
1544
|
+
] ) {
|
|
1545
|
+
for ( const relativePath of candidates ) {
|
|
1546
|
+
const entryPath = path.resolve( process.cwd(), relativePath );
|
|
1547
|
+
if ( ! fs.existsSync( entryPath ) ) {
|
|
1548
|
+
continue;
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
entries.push( [ entryName, entryPath ] );
|
|
1552
|
+
break;
|
|
1553
|
+
}
|
|
1554
|
+
}`;
|
|
1555
|
+
nextSource = source.replace(legacySharedEntriesBlockPattern, nextSharedEntriesBlock);
|
|
1556
|
+
if (nextSource === source) {
|
|
1557
|
+
throw new Error(`Unable to update ${path4.relative(workspace.projectDir, webpackConfigPath)} for admin view shared entries.`);
|
|
1558
|
+
}
|
|
1559
|
+
return nextSource;
|
|
1560
|
+
});
|
|
1561
|
+
}
|
|
1562
|
+
function resolveAdminViewRegistryPath(projectDir) {
|
|
1563
|
+
const adminViewsDir = path4.join(projectDir, "src", "admin-views");
|
|
1564
|
+
return [
|
|
1565
|
+
path4.join(adminViewsDir, "index.ts"),
|
|
1566
|
+
path4.join(adminViewsDir, "index.js")
|
|
1567
|
+
].find((candidatePath) => fs3.existsSync(candidatePath)) ?? path4.join(adminViewsDir, "index.ts");
|
|
1568
|
+
}
|
|
1569
|
+
function readAdminViewRegistrySlugs(registryPath) {
|
|
1570
|
+
if (!fs3.existsSync(registryPath)) {
|
|
1571
|
+
return [];
|
|
816
1572
|
}
|
|
817
|
-
|
|
1573
|
+
const source = fs3.readFileSync(registryPath, "utf8");
|
|
1574
|
+
return Array.from(source.matchAll(/^\s*import\s+['"]\.\/([^/'"]+)(?:\/index(?:\.[cm]?[jt]sx?)?)?['"];?\s*$/gmu)).map((match) => match[1]);
|
|
818
1575
|
}
|
|
819
|
-
function
|
|
820
|
-
const
|
|
821
|
-
|
|
822
|
-
|
|
1576
|
+
async function writeAdminViewRegistry(projectDir, adminViewSlug) {
|
|
1577
|
+
const adminViewsDir = path4.join(projectDir, "src", "admin-views");
|
|
1578
|
+
const registryPath = resolveAdminViewRegistryPath(projectDir);
|
|
1579
|
+
await fsp3.mkdir(adminViewsDir, { recursive: true });
|
|
1580
|
+
const existingAdminViewSlugs = readWorkspaceInventory(projectDir).adminViews.map((entry) => entry.slug);
|
|
1581
|
+
const existingRegistrySlugs = readAdminViewRegistrySlugs(registryPath);
|
|
1582
|
+
const nextAdminViewSlugs = Array.from(new Set([...existingAdminViewSlugs, ...existingRegistrySlugs, adminViewSlug])).sort();
|
|
1583
|
+
await fsp3.writeFile(registryPath, buildAdminViewRegistrySource(nextAdminViewSlugs), "utf8");
|
|
1584
|
+
}
|
|
1585
|
+
async function runAddAdminViewCommand({
|
|
1586
|
+
adminViewName,
|
|
1587
|
+
cwd = process.cwd(),
|
|
1588
|
+
source
|
|
1589
|
+
}) {
|
|
1590
|
+
const workspace = resolveWorkspaceProject(cwd);
|
|
1591
|
+
const adminViewSlug = assertValidGeneratedSlug("Admin view name", normalizeBlockSlug(adminViewName), "wp-typia add admin-view <name> [--source rest-resource:<slug>]");
|
|
1592
|
+
const parsedSource = parseAdminViewSource(source);
|
|
1593
|
+
const inventory = readWorkspaceInventory(workspace.projectDir);
|
|
1594
|
+
const restResource = resolveRestResourceSource(inventory.restResources, parsedSource);
|
|
1595
|
+
assertAdminViewDoesNotExist(workspace.projectDir, adminViewSlug, inventory);
|
|
1596
|
+
const blockConfigPath = path4.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
1597
|
+
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
1598
|
+
const buildScriptPath = path4.join(workspace.projectDir, "scripts", "build-workspace.mjs");
|
|
1599
|
+
const packageJsonPath = path4.join(workspace.projectDir, "package.json");
|
|
1600
|
+
const webpackConfigPath = path4.join(workspace.projectDir, "webpack.config.js");
|
|
1601
|
+
const adminViewsIndexPath = resolveAdminViewRegistryPath(workspace.projectDir);
|
|
1602
|
+
const adminViewDir = path4.join(workspace.projectDir, "src", "admin-views", adminViewSlug);
|
|
1603
|
+
const adminViewPhpPath = path4.join(workspace.projectDir, "inc", "admin-views", `${adminViewSlug}.php`);
|
|
1604
|
+
const mutationSnapshot = {
|
|
1605
|
+
fileSources: await snapshotWorkspaceFiles([
|
|
1606
|
+
adminViewsIndexPath,
|
|
1607
|
+
blockConfigPath,
|
|
1608
|
+
bootstrapPath,
|
|
1609
|
+
buildScriptPath,
|
|
1610
|
+
packageJsonPath,
|
|
1611
|
+
webpackConfigPath
|
|
1612
|
+
]),
|
|
1613
|
+
snapshotDirs: [],
|
|
1614
|
+
targetPaths: [adminViewDir, adminViewPhpPath]
|
|
1615
|
+
};
|
|
1616
|
+
try {
|
|
1617
|
+
await fsp3.mkdir(adminViewDir, { recursive: true });
|
|
1618
|
+
await fsp3.mkdir(path4.dirname(adminViewPhpPath), { recursive: true });
|
|
1619
|
+
await ensureAdminViewPackageDependencies(workspace);
|
|
1620
|
+
await ensureAdminViewBootstrapAnchors(workspace);
|
|
1621
|
+
await ensureAdminViewBuildScriptAnchors(workspace);
|
|
1622
|
+
await ensureAdminViewWebpackAnchors(workspace);
|
|
1623
|
+
await fsp3.writeFile(path4.join(adminViewDir, "types.ts"), buildAdminViewTypesSource(adminViewSlug, restResource), "utf8");
|
|
1624
|
+
await fsp3.writeFile(path4.join(adminViewDir, "config.ts"), buildAdminViewConfigSource(adminViewSlug, workspace.workspace.textDomain, restResource), "utf8");
|
|
1625
|
+
await fsp3.writeFile(path4.join(adminViewDir, "data.ts"), restResource ? buildRestAdminViewDataSource(adminViewSlug, restResource) : buildDefaultAdminViewDataSource(adminViewSlug), "utf8");
|
|
1626
|
+
await fsp3.writeFile(path4.join(adminViewDir, "Screen.tsx"), buildAdminViewScreenSource(adminViewSlug, workspace.workspace.textDomain), "utf8");
|
|
1627
|
+
await fsp3.writeFile(path4.join(adminViewDir, "index.tsx"), buildAdminViewEntrySource(adminViewSlug), "utf8");
|
|
1628
|
+
await fsp3.writeFile(path4.join(adminViewDir, "style.scss"), buildAdminViewStyleSource(), "utf8");
|
|
1629
|
+
await fsp3.writeFile(adminViewPhpPath, buildAdminViewPhpSource(adminViewSlug, workspace), "utf8");
|
|
1630
|
+
await writeAdminViewRegistry(workspace.projectDir, adminViewSlug);
|
|
1631
|
+
await appendWorkspaceInventoryEntries(workspace.projectDir, {
|
|
1632
|
+
adminViewEntries: [buildAdminViewConfigEntry(adminViewSlug, parsedSource)]
|
|
1633
|
+
});
|
|
1634
|
+
return {
|
|
1635
|
+
adminViewSlug,
|
|
1636
|
+
projectDir: workspace.projectDir,
|
|
1637
|
+
source: parsedSource ? `${parsedSource.kind}:${parsedSource.slug}` : undefined
|
|
1638
|
+
};
|
|
1639
|
+
} catch (error) {
|
|
1640
|
+
await rollbackWorkspaceMutation(mutationSnapshot);
|
|
1641
|
+
throw error;
|
|
823
1642
|
}
|
|
824
|
-
return [
|
|
825
|
-
source.slice(0, functionRange.start),
|
|
826
|
-
replacement,
|
|
827
|
-
source.slice(functionRange.end)
|
|
828
|
-
].join("");
|
|
829
1643
|
}
|
|
1644
|
+
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-assets.ts
|
|
1645
|
+
var import_typescript = __toESM(require_typescript(), 1);
|
|
1646
|
+
import fs4 from "fs";
|
|
1647
|
+
import { promises as fsp4 } from "fs";
|
|
1648
|
+
import path5 from "path";
|
|
1649
|
+
import {
|
|
1650
|
+
syncBlockMetadata as syncBlockMetadata2
|
|
1651
|
+
} from "@wp-typia/block-runtime/metadata-core";
|
|
1652
|
+
var PATTERN_BOOTSTRAP_CATEGORY = "register_block_pattern_category";
|
|
1653
|
+
var BINDING_SOURCE_SERVER_GLOB = "/src/bindings/*/server.php";
|
|
1654
|
+
var BINDING_SOURCE_EDITOR_SCRIPT = "build/bindings/index.js";
|
|
1655
|
+
var BINDING_SOURCE_EDITOR_ASSET = "build/bindings/index.asset.php";
|
|
1656
|
+
var EDITOR_PLUGIN_EDITOR_SCRIPT = "build/editor-plugins/index.js";
|
|
1657
|
+
var EDITOR_PLUGIN_EDITOR_ASSET = "build/editor-plugins/index.asset.php";
|
|
1658
|
+
var EDITOR_PLUGIN_EDITOR_STYLE = "build/editor-plugins/style-index.css";
|
|
1659
|
+
var EDITOR_PLUGIN_EDITOR_STYLE_RTL = "build/editor-plugins/style-index-rtl.css";
|
|
1660
|
+
var BINDING_ATTRIBUTE_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9_-]*$/u;
|
|
830
1661
|
function buildPatternConfigEntry(patternSlug) {
|
|
831
1662
|
return [
|
|
832
1663
|
"\t{",
|
|
@@ -836,9 +1667,11 @@ function buildPatternConfigEntry(patternSlug) {
|
|
|
836
1667
|
].join(`
|
|
837
1668
|
`);
|
|
838
1669
|
}
|
|
839
|
-
function buildBindingSourceConfigEntry(bindingSourceSlug) {
|
|
1670
|
+
function buildBindingSourceConfigEntry(bindingSourceSlug, target) {
|
|
840
1671
|
return [
|
|
841
1672
|
"\t{",
|
|
1673
|
+
...target ? [` attribute: ${quoteTsString(target.attributeName)},`] : [],
|
|
1674
|
+
...target ? [` block: ${quoteTsString(target.blockSlug)},`] : [],
|
|
842
1675
|
` editorFile: ${quoteTsString(`src/bindings/${bindingSourceSlug}/editor.ts`)},`,
|
|
843
1676
|
` serverFile: ${quoteTsString(`src/bindings/${bindingSourceSlug}/server.php`)},`,
|
|
844
1677
|
` slug: ${quoteTsString(bindingSourceSlug)},`,
|
|
@@ -846,6 +1679,34 @@ function buildBindingSourceConfigEntry(bindingSourceSlug) {
|
|
|
846
1679
|
].join(`
|
|
847
1680
|
`);
|
|
848
1681
|
}
|
|
1682
|
+
function assertValidBindingAttributeName(attributeName) {
|
|
1683
|
+
const trimmed = attributeName.trim();
|
|
1684
|
+
if (!trimmed) {
|
|
1685
|
+
throw new Error("`wp-typia add binding-source` requires --attribute <attribute> to include a value when --block is provided.");
|
|
1686
|
+
}
|
|
1687
|
+
if (!BINDING_ATTRIBUTE_NAME_PATTERN.test(trimmed)) {
|
|
1688
|
+
throw new Error(`Binding attribute "${attributeName}" must start with a letter and use only letters, numbers, underscores, or hyphens.`);
|
|
1689
|
+
}
|
|
1690
|
+
return trimmed;
|
|
1691
|
+
}
|
|
1692
|
+
function resolveBindingTargetBlockSlug(blockName, namespace) {
|
|
1693
|
+
const trimmed = blockName.trim();
|
|
1694
|
+
if (!trimmed) {
|
|
1695
|
+
throw new Error("`wp-typia add binding-source` requires --block <block-slug|namespace/block-slug> to include a value when --attribute is provided.");
|
|
1696
|
+
}
|
|
1697
|
+
const blockNameSegments = trimmed.split("/");
|
|
1698
|
+
if (blockNameSegments.length > 2) {
|
|
1699
|
+
throw new Error(`Binding target block "${trimmed}" must use <block-slug> or <namespace/block-slug> format.`);
|
|
1700
|
+
}
|
|
1701
|
+
if (blockNameSegments.some((segment) => segment.trim() === "")) {
|
|
1702
|
+
throw new Error(`Binding target block "${trimmed}" must use <block-slug> or <namespace/block-slug> format without empty path segments.`);
|
|
1703
|
+
}
|
|
1704
|
+
const [maybeNamespace, maybeSlug] = blockNameSegments.length === 2 ? blockNameSegments : [undefined, blockNameSegments[0]];
|
|
1705
|
+
if (maybeNamespace && maybeNamespace !== namespace) {
|
|
1706
|
+
throw new Error(`Binding target block "${trimmed}" uses namespace "${maybeNamespace}". Expected "${namespace}".`);
|
|
1707
|
+
}
|
|
1708
|
+
return normalizeBlockSlug(maybeSlug ?? "");
|
|
1709
|
+
}
|
|
849
1710
|
function buildEditorPluginConfigEntry(editorPluginSlug, slot) {
|
|
850
1711
|
return [
|
|
851
1712
|
"\t{",
|
|
@@ -856,9 +1717,6 @@ function buildEditorPluginConfigEntry(editorPluginSlug, slot) {
|
|
|
856
1717
|
].join(`
|
|
857
1718
|
`);
|
|
858
1719
|
}
|
|
859
|
-
function toPascalCaseFromSlug(slug) {
|
|
860
|
-
return normalizeBlockSlug(slug).split("-").filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join("");
|
|
861
|
-
}
|
|
862
1720
|
function buildPatternSource(patternSlug, namespace, textDomain) {
|
|
863
1721
|
const patternTitle = toTitleCase(patternSlug);
|
|
864
1722
|
return `<?php
|
|
@@ -877,12 +1735,32 @@ register_block_pattern(
|
|
|
877
1735
|
);
|
|
878
1736
|
`;
|
|
879
1737
|
}
|
|
880
|
-
function buildBindingSourceServerSource(bindingSourceSlug, phpPrefix, namespace, textDomain) {
|
|
1738
|
+
function buildBindingSourceServerSource(bindingSourceSlug, phpPrefix, namespace, textDomain, target) {
|
|
881
1739
|
const bindingSourceTitle = toTitleCase(bindingSourceSlug);
|
|
882
1740
|
const bindingSourcePhpId = bindingSourceSlug.replace(/-/g, "_");
|
|
883
1741
|
const bindingSourceValueFunctionName = `${phpPrefix}_${bindingSourcePhpId}_binding_source_values`;
|
|
884
1742
|
const bindingSourceResolveFunctionName = `${phpPrefix}_${bindingSourcePhpId}_resolve_binding_source_value`;
|
|
1743
|
+
const bindingSourceSupportedAttributesFunctionName = `${phpPrefix}_${bindingSourcePhpId}_supported_binding_attributes`;
|
|
885
1744
|
const starterValue = `${bindingSourceTitle} starter value`;
|
|
1745
|
+
const supportedAttributesSource = target ? `
|
|
1746
|
+
if ( ! function_exists( '${bindingSourceSupportedAttributesFunctionName}' ) ) {
|
|
1747
|
+
function ${bindingSourceSupportedAttributesFunctionName}( array $supported_attributes ) : array {
|
|
1748
|
+
if ( ! in_array( ${quotePhpString(target.attributeName)}, $supported_attributes, true ) ) {
|
|
1749
|
+
$supported_attributes[] = ${quotePhpString(target.attributeName)};
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
return $supported_attributes;
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
` : "";
|
|
1756
|
+
const supportedAttributesHook = target ? `
|
|
1757
|
+
if ( function_exists( '${bindingSourceSupportedAttributesFunctionName}' ) ) {
|
|
1758
|
+
add_filter(
|
|
1759
|
+
${quotePhpString(`block_bindings_supported_attributes_${namespace}/${target.blockSlug}`)},
|
|
1760
|
+
${quotePhpString(bindingSourceSupportedAttributesFunctionName)}
|
|
1761
|
+
);
|
|
1762
|
+
}
|
|
1763
|
+
` : "";
|
|
886
1764
|
return `<?php
|
|
887
1765
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
888
1766
|
return;
|
|
@@ -911,6 +1789,7 @@ if ( ! function_exists( '${bindingSourceResolveFunctionName}' ) ) {
|
|
|
911
1789
|
return is_string( $value ) ? $value : '';
|
|
912
1790
|
}
|
|
913
1791
|
}
|
|
1792
|
+
${supportedAttributesSource}
|
|
914
1793
|
|
|
915
1794
|
register_block_bindings_source(
|
|
916
1795
|
${quotePhpString(`${namespace}/${bindingSourceSlug}`)},
|
|
@@ -919,11 +1798,20 @@ register_block_bindings_source(
|
|
|
919
1798
|
'get_value_callback' => ${quotePhpString(bindingSourceResolveFunctionName)},
|
|
920
1799
|
)
|
|
921
1800
|
);
|
|
922
|
-
`;
|
|
1801
|
+
${supportedAttributesHook}`;
|
|
923
1802
|
}
|
|
924
|
-
function buildBindingSourceEditorSource(bindingSourceSlug, namespace, textDomain) {
|
|
1803
|
+
function buildBindingSourceEditorSource(bindingSourceSlug, namespace, textDomain, target) {
|
|
925
1804
|
const bindingSourceTitle = toTitleCase(bindingSourceSlug);
|
|
926
1805
|
const starterValue = `${bindingSourceTitle} starter value`;
|
|
1806
|
+
const bindingSourceName = `${namespace}/${bindingSourceSlug}`;
|
|
1807
|
+
const targetSource = target ? `
|
|
1808
|
+
export const BINDING_SOURCE_TARGET = {
|
|
1809
|
+
attribute: ${quoteTsString(target.attributeName)},
|
|
1810
|
+
block: ${quoteTsString(`${namespace}/${target.blockSlug}`)},
|
|
1811
|
+
field: ${quoteTsString(bindingSourceSlug)},
|
|
1812
|
+
source: ${quoteTsString(bindingSourceName)},
|
|
1813
|
+
} as const;
|
|
1814
|
+
` : "";
|
|
927
1815
|
return `import { registerBlockBindingsSource } from '@wordpress/blocks';
|
|
928
1816
|
import { __ } from '@wordpress/i18n';
|
|
929
1817
|
|
|
@@ -936,13 +1824,14 @@ interface BindingSourceRegistration {
|
|
|
936
1824
|
const BINDING_SOURCE_VALUES: Record<string, string> = {
|
|
937
1825
|
${quoteTsString(bindingSourceSlug)}: ${quoteTsString(starterValue)},
|
|
938
1826
|
};
|
|
1827
|
+
${targetSource}
|
|
939
1828
|
|
|
940
1829
|
function resolveBindingSourceValue( field: string ): string {
|
|
941
1830
|
return BINDING_SOURCE_VALUES[ field ] ?? '';
|
|
942
1831
|
}
|
|
943
1832
|
|
|
944
1833
|
registerBlockBindingsSource( {
|
|
945
|
-
name: ${quoteTsString(
|
|
1834
|
+
name: ${quoteTsString(bindingSourceName)},
|
|
946
1835
|
label: __( ${quoteTsString(bindingSourceTitle)}, ${quoteTsString(textDomain)} ),
|
|
947
1836
|
getFieldsList() {
|
|
948
1837
|
return [
|
|
@@ -971,8 +1860,98 @@ registerBlockBindingsSource( {
|
|
|
971
1860
|
} );
|
|
972
1861
|
`;
|
|
973
1862
|
}
|
|
1863
|
+
function resolveBindingTarget(options, namespace) {
|
|
1864
|
+
const hasBlock = options.blockName !== undefined && options.blockName.trim().length > 0;
|
|
1865
|
+
const hasAttribute = options.attributeName !== undefined && options.attributeName.trim().length > 0;
|
|
1866
|
+
if (!hasBlock && !hasAttribute) {
|
|
1867
|
+
return;
|
|
1868
|
+
}
|
|
1869
|
+
if (!hasBlock || !hasAttribute) {
|
|
1870
|
+
throw new Error("`wp-typia add binding-source` requires --block and --attribute to be provided together.");
|
|
1871
|
+
}
|
|
1872
|
+
return {
|
|
1873
|
+
attributeName: assertValidBindingAttributeName(options.attributeName ?? ""),
|
|
1874
|
+
blockSlug: resolveBindingTargetBlockSlug(options.blockName ?? "", namespace)
|
|
1875
|
+
};
|
|
1876
|
+
}
|
|
1877
|
+
function formatBindingAttributeTypeMember(attributeName) {
|
|
1878
|
+
const propertyName = /^[A-Za-z_$][\w$]*$/u.test(attributeName) ? attributeName : JSON.stringify(attributeName);
|
|
1879
|
+
return [
|
|
1880
|
+
"\t/**",
|
|
1881
|
+
"\t * Starter string attribute declared for WordPress Block Bindings.",
|
|
1882
|
+
"\t */",
|
|
1883
|
+
` ${propertyName}?: string;`
|
|
1884
|
+
].join(`
|
|
1885
|
+
`);
|
|
1886
|
+
}
|
|
1887
|
+
function getInterfaceDeclaration(source, interfaceName) {
|
|
1888
|
+
const sourceFile = import_typescript.default.createSourceFile("types.ts", source, import_typescript.default.ScriptTarget.Latest, true, import_typescript.default.ScriptKind.TS);
|
|
1889
|
+
let declaration;
|
|
1890
|
+
const visit = (node) => {
|
|
1891
|
+
if (import_typescript.default.isInterfaceDeclaration(node) && node.name.text === interfaceName) {
|
|
1892
|
+
declaration = node;
|
|
1893
|
+
return true;
|
|
1894
|
+
}
|
|
1895
|
+
return import_typescript.default.forEachChild(node, (child) => visit(child) ? true : undefined) ?? false;
|
|
1896
|
+
};
|
|
1897
|
+
visit(sourceFile);
|
|
1898
|
+
return declaration ? { declaration, sourceFile } : undefined;
|
|
1899
|
+
}
|
|
1900
|
+
function getPropertyNameText(name) {
|
|
1901
|
+
if (import_typescript.default.isIdentifier(name) || import_typescript.default.isStringLiteral(name) || import_typescript.default.isNumericLiteral(name)) {
|
|
1902
|
+
return name.text;
|
|
1903
|
+
}
|
|
1904
|
+
return;
|
|
1905
|
+
}
|
|
1906
|
+
function interfaceHasAttributeMember(declaration, attributeName) {
|
|
1907
|
+
return declaration.members.some((member) => import_typescript.default.isPropertySignature(member) && member.name !== undefined && getPropertyNameText(member.name) === attributeName);
|
|
1908
|
+
}
|
|
1909
|
+
function insertBindingAttributeTypeMember(source, declaration, attributeName) {
|
|
1910
|
+
let closeBracePosition = declaration.end - 1;
|
|
1911
|
+
while (closeBracePosition > declaration.pos && source[closeBracePosition] !== "}") {
|
|
1912
|
+
closeBracePosition -= 1;
|
|
1913
|
+
}
|
|
1914
|
+
if (source[closeBracePosition] !== "}") {
|
|
1915
|
+
throw new Error("Unable to locate the target interface closing brace.");
|
|
1916
|
+
}
|
|
1917
|
+
const lineEnding = source.includes(`\r
|
|
1918
|
+
`) ? `\r
|
|
1919
|
+
` : `
|
|
1920
|
+
`;
|
|
1921
|
+
const beforeCloseBrace = source.slice(0, closeBracePosition);
|
|
1922
|
+
const afterCloseBrace = source.slice(closeBracePosition);
|
|
1923
|
+
const memberSource = formatBindingAttributeTypeMember(attributeName).split(`
|
|
1924
|
+
`).join(lineEnding);
|
|
1925
|
+
const prefix = beforeCloseBrace.endsWith(lineEnding) ? "" : lineEnding;
|
|
1926
|
+
return `${beforeCloseBrace}${prefix}${memberSource}${lineEnding}${afterCloseBrace}`;
|
|
1927
|
+
}
|
|
1928
|
+
async function ensureBindingTargetBlockAttributeType(projectDir, block, target) {
|
|
1929
|
+
if (!block.attributeTypeName) {
|
|
1930
|
+
throw new Error(`Workspace block "${block.slug}" must include attributeTypeName in scripts/block-config.ts before it can receive binding-source targets.`);
|
|
1931
|
+
}
|
|
1932
|
+
const typesPath = path5.join(projectDir, block.typesFile);
|
|
1933
|
+
const source = await fsp4.readFile(typesPath, "utf8");
|
|
1934
|
+
const targetInterface = getInterfaceDeclaration(source, block.attributeTypeName);
|
|
1935
|
+
if (!targetInterface) {
|
|
1936
|
+
throw new Error(`Unable to locate interface ${block.attributeTypeName} in ${block.typesFile}.`);
|
|
1937
|
+
}
|
|
1938
|
+
let nextSource = source;
|
|
1939
|
+
if (!interfaceHasAttributeMember(targetInterface.declaration, target.attributeName)) {
|
|
1940
|
+
nextSource = insertBindingAttributeTypeMember(source, targetInterface.declaration, target.attributeName);
|
|
1941
|
+
await fsp4.writeFile(typesPath, nextSource, "utf8");
|
|
1942
|
+
}
|
|
1943
|
+
await syncBlockMetadata2({
|
|
1944
|
+
blockJsonFile: path5.join("src", "blocks", block.slug, "block.json"),
|
|
1945
|
+
jsonSchemaFile: path5.join("src", "blocks", block.slug, "typia.schema.json"),
|
|
1946
|
+
manifestFile: path5.join("src", "blocks", block.slug, "typia.manifest.json"),
|
|
1947
|
+
openApiFile: path5.join("src", "blocks", block.slug, "typia.openapi.json"),
|
|
1948
|
+
projectRoot: projectDir,
|
|
1949
|
+
sourceTypeName: block.attributeTypeName,
|
|
1950
|
+
typesFile: block.typesFile
|
|
1951
|
+
});
|
|
1952
|
+
}
|
|
974
1953
|
function buildEditorPluginTypesSource(editorPluginSlug) {
|
|
975
|
-
const typeName = `${
|
|
1954
|
+
const typeName = `${toPascalCase(editorPluginSlug)}EditorPluginModel`;
|
|
976
1955
|
return `export interface ${typeName} {
|
|
977
1956
|
primaryActionLabel: string;
|
|
978
1957
|
summary: string;
|
|
@@ -980,22 +1959,22 @@ function buildEditorPluginTypesSource(editorPluginSlug) {
|
|
|
980
1959
|
`;
|
|
981
1960
|
}
|
|
982
1961
|
function buildEditorPluginDataSource(editorPluginSlug, slot) {
|
|
983
|
-
const typeName = `${
|
|
1962
|
+
const typeName = `${toPascalCase(editorPluginSlug)}EditorPluginModel`;
|
|
984
1963
|
const pluginTitle = toTitleCase(editorPluginSlug);
|
|
985
|
-
const modelFactoryName = `get${
|
|
986
|
-
const enabledFactoryName = `is${
|
|
1964
|
+
const modelFactoryName = `get${toPascalCase(editorPluginSlug)}EditorPluginModel`;
|
|
1965
|
+
const enabledFactoryName = `is${toPascalCase(editorPluginSlug)}Enabled`;
|
|
987
1966
|
return `import type { ${typeName} } from './types';
|
|
988
1967
|
|
|
989
1968
|
export const EDITOR_PLUGIN_SLOT = ${quoteTsString(slot)} as const;
|
|
990
1969
|
export const REQUIRED_CAPABILITY = 'edit_posts' as const;
|
|
991
1970
|
|
|
992
|
-
const
|
|
1971
|
+
const DEFAULT_EDITOR_PLUGIN_MODEL: ${typeName} = {
|
|
993
1972
|
primaryActionLabel: ${quoteTsString(`Review ${pluginTitle}`)},
|
|
994
1973
|
summary: ${quoteTsString(`Replace this summary with your ${pluginTitle} workflow state.`)},
|
|
995
1974
|
};
|
|
996
1975
|
|
|
997
1976
|
export function ${modelFactoryName}(): ${typeName} {
|
|
998
|
-
return
|
|
1977
|
+
return DEFAULT_EDITOR_PLUGIN_MODEL;
|
|
999
1978
|
}
|
|
1000
1979
|
|
|
1001
1980
|
export function ${enabledFactoryName}(): boolean {
|
|
@@ -1003,11 +1982,52 @@ export function ${enabledFactoryName}(): boolean {
|
|
|
1003
1982
|
}
|
|
1004
1983
|
`;
|
|
1005
1984
|
}
|
|
1006
|
-
function
|
|
1007
|
-
const pascalName =
|
|
1008
|
-
const modelFactoryName = `get${pascalName}
|
|
1985
|
+
function buildEditorPluginSurfaceSource(editorPluginSlug, slot, textDomain) {
|
|
1986
|
+
const pascalName = toPascalCase(editorPluginSlug);
|
|
1987
|
+
const modelFactoryName = `get${pascalName}EditorPluginModel`;
|
|
1009
1988
|
const enabledFactoryName = `is${pascalName}Enabled`;
|
|
1010
|
-
const componentName = `${pascalName}
|
|
1989
|
+
const componentName = `${pascalName}Surface`;
|
|
1990
|
+
if (slot === "document-setting-panel") {
|
|
1991
|
+
return `import { Button } from '@wordpress/components';
|
|
1992
|
+
import { PluginDocumentSettingPanel } from '@wordpress/editor';
|
|
1993
|
+
import { __ } from '@wordpress/i18n';
|
|
1994
|
+
|
|
1995
|
+
import { ${modelFactoryName}, ${enabledFactoryName} } from './data';
|
|
1996
|
+
import './style.scss';
|
|
1997
|
+
|
|
1998
|
+
export interface ${componentName}Props {
|
|
1999
|
+
surfaceName: string;
|
|
2000
|
+
title: string;
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
export function ${componentName}( {
|
|
2004
|
+
surfaceName,
|
|
2005
|
+
title,
|
|
2006
|
+
}: ${componentName}Props ) {
|
|
2007
|
+
if ( ! ${enabledFactoryName}() ) {
|
|
2008
|
+
return null;
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
const editorPluginModel = ${modelFactoryName}();
|
|
2012
|
+
|
|
2013
|
+
return (
|
|
2014
|
+
<PluginDocumentSettingPanel
|
|
2015
|
+
className="wp-typia-editor-plugin-shell"
|
|
2016
|
+
name={ surfaceName }
|
|
2017
|
+
title={ title }
|
|
2018
|
+
>
|
|
2019
|
+
<p>{ editorPluginModel.summary }</p>
|
|
2020
|
+
<Button variant="secondary">
|
|
2021
|
+
{ editorPluginModel.primaryActionLabel }
|
|
2022
|
+
</Button>
|
|
2023
|
+
<p className="wp-typia-editor-plugin-shell__hint">
|
|
2024
|
+
{ __( 'Use data.ts to add post type, capability, or editor context guards before showing this panel.', ${quoteTsString(textDomain)} ) }
|
|
2025
|
+
</p>
|
|
2026
|
+
</PluginDocumentSettingPanel>
|
|
2027
|
+
);
|
|
2028
|
+
}
|
|
2029
|
+
`;
|
|
2030
|
+
}
|
|
1011
2031
|
return `import { Button, PanelBody } from '@wordpress/components';
|
|
1012
2032
|
import { PluginSidebar, PluginSidebarMoreMenuItem } from '@wordpress/editor';
|
|
1013
2033
|
import { __ } from '@wordpress/i18n';
|
|
@@ -1016,34 +2036,34 @@ import { ${modelFactoryName}, ${enabledFactoryName} } from './data';
|
|
|
1016
2036
|
import './style.scss';
|
|
1017
2037
|
|
|
1018
2038
|
export interface ${componentName}Props {
|
|
1019
|
-
|
|
2039
|
+
surfaceName: string;
|
|
1020
2040
|
title: string;
|
|
1021
2041
|
}
|
|
1022
2042
|
|
|
1023
2043
|
export function ${componentName}( {
|
|
1024
|
-
|
|
2044
|
+
surfaceName,
|
|
1025
2045
|
title,
|
|
1026
2046
|
}: ${componentName}Props ) {
|
|
1027
2047
|
if ( ! ${enabledFactoryName}() ) {
|
|
1028
2048
|
return null;
|
|
1029
2049
|
}
|
|
1030
2050
|
|
|
1031
|
-
const
|
|
2051
|
+
const editorPluginModel = ${modelFactoryName}();
|
|
1032
2052
|
|
|
1033
2053
|
return (
|
|
1034
2054
|
<>
|
|
1035
|
-
<PluginSidebarMoreMenuItem target={
|
|
2055
|
+
<PluginSidebarMoreMenuItem target={ surfaceName }>
|
|
1036
2056
|
{ title }
|
|
1037
2057
|
</PluginSidebarMoreMenuItem>
|
|
1038
|
-
<PluginSidebar name={
|
|
2058
|
+
<PluginSidebar name={ surfaceName } title={ title }>
|
|
1039
2059
|
<div className="wp-typia-editor-plugin-shell">
|
|
1040
2060
|
<PanelBody
|
|
1041
2061
|
initialOpen
|
|
1042
2062
|
title={ __( 'Document workflow', ${quoteTsString(textDomain)} ) }
|
|
1043
2063
|
>
|
|
1044
|
-
<p>{
|
|
2064
|
+
<p>{ editorPluginModel.summary }</p>
|
|
1045
2065
|
<Button variant="secondary">
|
|
1046
|
-
{
|
|
2066
|
+
{ editorPluginModel.primaryActionLabel }
|
|
1047
2067
|
</Button>
|
|
1048
2068
|
</PanelBody>
|
|
1049
2069
|
</div>
|
|
@@ -1054,24 +2074,26 @@ export function ${componentName}( {
|
|
|
1054
2074
|
`;
|
|
1055
2075
|
}
|
|
1056
2076
|
function buildEditorPluginEntrySource(editorPluginSlug, namespace, textDomain) {
|
|
1057
|
-
const pascalName =
|
|
1058
|
-
const componentName = `${pascalName}
|
|
2077
|
+
const pascalName = toPascalCase(editorPluginSlug);
|
|
2078
|
+
const componentName = `${pascalName}Surface`;
|
|
1059
2079
|
const pluginName = `${namespace}-${editorPluginSlug}`;
|
|
2080
|
+
const surfaceName = `${pluginName}-surface`;
|
|
1060
2081
|
const pluginTitle = toTitleCase(editorPluginSlug);
|
|
1061
2082
|
return `import { registerPlugin } from '@wordpress/plugins';
|
|
1062
2083
|
import { __ } from '@wordpress/i18n';
|
|
1063
2084
|
|
|
1064
2085
|
import { REQUIRED_CAPABILITY } from './data';
|
|
1065
|
-
import { ${componentName} } from './
|
|
2086
|
+
import { ${componentName} } from './Surface';
|
|
1066
2087
|
|
|
1067
2088
|
const EDITOR_PLUGIN_NAME = ${quoteTsString(pluginName)};
|
|
2089
|
+
const EDITOR_PLUGIN_SURFACE_NAME = ${quoteTsString(surfaceName)};
|
|
1068
2090
|
const EDITOR_PLUGIN_TITLE = __( ${quoteTsString(pluginTitle)}, ${quoteTsString(textDomain)} );
|
|
1069
2091
|
|
|
1070
2092
|
registerPlugin( EDITOR_PLUGIN_NAME, {
|
|
1071
2093
|
icon: 'admin-generic',
|
|
1072
2094
|
render: () => (
|
|
1073
2095
|
<${componentName}
|
|
1074
|
-
|
|
2096
|
+
surfaceName={ EDITOR_PLUGIN_SURFACE_NAME }
|
|
1075
2097
|
title={ EDITOR_PLUGIN_TITLE }
|
|
1076
2098
|
/>
|
|
1077
2099
|
),
|
|
@@ -1088,6 +2110,11 @@ function buildEditorPluginStyleSource() {
|
|
|
1088
2110
|
.wp-typia-editor-plugin-shell p {
|
|
1089
2111
|
margin: 0 0 12px;
|
|
1090
2112
|
}
|
|
2113
|
+
|
|
2114
|
+
.wp-typia-editor-plugin-shell__hint {
|
|
2115
|
+
color: #757575;
|
|
2116
|
+
font-size: 12px;
|
|
2117
|
+
}
|
|
1091
2118
|
`;
|
|
1092
2119
|
}
|
|
1093
2120
|
function buildBindingSourceIndexSource(bindingSourceSlugs) {
|
|
@@ -1156,7 +2183,7 @@ ${patternFunctions}
|
|
|
1156
2183
|
}
|
|
1157
2184
|
}
|
|
1158
2185
|
if (!nextSource.includes(patternCategoryFunctionName) || !nextSource.includes(patternRegistrationFunctionName)) {
|
|
1159
|
-
throw new Error(`Unable to inject pattern bootstrap functions into ${
|
|
2186
|
+
throw new Error(`Unable to inject pattern bootstrap functions into ${path5.basename(bootstrapPath)}.`);
|
|
1160
2187
|
}
|
|
1161
2188
|
if (!nextSource.includes(patternCategoryHook)) {
|
|
1162
2189
|
nextSource = `${nextSource.trimEnd()}
|
|
@@ -1216,7 +2243,6 @@ function ${bindingEditorEnqueueFunctionName}() {
|
|
|
1216
2243
|
/add_action\(\s*["']init["']\s*,\s*["'][^"']+_load_textdomain["']\s*\);\s*\n/u,
|
|
1217
2244
|
/\?>\s*$/u
|
|
1218
2245
|
];
|
|
1219
|
-
const hasPhpFunctionDefinition = (functionName) => new RegExp(`function\\s+${escapeRegex(functionName)}\\s*\\(`, "u").test(nextSource);
|
|
1220
2246
|
const insertPhpSnippet = (snippet) => {
|
|
1221
2247
|
for (const anchor of insertionAnchors) {
|
|
1222
2248
|
const candidate = nextSource.replace(anchor, (match) => `${snippet}
|
|
@@ -1241,10 +2267,10 @@ ${snippet}
|
|
|
1241
2267
|
${snippet}
|
|
1242
2268
|
`;
|
|
1243
2269
|
};
|
|
1244
|
-
if (!hasPhpFunctionDefinition(bindingRegistrationFunctionName)) {
|
|
2270
|
+
if (!hasPhpFunctionDefinition(nextSource, bindingRegistrationFunctionName)) {
|
|
1245
2271
|
insertPhpSnippet(bindingRegistrationFunction);
|
|
1246
2272
|
}
|
|
1247
|
-
if (!hasPhpFunctionDefinition(bindingEditorEnqueueFunctionName)) {
|
|
2273
|
+
if (!hasPhpFunctionDefinition(nextSource, bindingEditorEnqueueFunctionName)) {
|
|
1248
2274
|
insertPhpSnippet(bindingEditorEnqueueFunction);
|
|
1249
2275
|
}
|
|
1250
2276
|
if (!nextSource.includes(bindingRegistrationHook)) {
|
|
@@ -1305,7 +2331,6 @@ function ${enqueueFunctionName}() {
|
|
|
1305
2331
|
/add_action\(\s*["']init["']\s*,\s*["'][^"']+_load_textdomain["']\s*\);\s*\n/u,
|
|
1306
2332
|
/\?>\s*$/u
|
|
1307
2333
|
];
|
|
1308
|
-
const hasPhpFunctionDefinition = (functionName) => new RegExp(`function\\s+${escapeRegex(functionName)}\\s*\\(`, "u").test(nextSource);
|
|
1309
2334
|
const insertPhpSnippet = (snippet) => {
|
|
1310
2335
|
for (const anchor of insertionAnchors) {
|
|
1311
2336
|
const candidate = nextSource.replace(anchor, (match) => `${snippet}
|
|
@@ -1330,7 +2355,7 @@ ${snippet}
|
|
|
1330
2355
|
${snippet}
|
|
1331
2356
|
`;
|
|
1332
2357
|
};
|
|
1333
|
-
if (!hasPhpFunctionDefinition(enqueueFunctionName)) {
|
|
2358
|
+
if (!hasPhpFunctionDefinition(nextSource, enqueueFunctionName)) {
|
|
1334
2359
|
insertPhpSnippet(enqueueFunction);
|
|
1335
2360
|
} else {
|
|
1336
2361
|
const requiredReferences = [
|
|
@@ -1346,7 +2371,7 @@ ${snippet}
|
|
|
1346
2371
|
if (missingReferences.length > 0) {
|
|
1347
2372
|
const replacedSource = replacePhpFunctionDefinition(nextSource, enqueueFunctionName, enqueueFunction);
|
|
1348
2373
|
if (!replacedSource) {
|
|
1349
|
-
throw new Error(`Unable to repair ${
|
|
2374
|
+
throw new Error(`Unable to repair ${path5.basename(bootstrapPath)} for ${enqueueFunctionName}.`);
|
|
1350
2375
|
}
|
|
1351
2376
|
nextSource = replacedSource;
|
|
1352
2377
|
}
|
|
@@ -1358,7 +2383,7 @@ ${snippet}
|
|
|
1358
2383
|
});
|
|
1359
2384
|
}
|
|
1360
2385
|
async function ensureEditorPluginBuildScriptAnchors(workspace) {
|
|
1361
|
-
const buildScriptPath =
|
|
2386
|
+
const buildScriptPath = path5.join(workspace.projectDir, "scripts", "build-workspace.mjs");
|
|
1362
2387
|
await patchFile(buildScriptPath, (source) => {
|
|
1363
2388
|
if (/['"]src\/editor-plugins\/index\.(?:ts|js)['"]/u.test(source)) {
|
|
1364
2389
|
return source;
|
|
@@ -1371,13 +2396,13 @@ async function ensureEditorPluginBuildScriptAnchors(workspace) {
|
|
|
1371
2396
|
'src/editor-plugins/index.js',
|
|
1372
2397
|
]`);
|
|
1373
2398
|
if (nextSource === source) {
|
|
1374
|
-
throw new Error(`Unable to update ${
|
|
2399
|
+
throw new Error(`Unable to update ${path5.relative(workspace.projectDir, buildScriptPath)} for editor plugin shared entries.`);
|
|
1375
2400
|
}
|
|
1376
2401
|
return nextSource;
|
|
1377
2402
|
});
|
|
1378
2403
|
}
|
|
1379
2404
|
async function ensureEditorPluginWebpackAnchors(workspace) {
|
|
1380
|
-
const webpackConfigPath =
|
|
2405
|
+
const webpackConfigPath = path5.join(workspace.projectDir, "webpack.config.js");
|
|
1381
2406
|
await patchFile(webpackConfigPath, (source) => {
|
|
1382
2407
|
if (/['"]editor-plugins\/index['"]/u.test(source)) {
|
|
1383
2408
|
return source;
|
|
@@ -1405,45 +2430,45 @@ async function ensureEditorPluginWebpackAnchors(workspace) {
|
|
|
1405
2430
|
}`;
|
|
1406
2431
|
const nextSource = source.replace(legacySharedEntriesBlockPattern, nextSharedEntriesBlock);
|
|
1407
2432
|
if (nextSource === source) {
|
|
1408
|
-
throw new Error(`Unable to update ${
|
|
2433
|
+
throw new Error(`Unable to update ${path5.relative(workspace.projectDir, webpackConfigPath)} for editor plugin shared entries.`);
|
|
1409
2434
|
}
|
|
1410
2435
|
return nextSource;
|
|
1411
2436
|
});
|
|
1412
2437
|
}
|
|
1413
2438
|
function resolveBindingSourceRegistryPath(projectDir) {
|
|
1414
|
-
const bindingsDir =
|
|
1415
|
-
return [
|
|
2439
|
+
const bindingsDir = path5.join(projectDir, "src", "bindings");
|
|
2440
|
+
return [path5.join(bindingsDir, "index.ts"), path5.join(bindingsDir, "index.js")].find((candidatePath) => fs4.existsSync(candidatePath)) ?? path5.join(bindingsDir, "index.ts");
|
|
1416
2441
|
}
|
|
1417
2442
|
async function writeBindingSourceRegistry(projectDir, bindingSourceSlug) {
|
|
1418
|
-
const bindingsDir =
|
|
2443
|
+
const bindingsDir = path5.join(projectDir, "src", "bindings");
|
|
1419
2444
|
const bindingsIndexPath = resolveBindingSourceRegistryPath(projectDir);
|
|
1420
|
-
await
|
|
1421
|
-
const existingBindingSourceSlugs =
|
|
2445
|
+
await fsp4.mkdir(bindingsDir, { recursive: true });
|
|
2446
|
+
const existingBindingSourceSlugs = fs4.readdirSync(bindingsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
1422
2447
|
const nextBindingSourceSlugs = Array.from(new Set([...existingBindingSourceSlugs, bindingSourceSlug])).sort();
|
|
1423
|
-
await
|
|
2448
|
+
await fsp4.writeFile(bindingsIndexPath, buildBindingSourceIndexSource(nextBindingSourceSlugs), "utf8");
|
|
1424
2449
|
}
|
|
1425
2450
|
function resolveEditorPluginRegistryPath(projectDir) {
|
|
1426
|
-
const editorPluginsDir =
|
|
2451
|
+
const editorPluginsDir = path5.join(projectDir, "src", "editor-plugins");
|
|
1427
2452
|
return [
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
].find((candidatePath) =>
|
|
2453
|
+
path5.join(editorPluginsDir, "index.ts"),
|
|
2454
|
+
path5.join(editorPluginsDir, "index.js")
|
|
2455
|
+
].find((candidatePath) => fs4.existsSync(candidatePath)) ?? path5.join(editorPluginsDir, "index.ts");
|
|
1431
2456
|
}
|
|
1432
2457
|
function readEditorPluginRegistrySlugs(registryPath) {
|
|
1433
|
-
if (!
|
|
2458
|
+
if (!fs4.existsSync(registryPath)) {
|
|
1434
2459
|
return [];
|
|
1435
2460
|
}
|
|
1436
|
-
const source =
|
|
2461
|
+
const source = fs4.readFileSync(registryPath, "utf8");
|
|
1437
2462
|
return Array.from(source.matchAll(/^\s*import\s+['"]\.\/([^/'"]+)(?:\/index(?:\.[cm]?[jt]sx?)?)?['"];?\s*$/gmu)).map((match) => match[1]);
|
|
1438
2463
|
}
|
|
1439
2464
|
async function writeEditorPluginRegistry(projectDir, editorPluginSlug) {
|
|
1440
|
-
const editorPluginsDir =
|
|
2465
|
+
const editorPluginsDir = path5.join(projectDir, "src", "editor-plugins");
|
|
1441
2466
|
const registryPath = resolveEditorPluginRegistryPath(projectDir);
|
|
1442
|
-
await
|
|
2467
|
+
await fsp4.mkdir(editorPluginsDir, { recursive: true });
|
|
1443
2468
|
const existingEditorPluginSlugs = readWorkspaceInventory(projectDir).editorPlugins.map((entry) => entry.slug);
|
|
1444
2469
|
const existingRegistrySlugs = readEditorPluginRegistrySlugs(registryPath);
|
|
1445
2470
|
const nextEditorPluginSlugs = Array.from(new Set([...existingEditorPluginSlugs, ...existingRegistrySlugs, editorPluginSlug])).sort();
|
|
1446
|
-
await
|
|
2471
|
+
await fsp4.writeFile(registryPath, buildEditorPluginRegistrySource(nextEditorPluginSlugs), "utf8");
|
|
1447
2472
|
}
|
|
1448
2473
|
async function runAddEditorPluginCommand({
|
|
1449
2474
|
cwd = process.cwd(),
|
|
@@ -1451,21 +2476,21 @@ async function runAddEditorPluginCommand({
|
|
|
1451
2476
|
slot
|
|
1452
2477
|
}) {
|
|
1453
2478
|
const workspace = resolveWorkspaceProject(cwd);
|
|
1454
|
-
const editorPluginSlug = assertValidGeneratedSlug("Editor plugin name", normalizeBlockSlug(editorPluginName), "wp-typia add editor-plugin <name> [--slot <
|
|
2479
|
+
const editorPluginSlug = assertValidGeneratedSlug("Editor plugin name", normalizeBlockSlug(editorPluginName), "wp-typia add editor-plugin <name> [--slot <sidebar|document-setting-panel>]");
|
|
1455
2480
|
const resolvedSlot = assertValidEditorPluginSlot(slot);
|
|
1456
2481
|
const inventory = readWorkspaceInventory(workspace.projectDir);
|
|
1457
2482
|
assertEditorPluginDoesNotExist(workspace.projectDir, editorPluginSlug, inventory);
|
|
1458
|
-
const blockConfigPath =
|
|
2483
|
+
const blockConfigPath = path5.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
1459
2484
|
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
1460
|
-
const buildScriptPath =
|
|
2485
|
+
const buildScriptPath = path5.join(workspace.projectDir, "scripts", "build-workspace.mjs");
|
|
1461
2486
|
const editorPluginsIndexPath = resolveEditorPluginRegistryPath(workspace.projectDir);
|
|
1462
|
-
const webpackConfigPath =
|
|
1463
|
-
const editorPluginDir =
|
|
1464
|
-
const entryFilePath =
|
|
1465
|
-
const
|
|
1466
|
-
const dataFilePath =
|
|
1467
|
-
const typesFilePath =
|
|
1468
|
-
const styleFilePath =
|
|
2487
|
+
const webpackConfigPath = path5.join(workspace.projectDir, "webpack.config.js");
|
|
2488
|
+
const editorPluginDir = path5.join(workspace.projectDir, "src", "editor-plugins", editorPluginSlug);
|
|
2489
|
+
const entryFilePath = path5.join(editorPluginDir, "index.tsx");
|
|
2490
|
+
const surfaceFilePath = path5.join(editorPluginDir, "Surface.tsx");
|
|
2491
|
+
const dataFilePath = path5.join(editorPluginDir, "data.ts");
|
|
2492
|
+
const typesFilePath = path5.join(editorPluginDir, "types.ts");
|
|
2493
|
+
const styleFilePath = path5.join(editorPluginDir, "style.scss");
|
|
1469
2494
|
const mutationSnapshot = {
|
|
1470
2495
|
fileSources: await snapshotWorkspaceFiles([
|
|
1471
2496
|
blockConfigPath,
|
|
@@ -1478,15 +2503,15 @@ async function runAddEditorPluginCommand({
|
|
|
1478
2503
|
targetPaths: [editorPluginDir]
|
|
1479
2504
|
};
|
|
1480
2505
|
try {
|
|
1481
|
-
await
|
|
2506
|
+
await fsp4.mkdir(editorPluginDir, { recursive: true });
|
|
1482
2507
|
await ensureEditorPluginBootstrapAnchors(workspace);
|
|
1483
2508
|
await ensureEditorPluginBuildScriptAnchors(workspace);
|
|
1484
2509
|
await ensureEditorPluginWebpackAnchors(workspace);
|
|
1485
|
-
await
|
|
1486
|
-
await
|
|
1487
|
-
await
|
|
1488
|
-
await
|
|
1489
|
-
await
|
|
2510
|
+
await fsp4.writeFile(entryFilePath, buildEditorPluginEntrySource(editorPluginSlug, workspace.workspace.namespace, workspace.workspace.textDomain), "utf8");
|
|
2511
|
+
await fsp4.writeFile(surfaceFilePath, buildEditorPluginSurfaceSource(editorPluginSlug, resolvedSlot, workspace.workspace.textDomain), "utf8");
|
|
2512
|
+
await fsp4.writeFile(dataFilePath, buildEditorPluginDataSource(editorPluginSlug, resolvedSlot), "utf8");
|
|
2513
|
+
await fsp4.writeFile(typesFilePath, buildEditorPluginTypesSource(editorPluginSlug), "utf8");
|
|
2514
|
+
await fsp4.writeFile(styleFilePath, buildEditorPluginStyleSource(), "utf8");
|
|
1490
2515
|
await writeEditorPluginRegistry(workspace.projectDir, editorPluginSlug);
|
|
1491
2516
|
await appendWorkspaceInventoryEntries(workspace.projectDir, {
|
|
1492
2517
|
editorPluginEntries: [
|
|
@@ -1511,18 +2536,18 @@ async function runAddPatternCommand({
|
|
|
1511
2536
|
const patternSlug = assertValidGeneratedSlug("Pattern name", normalizeBlockSlug(patternName), "wp-typia add pattern <name>");
|
|
1512
2537
|
const inventory = readWorkspaceInventory(workspace.projectDir);
|
|
1513
2538
|
assertPatternDoesNotExist(workspace.projectDir, patternSlug, inventory);
|
|
1514
|
-
const blockConfigPath =
|
|
2539
|
+
const blockConfigPath = path5.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
1515
2540
|
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
1516
|
-
const patternFilePath =
|
|
2541
|
+
const patternFilePath = path5.join(workspace.projectDir, "src", "patterns", `${patternSlug}.php`);
|
|
1517
2542
|
const mutationSnapshot = {
|
|
1518
2543
|
fileSources: await snapshotWorkspaceFiles([blockConfigPath, bootstrapPath]),
|
|
1519
2544
|
snapshotDirs: [],
|
|
1520
2545
|
targetPaths: [patternFilePath]
|
|
1521
2546
|
};
|
|
1522
2547
|
try {
|
|
1523
|
-
await
|
|
2548
|
+
await fsp4.mkdir(path5.dirname(patternFilePath), { recursive: true });
|
|
1524
2549
|
await ensurePatternBootstrapAnchors(workspace);
|
|
1525
|
-
await
|
|
2550
|
+
await fsp4.writeFile(patternFilePath, buildPatternSource(patternSlug, workspace.workspace.namespace, workspace.workspace.textDomain), "utf8");
|
|
1526
2551
|
await appendWorkspaceInventoryEntries(workspace.projectDir, {
|
|
1527
2552
|
patternEntries: [buildPatternConfigEntry(patternSlug)]
|
|
1528
2553
|
});
|
|
@@ -1536,34 +2561,59 @@ async function runAddPatternCommand({
|
|
|
1536
2561
|
}
|
|
1537
2562
|
}
|
|
1538
2563
|
async function runAddBindingSourceCommand({
|
|
2564
|
+
attributeName,
|
|
1539
2565
|
bindingSourceName,
|
|
2566
|
+
blockName,
|
|
1540
2567
|
cwd = process.cwd()
|
|
1541
2568
|
}) {
|
|
1542
2569
|
const workspace = resolveWorkspaceProject(cwd);
|
|
1543
|
-
const bindingSourceSlug = assertValidGeneratedSlug("Binding source name", normalizeBlockSlug(bindingSourceName), "wp-typia add binding-source <name>");
|
|
2570
|
+
const bindingSourceSlug = assertValidGeneratedSlug("Binding source name", normalizeBlockSlug(bindingSourceName), "wp-typia add binding-source <name> [--block <block-slug|namespace/block-slug> --attribute <attribute>]");
|
|
1544
2571
|
const inventory = readWorkspaceInventory(workspace.projectDir);
|
|
1545
2572
|
assertBindingSourceDoesNotExist(workspace.projectDir, bindingSourceSlug, inventory);
|
|
1546
|
-
const
|
|
2573
|
+
const target = resolveBindingTarget({
|
|
2574
|
+
attributeName,
|
|
2575
|
+
blockName
|
|
2576
|
+
}, workspace.workspace.namespace);
|
|
2577
|
+
const targetBlock = target ? resolveWorkspaceBlock(inventory, target.blockSlug) : undefined;
|
|
2578
|
+
const blockConfigPath = path5.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
1547
2579
|
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
1548
2580
|
const bindingsIndexPath = resolveBindingSourceRegistryPath(workspace.projectDir);
|
|
1549
|
-
const bindingSourceDir =
|
|
1550
|
-
const serverFilePath =
|
|
1551
|
-
const editorFilePath =
|
|
2581
|
+
const bindingSourceDir = path5.join(workspace.projectDir, "src", "bindings", bindingSourceSlug);
|
|
2582
|
+
const serverFilePath = path5.join(bindingSourceDir, "server.php");
|
|
2583
|
+
const editorFilePath = path5.join(bindingSourceDir, "editor.ts");
|
|
2584
|
+
const blockJsonPath = target ? path5.join(workspace.projectDir, "src", "blocks", target.blockSlug, "block.json") : undefined;
|
|
2585
|
+
const targetGeneratedMetadataPaths = target ? [
|
|
2586
|
+
path5.join(workspace.projectDir, "src", "blocks", target.blockSlug, "typia.manifest.json"),
|
|
2587
|
+
path5.join(workspace.projectDir, "src", "blocks", target.blockSlug, "typia.openapi.json"),
|
|
2588
|
+
path5.join(workspace.projectDir, "src", "blocks", target.blockSlug, "typia.schema.json"),
|
|
2589
|
+
path5.join(workspace.projectDir, "src", "blocks", target.blockSlug, "typia-validator.php")
|
|
2590
|
+
] : [];
|
|
1552
2591
|
const mutationSnapshot = {
|
|
1553
|
-
fileSources: await snapshotWorkspaceFiles([
|
|
2592
|
+
fileSources: await snapshotWorkspaceFiles([
|
|
2593
|
+
blockConfigPath,
|
|
2594
|
+
bootstrapPath,
|
|
2595
|
+
bindingsIndexPath,
|
|
2596
|
+
...blockJsonPath ? [blockJsonPath] : [],
|
|
2597
|
+
...targetBlock ? [path5.join(workspace.projectDir, targetBlock.typesFile)] : [],
|
|
2598
|
+
...targetGeneratedMetadataPaths
|
|
2599
|
+
]),
|
|
1554
2600
|
snapshotDirs: [],
|
|
1555
2601
|
targetPaths: [bindingSourceDir]
|
|
1556
2602
|
};
|
|
1557
2603
|
try {
|
|
1558
|
-
await
|
|
2604
|
+
await fsp4.mkdir(bindingSourceDir, { recursive: true });
|
|
1559
2605
|
await ensureBindingSourceBootstrapAnchors(workspace);
|
|
1560
|
-
await
|
|
1561
|
-
await
|
|
2606
|
+
await fsp4.writeFile(serverFilePath, buildBindingSourceServerSource(bindingSourceSlug, workspace.workspace.phpPrefix, workspace.workspace.namespace, workspace.workspace.textDomain, target), "utf8");
|
|
2607
|
+
await fsp4.writeFile(editorFilePath, buildBindingSourceEditorSource(bindingSourceSlug, workspace.workspace.namespace, workspace.workspace.textDomain, target), "utf8");
|
|
2608
|
+
if (target && targetBlock) {
|
|
2609
|
+
await ensureBindingTargetBlockAttributeType(workspace.projectDir, targetBlock, target);
|
|
2610
|
+
}
|
|
1562
2611
|
await writeBindingSourceRegistry(workspace.projectDir, bindingSourceSlug);
|
|
1563
2612
|
await appendWorkspaceInventoryEntries(workspace.projectDir, {
|
|
1564
|
-
bindingSourceEntries: [buildBindingSourceConfigEntry(bindingSourceSlug)]
|
|
2613
|
+
bindingSourceEntries: [buildBindingSourceConfigEntry(bindingSourceSlug, target)]
|
|
1565
2614
|
});
|
|
1566
2615
|
return {
|
|
2616
|
+
...target ? { attributeName: target.attributeName, blockSlug: target.blockSlug } : {},
|
|
1567
2617
|
bindingSourceSlug,
|
|
1568
2618
|
projectDir: workspace.projectDir
|
|
1569
2619
|
};
|
|
@@ -1573,15 +2623,12 @@ async function runAddBindingSourceCommand({
|
|
|
1573
2623
|
}
|
|
1574
2624
|
}
|
|
1575
2625
|
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-rest.ts
|
|
1576
|
-
import { promises as
|
|
1577
|
-
import
|
|
2626
|
+
import { promises as fsp5 } from "fs";
|
|
2627
|
+
import path8 from "path";
|
|
1578
2628
|
|
|
1579
2629
|
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-rest-anchors.ts
|
|
1580
|
-
import
|
|
2630
|
+
import path6 from "path";
|
|
1581
2631
|
var REST_RESOURCE_SERVER_GLOB = "/inc/rest/*.php";
|
|
1582
|
-
function escapeRegex2(value) {
|
|
1583
|
-
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
1584
|
-
}
|
|
1585
2632
|
async function ensureRestResourceBootstrapAnchors(workspace) {
|
|
1586
2633
|
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
1587
2634
|
await patchFile(bootstrapPath, (source) => {
|
|
@@ -1600,7 +2647,6 @@ function ${registerFunctionName}() {
|
|
|
1600
2647
|
/add_action\(\s*["']init["']\s*,\s*["'][^"']+_load_textdomain["']\s*\);\s*\n/u,
|
|
1601
2648
|
/\?>\s*$/u
|
|
1602
2649
|
];
|
|
1603
|
-
const hasPhpFunctionDefinition = (functionName) => new RegExp(`function\\s+${escapeRegex2(functionName)}\\s*\\(`, "u").test(nextSource);
|
|
1604
2650
|
const insertPhpSnippet = (snippet) => {
|
|
1605
2651
|
for (const anchor of insertionAnchors) {
|
|
1606
2652
|
const candidate = nextSource.replace(anchor, (match) => `${snippet}
|
|
@@ -1625,11 +2671,11 @@ ${snippet}
|
|
|
1625
2671
|
${snippet}
|
|
1626
2672
|
`;
|
|
1627
2673
|
};
|
|
1628
|
-
if (!hasPhpFunctionDefinition(registerFunctionName)) {
|
|
2674
|
+
if (!hasPhpFunctionDefinition(nextSource, registerFunctionName)) {
|
|
1629
2675
|
insertPhpSnippet(registerFunction);
|
|
1630
2676
|
} else if (!nextSource.includes(REST_RESOURCE_SERVER_GLOB)) {
|
|
1631
2677
|
throw new Error([
|
|
1632
|
-
`Unable to patch ${
|
|
2678
|
+
`Unable to patch ${path6.basename(bootstrapPath)} in ensureRestResourceBootstrapAnchors.`,
|
|
1633
2679
|
`The existing ${registerFunctionName}() definition does not include ${REST_RESOURCE_SERVER_GLOB}.`,
|
|
1634
2680
|
"Restore the generated bootstrap shape or wire the REST resource loader manually before retrying."
|
|
1635
2681
|
].join(" "));
|
|
@@ -1643,7 +2689,7 @@ ${snippet}
|
|
|
1643
2689
|
function assertSyncRestAnchor(nextSource, target, anchorDescription, hasAnchor, syncRestScriptPath) {
|
|
1644
2690
|
if (!nextSource.includes(target) && !hasAnchor) {
|
|
1645
2691
|
throw new Error([
|
|
1646
|
-
`ensureRestResourceSyncScriptAnchors could not patch ${
|
|
2692
|
+
`ensureRestResourceSyncScriptAnchors could not patch ${path6.basename(syncRestScriptPath)}.`,
|
|
1647
2693
|
`Missing expected ${anchorDescription} anchor in scripts/sync-rest-contracts.ts.`,
|
|
1648
2694
|
"Restore the generated template or add the REST_RESOURCES wiring manually before retrying."
|
|
1649
2695
|
].join(" "));
|
|
@@ -1658,7 +2704,7 @@ function replaceRequiredSyncRestSource(nextSource, target, anchor, replacement,
|
|
|
1658
2704
|
return nextSource.replace(anchor, replacement);
|
|
1659
2705
|
}
|
|
1660
2706
|
async function ensureRestResourceSyncScriptAnchors(workspace) {
|
|
1661
|
-
const syncRestScriptPath =
|
|
2707
|
+
const syncRestScriptPath = path6.join(workspace.projectDir, "scripts", "sync-rest-contracts.ts");
|
|
1662
2708
|
await patchFile(syncRestScriptPath, (source) => {
|
|
1663
2709
|
let nextSource = source;
|
|
1664
2710
|
const importAnchor = "import { BLOCKS, type WorkspaceBlockConfig } from './block-config';";
|
|
@@ -1776,7 +2822,7 @@ async function ensureRestResourceSyncScriptAnchors(workspace) {
|
|
|
1776
2822
|
}
|
|
1777
2823
|
|
|
1778
2824
|
// ../wp-typia-project-tools/src/runtime/rest-resource-artifacts.ts
|
|
1779
|
-
import
|
|
2825
|
+
import path7 from "path";
|
|
1780
2826
|
import {
|
|
1781
2827
|
defineEndpointManifest,
|
|
1782
2828
|
syncEndpointClient,
|
|
@@ -1912,8 +2958,8 @@ async function syncRestResourceArtifacts({
|
|
|
1912
2958
|
const manifest = buildRestResourceEndpointManifest(variables, methods);
|
|
1913
2959
|
for (const [baseName, contract] of Object.entries(manifest.contracts)) {
|
|
1914
2960
|
await syncTypeSchemas({
|
|
1915
|
-
jsonSchemaFile:
|
|
1916
|
-
openApiFile:
|
|
2961
|
+
jsonSchemaFile: path7.join(outputDir, "api-schemas", `${baseName}.schema.json`),
|
|
2962
|
+
openApiFile: path7.join(outputDir, "api-schemas", `${baseName}.openapi.json`),
|
|
1917
2963
|
projectRoot: projectDir,
|
|
1918
2964
|
sourceTypeName: contract.sourceTypeName,
|
|
1919
2965
|
typesFile
|
|
@@ -1921,7 +2967,7 @@ async function syncRestResourceArtifacts({
|
|
|
1921
2967
|
}
|
|
1922
2968
|
await syncRestOpenApi({
|
|
1923
2969
|
manifest,
|
|
1924
|
-
openApiFile:
|
|
2970
|
+
openApiFile: path7.join(outputDir, "api.openapi.json"),
|
|
1925
2971
|
projectRoot: projectDir,
|
|
1926
2972
|
typesFile
|
|
1927
2973
|
});
|
|
@@ -1935,16 +2981,13 @@ async function syncRestResourceArtifacts({
|
|
|
1935
2981
|
}
|
|
1936
2982
|
|
|
1937
2983
|
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-rest-source-emitters.ts
|
|
1938
|
-
function toPascalCaseFromSlug2(slug) {
|
|
1939
|
-
return normalizeBlockSlug(slug).split("-").filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join("");
|
|
1940
|
-
}
|
|
1941
2984
|
function indentMultiline(source, prefix) {
|
|
1942
2985
|
return source.split(`
|
|
1943
2986
|
`).map((line) => `${prefix}${line}`).join(`
|
|
1944
2987
|
`);
|
|
1945
2988
|
}
|
|
1946
2989
|
function buildRestResourceConfigEntry(restResourceSlug, namespace, methods) {
|
|
1947
|
-
const pascalCase =
|
|
2990
|
+
const pascalCase = toPascalCase(restResourceSlug);
|
|
1948
2991
|
const title = toTitleCase(restResourceSlug);
|
|
1949
2992
|
const manifest = buildRestResourceEndpointManifest({
|
|
1950
2993
|
namespace,
|
|
@@ -1972,7 +3015,7 @@ function buildRestResourceConfigEntry(restResourceSlug, namespace, methods) {
|
|
|
1972
3015
|
`);
|
|
1973
3016
|
}
|
|
1974
3017
|
function buildRestResourceTypesSource(restResourceSlug, methods) {
|
|
1975
|
-
const pascalCase =
|
|
3018
|
+
const pascalCase = toPascalCase(restResourceSlug);
|
|
1976
3019
|
const lines = [
|
|
1977
3020
|
"import { tags } from 'typia';",
|
|
1978
3021
|
"",
|
|
@@ -2006,7 +3049,7 @@ function buildRestResourceTypesSource(restResourceSlug, methods) {
|
|
|
2006
3049
|
`;
|
|
2007
3050
|
}
|
|
2008
3051
|
function buildRestResourceValidatorsSource(restResourceSlug, methods) {
|
|
2009
|
-
const pascalCase =
|
|
3052
|
+
const pascalCase = toPascalCase(restResourceSlug);
|
|
2010
3053
|
const importedTypes = new Set;
|
|
2011
3054
|
const validatorDeclarations = [];
|
|
2012
3055
|
const validatorEntries = [];
|
|
@@ -2054,7 +3097,7 @@ ${validatorEntries.join(`
|
|
|
2054
3097
|
`;
|
|
2055
3098
|
}
|
|
2056
3099
|
function buildRestResourceApiSource(restResourceSlug, methods) {
|
|
2057
|
-
const pascalCase =
|
|
3100
|
+
const pascalCase = toPascalCase(restResourceSlug);
|
|
2058
3101
|
const typeImports = new Set;
|
|
2059
3102
|
const clientEndpointImports = [];
|
|
2060
3103
|
const exportedBindings = [];
|
|
@@ -2199,7 +3242,7 @@ ${exportedBindings.join(`
|
|
|
2199
3242
|
`;
|
|
2200
3243
|
}
|
|
2201
3244
|
function buildRestResourceDataSource(restResourceSlug, methods) {
|
|
2202
|
-
const pascalCase =
|
|
3245
|
+
const pascalCase = toPascalCase(restResourceSlug);
|
|
2203
3246
|
const typeImports = new Set;
|
|
2204
3247
|
const endpointImports = [];
|
|
2205
3248
|
const exportedBindings = [];
|
|
@@ -2320,9 +3363,6 @@ ${exportedBindings.join(`
|
|
|
2320
3363
|
}
|
|
2321
3364
|
|
|
2322
3365
|
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-rest.ts
|
|
2323
|
-
function quotePhpString2(value) {
|
|
2324
|
-
return `'${value.replace(/\\/gu, "\\\\").replace(/'/gu, "\\'")}'`;
|
|
2325
|
-
}
|
|
2326
3366
|
function buildRestResourceRouteRegistrations(restResourceSlug, methods, functions) {
|
|
2327
3367
|
const collectionRoutes = [];
|
|
2328
3368
|
const itemRoutes = [];
|
|
@@ -2418,7 +3458,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
|
2418
3458
|
|
|
2419
3459
|
if ( ! function_exists( '${getOptionNameFunctionName}' ) ) {
|
|
2420
3460
|
function ${getOptionNameFunctionName}() {
|
|
2421
|
-
return ${
|
|
3461
|
+
return ${quotePhpString(`${phpPrefix}_${restResourcePhpId}_rest_resource_items`)};
|
|
2422
3462
|
}
|
|
2423
3463
|
}
|
|
2424
3464
|
|
|
@@ -2439,8 +3479,8 @@ if ( ! function_exists( '${getItemsFunctionName}' ) ) {
|
|
|
2439
3479
|
$seed_items = array(
|
|
2440
3480
|
array(
|
|
2441
3481
|
'id' => 1,
|
|
2442
|
-
'title' => ${
|
|
2443
|
-
'content' => ${
|
|
3482
|
+
'title' => ${quotePhpString(`${restResourceTitle} Starter`)},
|
|
3483
|
+
'content' => ${quotePhpString(`Replace this seeded ${restResourceTitle.toLowerCase()} content with your plugin data source.`)},
|
|
2444
3484
|
'status' => 'draft',
|
|
2445
3485
|
'updatedAt' => '2026-01-01T00:00:00Z',
|
|
2446
3486
|
),
|
|
@@ -2731,7 +3771,7 @@ if ( ! function_exists( '${deleteHandlerName}' ) ) {
|
|
|
2731
3771
|
|
|
2732
3772
|
if ( ! function_exists( '${registerRoutesFunctionName}' ) ) {
|
|
2733
3773
|
function ${registerRoutesFunctionName}() {
|
|
2734
|
-
$namespace = ${
|
|
3774
|
+
$namespace = ${quotePhpString(namespace)};
|
|
2735
3775
|
|
|
2736
3776
|
${routeRegistrations}
|
|
2737
3777
|
}
|
|
@@ -2752,15 +3792,15 @@ async function runAddRestResourceCommand({
|
|
|
2752
3792
|
const resolvedNamespace = resolveRestResourceNamespace(workspace.workspace.namespace, namespace);
|
|
2753
3793
|
const inventory = readWorkspaceInventory(workspace.projectDir);
|
|
2754
3794
|
assertRestResourceDoesNotExist(workspace.projectDir, restResourceSlug, inventory);
|
|
2755
|
-
const blockConfigPath =
|
|
3795
|
+
const blockConfigPath = path8.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
2756
3796
|
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
2757
|
-
const syncRestScriptPath =
|
|
2758
|
-
const restResourceDir =
|
|
2759
|
-
const typesFilePath =
|
|
2760
|
-
const validatorsFilePath =
|
|
2761
|
-
const apiFilePath =
|
|
2762
|
-
const dataFilePath =
|
|
2763
|
-
const phpFilePath =
|
|
3797
|
+
const syncRestScriptPath = path8.join(workspace.projectDir, "scripts", "sync-rest-contracts.ts");
|
|
3798
|
+
const restResourceDir = path8.join(workspace.projectDir, "src", "rest", restResourceSlug);
|
|
3799
|
+
const typesFilePath = path8.join(restResourceDir, "api-types.ts");
|
|
3800
|
+
const validatorsFilePath = path8.join(restResourceDir, "api-validators.ts");
|
|
3801
|
+
const apiFilePath = path8.join(restResourceDir, "api.ts");
|
|
3802
|
+
const dataFilePath = path8.join(restResourceDir, "data.ts");
|
|
3803
|
+
const phpFilePath = path8.join(workspace.projectDir, "inc", "rest", `${restResourceSlug}.php`);
|
|
2764
3804
|
const mutationSnapshot = {
|
|
2765
3805
|
fileSources: await snapshotWorkspaceFiles([
|
|
2766
3806
|
blockConfigPath,
|
|
@@ -2771,15 +3811,15 @@ async function runAddRestResourceCommand({
|
|
|
2771
3811
|
targetPaths: [restResourceDir, phpFilePath]
|
|
2772
3812
|
};
|
|
2773
3813
|
try {
|
|
2774
|
-
await
|
|
2775
|
-
await
|
|
3814
|
+
await fsp5.mkdir(restResourceDir, { recursive: true });
|
|
3815
|
+
await fsp5.mkdir(path8.dirname(phpFilePath), { recursive: true });
|
|
2776
3816
|
await ensureRestResourceBootstrapAnchors(workspace);
|
|
2777
3817
|
await ensureRestResourceSyncScriptAnchors(workspace);
|
|
2778
|
-
await
|
|
2779
|
-
await
|
|
2780
|
-
await
|
|
2781
|
-
await
|
|
2782
|
-
await
|
|
3818
|
+
await fsp5.writeFile(typesFilePath, buildRestResourceTypesSource(restResourceSlug, resolvedMethods), "utf8");
|
|
3819
|
+
await fsp5.writeFile(validatorsFilePath, buildRestResourceValidatorsSource(restResourceSlug, resolvedMethods), "utf8");
|
|
3820
|
+
await fsp5.writeFile(apiFilePath, buildRestResourceApiSource(restResourceSlug, resolvedMethods), "utf8");
|
|
3821
|
+
await fsp5.writeFile(dataFilePath, buildRestResourceDataSource(restResourceSlug, resolvedMethods), "utf8");
|
|
3822
|
+
await fsp5.writeFile(phpFilePath, buildRestResourcePhpSource(restResourceSlug, resolvedNamespace, workspace.workspace.phpPrefix, workspace.workspace.textDomain, resolvedMethods), "utf8");
|
|
2783
3823
|
await syncRestResourceArtifacts({
|
|
2784
3824
|
clientFile: `src/rest/${restResourceSlug}/api-client.ts`,
|
|
2785
3825
|
methods: resolvedMethods,
|
|
@@ -2789,7 +3829,7 @@ async function runAddRestResourceCommand({
|
|
|
2789
3829
|
validatorsFile: `src/rest/${restResourceSlug}/api-validators.ts`,
|
|
2790
3830
|
variables: {
|
|
2791
3831
|
namespace: resolvedNamespace,
|
|
2792
|
-
pascalCase:
|
|
3832
|
+
pascalCase: toPascalCase(restResourceSlug),
|
|
2793
3833
|
slugKebabCase: restResourceSlug,
|
|
2794
3834
|
title: toTitleCase(restResourceSlug)
|
|
2795
3835
|
}
|
|
@@ -2813,9 +3853,9 @@ async function runAddRestResourceCommand({
|
|
|
2813
3853
|
}
|
|
2814
3854
|
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-ability.ts
|
|
2815
3855
|
var import_semver = __toESM(require_semver(), 1);
|
|
2816
|
-
import
|
|
2817
|
-
import { promises as
|
|
2818
|
-
import
|
|
3856
|
+
import fs5 from "fs";
|
|
3857
|
+
import { promises as fsp6 } from "fs";
|
|
3858
|
+
import path9 from "path";
|
|
2819
3859
|
import { syncTypeSchemas as syncTypeSchemas2 } from "@wp-typia/block-runtime/metadata-core";
|
|
2820
3860
|
var ABILITY_SERVER_GLOB = "/inc/abilities/*.php";
|
|
2821
3861
|
var ABILITY_EDITOR_SCRIPT = "build/abilities/index.js";
|
|
@@ -2826,45 +3866,6 @@ var WP_ABILITIES_PACKAGE_VERSION = "^0.10.0";
|
|
|
2826
3866
|
var WP_CORE_ABILITIES_PACKAGE_VERSION = "^0.9.0";
|
|
2827
3867
|
var WP_ABILITIES_SCRIPT_MODULE_ID = "@wordpress/abilities";
|
|
2828
3868
|
var WP_CORE_ABILITIES_SCRIPT_MODULE_ID = "@wordpress/core-abilities";
|
|
2829
|
-
function escapeRegex3(value) {
|
|
2830
|
-
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
2831
|
-
}
|
|
2832
|
-
function quotePhpString3(value) {
|
|
2833
|
-
return `'${value.replace(/\\/gu, "\\\\").replace(/'/gu, "\\'")}'`;
|
|
2834
|
-
}
|
|
2835
|
-
function findPhpFunctionRange2(source, functionName) {
|
|
2836
|
-
const functionPattern = new RegExp(`function\\s+${escapeRegex3(functionName)}\\s*\\([^)]*\\)\\s*\\{`, "u");
|
|
2837
|
-
const match = functionPattern.exec(source);
|
|
2838
|
-
if (!match) {
|
|
2839
|
-
return null;
|
|
2840
|
-
}
|
|
2841
|
-
const openingBraceIndex = match.index + match[0].length - 1;
|
|
2842
|
-
let depth = 0;
|
|
2843
|
-
for (let index = openingBraceIndex;index < source.length; index += 1) {
|
|
2844
|
-
const character = source[index];
|
|
2845
|
-
if (character === "{") {
|
|
2846
|
-
depth += 1;
|
|
2847
|
-
} else if (character === "}") {
|
|
2848
|
-
depth -= 1;
|
|
2849
|
-
if (depth === 0) {
|
|
2850
|
-
const end = index + 1;
|
|
2851
|
-
return {
|
|
2852
|
-
end,
|
|
2853
|
-
source: source.slice(match.index, end),
|
|
2854
|
-
start: match.index
|
|
2855
|
-
};
|
|
2856
|
-
}
|
|
2857
|
-
}
|
|
2858
|
-
}
|
|
2859
|
-
return null;
|
|
2860
|
-
}
|
|
2861
|
-
function replacePhpFunctionDefinition2(source, functionName, replacement) {
|
|
2862
|
-
const functionRange = findPhpFunctionRange2(source, functionName);
|
|
2863
|
-
if (!functionRange) {
|
|
2864
|
-
return source;
|
|
2865
|
-
}
|
|
2866
|
-
return `${source.slice(0, functionRange.start)}${replacement.trimStart()}${source.slice(functionRange.end)}`;
|
|
2867
|
-
}
|
|
2868
3869
|
function resolveManagedDependencyVersion(existingVersion, requiredVersion) {
|
|
2869
3870
|
if (!existingVersion) {
|
|
2870
3871
|
return requiredVersion;
|
|
@@ -2876,15 +3877,12 @@ function resolveManagedDependencyVersion(existingVersion, requiredVersion) {
|
|
|
2876
3877
|
}
|
|
2877
3878
|
return import_semver.default.gte(existingMinimum, requiredMinimum) ? existingVersion : requiredVersion;
|
|
2878
3879
|
}
|
|
2879
|
-
function toPascalCaseFromAbilitySlug(abilitySlug) {
|
|
2880
|
-
return normalizeBlockSlug(abilitySlug).split("-").filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join("");
|
|
2881
|
-
}
|
|
2882
3880
|
function toAbilityCategorySlug(workspaceNamespace) {
|
|
2883
3881
|
const normalizedNamespace = workspaceNamespace.replace(/[^a-z0-9-]+/gu, "-").replace(/-{2,}/gu, "-").replace(/^-|-$/gu, "");
|
|
2884
3882
|
return `${normalizedNamespace || "workspace"}-workflows`;
|
|
2885
3883
|
}
|
|
2886
3884
|
function buildAbilityConfigEntry(abilitySlug, compatibilityPolicy) {
|
|
2887
|
-
const pascalCase =
|
|
3885
|
+
const pascalCase = toPascalCase(abilitySlug);
|
|
2888
3886
|
return [
|
|
2889
3887
|
"\t{",
|
|
2890
3888
|
` clientFile: ${quoteTsString(`src/abilities/${abilitySlug}/client.ts`)},`,
|
|
@@ -2928,7 +3926,7 @@ function buildAbilityConfigSource(abilitySlug, workspaceNamespace) {
|
|
|
2928
3926
|
`;
|
|
2929
3927
|
}
|
|
2930
3928
|
function buildAbilityTypesSource(abilitySlug) {
|
|
2931
|
-
const pascalCase =
|
|
3929
|
+
const pascalCase = toPascalCase(abilitySlug);
|
|
2932
3930
|
return `export interface ${pascalCase}AbilityInput {
|
|
2933
3931
|
contextId: number;
|
|
2934
3932
|
note?: string;
|
|
@@ -2943,7 +3941,7 @@ export interface ${pascalCase}AbilityOutput {
|
|
|
2943
3941
|
`;
|
|
2944
3942
|
}
|
|
2945
3943
|
function buildAbilityDataSource(abilitySlug) {
|
|
2946
|
-
const pascalCase =
|
|
3944
|
+
const pascalCase = toPascalCase(abilitySlug);
|
|
2947
3945
|
const abilityConstBase = abilitySlug.toUpperCase().replace(/[^A-Z0-9]+/gu, "_").replace(/_{2,}/gu, "_").replace(/^_|_$/gu, "");
|
|
2948
3946
|
return `import {
|
|
2949
3947
|
executeAbility,
|
|
@@ -3039,7 +4037,7 @@ export async function run${pascalCase}Ability(
|
|
|
3039
4037
|
`;
|
|
3040
4038
|
}
|
|
3041
4039
|
function buildAbilityClientSource(abilitySlug) {
|
|
3042
|
-
const pascalCase =
|
|
4040
|
+
const pascalCase = toPascalCase(abilitySlug);
|
|
3043
4041
|
return `/**
|
|
3044
4042
|
* Re-export the typed ${pascalCase} ability client helpers.
|
|
3045
4043
|
*
|
|
@@ -3243,8 +4241,8 @@ if ( ! function_exists( '${executeFunctionName}' ) ) {
|
|
|
3243
4241
|
'status' => 'ready',
|
|
3244
4242
|
'summary' => sprintf(
|
|
3245
4243
|
/* translators: 1: workflow title, 2: context id */
|
|
3246
|
-
__( '%1$s processed context %2$d.', ${
|
|
3247
|
-
${
|
|
4244
|
+
__( '%1$s processed context %2$d.', ${quotePhpString(workspace.workspace.textDomain)} ),
|
|
4245
|
+
${quotePhpString(abilityTitle)},
|
|
3248
4246
|
$context_id
|
|
3249
4247
|
),
|
|
3250
4248
|
);
|
|
@@ -3310,11 +4308,11 @@ if ( ! function_exists( '${abilityRegisterFunctionName}' ) ) {
|
|
|
3310
4308
|
$args = array(
|
|
3311
4309
|
'category' => (string) $config['category']['slug'],
|
|
3312
4310
|
'description' => (string) $config['description'],
|
|
3313
|
-
'execute_callback' => ${
|
|
4311
|
+
'execute_callback' => ${quotePhpString(executeFunctionName)},
|
|
3314
4312
|
'label' => (string) $config['label'],
|
|
3315
4313
|
'meta' => ${metaFactoryFunctionName}( $config ),
|
|
3316
4314
|
'output_schema' => $output_schema,
|
|
3317
|
-
'permission_callback' => ${
|
|
4315
|
+
'permission_callback' => ${quotePhpString(permissionFunctionName)},
|
|
3318
4316
|
);
|
|
3319
4317
|
|
|
3320
4318
|
if ( is_array( $input_schema ) ) {
|
|
@@ -3344,30 +4342,30 @@ function buildAbilityRegistrySource(abilitySlugs) {
|
|
|
3344
4342
|
`);
|
|
3345
4343
|
}
|
|
3346
4344
|
function resolveAbilityRegistryPath(projectDir) {
|
|
3347
|
-
const abilitiesDir =
|
|
3348
|
-
return [
|
|
4345
|
+
const abilitiesDir = path9.join(projectDir, "src", "abilities");
|
|
4346
|
+
return [path9.join(abilitiesDir, "index.ts"), path9.join(abilitiesDir, "index.js")].find((candidatePath) => fs5.existsSync(candidatePath)) ?? path9.join(abilitiesDir, "index.ts");
|
|
3349
4347
|
}
|
|
3350
4348
|
function readAbilityRegistrySlugs(registryPath) {
|
|
3351
|
-
if (!
|
|
4349
|
+
if (!fs5.existsSync(registryPath)) {
|
|
3352
4350
|
return [];
|
|
3353
4351
|
}
|
|
3354
|
-
const source =
|
|
4352
|
+
const source = fs5.readFileSync(registryPath, "utf8");
|
|
3355
4353
|
return Array.from(source.matchAll(/^\s*export\s+\*\s+from\s+['"]\.\/([^/'"]+)\/client['"];?\s*$/gmu)).map((match) => match[1]);
|
|
3356
4354
|
}
|
|
3357
4355
|
async function writeAbilityRegistry(projectDir, abilitySlug) {
|
|
3358
|
-
const abilitiesDir =
|
|
4356
|
+
const abilitiesDir = path9.join(projectDir, "src", "abilities");
|
|
3359
4357
|
const registryPath = resolveAbilityRegistryPath(projectDir);
|
|
3360
|
-
await
|
|
4358
|
+
await fsp6.mkdir(abilitiesDir, { recursive: true });
|
|
3361
4359
|
const existingAbilitySlugs = readWorkspaceInventory(projectDir).abilities.map((entry) => entry.slug);
|
|
3362
4360
|
const existingRegistrySlugs = readAbilityRegistrySlugs(registryPath);
|
|
3363
4361
|
const nextAbilitySlugs = Array.from(new Set([...existingAbilitySlugs, ...existingRegistrySlugs, abilitySlug])).sort();
|
|
3364
4362
|
const generatedSection = buildAbilityRegistrySource(nextAbilitySlugs);
|
|
3365
|
-
const existingSource =
|
|
3366
|
-
const generatedSectionPattern = new RegExp(`${
|
|
4363
|
+
const existingSource = fs5.existsSync(registryPath) ? fs5.readFileSync(registryPath, "utf8") : "";
|
|
4364
|
+
const generatedSectionPattern = new RegExp(`${escapeRegex(ABILITY_REGISTRY_START_MARKER)}[\\s\\S]*?${escapeRegex(ABILITY_REGISTRY_END_MARKER)}\\n?`, "u");
|
|
3367
4365
|
const nextSource = existingSource ? generatedSectionPattern.test(existingSource) ? existingSource.replace(generatedSectionPattern, generatedSection) : `${existingSource.trimEnd()}
|
|
3368
4366
|
|
|
3369
4367
|
${generatedSection}` : generatedSection;
|
|
3370
|
-
await
|
|
4368
|
+
await fsp6.writeFile(registryPath, nextSource, "utf8");
|
|
3371
4369
|
}
|
|
3372
4370
|
async function ensureAbilityBootstrapAnchors(workspace) {
|
|
3373
4371
|
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
@@ -3442,7 +4440,6 @@ function ${enqueueFunctionName}() {
|
|
|
3442
4440
|
/add_action\(\s*["']init["']\s*,\s*["'][^"']+_load_textdomain["']\s*\);\s*\n/u,
|
|
3443
4441
|
/\?>\s*$/u
|
|
3444
4442
|
];
|
|
3445
|
-
const hasPhpFunctionDefinition = (functionName) => new RegExp(`function\\s+${escapeRegex3(functionName)}\\s*\\(`, "u").test(nextSource);
|
|
3446
4443
|
const insertPhpSnippet = (snippet) => {
|
|
3447
4444
|
for (const anchor of insertionAnchors) {
|
|
3448
4445
|
const candidate = nextSource.replace(anchor, (match) => `${snippet}
|
|
@@ -3467,13 +4464,13 @@ ${snippet}
|
|
|
3467
4464
|
${snippet}
|
|
3468
4465
|
`;
|
|
3469
4466
|
};
|
|
3470
|
-
if (!hasPhpFunctionDefinition(loadFunctionName)) {
|
|
4467
|
+
if (!hasPhpFunctionDefinition(nextSource, loadFunctionName)) {
|
|
3471
4468
|
insertPhpSnippet(loadFunction);
|
|
3472
4469
|
}
|
|
3473
|
-
if (!hasPhpFunctionDefinition(enqueueFunctionName)) {
|
|
4470
|
+
if (!hasPhpFunctionDefinition(nextSource, enqueueFunctionName)) {
|
|
3474
4471
|
insertPhpSnippet(enqueueFunction);
|
|
3475
|
-
} else if (!
|
|
3476
|
-
nextSource =
|
|
4472
|
+
} else if (!findPhpFunctionRange(nextSource, enqueueFunctionName)?.source.includes("wp_enqueue_script_module")) {
|
|
4473
|
+
nextSource = replacePhpFunctionDefinition(nextSource, enqueueFunctionName, enqueueFunction, { trimReplacementStart: true }) ?? nextSource;
|
|
3477
4474
|
}
|
|
3478
4475
|
if (!nextSource.includes(loadHook)) {
|
|
3479
4476
|
appendPhpSnippet(loadHook);
|
|
@@ -3488,8 +4485,8 @@ ${snippet}
|
|
|
3488
4485
|
});
|
|
3489
4486
|
}
|
|
3490
4487
|
async function ensureAbilityPackageScripts(workspace) {
|
|
3491
|
-
const packageJsonPath =
|
|
3492
|
-
const packageJson = JSON.parse(await
|
|
4488
|
+
const packageJsonPath = path9.join(workspace.projectDir, "package.json");
|
|
4489
|
+
const packageJson = JSON.parse(await fsp6.readFile(packageJsonPath, "utf8"));
|
|
3493
4490
|
const nextScripts = {
|
|
3494
4491
|
...packageJson.scripts ?? {},
|
|
3495
4492
|
"sync-abilities": packageJson.scripts?.["sync-abilities"] ?? "tsx scripts/sync-abilities.ts"
|
|
@@ -3504,11 +4501,11 @@ async function ensureAbilityPackageScripts(workspace) {
|
|
|
3504
4501
|
}
|
|
3505
4502
|
packageJson.scripts = nextScripts;
|
|
3506
4503
|
packageJson.dependencies = nextDependencies;
|
|
3507
|
-
await
|
|
4504
|
+
await fsp6.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, "\t")}
|
|
3508
4505
|
`, "utf8");
|
|
3509
4506
|
}
|
|
3510
4507
|
async function ensureAbilitySyncProjectAnchors(workspace) {
|
|
3511
|
-
const syncProjectScriptPath =
|
|
4508
|
+
const syncProjectScriptPath = path9.join(workspace.projectDir, "scripts", "sync-project.ts");
|
|
3512
4509
|
await patchFile(syncProjectScriptPath, (source) => {
|
|
3513
4510
|
let nextSource = source;
|
|
3514
4511
|
const syncRestConst = "const syncRestScriptPath = path.join( 'scripts', 'sync-rest-contracts.ts' );";
|
|
@@ -3523,7 +4520,7 @@ async function ensureAbilitySyncProjectAnchors(workspace) {
|
|
|
3523
4520
|
if (!nextSource.includes(syncAbilitiesConst)) {
|
|
3524
4521
|
if (!nextSource.includes(syncRestConst)) {
|
|
3525
4522
|
throw new Error([
|
|
3526
|
-
`ensureAbilitySyncProjectAnchors could not patch ${
|
|
4523
|
+
`ensureAbilitySyncProjectAnchors could not patch ${path9.basename(syncProjectScriptPath)}.`,
|
|
3527
4524
|
"Missing the expected sync-rest script constant in scripts/sync-project.ts.",
|
|
3528
4525
|
"Restore the generated template or wire sync-abilities manually before retrying."
|
|
3529
4526
|
].join(" "));
|
|
@@ -3534,7 +4531,7 @@ ${syncAbilitiesConst}`);
|
|
|
3534
4531
|
if (!nextSource.includes("runSyncScript( syncAbilitiesScriptPath, options );")) {
|
|
3535
4532
|
if (!syncRestBlockPattern.test(nextSource)) {
|
|
3536
4533
|
throw new Error([
|
|
3537
|
-
`ensureAbilitySyncProjectAnchors could not patch ${
|
|
4534
|
+
`ensureAbilitySyncProjectAnchors could not patch ${path9.basename(syncProjectScriptPath)}.`,
|
|
3538
4535
|
"Missing the expected sync-rest invocation block in scripts/sync-project.ts.",
|
|
3539
4536
|
"Restore the generated template or wire sync-abilities manually before retrying."
|
|
3540
4537
|
].join(" "));
|
|
@@ -3547,7 +4544,7 @@ ${syncAbilitiesBlock}`);
|
|
|
3547
4544
|
});
|
|
3548
4545
|
}
|
|
3549
4546
|
async function ensureAbilityBuildScriptAnchors(workspace) {
|
|
3550
|
-
const buildScriptPath =
|
|
4547
|
+
const buildScriptPath = path9.join(workspace.projectDir, "scripts", "build-workspace.mjs");
|
|
3551
4548
|
await patchFile(buildScriptPath, (source) => {
|
|
3552
4549
|
let nextSource = source;
|
|
3553
4550
|
if (/['"]src\/abilities\/index\.(?:ts|js)['"]/u.test(nextSource)) {
|
|
@@ -3557,7 +4554,7 @@ async function ensureAbilityBuildScriptAnchors(workspace) {
|
|
|
3557
4554
|
const match = nextSource.match(sharedEntriesPattern);
|
|
3558
4555
|
if (!match || !match[2].includes("src/bindings/index.ts") || !match[2].includes("src/editor-plugins/index.ts")) {
|
|
3559
4556
|
throw new Error([
|
|
3560
|
-
`ensureAbilityBuildScriptAnchors could not patch ${
|
|
4557
|
+
`ensureAbilityBuildScriptAnchors could not patch ${path9.basename(buildScriptPath)}.`,
|
|
3561
4558
|
"Missing the expected shared editor entries array in scripts/build-workspace.mjs.",
|
|
3562
4559
|
"Restore the generated template or wire abilities/index manually before retrying."
|
|
3563
4560
|
].join(" "));
|
|
@@ -3574,7 +4571,7 @@ async function ensureAbilityBuildScriptAnchors(workspace) {
|
|
|
3574
4571
|
});
|
|
3575
4572
|
}
|
|
3576
4573
|
async function ensureAbilityWebpackAnchors(workspace) {
|
|
3577
|
-
const webpackConfigPath =
|
|
4574
|
+
const webpackConfigPath = path9.join(workspace.projectDir, "webpack.config.js");
|
|
3578
4575
|
await patchFile(webpackConfigPath, (source) => {
|
|
3579
4576
|
if (/['"]abilities\/index['"]/u.test(source)) {
|
|
3580
4577
|
return source;
|
|
@@ -3605,7 +4602,7 @@ $2`);
|
|
|
3605
4602
|
const match = source.match(sharedEntriesPattern);
|
|
3606
4603
|
if (!match || !match[1].includes("bindings/index") || !match[1].includes("editor-plugins/index")) {
|
|
3607
4604
|
throw new Error([
|
|
3608
|
-
`ensureAbilityWebpackAnchors could not patch ${
|
|
4605
|
+
`ensureAbilityWebpackAnchors could not patch ${path9.basename(webpackConfigPath)}.`,
|
|
3609
4606
|
"Missing the expected shared editor entries block in webpack.config.js.",
|
|
3610
4607
|
"Restore the generated template or wire abilities/index manually before retrying."
|
|
3611
4608
|
].join(" "));
|
|
@@ -3635,20 +4632,20 @@ async function runAddAbilityCommand({
|
|
|
3635
4632
|
const inventory = readWorkspaceInventory(workspace.projectDir);
|
|
3636
4633
|
assertAbilityDoesNotExist(workspace.projectDir, abilitySlug, inventory);
|
|
3637
4634
|
const compatibilityPolicy = resolveScaffoldCompatibilityPolicy(REQUIRED_WORKSPACE_ABILITY_COMPATIBILITY);
|
|
3638
|
-
const blockConfigPath =
|
|
4635
|
+
const blockConfigPath = path9.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
3639
4636
|
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
3640
|
-
const buildScriptPath =
|
|
3641
|
-
const packageJsonPath =
|
|
3642
|
-
const syncAbilitiesScriptPath =
|
|
3643
|
-
const syncProjectScriptPath =
|
|
3644
|
-
const webpackConfigPath =
|
|
4637
|
+
const buildScriptPath = path9.join(workspace.projectDir, "scripts", "build-workspace.mjs");
|
|
4638
|
+
const packageJsonPath = path9.join(workspace.projectDir, "package.json");
|
|
4639
|
+
const syncAbilitiesScriptPath = path9.join(workspace.projectDir, "scripts", "sync-abilities.ts");
|
|
4640
|
+
const syncProjectScriptPath = path9.join(workspace.projectDir, "scripts", "sync-project.ts");
|
|
4641
|
+
const webpackConfigPath = path9.join(workspace.projectDir, "webpack.config.js");
|
|
3645
4642
|
const abilitiesIndexPath = resolveAbilityRegistryPath(workspace.projectDir);
|
|
3646
|
-
const abilityDir =
|
|
3647
|
-
const configFilePath =
|
|
3648
|
-
const typesFilePath =
|
|
3649
|
-
const dataFilePath =
|
|
3650
|
-
const clientFilePath =
|
|
3651
|
-
const phpFilePath =
|
|
4643
|
+
const abilityDir = path9.join(workspace.projectDir, "src", "abilities", abilitySlug);
|
|
4644
|
+
const configFilePath = path9.join(abilityDir, "ability.config.json");
|
|
4645
|
+
const typesFilePath = path9.join(abilityDir, "types.ts");
|
|
4646
|
+
const dataFilePath = path9.join(abilityDir, "data.ts");
|
|
4647
|
+
const clientFilePath = path9.join(abilityDir, "client.ts");
|
|
4648
|
+
const phpFilePath = path9.join(workspace.projectDir, "inc", "abilities", `${abilitySlug}.php`);
|
|
3652
4649
|
const mutationSnapshot = {
|
|
3653
4650
|
fileSources: await snapshotWorkspaceFiles([
|
|
3654
4651
|
blockConfigPath,
|
|
@@ -3664,21 +4661,21 @@ async function runAddAbilityCommand({
|
|
|
3664
4661
|
targetPaths: [abilityDir, phpFilePath, syncAbilitiesScriptPath]
|
|
3665
4662
|
};
|
|
3666
4663
|
try {
|
|
3667
|
-
await
|
|
3668
|
-
await
|
|
4664
|
+
await fsp6.mkdir(abilityDir, { recursive: true });
|
|
4665
|
+
await fsp6.mkdir(path9.dirname(phpFilePath), { recursive: true });
|
|
3669
4666
|
await ensureAbilityBootstrapAnchors(workspace);
|
|
3670
4667
|
await patchFile(bootstrapPath, (source) => updatePluginHeaderCompatibility(source, compatibilityPolicy));
|
|
3671
4668
|
await ensureAbilityPackageScripts(workspace);
|
|
3672
4669
|
await ensureAbilitySyncProjectAnchors(workspace);
|
|
3673
4670
|
await ensureAbilityBuildScriptAnchors(workspace);
|
|
3674
4671
|
await ensureAbilityWebpackAnchors(workspace);
|
|
3675
|
-
await
|
|
3676
|
-
await
|
|
3677
|
-
await
|
|
3678
|
-
await
|
|
3679
|
-
await
|
|
3680
|
-
await
|
|
3681
|
-
const pascalCase =
|
|
4672
|
+
await fsp6.writeFile(syncAbilitiesScriptPath, buildAbilitySyncScriptSource(), "utf8");
|
|
4673
|
+
await fsp6.writeFile(configFilePath, buildAbilityConfigSource(abilitySlug, workspace.workspace.namespace), "utf8");
|
|
4674
|
+
await fsp6.writeFile(typesFilePath, buildAbilityTypesSource(abilitySlug), "utf8");
|
|
4675
|
+
await fsp6.writeFile(dataFilePath, buildAbilityDataSource(abilitySlug), "utf8");
|
|
4676
|
+
await fsp6.writeFile(clientFilePath, buildAbilityClientSource(abilitySlug), "utf8");
|
|
4677
|
+
await fsp6.writeFile(phpFilePath, buildAbilityPhpSource(abilitySlug, workspace), "utf8");
|
|
4678
|
+
const pascalCase = toPascalCase(abilitySlug);
|
|
3682
4679
|
await syncTypeSchemas2({
|
|
3683
4680
|
jsonSchemaFile: `src/abilities/${abilitySlug}/input.schema.json`,
|
|
3684
4681
|
projectRoot: workspace.projectDir,
|
|
@@ -3705,12 +4702,12 @@ async function runAddAbilityCommand({
|
|
|
3705
4702
|
}
|
|
3706
4703
|
}
|
|
3707
4704
|
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-ai.ts
|
|
3708
|
-
import { promises as
|
|
3709
|
-
import
|
|
4705
|
+
import { promises as fsp8 } from "fs";
|
|
4706
|
+
import path12 from "path";
|
|
3710
4707
|
|
|
3711
4708
|
// ../wp-typia-project-tools/src/runtime/ai-feature-artifacts.ts
|
|
3712
4709
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
3713
|
-
import
|
|
4710
|
+
import path10 from "path";
|
|
3714
4711
|
import {
|
|
3715
4712
|
defineEndpointManifest as defineEndpointManifest2,
|
|
3716
4713
|
syncEndpointClient as syncEndpointClient2,
|
|
@@ -3736,7 +4733,7 @@ function normalizeGeneratedArtifactContent(content) {
|
|
|
3736
4733
|
}
|
|
3737
4734
|
async function reconcileGeneratedArtifact(options) {
|
|
3738
4735
|
if (!options.check) {
|
|
3739
|
-
await mkdir(
|
|
4736
|
+
await mkdir(path10.dirname(options.filePath), {
|
|
3740
4737
|
recursive: true
|
|
3741
4738
|
});
|
|
3742
4739
|
await writeFile(options.filePath, options.content, "utf8");
|
|
@@ -3808,8 +4805,8 @@ async function syncAiFeatureRestArtifacts({
|
|
|
3808
4805
|
const manifest = buildAiFeatureEndpointManifest(variables);
|
|
3809
4806
|
for (const [baseName, contract] of Object.entries(manifest.contracts)) {
|
|
3810
4807
|
await syncTypeSchemas3({
|
|
3811
|
-
jsonSchemaFile:
|
|
3812
|
-
openApiFile:
|
|
4808
|
+
jsonSchemaFile: path10.join(outputDir, "api-schemas", `${baseName}.schema.json`),
|
|
4809
|
+
openApiFile: path10.join(outputDir, "api-schemas", `${baseName}.openapi.json`),
|
|
3813
4810
|
projectRoot: projectDir,
|
|
3814
4811
|
sourceTypeName: contract.sourceTypeName,
|
|
3815
4812
|
typesFile
|
|
@@ -3817,7 +4814,7 @@ async function syncAiFeatureRestArtifacts({
|
|
|
3817
4814
|
}
|
|
3818
4815
|
await syncRestOpenApi2({
|
|
3819
4816
|
manifest,
|
|
3820
|
-
openApiFile:
|
|
4817
|
+
openApiFile: path10.join(outputDir, "api.openapi.json"),
|
|
3821
4818
|
projectRoot: projectDir,
|
|
3822
4819
|
typesFile
|
|
3823
4820
|
}, executionOptions);
|
|
@@ -3835,35 +4832,32 @@ async function syncAiFeatureSchemaArtifact({
|
|
|
3835
4832
|
outputDir,
|
|
3836
4833
|
projectDir
|
|
3837
4834
|
}) {
|
|
3838
|
-
const sourceSchemaPath =
|
|
4835
|
+
const sourceSchemaPath = path10.join(projectDir, outputDir, "api-schemas", "feature-result.schema.json");
|
|
3839
4836
|
const responseSchema = assertJsonObject(JSON.parse(await readFile(sourceSchemaPath, "utf8")), sourceSchemaPath);
|
|
3840
4837
|
const aiSchema = projectWordPressAiSchema(responseSchema);
|
|
3841
4838
|
await reconcileGeneratedArtifact({
|
|
3842
4839
|
check,
|
|
3843
4840
|
content: `${JSON.stringify(aiSchema, null, 2)}
|
|
3844
4841
|
`,
|
|
3845
|
-
filePath:
|
|
4842
|
+
filePath: path10.join(projectDir, aiSchemaFile),
|
|
3846
4843
|
label: "AI feature schema"
|
|
3847
4844
|
});
|
|
3848
4845
|
return {
|
|
3849
4846
|
aiSchema,
|
|
3850
|
-
aiSchemaPath:
|
|
4847
|
+
aiSchemaPath: path10.join(projectDir, aiSchemaFile),
|
|
3851
4848
|
check,
|
|
3852
4849
|
sourceSchemaPath
|
|
3853
4850
|
};
|
|
3854
4851
|
}
|
|
3855
4852
|
|
|
3856
4853
|
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-ai-source-emitters.ts
|
|
3857
|
-
function toPascalCaseFromAiFeatureSlug(slug) {
|
|
3858
|
-
return normalizeBlockSlug(slug).split("-").filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join("");
|
|
3859
|
-
}
|
|
3860
4854
|
function indentMultiline2(source, prefix) {
|
|
3861
4855
|
return source.split(`
|
|
3862
4856
|
`).map((line) => `${prefix}${line}`).join(`
|
|
3863
4857
|
`);
|
|
3864
4858
|
}
|
|
3865
4859
|
function buildAiFeatureConfigEntry(aiFeatureSlug, namespace) {
|
|
3866
|
-
const pascalCase =
|
|
4860
|
+
const pascalCase = toPascalCase(aiFeatureSlug);
|
|
3867
4861
|
const title = toTitleCase(aiFeatureSlug);
|
|
3868
4862
|
const compatibilityPolicy = resolveScaffoldCompatibilityPolicy(OPTIONAL_WORDPRESS_AI_CLIENT_COMPATIBILITY);
|
|
3869
4863
|
const manifest = buildAiFeatureEndpointManifest({
|
|
@@ -3893,7 +4887,7 @@ function buildAiFeatureConfigEntry(aiFeatureSlug, namespace) {
|
|
|
3893
4887
|
`);
|
|
3894
4888
|
}
|
|
3895
4889
|
function buildAiFeatureTypesSource(aiFeatureSlug) {
|
|
3896
|
-
const pascalCase =
|
|
4890
|
+
const pascalCase = toPascalCase(aiFeatureSlug);
|
|
3897
4891
|
return `import { tags } from 'typia';
|
|
3898
4892
|
|
|
3899
4893
|
export interface ${pascalCase}AiFeatureRequest {
|
|
@@ -3931,7 +4925,7 @@ export interface ${pascalCase}AiFeatureResponse {
|
|
|
3931
4925
|
`;
|
|
3932
4926
|
}
|
|
3933
4927
|
function buildAiFeatureValidatorsSource(aiFeatureSlug) {
|
|
3934
|
-
const pascalCase =
|
|
4928
|
+
const pascalCase = toPascalCase(aiFeatureSlug);
|
|
3935
4929
|
return `import typia from 'typia';
|
|
3936
4930
|
|
|
3937
4931
|
import { toValidationResult } from '@wp-typia/rest';
|
|
@@ -3962,7 +4956,7 @@ export const apiValidators = {
|
|
|
3962
4956
|
`;
|
|
3963
4957
|
}
|
|
3964
4958
|
function buildAiFeatureApiSource(aiFeatureSlug) {
|
|
3965
|
-
const pascalCase =
|
|
4959
|
+
const pascalCase = toPascalCase(aiFeatureSlug);
|
|
3966
4960
|
return `import {
|
|
3967
4961
|
callEndpoint,
|
|
3968
4962
|
resolveRestRouteUrl,
|
|
@@ -4017,7 +5011,7 @@ export function runAiFeature( request: ${pascalCase}AiFeatureRequest ) {
|
|
|
4017
5011
|
`;
|
|
4018
5012
|
}
|
|
4019
5013
|
function buildAiFeatureDataSource(aiFeatureSlug) {
|
|
4020
|
-
const pascalCase =
|
|
5014
|
+
const pascalCase = toPascalCase(aiFeatureSlug);
|
|
4021
5015
|
return `import {
|
|
4022
5016
|
useEndpointMutation,
|
|
4023
5017
|
type UseEndpointMutationOptions,
|
|
@@ -4173,12 +5167,9 @@ main().catch( ( error ) => {
|
|
|
4173
5167
|
}
|
|
4174
5168
|
|
|
4175
5169
|
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-ai-anchors.ts
|
|
4176
|
-
import { promises as
|
|
4177
|
-
import
|
|
5170
|
+
import { promises as fsp7 } from "fs";
|
|
5171
|
+
import path11 from "path";
|
|
4178
5172
|
var AI_FEATURE_SERVER_GLOB = "/inc/ai-features/*.php";
|
|
4179
|
-
function escapeRegex4(value) {
|
|
4180
|
-
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
4181
|
-
}
|
|
4182
5173
|
async function ensureAiFeatureBootstrapAnchors(workspace) {
|
|
4183
5174
|
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
4184
5175
|
await patchFile(bootstrapPath, (source) => {
|
|
@@ -4197,7 +5188,6 @@ function ${registerFunctionName}() {
|
|
|
4197
5188
|
/add_action\(\s*["']init["']\s*,\s*["'][^"']+_load_textdomain["']\s*\);\s*\n/u,
|
|
4198
5189
|
/\?>\s*$/u
|
|
4199
5190
|
];
|
|
4200
|
-
const hasPhpFunctionDefinition = (functionName) => new RegExp(`function\\s+${escapeRegex4(functionName)}\\s*\\(`, "u").test(nextSource);
|
|
4201
5191
|
const insertPhpSnippet = (snippet) => {
|
|
4202
5192
|
for (const anchor of insertionAnchors) {
|
|
4203
5193
|
const candidate = nextSource.replace(anchor, (match) => `${snippet}
|
|
@@ -4222,11 +5212,11 @@ ${snippet}
|
|
|
4222
5212
|
${snippet}
|
|
4223
5213
|
`;
|
|
4224
5214
|
};
|
|
4225
|
-
if (!hasPhpFunctionDefinition(registerFunctionName)) {
|
|
5215
|
+
if (!hasPhpFunctionDefinition(nextSource, registerFunctionName)) {
|
|
4226
5216
|
insertPhpSnippet(registerFunction);
|
|
4227
5217
|
} else if (!nextSource.includes(AI_FEATURE_SERVER_GLOB)) {
|
|
4228
5218
|
throw new Error([
|
|
4229
|
-
`Unable to patch ${
|
|
5219
|
+
`Unable to patch ${path11.basename(bootstrapPath)} in ensureAiFeatureBootstrapAnchors.`,
|
|
4230
5220
|
`The existing ${registerFunctionName}() definition does not include ${AI_FEATURE_SERVER_GLOB}.`,
|
|
4231
5221
|
"Restore the generated bootstrap shape or wire the AI feature loader manually before retrying."
|
|
4232
5222
|
].join(" "));
|
|
@@ -4238,8 +5228,8 @@ ${snippet}
|
|
|
4238
5228
|
});
|
|
4239
5229
|
}
|
|
4240
5230
|
async function ensureAiFeaturePackageScripts(workspace) {
|
|
4241
|
-
const packageJsonPath =
|
|
4242
|
-
const packageJson = JSON.parse(await
|
|
5231
|
+
const packageJsonPath = path11.join(workspace.projectDir, "package.json");
|
|
5232
|
+
const packageJson = JSON.parse(await fsp7.readFile(packageJsonPath, "utf8"));
|
|
4243
5233
|
const nextScripts = {
|
|
4244
5234
|
...packageJson.scripts ?? {},
|
|
4245
5235
|
"sync-ai": packageJson.scripts?.["sync-ai"] ?? "tsx scripts/sync-ai-features.ts"
|
|
@@ -4258,7 +5248,7 @@ async function ensureAiFeaturePackageScripts(workspace) {
|
|
|
4258
5248
|
}
|
|
4259
5249
|
packageJson.scripts = nextScripts;
|
|
4260
5250
|
packageJson.devDependencies = nextDevDependencies;
|
|
4261
|
-
await
|
|
5251
|
+
await fsp7.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, "\t")}
|
|
4262
5252
|
`, "utf8");
|
|
4263
5253
|
return {
|
|
4264
5254
|
addedProjectToolsDependency,
|
|
@@ -4266,7 +5256,7 @@ async function ensureAiFeaturePackageScripts(workspace) {
|
|
|
4266
5256
|
};
|
|
4267
5257
|
}
|
|
4268
5258
|
async function ensureAiFeatureSyncProjectAnchors(workspace) {
|
|
4269
|
-
const syncProjectScriptPath =
|
|
5259
|
+
const syncProjectScriptPath = path11.join(workspace.projectDir, "scripts", "sync-project.ts");
|
|
4270
5260
|
await patchFile(syncProjectScriptPath, (source) => {
|
|
4271
5261
|
let nextSource = source;
|
|
4272
5262
|
const syncRestConst = "const syncRestScriptPath = path.join( 'scripts', 'sync-rest-contracts.ts' );";
|
|
@@ -4281,7 +5271,7 @@ async function ensureAiFeatureSyncProjectAnchors(workspace) {
|
|
|
4281
5271
|
if (!nextSource.includes(syncAiConst)) {
|
|
4282
5272
|
if (!nextSource.includes(syncRestConst)) {
|
|
4283
5273
|
throw new Error([
|
|
4284
|
-
`ensureAiFeatureSyncProjectAnchors could not patch ${
|
|
5274
|
+
`ensureAiFeatureSyncProjectAnchors could not patch ${path11.basename(syncProjectScriptPath)}.`,
|
|
4285
5275
|
"Missing the expected sync-rest script constant in scripts/sync-project.ts.",
|
|
4286
5276
|
"Restore the generated template or wire sync-ai manually before retrying."
|
|
4287
5277
|
].join(" "));
|
|
@@ -4292,7 +5282,7 @@ ${syncAiConst}`);
|
|
|
4292
5282
|
if (!nextSource.includes("runSyncScript( syncAiScriptPath, options );")) {
|
|
4293
5283
|
if (!syncRestBlockPattern.test(nextSource)) {
|
|
4294
5284
|
throw new Error([
|
|
4295
|
-
`ensureAiFeatureSyncProjectAnchors could not patch ${
|
|
5285
|
+
`ensureAiFeatureSyncProjectAnchors could not patch ${path11.basename(syncProjectScriptPath)}.`,
|
|
4296
5286
|
"Missing the expected sync-rest invocation block in scripts/sync-project.ts.",
|
|
4297
5287
|
"Restore the generated template or wire sync-ai manually before retrying."
|
|
4298
5288
|
].join(" "));
|
|
@@ -4307,7 +5297,7 @@ ${syncAiBlock}`);
|
|
|
4307
5297
|
function assertSyncRestAnchor2(nextSource, target, anchorDescription, hasAnchor, syncRestScriptPath) {
|
|
4308
5298
|
if (!nextSource.includes(target) && !hasAnchor) {
|
|
4309
5299
|
throw new Error([
|
|
4310
|
-
`ensureAiFeatureSyncRestAnchors could not patch ${
|
|
5300
|
+
`ensureAiFeatureSyncRestAnchors could not patch ${path11.basename(syncRestScriptPath)}.`,
|
|
4311
5301
|
`Missing expected ${anchorDescription} anchor in scripts/sync-rest-contracts.ts.`,
|
|
4312
5302
|
"Restore the generated template or add the AI feature wiring manually before retrying."
|
|
4313
5303
|
].join(" "));
|
|
@@ -4322,7 +5312,7 @@ function replaceRequiredSyncRestSource2(nextSource, target, anchor, replacement,
|
|
|
4322
5312
|
return nextSource.replace(anchor, replacement);
|
|
4323
5313
|
}
|
|
4324
5314
|
async function ensureAiFeatureSyncRestAnchors(workspace) {
|
|
4325
|
-
const syncRestScriptPath =
|
|
5315
|
+
const syncRestScriptPath = path11.join(workspace.projectDir, "scripts", "sync-rest-contracts.ts");
|
|
4326
5316
|
await patchFile(syncRestScriptPath, (source) => {
|
|
4327
5317
|
let nextSource = source;
|
|
4328
5318
|
const importAnchor = [
|
|
@@ -4451,9 +5441,6 @@ async function ensureAiFeatureSyncRestAnchors(workspace) {
|
|
|
4451
5441
|
}
|
|
4452
5442
|
|
|
4453
5443
|
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-ai.ts
|
|
4454
|
-
function quotePhpString4(value) {
|
|
4455
|
-
return `'${value.replace(/\\/gu, "\\\\").replace(/'/gu, "\\'")}'`;
|
|
4456
|
-
}
|
|
4457
5444
|
function buildAiFeaturePhpSource(aiFeatureSlug, namespace, phpPrefix, textDomain) {
|
|
4458
5445
|
const aiFeatureTitle = toTitleCase(aiFeatureSlug);
|
|
4459
5446
|
const aiFeaturePhpId = aiFeatureSlug.replace(/-/g, "_");
|
|
@@ -4549,7 +5536,7 @@ if ( ! function_exists( '${buildPromptFunctionName}' ) ) {
|
|
|
4549
5536
|
function ${buildPromptFunctionName}( array $payload ) {
|
|
4550
5537
|
return sprintf(
|
|
4551
5538
|
'You are helping with the %1$s AI workflow. Read the JSON request payload and return JSON that matches the provided schema. Request payload: %2$s',
|
|
4552
|
-
${
|
|
5539
|
+
${quotePhpString(aiFeatureTitle)},
|
|
4553
5540
|
wp_json_encode( $payload )
|
|
4554
5541
|
);
|
|
4555
5542
|
}
|
|
@@ -4678,8 +5665,8 @@ if ( ! function_exists( '${adminNoticeFunctionName}' ) ) {
|
|
|
4678
5665
|
|
|
4679
5666
|
$message = sprintf(
|
|
4680
5667
|
/* translators: %s: AI feature name. */
|
|
4681
|
-
__( 'The %s AI feature is optional and remains disabled until the WordPress AI Client is available with structured text generation support for the generated schema.', ${
|
|
4682
|
-
${
|
|
5668
|
+
__( 'The %s AI feature is optional and remains disabled until the WordPress AI Client is available with structured text generation support for the generated schema.', ${quotePhpString(textDomain)} ),
|
|
5669
|
+
${quotePhpString(aiFeatureTitle)}
|
|
4683
5670
|
);
|
|
4684
5671
|
printf( '<div class="notice notice-warning"><p>%s</p></div>', esc_html( $message ) );
|
|
4685
5672
|
}
|
|
@@ -4814,7 +5801,7 @@ if ( ! function_exists( '${handlerFunctionName}' ) ) {
|
|
|
4814
5801
|
if ( ! function_exists( '${registerRoutesFunctionName}' ) ) {
|
|
4815
5802
|
function ${registerRoutesFunctionName}() {
|
|
4816
5803
|
register_rest_route(
|
|
4817
|
-
${
|
|
5804
|
+
${quotePhpString(namespace)},
|
|
4818
5805
|
'/ai/${aiFeatureSlug}',
|
|
4819
5806
|
array(
|
|
4820
5807
|
array(
|
|
@@ -4842,18 +5829,18 @@ async function runAddAiFeatureCommand({
|
|
|
4842
5829
|
const compatibilityPolicy = resolveScaffoldCompatibilityPolicy(OPTIONAL_WORDPRESS_AI_CLIENT_COMPATIBILITY);
|
|
4843
5830
|
const inventory = readWorkspaceInventory(workspace.projectDir);
|
|
4844
5831
|
assertAiFeatureDoesNotExist(workspace.projectDir, aiFeatureSlug, inventory);
|
|
4845
|
-
const blockConfigPath =
|
|
5832
|
+
const blockConfigPath = path12.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
4846
5833
|
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
4847
|
-
const packageJsonPath =
|
|
4848
|
-
const syncAiScriptPath =
|
|
4849
|
-
const syncProjectScriptPath =
|
|
4850
|
-
const syncRestScriptPath =
|
|
4851
|
-
const aiFeatureDir =
|
|
4852
|
-
const typesFilePath =
|
|
4853
|
-
const validatorsFilePath =
|
|
4854
|
-
const apiFilePath =
|
|
4855
|
-
const dataFilePath =
|
|
4856
|
-
const phpFilePath =
|
|
5834
|
+
const packageJsonPath = path12.join(workspace.projectDir, "package.json");
|
|
5835
|
+
const syncAiScriptPath = path12.join(workspace.projectDir, "scripts", "sync-ai-features.ts");
|
|
5836
|
+
const syncProjectScriptPath = path12.join(workspace.projectDir, "scripts", "sync-project.ts");
|
|
5837
|
+
const syncRestScriptPath = path12.join(workspace.projectDir, "scripts", "sync-rest-contracts.ts");
|
|
5838
|
+
const aiFeatureDir = path12.join(workspace.projectDir, "src", "ai-features", aiFeatureSlug);
|
|
5839
|
+
const typesFilePath = path12.join(aiFeatureDir, "api-types.ts");
|
|
5840
|
+
const validatorsFilePath = path12.join(aiFeatureDir, "api-validators.ts");
|
|
5841
|
+
const apiFilePath = path12.join(aiFeatureDir, "api.ts");
|
|
5842
|
+
const dataFilePath = path12.join(aiFeatureDir, "data.ts");
|
|
5843
|
+
const phpFilePath = path12.join(workspace.projectDir, "inc", "ai-features", `${aiFeatureSlug}.php`);
|
|
4857
5844
|
const mutationSnapshot = {
|
|
4858
5845
|
fileSources: await snapshotWorkspaceFiles([
|
|
4859
5846
|
blockConfigPath,
|
|
@@ -4867,23 +5854,23 @@ async function runAddAiFeatureCommand({
|
|
|
4867
5854
|
targetPaths: [aiFeatureDir, phpFilePath, syncAiScriptPath]
|
|
4868
5855
|
};
|
|
4869
5856
|
try {
|
|
4870
|
-
await
|
|
4871
|
-
await
|
|
5857
|
+
await fsp8.mkdir(aiFeatureDir, { recursive: true });
|
|
5858
|
+
await fsp8.mkdir(path12.dirname(phpFilePath), { recursive: true });
|
|
4872
5859
|
await ensureAiFeatureBootstrapAnchors(workspace);
|
|
4873
5860
|
await patchFile(bootstrapPath, (source) => updatePluginHeaderCompatibility(source, compatibilityPolicy));
|
|
4874
5861
|
const packageScriptChanges = await ensureAiFeaturePackageScripts(workspace);
|
|
4875
5862
|
await ensureAiFeatureSyncProjectAnchors(workspace);
|
|
4876
5863
|
await ensureAiFeatureSyncRestAnchors(workspace);
|
|
4877
|
-
await
|
|
4878
|
-
await
|
|
4879
|
-
await
|
|
4880
|
-
await
|
|
4881
|
-
await
|
|
4882
|
-
await
|
|
4883
|
-
const pascalCase =
|
|
5864
|
+
await fsp8.writeFile(syncAiScriptPath, buildAiFeatureSyncScriptSource(), "utf8");
|
|
5865
|
+
await fsp8.writeFile(typesFilePath, buildAiFeatureTypesSource(aiFeatureSlug), "utf8");
|
|
5866
|
+
await fsp8.writeFile(validatorsFilePath, buildAiFeatureValidatorsSource(aiFeatureSlug), "utf8");
|
|
5867
|
+
await fsp8.writeFile(apiFilePath, buildAiFeatureApiSource(aiFeatureSlug), "utf8");
|
|
5868
|
+
await fsp8.writeFile(dataFilePath, buildAiFeatureDataSource(aiFeatureSlug), "utf8");
|
|
5869
|
+
await fsp8.writeFile(phpFilePath, buildAiFeaturePhpSource(aiFeatureSlug, resolvedNamespace, workspace.workspace.phpPrefix, workspace.workspace.textDomain), "utf8");
|
|
5870
|
+
const pascalCase = toPascalCase(aiFeatureSlug);
|
|
4884
5871
|
await syncAiFeatureRestArtifacts({
|
|
4885
5872
|
clientFile: `src/ai-features/${aiFeatureSlug}/api-client.ts`,
|
|
4886
|
-
outputDir:
|
|
5873
|
+
outputDir: path12.join("src", "ai-features", aiFeatureSlug),
|
|
4887
5874
|
projectDir: workspace.projectDir,
|
|
4888
5875
|
typesFile: `src/ai-features/${aiFeatureSlug}/api-types.ts`,
|
|
4889
5876
|
validatorsFile: `src/ai-features/${aiFeatureSlug}/api-validators.ts`,
|
|
@@ -4896,7 +5883,7 @@ async function runAddAiFeatureCommand({
|
|
|
4896
5883
|
});
|
|
4897
5884
|
await syncAiFeatureSchemaArtifact({
|
|
4898
5885
|
aiSchemaFile: `src/ai-features/${aiFeatureSlug}/ai-schemas/feature-result.ai.schema.json`,
|
|
4899
|
-
outputDir:
|
|
5886
|
+
outputDir: path12.join("src", "ai-features", aiFeatureSlug),
|
|
4900
5887
|
projectDir: workspace.projectDir
|
|
4901
5888
|
});
|
|
4902
5889
|
await appendWorkspaceInventoryEntries(workspace.projectDir, {
|
|
@@ -4921,7 +5908,168 @@ async function runAddAiFeatureCommand({
|
|
|
4921
5908
|
|
|
4922
5909
|
// ../wp-typia-project-tools/src/runtime/cli-add-workspace.ts
|
|
4923
5910
|
var VARIATIONS_IMPORT_LINE = "import { registerWorkspaceVariations } from './variations';";
|
|
5911
|
+
var VARIATIONS_IMPORT_PATTERN = /^\s*import\s*\{\s*registerWorkspaceVariations\s*\}\s*from\s*["']\.\/variations["']\s*;?\s*$/mu;
|
|
4924
5912
|
var VARIATIONS_CALL_LINE = "registerWorkspaceVariations();";
|
|
5913
|
+
var VARIATIONS_CALL_PATTERN = /registerWorkspaceVariations\s*\(\s*\)\s*;?/u;
|
|
5914
|
+
var BLOCK_STYLES_IMPORT_LINE = "import { registerWorkspaceBlockStyles } from './styles';";
|
|
5915
|
+
var BLOCK_STYLES_IMPORT_PATTERN = /^\s*import\s*\{\s*registerWorkspaceBlockStyles\s*\}\s*from\s*["']\.\/styles["']\s*;?\s*$/mu;
|
|
5916
|
+
var BLOCK_STYLES_CALL_LINE = "registerWorkspaceBlockStyles();";
|
|
5917
|
+
var BLOCK_STYLES_CALL_PATTERN = /registerWorkspaceBlockStyles\s*\(\s*\)\s*;?/u;
|
|
5918
|
+
var BLOCK_TRANSFORMS_IMPORT_LINE = "import { applyWorkspaceBlockTransforms } from './transforms';";
|
|
5919
|
+
var BLOCK_TRANSFORMS_IMPORT_PATTERN = /^\s*import\s*\{\s*applyWorkspaceBlockTransforms\s*\}\s*from\s*["']\.\/transforms["']\s*;?\s*$/mu;
|
|
5920
|
+
var BLOCK_TRANSFORMS_CALL_LINE = "applyWorkspaceBlockTransforms(registration.settings);";
|
|
5921
|
+
var BLOCK_TRANSFORMS_CALL_PATTERN = /applyWorkspaceBlockTransforms\s*\(\s*registration\s*\.\s*settings\s*\)\s*;?/u;
|
|
5922
|
+
var SCAFFOLD_REGISTRATION_SETTINGS_CALL_PATTERN = /registerScaffoldBlockType\s*\(\s*registration\s*\.\s*name\s*,\s*registration\s*\.\s*settings\s*\)\s*;?/u;
|
|
5923
|
+
var FULL_BLOCK_NAME_PATTERN = /^[a-z0-9-]+\/[a-z0-9-]+$/u;
|
|
5924
|
+
function maskSourceSegment(segment) {
|
|
5925
|
+
return segment.replace(/[^\n\r]/gu, " ");
|
|
5926
|
+
}
|
|
5927
|
+
function maskTypeScriptComments(source) {
|
|
5928
|
+
return source.replace(/\/\*[\s\S]*?\*\//gu, maskSourceSegment).replace(/\/\/[^\n\r]*/gu, maskSourceSegment);
|
|
5929
|
+
}
|
|
5930
|
+
function maskTypeScriptCommentsAndLiterals(source) {
|
|
5931
|
+
let maskedSource = "";
|
|
5932
|
+
let index = 0;
|
|
5933
|
+
while (index < source.length) {
|
|
5934
|
+
const current = source[index];
|
|
5935
|
+
const next = source[index + 1];
|
|
5936
|
+
if (current === "/" && next === "/") {
|
|
5937
|
+
const start = index;
|
|
5938
|
+
index += 2;
|
|
5939
|
+
while (index < source.length && source[index] !== `
|
|
5940
|
+
` && source[index] !== "\r") {
|
|
5941
|
+
index += 1;
|
|
5942
|
+
}
|
|
5943
|
+
maskedSource += maskSourceSegment(source.slice(start, index));
|
|
5944
|
+
continue;
|
|
5945
|
+
}
|
|
5946
|
+
if (current === "/" && next === "*") {
|
|
5947
|
+
const start = index;
|
|
5948
|
+
index += 2;
|
|
5949
|
+
while (index < source.length && !(source[index] === "*" && source[index + 1] === "/")) {
|
|
5950
|
+
index += 1;
|
|
5951
|
+
}
|
|
5952
|
+
index = Math.min(index + 2, source.length);
|
|
5953
|
+
maskedSource += maskSourceSegment(source.slice(start, index));
|
|
5954
|
+
continue;
|
|
5955
|
+
}
|
|
5956
|
+
if (current === "'" || current === '"' || current === "`") {
|
|
5957
|
+
const start = index;
|
|
5958
|
+
const quote = current;
|
|
5959
|
+
index += 1;
|
|
5960
|
+
while (index < source.length) {
|
|
5961
|
+
const char = source[index];
|
|
5962
|
+
if (char === "\\") {
|
|
5963
|
+
index += 2;
|
|
5964
|
+
continue;
|
|
5965
|
+
}
|
|
5966
|
+
index += 1;
|
|
5967
|
+
if (char === quote) {
|
|
5968
|
+
break;
|
|
5969
|
+
}
|
|
5970
|
+
}
|
|
5971
|
+
maskedSource += maskSourceSegment(source.slice(start, index));
|
|
5972
|
+
continue;
|
|
5973
|
+
}
|
|
5974
|
+
maskedSource += current;
|
|
5975
|
+
index += 1;
|
|
5976
|
+
}
|
|
5977
|
+
return maskedSource;
|
|
5978
|
+
}
|
|
5979
|
+
function hasExecutablePattern(source, pattern) {
|
|
5980
|
+
return pattern.test(maskTypeScriptCommentsAndLiterals(source));
|
|
5981
|
+
}
|
|
5982
|
+
function hasUncommentedPattern(source, pattern) {
|
|
5983
|
+
return pattern.test(maskTypeScriptComments(source));
|
|
5984
|
+
}
|
|
5985
|
+
function findExecutablePatternMatch(source, patterns) {
|
|
5986
|
+
const maskedSource = maskTypeScriptCommentsAndLiterals(source);
|
|
5987
|
+
for (const pattern of patterns) {
|
|
5988
|
+
const match = pattern.exec(maskedSource);
|
|
5989
|
+
if (match && match.index !== undefined) {
|
|
5990
|
+
return {
|
|
5991
|
+
end: match.index + match[0].length,
|
|
5992
|
+
start: match.index
|
|
5993
|
+
};
|
|
5994
|
+
}
|
|
5995
|
+
}
|
|
5996
|
+
return;
|
|
5997
|
+
}
|
|
5998
|
+
function isIdentifierBoundary(source, index) {
|
|
5999
|
+
if (index < 0 || index >= source.length) {
|
|
6000
|
+
return true;
|
|
6001
|
+
}
|
|
6002
|
+
return !/[A-Za-z0-9_$]/u.test(source[index] ?? "");
|
|
6003
|
+
}
|
|
6004
|
+
function skipWhitespace(source, index) {
|
|
6005
|
+
let cursor = index;
|
|
6006
|
+
while (cursor < source.length && /\s/u.test(source[cursor] ?? "")) {
|
|
6007
|
+
cursor += 1;
|
|
6008
|
+
}
|
|
6009
|
+
return cursor;
|
|
6010
|
+
}
|
|
6011
|
+
function findMatchingDelimiterEnd(source, openIndex, openDelimiter, closeDelimiter) {
|
|
6012
|
+
let depth = 0;
|
|
6013
|
+
for (let index = openIndex;index < source.length; index += 1) {
|
|
6014
|
+
const char = source[index];
|
|
6015
|
+
if (char === openDelimiter) {
|
|
6016
|
+
depth += 1;
|
|
6017
|
+
continue;
|
|
6018
|
+
}
|
|
6019
|
+
if (char === closeDelimiter) {
|
|
6020
|
+
depth -= 1;
|
|
6021
|
+
if (depth === 0) {
|
|
6022
|
+
return index + 1;
|
|
6023
|
+
}
|
|
6024
|
+
}
|
|
6025
|
+
}
|
|
6026
|
+
return;
|
|
6027
|
+
}
|
|
6028
|
+
function findExecutableCallRange(source, callName) {
|
|
6029
|
+
const maskedSource = maskTypeScriptCommentsAndLiterals(source);
|
|
6030
|
+
let searchIndex = 0;
|
|
6031
|
+
while (searchIndex < maskedSource.length) {
|
|
6032
|
+
const callNameIndex = maskedSource.indexOf(callName, searchIndex);
|
|
6033
|
+
if (callNameIndex === -1) {
|
|
6034
|
+
return;
|
|
6035
|
+
}
|
|
6036
|
+
const callNameEnd = callNameIndex + callName.length;
|
|
6037
|
+
if (!isIdentifierBoundary(maskedSource, callNameIndex - 1) || !isIdentifierBoundary(maskedSource, callNameEnd)) {
|
|
6038
|
+
searchIndex = callNameEnd;
|
|
6039
|
+
continue;
|
|
6040
|
+
}
|
|
6041
|
+
let cursor = skipWhitespace(maskedSource, callNameEnd);
|
|
6042
|
+
if (maskedSource[cursor] === "<") {
|
|
6043
|
+
const genericEnd = findMatchingDelimiterEnd(maskedSource, cursor, "<", ">");
|
|
6044
|
+
if (genericEnd === undefined) {
|
|
6045
|
+
searchIndex = callNameEnd;
|
|
6046
|
+
continue;
|
|
6047
|
+
}
|
|
6048
|
+
cursor = skipWhitespace(maskedSource, genericEnd);
|
|
6049
|
+
}
|
|
6050
|
+
if (maskedSource[cursor] !== "(") {
|
|
6051
|
+
searchIndex = callNameEnd;
|
|
6052
|
+
continue;
|
|
6053
|
+
}
|
|
6054
|
+
const callEnd = findMatchingDelimiterEnd(maskedSource, cursor, "(", ")");
|
|
6055
|
+
if (callEnd === undefined) {
|
|
6056
|
+
searchIndex = callNameEnd;
|
|
6057
|
+
continue;
|
|
6058
|
+
}
|
|
6059
|
+
let end = skipWhitespace(maskedSource, callEnd);
|
|
6060
|
+
if (maskedSource[end] === ";") {
|
|
6061
|
+
end += 1;
|
|
6062
|
+
}
|
|
6063
|
+
return {
|
|
6064
|
+
end,
|
|
6065
|
+
start: callNameIndex
|
|
6066
|
+
};
|
|
6067
|
+
}
|
|
6068
|
+
return;
|
|
6069
|
+
}
|
|
6070
|
+
function findBlockRegistrationCallRange(source) {
|
|
6071
|
+
return findExecutableCallRange(source, "registerScaffoldBlockType") ?? findExecutableCallRange(source, "registerBlockType");
|
|
6072
|
+
}
|
|
4925
6073
|
function buildVariationConfigEntry(blockSlug, variationSlug) {
|
|
4926
6074
|
return [
|
|
4927
6075
|
"\t{",
|
|
@@ -4991,49 +6139,300 @@ export function registerWorkspaceVariations() {
|
|
|
4991
6139
|
}
|
|
4992
6140
|
`;
|
|
4993
6141
|
}
|
|
6142
|
+
function buildWorkspaceConstName(prefix, slug) {
|
|
6143
|
+
return `workspace${prefix}_${toSnakeCase(slug)}`;
|
|
6144
|
+
}
|
|
6145
|
+
function buildBlockStyleConfigEntry(blockSlug, styleSlug) {
|
|
6146
|
+
return [
|
|
6147
|
+
"\t{",
|
|
6148
|
+
` block: ${quoteTsString(blockSlug)},`,
|
|
6149
|
+
` file: ${quoteTsString(`src/blocks/${blockSlug}/styles/${styleSlug}.ts`)},`,
|
|
6150
|
+
` slug: ${quoteTsString(styleSlug)},`,
|
|
6151
|
+
"\t},"
|
|
6152
|
+
].join(`
|
|
6153
|
+
`);
|
|
6154
|
+
}
|
|
6155
|
+
function buildBlockTransformConfigEntry(options) {
|
|
6156
|
+
return [
|
|
6157
|
+
"\t{",
|
|
6158
|
+
` block: ${quoteTsString(options.blockSlug)},`,
|
|
6159
|
+
` file: ${quoteTsString(`src/blocks/${options.blockSlug}/transforms/${options.transformSlug}.ts`)},`,
|
|
6160
|
+
` from: ${quoteTsString(options.fromBlockName)},`,
|
|
6161
|
+
` slug: ${quoteTsString(options.transformSlug)},`,
|
|
6162
|
+
` to: ${quoteTsString(options.toBlockName)},`,
|
|
6163
|
+
"\t},"
|
|
6164
|
+
].join(`
|
|
6165
|
+
`);
|
|
6166
|
+
}
|
|
6167
|
+
function getBlockStyleConstBindings(styleSlugs) {
|
|
6168
|
+
const seenConstNames = new Map;
|
|
6169
|
+
return styleSlugs.map((styleSlug) => {
|
|
6170
|
+
const constName = buildWorkspaceConstName("BlockStyle", styleSlug);
|
|
6171
|
+
const previousSlug = seenConstNames.get(constName);
|
|
6172
|
+
if (previousSlug && previousSlug !== styleSlug) {
|
|
6173
|
+
throw new Error(`Style slugs "${previousSlug}" and "${styleSlug}" generate the same registry identifier "${constName}". Rename one of the styles.`);
|
|
6174
|
+
}
|
|
6175
|
+
seenConstNames.set(constName, styleSlug);
|
|
6176
|
+
return { constName, styleSlug };
|
|
6177
|
+
});
|
|
6178
|
+
}
|
|
6179
|
+
function getBlockTransformConstBindings(transformSlugs) {
|
|
6180
|
+
const seenConstNames = new Map;
|
|
6181
|
+
return transformSlugs.map((transformSlug) => {
|
|
6182
|
+
const constName = buildWorkspaceConstName("BlockTransform", transformSlug);
|
|
6183
|
+
const previousSlug = seenConstNames.get(constName);
|
|
6184
|
+
if (previousSlug && previousSlug !== transformSlug) {
|
|
6185
|
+
throw new Error(`Transform slugs "${previousSlug}" and "${transformSlug}" generate the same registry identifier "${constName}". Rename one of the transforms.`);
|
|
6186
|
+
}
|
|
6187
|
+
seenConstNames.set(constName, transformSlug);
|
|
6188
|
+
return { constName, transformSlug };
|
|
6189
|
+
});
|
|
6190
|
+
}
|
|
6191
|
+
function buildBlockStyleSource(styleSlug, textDomain) {
|
|
6192
|
+
const styleTitle = toTitleCase(styleSlug);
|
|
6193
|
+
const styleConstName = buildWorkspaceConstName("BlockStyle", styleSlug);
|
|
6194
|
+
return `import { __ } from '@wordpress/i18n';
|
|
6195
|
+
|
|
6196
|
+
export const ${styleConstName} = {
|
|
6197
|
+
name: ${quoteTsString(styleSlug)},
|
|
6198
|
+
label: __( ${quoteTsString(styleTitle)}, ${quoteTsString(textDomain)} ),
|
|
6199
|
+
} as const;
|
|
6200
|
+
`;
|
|
6201
|
+
}
|
|
6202
|
+
function buildBlockStyleIndexSource(styleSlugs) {
|
|
6203
|
+
const styleBindings = getBlockStyleConstBindings(styleSlugs);
|
|
6204
|
+
const importLines = styleBindings.map(({ constName, styleSlug }) => `import { ${constName} } from './${styleSlug}';`).join(`
|
|
6205
|
+
`);
|
|
6206
|
+
const styleConstNames = styleBindings.map(({ constName }) => constName).join(`,
|
|
6207
|
+
`);
|
|
6208
|
+
return `import { registerBlockStyle } from '@wordpress/blocks';
|
|
6209
|
+
import metadata from '../block.json';
|
|
6210
|
+
${importLines ? `
|
|
6211
|
+
${importLines}` : ""}
|
|
6212
|
+
|
|
6213
|
+
const WORKSPACE_BLOCK_STYLES = [
|
|
6214
|
+
${styleConstNames}
|
|
6215
|
+
// wp-typia add style entries
|
|
6216
|
+
] as const;
|
|
6217
|
+
|
|
6218
|
+
export function registerWorkspaceBlockStyles() {
|
|
6219
|
+
for (const style of WORKSPACE_BLOCK_STYLES) {
|
|
6220
|
+
registerBlockStyle(metadata.name, style);
|
|
6221
|
+
}
|
|
6222
|
+
}
|
|
6223
|
+
`;
|
|
6224
|
+
}
|
|
6225
|
+
function buildBlockTransformSource(options) {
|
|
6226
|
+
const transformTitle = toTitleCase(options.transformSlug);
|
|
6227
|
+
const transformConstName = buildWorkspaceConstName("BlockTransform", options.transformSlug);
|
|
6228
|
+
return `import { createBlock } from '@wordpress/blocks';
|
|
6229
|
+
import { __ } from '@wordpress/i18n';
|
|
6230
|
+
import metadata from '../block.json';
|
|
6231
|
+
|
|
6232
|
+
type TransformAttributes = Record<string, unknown>;
|
|
6233
|
+
type TransformInnerBlock = ReturnType<typeof createBlock>;
|
|
6234
|
+
|
|
6235
|
+
function mapTransformAttributes(attributes: TransformAttributes): TransformAttributes {
|
|
6236
|
+
const content = attributes.content;
|
|
6237
|
+
|
|
6238
|
+
return typeof content === 'string' ? { content } : {};
|
|
6239
|
+
}
|
|
6240
|
+
|
|
6241
|
+
export const ${transformConstName} = {
|
|
6242
|
+
type: 'block',
|
|
6243
|
+
blocks: [${quoteTsString(options.fromBlockName)}],
|
|
6244
|
+
title: __( ${quoteTsString(transformTitle)}, ${quoteTsString(options.textDomain)} ),
|
|
6245
|
+
transform: (
|
|
6246
|
+
attributes: TransformAttributes,
|
|
6247
|
+
innerBlocks: TransformInnerBlock[] = [],
|
|
6248
|
+
) => createBlock(metadata.name, mapTransformAttributes(attributes), innerBlocks),
|
|
6249
|
+
} as const;
|
|
6250
|
+
`;
|
|
6251
|
+
}
|
|
6252
|
+
function buildBlockTransformIndexSource(transformSlugs) {
|
|
6253
|
+
const transformBindings = getBlockTransformConstBindings(transformSlugs);
|
|
6254
|
+
const importLines = transformBindings.map(({ constName, transformSlug }) => `import { ${constName} } from './${transformSlug}';`).join(`
|
|
6255
|
+
`);
|
|
6256
|
+
const transformConstNames = transformBindings.map(({ constName }) => constName).join(`,
|
|
6257
|
+
`);
|
|
6258
|
+
return `${importLines ? `${importLines}
|
|
6259
|
+
|
|
6260
|
+
` : ""}type BlockSettingsWithTransforms = {
|
|
6261
|
+
transforms?: {
|
|
6262
|
+
from?: unknown[];
|
|
6263
|
+
to?: unknown[];
|
|
6264
|
+
};
|
|
6265
|
+
};
|
|
6266
|
+
|
|
6267
|
+
const WORKSPACE_BLOCK_TRANSFORMS = [
|
|
6268
|
+
${transformConstNames}
|
|
6269
|
+
// wp-typia add transform entries
|
|
6270
|
+
] as const;
|
|
6271
|
+
|
|
6272
|
+
export function applyWorkspaceBlockTransforms(settings: BlockSettingsWithTransforms) {
|
|
6273
|
+
const transforms = settings.transforms ?? {};
|
|
6274
|
+
|
|
6275
|
+
settings.transforms = {
|
|
6276
|
+
...transforms,
|
|
6277
|
+
from: [...(transforms.from ?? []), ...WORKSPACE_BLOCK_TRANSFORMS],
|
|
6278
|
+
};
|
|
6279
|
+
}
|
|
6280
|
+
`;
|
|
6281
|
+
}
|
|
4994
6282
|
async function ensureVariationRegistrationHook(blockIndexPath) {
|
|
4995
6283
|
await patchFile(blockIndexPath, (source) => {
|
|
4996
6284
|
let nextSource = source;
|
|
4997
|
-
if (!nextSource
|
|
6285
|
+
if (!hasUncommentedPattern(nextSource, VARIATIONS_IMPORT_PATTERN)) {
|
|
4998
6286
|
nextSource = `${VARIATIONS_IMPORT_LINE}
|
|
4999
6287
|
${nextSource}`;
|
|
5000
6288
|
}
|
|
5001
|
-
if (!nextSource
|
|
5002
|
-
const
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
for (const pattern of callInsertionPatterns) {
|
|
5008
|
-
const candidate = nextSource.replace(pattern, (match) => `${match}
|
|
6289
|
+
if (!hasExecutablePattern(nextSource, VARIATIONS_CALL_PATTERN)) {
|
|
6290
|
+
const callRange = findBlockRegistrationCallRange(nextSource);
|
|
6291
|
+
if (callRange) {
|
|
6292
|
+
nextSource = [
|
|
6293
|
+
nextSource.slice(0, callRange.end),
|
|
6294
|
+
`
|
|
5009
6295
|
${VARIATIONS_CALL_LINE}
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
break;
|
|
5015
|
-
}
|
|
5016
|
-
}
|
|
5017
|
-
if (!inserted) {
|
|
6296
|
+
`,
|
|
6297
|
+
nextSource.slice(callRange.end)
|
|
6298
|
+
].join("");
|
|
6299
|
+
} else {
|
|
5018
6300
|
nextSource = `${nextSource.trimEnd()}
|
|
5019
6301
|
|
|
5020
6302
|
${VARIATIONS_CALL_LINE}
|
|
5021
6303
|
`;
|
|
5022
6304
|
}
|
|
5023
6305
|
}
|
|
5024
|
-
if (!nextSource
|
|
5025
|
-
throw new Error(`Unable to inject ${VARIATIONS_CALL_LINE} into ${
|
|
6306
|
+
if (!hasExecutablePattern(nextSource, VARIATIONS_CALL_PATTERN)) {
|
|
6307
|
+
throw new Error(`Unable to inject ${VARIATIONS_CALL_LINE} into ${path13.basename(blockIndexPath)}.`);
|
|
6308
|
+
}
|
|
6309
|
+
return nextSource;
|
|
6310
|
+
});
|
|
6311
|
+
}
|
|
6312
|
+
async function ensureBlockStyleRegistrationHook(blockIndexPath) {
|
|
6313
|
+
await patchFile(blockIndexPath, (source) => {
|
|
6314
|
+
let nextSource = source;
|
|
6315
|
+
if (!hasUncommentedPattern(nextSource, BLOCK_STYLES_IMPORT_PATTERN)) {
|
|
6316
|
+
nextSource = `${BLOCK_STYLES_IMPORT_LINE}
|
|
6317
|
+
${nextSource}`;
|
|
6318
|
+
}
|
|
6319
|
+
if (!hasExecutablePattern(nextSource, BLOCK_STYLES_CALL_PATTERN)) {
|
|
6320
|
+
const callRange = findBlockRegistrationCallRange(nextSource);
|
|
6321
|
+
if (callRange) {
|
|
6322
|
+
nextSource = [
|
|
6323
|
+
nextSource.slice(0, callRange.end),
|
|
6324
|
+
`
|
|
6325
|
+
${BLOCK_STYLES_CALL_LINE}
|
|
6326
|
+
`,
|
|
6327
|
+
nextSource.slice(callRange.end)
|
|
6328
|
+
].join("");
|
|
6329
|
+
} else {
|
|
6330
|
+
nextSource = `${nextSource.trimEnd()}
|
|
6331
|
+
|
|
6332
|
+
${BLOCK_STYLES_CALL_LINE}
|
|
6333
|
+
`;
|
|
6334
|
+
}
|
|
6335
|
+
}
|
|
6336
|
+
if (!hasExecutablePattern(nextSource, BLOCK_STYLES_CALL_PATTERN)) {
|
|
6337
|
+
throw new Error(`Unable to inject ${BLOCK_STYLES_CALL_LINE} into ${path13.basename(blockIndexPath)}.`);
|
|
6338
|
+
}
|
|
6339
|
+
return nextSource;
|
|
6340
|
+
});
|
|
6341
|
+
}
|
|
6342
|
+
async function ensureBlockTransformRegistrationHook(blockIndexPath) {
|
|
6343
|
+
await patchFile(blockIndexPath, (source) => {
|
|
6344
|
+
let nextSource = source;
|
|
6345
|
+
if (!hasUncommentedPattern(nextSource, BLOCK_TRANSFORMS_IMPORT_PATTERN)) {
|
|
6346
|
+
nextSource = `${BLOCK_TRANSFORMS_IMPORT_LINE}
|
|
6347
|
+
${nextSource}`;
|
|
6348
|
+
}
|
|
6349
|
+
if (!hasExecutablePattern(nextSource, BLOCK_TRANSFORMS_CALL_PATTERN)) {
|
|
6350
|
+
const callRange = findExecutablePatternMatch(nextSource, [
|
|
6351
|
+
SCAFFOLD_REGISTRATION_SETTINGS_CALL_PATTERN
|
|
6352
|
+
]);
|
|
6353
|
+
if (!callRange) {
|
|
6354
|
+
throw new Error(`Unable to inject ${BLOCK_TRANSFORMS_CALL_LINE} into ${path13.basename(blockIndexPath)} because it does not expose a scaffold registration settings object.`);
|
|
6355
|
+
}
|
|
6356
|
+
nextSource = [
|
|
6357
|
+
nextSource.slice(0, callRange.start),
|
|
6358
|
+
`${BLOCK_TRANSFORMS_CALL_LINE}
|
|
6359
|
+
`,
|
|
6360
|
+
nextSource.slice(callRange.start)
|
|
6361
|
+
].join("");
|
|
5026
6362
|
}
|
|
5027
6363
|
return nextSource;
|
|
5028
6364
|
});
|
|
5029
6365
|
}
|
|
5030
6366
|
async function writeVariationRegistry(projectDir, blockSlug, variationSlug) {
|
|
5031
|
-
const variationsDir =
|
|
5032
|
-
const variationsIndexPath =
|
|
5033
|
-
await
|
|
5034
|
-
const existingVariationSlugs =
|
|
6367
|
+
const variationsDir = path13.join(projectDir, "src", "blocks", blockSlug, "variations");
|
|
6368
|
+
const variationsIndexPath = path13.join(variationsDir, "index.ts");
|
|
6369
|
+
await fsp9.mkdir(variationsDir, { recursive: true });
|
|
6370
|
+
const existingVariationSlugs = fs6.readdirSync(variationsDir).filter((entry) => entry.endsWith(".ts") && entry !== "index.ts").map((entry) => entry.replace(/\.ts$/u, ""));
|
|
5035
6371
|
const nextVariationSlugs = Array.from(new Set([...existingVariationSlugs, variationSlug])).sort();
|
|
5036
|
-
await
|
|
6372
|
+
await fsp9.writeFile(variationsIndexPath, buildVariationIndexSource(nextVariationSlugs), "utf8");
|
|
6373
|
+
}
|
|
6374
|
+
async function writeBlockStyleRegistry(projectDir, blockSlug, styleSlug) {
|
|
6375
|
+
const stylesDir = path13.join(projectDir, "src", "blocks", blockSlug, "styles");
|
|
6376
|
+
const stylesIndexPath = path13.join(stylesDir, "index.ts");
|
|
6377
|
+
await fsp9.mkdir(stylesDir, { recursive: true });
|
|
6378
|
+
const existingStyleSlugs = fs6.readdirSync(stylesDir).filter((entry) => entry.endsWith(".ts") && entry !== "index.ts").map((entry) => entry.replace(/\.ts$/u, ""));
|
|
6379
|
+
const nextStyleSlugs = Array.from(new Set([...existingStyleSlugs, styleSlug])).sort();
|
|
6380
|
+
await fsp9.writeFile(stylesIndexPath, buildBlockStyleIndexSource(nextStyleSlugs), "utf8");
|
|
6381
|
+
}
|
|
6382
|
+
async function writeBlockTransformRegistry(projectDir, blockSlug, transformSlug) {
|
|
6383
|
+
const transformsDir = path13.join(projectDir, "src", "blocks", blockSlug, "transforms");
|
|
6384
|
+
const transformsIndexPath = path13.join(transformsDir, "index.ts");
|
|
6385
|
+
await fsp9.mkdir(transformsDir, { recursive: true });
|
|
6386
|
+
const existingTransformSlugs = fs6.readdirSync(transformsDir).filter((entry) => entry.endsWith(".ts") && entry !== "index.ts").map((entry) => entry.replace(/\.ts$/u, ""));
|
|
6387
|
+
const nextTransformSlugs = Array.from(new Set([...existingTransformSlugs, transformSlug])).sort();
|
|
6388
|
+
await fsp9.writeFile(transformsIndexPath, buildBlockTransformIndexSource(nextTransformSlugs), "utf8");
|
|
6389
|
+
}
|
|
6390
|
+
function assertBlockStyleDoesNotExist(projectDir, blockSlug, styleSlug, inventory) {
|
|
6391
|
+
const stylePath = path13.join(projectDir, "src", "blocks", blockSlug, "styles", `${styleSlug}.ts`);
|
|
6392
|
+
if (fs6.existsSync(stylePath)) {
|
|
6393
|
+
throw new Error(`A block style already exists at ${path13.relative(projectDir, stylePath)}. Choose a different name.`);
|
|
6394
|
+
}
|
|
6395
|
+
if (inventory.blockStyles.some((entry) => entry.block === blockSlug && entry.slug === styleSlug)) {
|
|
6396
|
+
throw new Error(`A block style inventory entry already exists for ${blockSlug}/${styleSlug}. Choose a different name.`);
|
|
6397
|
+
}
|
|
6398
|
+
}
|
|
6399
|
+
function assertBlockTransformDoesNotExist(projectDir, blockSlug, transformSlug, inventory) {
|
|
6400
|
+
const transformPath = path13.join(projectDir, "src", "blocks", blockSlug, "transforms", `${transformSlug}.ts`);
|
|
6401
|
+
if (fs6.existsSync(transformPath)) {
|
|
6402
|
+
throw new Error(`A block transform already exists at ${path13.relative(projectDir, transformPath)}. Choose a different name.`);
|
|
6403
|
+
}
|
|
6404
|
+
if (inventory.blockTransforms.some((entry) => entry.block === blockSlug && entry.slug === transformSlug)) {
|
|
6405
|
+
throw new Error(`A block transform inventory entry already exists for ${blockSlug}/${transformSlug}. Choose a different name.`);
|
|
6406
|
+
}
|
|
6407
|
+
}
|
|
6408
|
+
function assertFullBlockName(blockName, flagName) {
|
|
6409
|
+
const trimmed = blockName.trim();
|
|
6410
|
+
if (!trimmed) {
|
|
6411
|
+
throw new Error(`\`${flagName}\` requires a block name.`);
|
|
6412
|
+
}
|
|
6413
|
+
if (!FULL_BLOCK_NAME_PATTERN.test(trimmed)) {
|
|
6414
|
+
throw new Error(`\`${flagName}\` must use <namespace/block-slug> format.`);
|
|
6415
|
+
}
|
|
6416
|
+
return trimmed;
|
|
6417
|
+
}
|
|
6418
|
+
function resolveWorkspaceTargetBlockName(blockName, namespace, flagName) {
|
|
6419
|
+
const trimmed = blockName.trim();
|
|
6420
|
+
if (!trimmed) {
|
|
6421
|
+
throw new Error(`\`${flagName}\` requires <block-slug|namespace/block-slug>.`);
|
|
6422
|
+
}
|
|
6423
|
+
const blockNameSegments = trimmed.split("/");
|
|
6424
|
+
if (blockNameSegments.length > 2 || blockNameSegments.some((segment) => segment.trim() === "")) {
|
|
6425
|
+
throw new Error(`\`${flagName}\` must use <block-slug|namespace/block-slug> format.`);
|
|
6426
|
+
}
|
|
6427
|
+
const [maybeNamespace, maybeSlug] = blockNameSegments.length === 2 ? blockNameSegments : [undefined, blockNameSegments[0]];
|
|
6428
|
+
if (maybeNamespace && maybeNamespace !== namespace) {
|
|
6429
|
+
throw new Error(`\`${flagName}\` references namespace "${maybeNamespace}". Expected "${namespace}".`);
|
|
6430
|
+
}
|
|
6431
|
+
const blockSlug = normalizeBlockSlug(maybeSlug ?? "");
|
|
6432
|
+
return {
|
|
6433
|
+
blockName: `${namespace}/${blockSlug}`,
|
|
6434
|
+
blockSlug
|
|
6435
|
+
};
|
|
5037
6436
|
}
|
|
5038
6437
|
async function runAddVariationCommand({
|
|
5039
6438
|
blockName,
|
|
@@ -5046,11 +6445,12 @@ async function runAddVariationCommand({
|
|
|
5046
6445
|
const inventory = readWorkspaceInventory(workspace.projectDir);
|
|
5047
6446
|
resolveWorkspaceBlock(inventory, blockSlug);
|
|
5048
6447
|
assertVariationDoesNotExist(workspace.projectDir, blockSlug, variationSlug, inventory);
|
|
5049
|
-
const blockConfigPath =
|
|
5050
|
-
const blockIndexPath =
|
|
5051
|
-
const variationsDir =
|
|
5052
|
-
const variationFilePath =
|
|
5053
|
-
const variationsIndexPath =
|
|
6448
|
+
const blockConfigPath = path13.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
6449
|
+
const blockIndexPath = path13.join(workspace.projectDir, "src", "blocks", blockSlug, "index.tsx");
|
|
6450
|
+
const variationsDir = path13.join(workspace.projectDir, "src", "blocks", blockSlug, "variations");
|
|
6451
|
+
const variationFilePath = path13.join(variationsDir, `${variationSlug}.ts`);
|
|
6452
|
+
const variationsIndexPath = path13.join(variationsDir, "index.ts");
|
|
6453
|
+
const shouldRemoveVariationsDirOnRollback = !fs6.existsSync(variationsDir);
|
|
5054
6454
|
const mutationSnapshot = {
|
|
5055
6455
|
fileSources: await snapshotWorkspaceFiles([
|
|
5056
6456
|
blockConfigPath,
|
|
@@ -5058,11 +6458,14 @@ async function runAddVariationCommand({
|
|
|
5058
6458
|
variationsIndexPath
|
|
5059
6459
|
]),
|
|
5060
6460
|
snapshotDirs: [],
|
|
5061
|
-
targetPaths: [
|
|
6461
|
+
targetPaths: [
|
|
6462
|
+
variationFilePath,
|
|
6463
|
+
...shouldRemoveVariationsDirOnRollback ? [variationsDir] : []
|
|
6464
|
+
]
|
|
5062
6465
|
};
|
|
5063
6466
|
try {
|
|
5064
|
-
await
|
|
5065
|
-
await
|
|
6467
|
+
await fsp9.mkdir(variationsDir, { recursive: true });
|
|
6468
|
+
await fsp9.writeFile(variationFilePath, buildVariationSource(variationSlug, workspace.workspace.textDomain), "utf8");
|
|
5066
6469
|
await writeVariationRegistry(workspace.projectDir, blockSlug, variationSlug);
|
|
5067
6470
|
await ensureVariationRegistrationHook(blockIndexPath);
|
|
5068
6471
|
await appendWorkspaceInventoryEntries(workspace.projectDir, {
|
|
@@ -5078,6 +6481,115 @@ async function runAddVariationCommand({
|
|
|
5078
6481
|
throw error;
|
|
5079
6482
|
}
|
|
5080
6483
|
}
|
|
6484
|
+
async function runAddBlockStyleCommand({
|
|
6485
|
+
blockName,
|
|
6486
|
+
cwd = process.cwd(),
|
|
6487
|
+
styleName
|
|
6488
|
+
}) {
|
|
6489
|
+
const workspace = resolveWorkspaceProject(cwd);
|
|
6490
|
+
const blockSlug = normalizeBlockSlug(blockName);
|
|
6491
|
+
const styleSlug = assertValidGeneratedSlug("Style name", normalizeBlockSlug(styleName), "wp-typia add style <name> --block <block-slug>");
|
|
6492
|
+
const inventory = readWorkspaceInventory(workspace.projectDir);
|
|
6493
|
+
resolveWorkspaceBlock(inventory, blockSlug);
|
|
6494
|
+
assertBlockStyleDoesNotExist(workspace.projectDir, blockSlug, styleSlug, inventory);
|
|
6495
|
+
const blockConfigPath = path13.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
6496
|
+
const blockIndexPath = path13.join(workspace.projectDir, "src", "blocks", blockSlug, "index.tsx");
|
|
6497
|
+
const stylesDir = path13.join(workspace.projectDir, "src", "blocks", blockSlug, "styles");
|
|
6498
|
+
const styleFilePath = path13.join(stylesDir, `${styleSlug}.ts`);
|
|
6499
|
+
const stylesIndexPath = path13.join(stylesDir, "index.ts");
|
|
6500
|
+
const shouldRemoveStylesDirOnRollback = !fs6.existsSync(stylesDir);
|
|
6501
|
+
const mutationSnapshot = {
|
|
6502
|
+
fileSources: await snapshotWorkspaceFiles([
|
|
6503
|
+
blockConfigPath,
|
|
6504
|
+
blockIndexPath,
|
|
6505
|
+
stylesIndexPath
|
|
6506
|
+
]),
|
|
6507
|
+
snapshotDirs: [],
|
|
6508
|
+
targetPaths: [
|
|
6509
|
+
styleFilePath,
|
|
6510
|
+
...shouldRemoveStylesDirOnRollback ? [stylesDir] : []
|
|
6511
|
+
]
|
|
6512
|
+
};
|
|
6513
|
+
try {
|
|
6514
|
+
await fsp9.mkdir(stylesDir, { recursive: true });
|
|
6515
|
+
await fsp9.writeFile(styleFilePath, buildBlockStyleSource(styleSlug, workspace.workspace.textDomain), "utf8");
|
|
6516
|
+
await writeBlockStyleRegistry(workspace.projectDir, blockSlug, styleSlug);
|
|
6517
|
+
await ensureBlockStyleRegistrationHook(blockIndexPath);
|
|
6518
|
+
await appendWorkspaceInventoryEntries(workspace.projectDir, {
|
|
6519
|
+
blockStyleEntries: [buildBlockStyleConfigEntry(blockSlug, styleSlug)]
|
|
6520
|
+
});
|
|
6521
|
+
return {
|
|
6522
|
+
blockSlug,
|
|
6523
|
+
projectDir: workspace.projectDir,
|
|
6524
|
+
styleSlug
|
|
6525
|
+
};
|
|
6526
|
+
} catch (error) {
|
|
6527
|
+
await rollbackWorkspaceMutation(mutationSnapshot);
|
|
6528
|
+
throw error;
|
|
6529
|
+
}
|
|
6530
|
+
}
|
|
6531
|
+
async function runAddBlockTransformCommand({
|
|
6532
|
+
cwd = process.cwd(),
|
|
6533
|
+
fromBlockName,
|
|
6534
|
+
toBlockName,
|
|
6535
|
+
transformName
|
|
6536
|
+
}) {
|
|
6537
|
+
const workspace = resolveWorkspaceProject(cwd);
|
|
6538
|
+
const transformSlug = assertValidGeneratedSlug("Transform name", normalizeBlockSlug(transformName), "wp-typia add transform <name> --from <namespace/block> --to <block-slug|namespace/block-slug>");
|
|
6539
|
+
const resolvedFromBlockName = assertFullBlockName(fromBlockName, "--from");
|
|
6540
|
+
const target = resolveWorkspaceTargetBlockName(toBlockName, workspace.workspace.namespace, "--to");
|
|
6541
|
+
const inventory = readWorkspaceInventory(workspace.projectDir);
|
|
6542
|
+
resolveWorkspaceBlock(inventory, target.blockSlug);
|
|
6543
|
+
assertBlockTransformDoesNotExist(workspace.projectDir, target.blockSlug, transformSlug, inventory);
|
|
6544
|
+
const blockConfigPath = path13.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
6545
|
+
const blockIndexPath = path13.join(workspace.projectDir, "src", "blocks", target.blockSlug, "index.tsx");
|
|
6546
|
+
const transformsDir = path13.join(workspace.projectDir, "src", "blocks", target.blockSlug, "transforms");
|
|
6547
|
+
const transformFilePath = path13.join(transformsDir, `${transformSlug}.ts`);
|
|
6548
|
+
const transformsIndexPath = path13.join(transformsDir, "index.ts");
|
|
6549
|
+
const shouldRemoveTransformsDirOnRollback = !fs6.existsSync(transformsDir);
|
|
6550
|
+
const mutationSnapshot = {
|
|
6551
|
+
fileSources: await snapshotWorkspaceFiles([
|
|
6552
|
+
blockConfigPath,
|
|
6553
|
+
blockIndexPath,
|
|
6554
|
+
transformsIndexPath
|
|
6555
|
+
]),
|
|
6556
|
+
snapshotDirs: [],
|
|
6557
|
+
targetPaths: [
|
|
6558
|
+
transformFilePath,
|
|
6559
|
+
...shouldRemoveTransformsDirOnRollback ? [transformsDir] : []
|
|
6560
|
+
]
|
|
6561
|
+
};
|
|
6562
|
+
try {
|
|
6563
|
+
await fsp9.mkdir(transformsDir, { recursive: true });
|
|
6564
|
+
await fsp9.writeFile(transformFilePath, buildBlockTransformSource({
|
|
6565
|
+
fromBlockName: resolvedFromBlockName,
|
|
6566
|
+
textDomain: workspace.workspace.textDomain,
|
|
6567
|
+
transformSlug
|
|
6568
|
+
}), "utf8");
|
|
6569
|
+
await writeBlockTransformRegistry(workspace.projectDir, target.blockSlug, transformSlug);
|
|
6570
|
+
await ensureBlockTransformRegistrationHook(blockIndexPath);
|
|
6571
|
+
await appendWorkspaceInventoryEntries(workspace.projectDir, {
|
|
6572
|
+
blockTransformEntries: [
|
|
6573
|
+
buildBlockTransformConfigEntry({
|
|
6574
|
+
blockSlug: target.blockSlug,
|
|
6575
|
+
fromBlockName: resolvedFromBlockName,
|
|
6576
|
+
toBlockName: target.blockName,
|
|
6577
|
+
transformSlug
|
|
6578
|
+
})
|
|
6579
|
+
]
|
|
6580
|
+
});
|
|
6581
|
+
return {
|
|
6582
|
+
blockSlug: target.blockSlug,
|
|
6583
|
+
fromBlockName: resolvedFromBlockName,
|
|
6584
|
+
projectDir: workspace.projectDir,
|
|
6585
|
+
toBlockName: target.blockName,
|
|
6586
|
+
transformSlug
|
|
6587
|
+
};
|
|
6588
|
+
} catch (error) {
|
|
6589
|
+
await rollbackWorkspaceMutation(mutationSnapshot);
|
|
6590
|
+
throw error;
|
|
6591
|
+
}
|
|
6592
|
+
}
|
|
5081
6593
|
async function runAddHookedBlockCommand({
|
|
5082
6594
|
anchorBlockName,
|
|
5083
6595
|
blockName,
|
|
@@ -5095,7 +6607,7 @@ async function runAddHookedBlockCommand({
|
|
|
5095
6607
|
throw new Error("`wp-typia add hooked-block` cannot hook a block relative to its own block name.");
|
|
5096
6608
|
}
|
|
5097
6609
|
const { blockJson, blockJsonPath } = readWorkspaceBlockJson(workspace.projectDir, blockSlug);
|
|
5098
|
-
const blockJsonRelativePath =
|
|
6610
|
+
const blockJsonRelativePath = path13.relative(workspace.projectDir, blockJsonPath);
|
|
5099
6611
|
const blockHooks = getMutableBlockHooks(blockJson, blockJsonRelativePath);
|
|
5100
6612
|
if (Object.prototype.hasOwnProperty.call(blockHooks, resolvedAnchorBlockName)) {
|
|
5101
6613
|
throw new Error(`${blockJsonRelativePath} already defines a blockHooks entry for "${resolvedAnchorBlockName}".`);
|
|
@@ -5107,7 +6619,7 @@ async function runAddHookedBlockCommand({
|
|
|
5107
6619
|
};
|
|
5108
6620
|
try {
|
|
5109
6621
|
blockHooks[resolvedAnchorBlockName] = resolvedPosition;
|
|
5110
|
-
await
|
|
6622
|
+
await fsp9.writeFile(blockJsonPath, JSON.stringify(blockJson, null, "\t"), "utf8");
|
|
5111
6623
|
return {
|
|
5112
6624
|
anchorBlockName: resolvedAnchorBlockName,
|
|
5113
6625
|
blockSlug,
|
|
@@ -5126,9 +6638,12 @@ export {
|
|
|
5126
6638
|
runAddPatternCommand,
|
|
5127
6639
|
runAddHookedBlockCommand,
|
|
5128
6640
|
runAddEditorPluginCommand,
|
|
6641
|
+
runAddBlockTransformCommand,
|
|
6642
|
+
runAddBlockStyleCommand,
|
|
5129
6643
|
runAddBlockCommand,
|
|
5130
6644
|
runAddBindingSourceCommand,
|
|
5131
6645
|
runAddAiFeatureCommand,
|
|
6646
|
+
runAddAdminViewCommand,
|
|
5132
6647
|
runAddAbilityCommand,
|
|
5133
6648
|
getWorkspaceBlockSelectOptions,
|
|
5134
6649
|
formatAddHelpText,
|
|
@@ -5137,4 +6652,4 @@ export {
|
|
|
5137
6652
|
ADD_BLOCK_TEMPLATE_IDS
|
|
5138
6653
|
};
|
|
5139
6654
|
|
|
5140
|
-
//# debugId=
|
|
6655
|
+
//# debugId=7C11AF8BB98BD78764756E2164756E21
|