wp-typia 0.23.0 → 0.23.1
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 +2 -1
- package/bin/routing-metadata.generated.js +4 -0
- package/dist-bunli/.bunli/commands.gen.js +4103 -3086
- package/dist-bunli/{cli-hhp1d348.js → cli-1170yyve.js} +8 -7
- package/dist-bunli/{cli-qse6myha.js → cli-8hxf9qw6.js} +11 -3
- package/dist-bunli/{cli-8reep89s.js → cli-9fx0qgb7.js} +2 -2
- package/dist-bunli/{cli-add-21bvpfgw.js → cli-add-xjaaa01x.js} +1560 -1525
- package/dist-bunli/{cli-52ke0ptp.js → cli-am5x7tb4.js} +8 -2
- package/dist-bunli/cli-ccax7s0s.js +34 -0
- package/dist-bunli/{cli-diagnostics-5dvztm7q.js → cli-diagnostics-10drxh34.js} +1 -1
- package/dist-bunli/{cli-doctor-wy2yjsge.js → cli-doctor-19e8313m.js} +602 -459
- package/dist-bunli/{cli-2rqf6t0b.js → cli-e4bwd81c.js} +8 -11
- package/dist-bunli/{cli-9npd9was.js → cli-epsczb1c.js} +12 -10
- package/dist-bunli/{cli-agywa5n6.js → cli-fp16mntv.js} +8 -4
- package/dist-bunli/{cli-init-xnsbxncv.js → cli-init-2b4yn2cc.js} +14 -10
- package/dist-bunli/{cli-ts9thts5.js → cli-k5q5v8g6.js} +184 -162
- package/dist-bunli/{cli-c2acv5dv.js → cli-nvs5atj1.js} +2 -2
- package/dist-bunli/{cli-prompt-614tq57c.js → cli-prompt-ncyg68rn.js} +1 -1
- package/dist-bunli/{cli-bq2v559b.js → cli-rdcga1bd.js} +31 -13
- package/dist-bunli/{cli-scaffold-zhp2ym8z.js → cli-scaffold-4tjw4jk5.js} +27 -15
- package/dist-bunli/{cli-templates-hc71dfc2.js → cli-templates-g8t4fm11.js} +3 -2
- package/dist-bunli/{cli-p95wr1q8.js → cli-tq730sqt.js} +6 -3
- package/dist-bunli/{cli-1meywwsy.js → cli-y7w3pybs.js} +848 -246
- package/dist-bunli/{cli-z5qkx2pn.js → cli-ymecd15q.js} +37 -10
- package/dist-bunli/cli.js +4 -4
- package/dist-bunli/{command-list-aqrkx021.js → command-list-vme7dr5v.js} +81 -45
- package/dist-bunli/{create-template-validation-rtec5sng.js → create-template-validation-4fr851vg.js} +5 -4
- package/dist-bunli/{migrations-bx0yvc2v.js → migrations-pb5vvtdp.js} +9 -8
- package/dist-bunli/node-cli.js +399 -317
- package/dist-bunli/{workspace-project-csnnggz6.js → workspace-project-gmv2a71z.js} +4 -3
- package/package.json +2 -2
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
import {
|
|
3
3
|
OPTIONAL_WORDPRESS_AI_CLIENT_COMPATIBILITY,
|
|
4
4
|
REQUIRED_WORKSPACE_ABILITY_COMPATIBILITY,
|
|
5
|
+
appendPhpSnippetBeforeClosingTag,
|
|
5
6
|
assertExternalLayerCompositionOptions,
|
|
6
7
|
copyInterpolatedDirectory,
|
|
7
8
|
createScaffoldCompatibilityConfig,
|
|
9
|
+
executeWorkspaceMutationPlan,
|
|
8
10
|
getDefaultAnswers,
|
|
11
|
+
insertPhpSnippetBeforeWorkspaceAnchors,
|
|
9
12
|
isCompoundPersistenceEnabled,
|
|
10
13
|
listInterpolatedDirectoryOutputs,
|
|
11
14
|
normalizeOptionalCliString,
|
|
@@ -17,14 +20,15 @@ import {
|
|
|
17
20
|
resolveOptionalInteractiveExternalLayerId,
|
|
18
21
|
resolveScaffoldCompatibilityPolicy,
|
|
19
22
|
resolveTemplateSeed,
|
|
23
|
+
runAddIntegrationEnvCommand,
|
|
20
24
|
scaffoldProject,
|
|
21
25
|
syncPersistenceRestArtifacts,
|
|
22
26
|
updatePluginHeaderCompatibility
|
|
23
|
-
} from "./cli-
|
|
27
|
+
} from "./cli-y7w3pybs.js";
|
|
24
28
|
import {
|
|
25
29
|
parseTemplateLocator,
|
|
26
30
|
require_semver
|
|
27
|
-
} from "./cli-
|
|
31
|
+
} from "./cli-9fx0qgb7.js";
|
|
28
32
|
import {
|
|
29
33
|
ADMIN_VIEWS_ASSET,
|
|
30
34
|
ADMIN_VIEWS_PHP_GLOB,
|
|
@@ -47,7 +51,7 @@ import {
|
|
|
47
51
|
isAdminViewRestResourceSource,
|
|
48
52
|
maskTypeScriptCommentsAndLiterals
|
|
49
53
|
} from "./cli-j8et6jvr.js";
|
|
50
|
-
import"./cli-
|
|
54
|
+
import"./cli-nvs5atj1.js";
|
|
51
55
|
import {
|
|
52
56
|
DEFAULT_WORDPRESS_ABILITIES_VERSION,
|
|
53
57
|
DEFAULT_WORDPRESS_CORE_ABILITIES_VERSION,
|
|
@@ -57,19 +61,19 @@ import {
|
|
|
57
61
|
DEFAULT_WP_TYPIA_DATAVIEWS_VERSION,
|
|
58
62
|
getPackageVersions,
|
|
59
63
|
resolveManagedPackageVersionRange
|
|
60
|
-
} from "./cli-
|
|
64
|
+
} from "./cli-fp16mntv.js";
|
|
61
65
|
import {
|
|
62
66
|
SHARED_WORKSPACE_TEMPLATE_ROOT
|
|
63
|
-
} from "./cli-
|
|
67
|
+
} from "./cli-8hxf9qw6.js";
|
|
64
68
|
import {
|
|
65
69
|
snapshotProjectVersion
|
|
66
|
-
} from "./cli-
|
|
70
|
+
} from "./cli-epsczb1c.js";
|
|
67
71
|
import {
|
|
68
72
|
ensureMigrationDirectories,
|
|
69
73
|
parseMigrationConfig,
|
|
70
74
|
writeInitialMigrationScaffold,
|
|
71
75
|
writeMigrationConfig
|
|
72
|
-
} from "./cli-
|
|
76
|
+
} from "./cli-e4bwd81c.js";
|
|
73
77
|
import {
|
|
74
78
|
ADD_BLOCK_TEMPLATE_IDS,
|
|
75
79
|
EDITOR_PLUGIN_SLOT_IDS,
|
|
@@ -89,7 +93,6 @@ import {
|
|
|
89
93
|
assertValidGeneratedSlug,
|
|
90
94
|
assertValidHookAnchor,
|
|
91
95
|
assertValidHookedBlockPosition,
|
|
92
|
-
assertValidIntegrationEnvService,
|
|
93
96
|
assertValidManualRestContractAuth,
|
|
94
97
|
assertValidManualRestContractHttpMethod,
|
|
95
98
|
assertValidPostMetaPostType,
|
|
@@ -97,6 +100,7 @@ import {
|
|
|
97
100
|
assertValidTypeScriptIdentifier,
|
|
98
101
|
assertVariationDoesNotExist,
|
|
99
102
|
buildWorkspacePhpPrefix,
|
|
103
|
+
collectRestRouteNamedCaptureNames,
|
|
100
104
|
escapeRegex,
|
|
101
105
|
findPhpFunctionRange,
|
|
102
106
|
formatAddHelpText,
|
|
@@ -136,7 +140,7 @@ import {
|
|
|
136
140
|
toPascalCase,
|
|
137
141
|
toSnakeCase,
|
|
138
142
|
toTitleCase
|
|
139
|
-
} from "./cli-
|
|
143
|
+
} from "./cli-k5q5v8g6.js";
|
|
140
144
|
import"./cli-cvxvcw7c.js";
|
|
141
145
|
import {
|
|
142
146
|
createManagedTempRoot
|
|
@@ -144,15 +148,18 @@ import {
|
|
|
144
148
|
import {
|
|
145
149
|
ADD_KIND_IDS
|
|
146
150
|
} from "./cli-43mx1vfb.js";
|
|
147
|
-
import"./cli-
|
|
151
|
+
import"./cli-tq730sqt.js";
|
|
148
152
|
import {
|
|
149
153
|
resolveWorkspaceProject
|
|
150
|
-
} from "./cli-
|
|
154
|
+
} from "./cli-1170yyve.js";
|
|
155
|
+
import {
|
|
156
|
+
formatInstallCommand
|
|
157
|
+
} from "./cli-am5x7tb4.js";
|
|
151
158
|
import {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
} from "./cli-
|
|
155
|
-
import"./cli-
|
|
159
|
+
readJsonFile,
|
|
160
|
+
safeJsonParse
|
|
161
|
+
} from "./cli-ccax7s0s.js";
|
|
162
|
+
import"./cli-rdcga1bd.js";
|
|
156
163
|
import {
|
|
157
164
|
__reExport,
|
|
158
165
|
__toESM
|
|
@@ -980,58 +987,14 @@ function resolveAdminViewCoreDataSource(source) {
|
|
|
980
987
|
import { promises as fsp3 } from "fs";
|
|
981
988
|
import path6 from "path";
|
|
982
989
|
|
|
983
|
-
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-admin-view-templates.ts
|
|
984
|
-
|
|
985
|
-
function getAdminViewRelativeModuleSpecifier(adminViewSlug, workspaceFile) {
|
|
986
|
-
const adminViewDir = `src/admin-views/${adminViewSlug}`;
|
|
987
|
-
const normalizedFile = workspaceFile.replace(/\\/gu, "/");
|
|
988
|
-
const modulePath = normalizedFile.replace(/\.[cm]?[jt]sx?$/u, "");
|
|
989
|
-
const relativeModulePath = path4.posix.relative(adminViewDir, modulePath);
|
|
990
|
-
return relativeModulePath.startsWith(".") ? relativeModulePath : `./${relativeModulePath}`;
|
|
991
|
-
}
|
|
992
|
-
function buildAdminViewConfigEntry(adminViewSlug, source) {
|
|
993
|
-
return [
|
|
994
|
-
"\t{",
|
|
995
|
-
` file: ${quoteTsString(`src/admin-views/${adminViewSlug}/index.tsx`)},`,
|
|
996
|
-
` phpFile: ${quoteTsString(`inc/admin-views/${adminViewSlug}.php`)},`,
|
|
997
|
-
` slug: ${quoteTsString(adminViewSlug)},`,
|
|
998
|
-
source ? ` source: ${quoteTsString(formatAdminViewSourceLocator(source))},` : null,
|
|
999
|
-
"\t},"
|
|
1000
|
-
].filter((line) => typeof line === "string").join(`
|
|
1001
|
-
`);
|
|
1002
|
-
}
|
|
1003
|
-
function buildAdminViewRegistrySource(adminViewSlugs) {
|
|
1004
|
-
const importLines = adminViewSlugs.map((adminViewSlug) => `import './${adminViewSlug}';`).join(`
|
|
1005
|
-
`);
|
|
1006
|
-
return `${importLines}${importLines ? `
|
|
1007
|
-
|
|
1008
|
-
` : ""}// wp-typia add admin-view entries
|
|
1009
|
-
`;
|
|
1010
|
-
}
|
|
1011
|
-
function buildAdminViewTypesSource(adminViewSlug, restResource, coreDataSource) {
|
|
990
|
+
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-admin-view-templates-core-data.ts
|
|
991
|
+
function buildCoreDataAdminViewTypesSource(adminViewSlug, coreDataSource) {
|
|
1012
992
|
const pascalName = toPascalCase(adminViewSlug);
|
|
1013
993
|
const coreDataRecordTypeName = `${pascalName}CoreDataRecord`;
|
|
1014
994
|
const itemTypeName = `${pascalName}AdminViewItem`;
|
|
1015
995
|
const dataSetTypeName = `${pascalName}AdminViewDataSet`;
|
|
1016
|
-
if (
|
|
1017
|
-
|
|
1018
|
-
const restTypesModule = getAdminViewRelativeModuleSpecifier(adminViewSlug, restResource.typesFile);
|
|
1019
|
-
return `import type { ${restPascalName}Record } from ${quoteTsString(restTypesModule)};
|
|
1020
|
-
|
|
1021
|
-
export type ${itemTypeName} = ${restPascalName}Record;
|
|
1022
|
-
|
|
1023
|
-
export interface ${dataSetTypeName} {
|
|
1024
|
-
items: ${itemTypeName}[];
|
|
1025
|
-
paginationInfo: {
|
|
1026
|
-
totalItems: number;
|
|
1027
|
-
totalPages: number;
|
|
1028
|
-
};
|
|
1029
|
-
}
|
|
1030
|
-
`;
|
|
1031
|
-
}
|
|
1032
|
-
if (coreDataSource) {
|
|
1033
|
-
if (coreDataSource.entityKind === "taxonomy") {
|
|
1034
|
-
return `export interface ${coreDataRecordTypeName} {
|
|
996
|
+
if (coreDataSource.entityKind === "taxonomy") {
|
|
997
|
+
return `export interface ${coreDataRecordTypeName} {
|
|
1035
998
|
count?: number;
|
|
1036
999
|
description?: string;
|
|
1037
1000
|
id: number;
|
|
@@ -1064,8 +1027,8 @@ export interface ${dataSetTypeName} {
|
|
|
1064
1027
|
};
|
|
1065
1028
|
}
|
|
1066
1029
|
`;
|
|
1067
|
-
|
|
1068
|
-
|
|
1030
|
+
}
|
|
1031
|
+
return `export interface ${coreDataRecordTypeName} {
|
|
1069
1032
|
id: number;
|
|
1070
1033
|
date?: string;
|
|
1071
1034
|
modified?: string;
|
|
@@ -1088,25 +1051,6 @@ export interface ${itemTypeName} {
|
|
|
1088
1051
|
updatedAt: string;
|
|
1089
1052
|
}
|
|
1090
1053
|
|
|
1091
|
-
export interface ${dataSetTypeName} {
|
|
1092
|
-
items: ${itemTypeName}[];
|
|
1093
|
-
paginationInfo: {
|
|
1094
|
-
totalItems: number;
|
|
1095
|
-
totalPages: number;
|
|
1096
|
-
};
|
|
1097
|
-
}
|
|
1098
|
-
`;
|
|
1099
|
-
}
|
|
1100
|
-
return `export type ${pascalName}AdminViewStatus = 'draft' | 'published';
|
|
1101
|
-
|
|
1102
|
-
export interface ${itemTypeName} {
|
|
1103
|
-
id: number;
|
|
1104
|
-
owner: string;
|
|
1105
|
-
status: ${pascalName}AdminViewStatus;
|
|
1106
|
-
title: string;
|
|
1107
|
-
updatedAt: string;
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
1054
|
export interface ${dataSetTypeName} {
|
|
1111
1055
|
items: ${itemTypeName}[];
|
|
1112
1056
|
paginationInfo: {
|
|
@@ -1116,27 +1060,20 @@ export interface ${dataSetTypeName} {
|
|
|
1116
1060
|
}
|
|
1117
1061
|
`;
|
|
1118
1062
|
}
|
|
1119
|
-
function
|
|
1063
|
+
function buildCoreDataAdminViewConfigSource(adminViewSlug, textDomain, coreDataSource) {
|
|
1120
1064
|
const pascalName = toPascalCase(adminViewSlug);
|
|
1121
1065
|
const camelName = toCamelCase(adminViewSlug);
|
|
1122
1066
|
const itemTypeName = `${pascalName}AdminViewItem`;
|
|
1123
1067
|
const dataViewsName = `${camelName}AdminDataViews`;
|
|
1124
|
-
const
|
|
1125
|
-
const
|
|
1126
|
-
const
|
|
1127
|
-
const searchEnabled = restResource ? "false" : "true";
|
|
1128
|
-
const titleFieldSource = restResource ? "" : isTaxonomyCoreDataSource ? ` titleField: 'name',
|
|
1068
|
+
const isTaxonomyCoreDataSource = coreDataSource.entityKind === "taxonomy";
|
|
1069
|
+
const defaultViewFields = isTaxonomyCoreDataSource ? "['name', 'slug', 'count']" : "['title', 'slug', 'status', 'updatedAt']";
|
|
1070
|
+
const titleFieldSource = isTaxonomyCoreDataSource ? ` titleField: 'name',
|
|
1129
1071
|
` : ` titleField: 'title',
|
|
1130
1072
|
`;
|
|
1131
|
-
const defaultViewEnhancementsSource =
|
|
1132
|
-
` :
|
|
1133
|
-
` : ` sort: {
|
|
1134
|
-
direction: 'desc',
|
|
1135
|
-
field: 'updatedAt',
|
|
1136
|
-
},
|
|
1137
|
-
titleField: 'title',
|
|
1073
|
+
const defaultViewEnhancementsSource = isTaxonomyCoreDataSource ? ` titleField: 'name',
|
|
1074
|
+
` : ` titleField: 'title',
|
|
1138
1075
|
`;
|
|
1139
|
-
const additionalFieldsSource =
|
|
1076
|
+
const additionalFieldsSource = isTaxonomyCoreDataSource ? ` count: {
|
|
1140
1077
|
label: __( 'Count', ${quoteTsString(textDomain)} ),
|
|
1141
1078
|
schema: { type: 'integer' },
|
|
1142
1079
|
},
|
|
@@ -1165,7 +1102,7 @@ function buildAdminViewConfigSource(adminViewSlug, textDomain, source, restResou
|
|
|
1165
1102
|
taxonomy: {
|
|
1166
1103
|
label: __( 'Taxonomy', ${quoteTsString(textDomain)} ),
|
|
1167
1104
|
schema: { type: 'string' },
|
|
1168
|
-
},` :
|
|
1105
|
+
},` : ` slug: {
|
|
1169
1106
|
enableGlobalSearch: true,
|
|
1170
1107
|
label: __( 'Slug', ${quoteTsString(textDomain)} ),
|
|
1171
1108
|
schema: { type: 'string' },
|
|
@@ -1176,37 +1113,10 @@ function buildAdminViewConfigSource(adminViewSlug, textDomain, source, restResou
|
|
|
1176
1113
|
},
|
|
1177
1114
|
title: {
|
|
1178
1115
|
enableGlobalSearch: true,
|
|
1179
|
-
label: __( 'Name', ${quoteTsString(textDomain)} ),
|
|
1180
|
-
schema: { type: 'string' },
|
|
1181
|
-
},
|
|
1182
|
-
updatedAt: {
|
|
1183
|
-
label: __( 'Updated', ${quoteTsString(textDomain)} ),
|
|
1184
|
-
schema: { format: 'date-time', type: 'string' },
|
|
1185
|
-
type: 'datetime',
|
|
1186
|
-
},` : ` owner: {
|
|
1187
|
-
label: __( 'Owner', ${quoteTsString(textDomain)} ),
|
|
1188
|
-
schema: { type: 'string' },
|
|
1189
|
-
},
|
|
1190
|
-
status: {
|
|
1191
|
-
filterBy: { operators: ['isAny', 'isNone'] },
|
|
1192
|
-
label: __( 'Status', ${quoteTsString(textDomain)} ),
|
|
1193
|
-
schema: {
|
|
1194
|
-
enum: ['draft', 'published'],
|
|
1195
|
-
enumLabels: {
|
|
1196
|
-
draft: __( 'Draft', ${quoteTsString(textDomain)} ),
|
|
1197
|
-
published: __( 'Published', ${quoteTsString(textDomain)} ),
|
|
1198
|
-
},
|
|
1199
|
-
type: 'string',
|
|
1200
|
-
},
|
|
1201
|
-
},
|
|
1202
|
-
title: {
|
|
1203
|
-
enableGlobalSearch: true,
|
|
1204
|
-
enableSorting: true,
|
|
1205
1116
|
label: __( 'Title', ${quoteTsString(textDomain)} ),
|
|
1206
1117
|
schema: { type: 'string' },
|
|
1207
1118
|
},
|
|
1208
1119
|
updatedAt: {
|
|
1209
|
-
enableSorting: true,
|
|
1210
1120
|
label: __( 'Updated', ${quoteTsString(textDomain)} ),
|
|
1211
1121
|
schema: { format: 'date-time', type: 'string' },
|
|
1212
1122
|
type: 'datetime',
|
|
@@ -1218,7 +1128,7 @@ import type { ${itemTypeName} } from './types';
|
|
|
1218
1128
|
|
|
1219
1129
|
export const ${dataViewsName} = defineDataViews<${itemTypeName}>({
|
|
1220
1130
|
idField: 'id',
|
|
1221
|
-
search:
|
|
1131
|
+
search: true,
|
|
1222
1132
|
searchLabel: __( 'Search records', ${quoteTsString(textDomain)} ),
|
|
1223
1133
|
${titleFieldSource}
|
|
1224
1134
|
defaultView: {
|
|
@@ -1240,406 +1150,279 @@ ${additionalFieldsSource}
|
|
|
1240
1150
|
});
|
|
1241
1151
|
`;
|
|
1242
1152
|
}
|
|
1243
|
-
function
|
|
1153
|
+
function buildCoreDataAdminViewDataSource(adminViewSlug, coreDataSource) {
|
|
1244
1154
|
const pascalName = toPascalCase(adminViewSlug);
|
|
1245
1155
|
const camelName = toCamelCase(adminViewSlug);
|
|
1246
|
-
const
|
|
1247
|
-
const itemTypeName = `${pascalName}AdminViewItem`;
|
|
1156
|
+
const coreDataRecordTypeName = `${pascalName}CoreDataRecord`;
|
|
1248
1157
|
const dataSetTypeName = `${pascalName}AdminViewDataSet`;
|
|
1158
|
+
const itemTypeName = `${pascalName}AdminViewItem`;
|
|
1249
1159
|
const queryTypeName = `${pascalName}AdminViewQuery`;
|
|
1250
1160
|
const dataViewsName = `${camelName}AdminDataViews`;
|
|
1251
|
-
const
|
|
1252
|
-
|
|
1161
|
+
const useEntityRecordName = `use${pascalName}EntityRecord`;
|
|
1162
|
+
const useEntityRecordsName = `use${pascalName}EntityRecords`;
|
|
1163
|
+
const useAdminViewDataName = `use${pascalName}AdminViewData`;
|
|
1164
|
+
if (coreDataSource.entityKind === "taxonomy") {
|
|
1165
|
+
return `import type { DataViewsView } from '@wp-typia/dataviews';
|
|
1166
|
+
import { useEntityRecord, useEntityRecords } from '@wordpress/core-data';
|
|
1167
|
+
import { useMemo } from '@wordpress/element';
|
|
1253
1168
|
|
|
1254
1169
|
import { ${dataViewsName} } from './config';
|
|
1255
|
-
import type {
|
|
1170
|
+
import type {
|
|
1171
|
+
${coreDataRecordTypeName},
|
|
1172
|
+
${dataSetTypeName},
|
|
1173
|
+
${itemTypeName},
|
|
1174
|
+
} from './types';
|
|
1256
1175
|
|
|
1257
1176
|
export interface ${queryTypeName} {
|
|
1258
1177
|
page?: number;
|
|
1259
|
-
|
|
1178
|
+
per_page?: number;
|
|
1260
1179
|
search?: string;
|
|
1261
1180
|
}
|
|
1262
1181
|
|
|
1263
|
-
const
|
|
1264
|
-
|
|
1265
|
-
id: 1,
|
|
1266
|
-
owner: 'Editorial',
|
|
1267
|
-
status: 'published',
|
|
1268
|
-
title: ${quoteTsString(`${title} launch checklist`)},
|
|
1269
|
-
updatedAt: '2026-04-01T10:30:00Z',
|
|
1270
|
-
},
|
|
1271
|
-
{
|
|
1272
|
-
id: 2,
|
|
1273
|
-
owner: 'Design',
|
|
1274
|
-
status: 'draft',
|
|
1275
|
-
title: ${quoteTsString(`${title} content refresh`)},
|
|
1276
|
-
updatedAt: '2026-04-03T14:15:00Z',
|
|
1277
|
-
},
|
|
1278
|
-
{
|
|
1279
|
-
id: 3,
|
|
1280
|
-
owner: 'Operations',
|
|
1281
|
-
status: 'published',
|
|
1282
|
-
title: ${quoteTsString(`${title} support handoff`)},
|
|
1283
|
-
updatedAt: '2026-04-08T08:45:00Z',
|
|
1284
|
-
},
|
|
1285
|
-
];
|
|
1182
|
+
const CORE_DATA_ENTITY_KIND = ${quoteTsString(coreDataSource.entityKind)};
|
|
1183
|
+
const CORE_DATA_ENTITY_NAME = ${quoteTsString(coreDataSource.entityName)};
|
|
1286
1184
|
|
|
1287
|
-
function
|
|
1288
|
-
|
|
1289
|
-
|
|
1185
|
+
function normalizeCoreDataNumber(value: unknown): number {
|
|
1186
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
function normalizeCoreDataString(value: unknown): string {
|
|
1190
|
+
return typeof value === 'string' ? value : '';
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
function normalizeTaxonomyRecord(record: ${coreDataRecordTypeName}): ${itemTypeName} {
|
|
1194
|
+
return {
|
|
1195
|
+
count: normalizeCoreDataNumber(record.count),
|
|
1196
|
+
description: normalizeCoreDataString(record.description),
|
|
1197
|
+
id: record.id,
|
|
1198
|
+
link: normalizeCoreDataString(record.link),
|
|
1199
|
+
name: normalizeCoreDataString(record.name) || normalizeCoreDataString(record.slug),
|
|
1200
|
+
parent: normalizeCoreDataNumber(record.parent),
|
|
1201
|
+
raw: record,
|
|
1202
|
+
slug: normalizeCoreDataString(record.slug),
|
|
1203
|
+
taxonomy: normalizeCoreDataString(record.taxonomy),
|
|
1204
|
+
};
|
|
1290
1205
|
}
|
|
1291
1206
|
|
|
1292
|
-
|
|
1293
|
-
return
|
|
1294
|
-
|
|
1207
|
+
export function ${useEntityRecordName}(recordId: number | undefined) {
|
|
1208
|
+
return useEntityRecord<${coreDataRecordTypeName}>(
|
|
1209
|
+
CORE_DATA_ENTITY_KIND,
|
|
1210
|
+
CORE_DATA_ENTITY_NAME,
|
|
1211
|
+
recordId ?? 0,
|
|
1212
|
+
{ enabled: typeof recordId === 'number' },
|
|
1295
1213
|
);
|
|
1296
|
-
}
|
|
1214
|
+
}
|
|
1297
1215
|
|
|
1298
|
-
export
|
|
1299
|
-
view: DataViewsView<${itemTypeName}>,
|
|
1300
|
-
): Promise<${dataSetTypeName}> {
|
|
1216
|
+
export function ${useEntityRecordsName}(view: DataViewsView<${itemTypeName}>) {
|
|
1301
1217
|
const query = ${dataViewsName}.toQueryArgs<${queryTypeName}>(view, {
|
|
1302
|
-
perPageParam: '
|
|
1218
|
+
perPageParam: 'per_page',
|
|
1303
1219
|
});
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
matchesSearch(item, query.search),
|
|
1220
|
+
|
|
1221
|
+
return useEntityRecords<${coreDataRecordTypeName}>(
|
|
1222
|
+
CORE_DATA_ENTITY_KIND,
|
|
1223
|
+
CORE_DATA_ENTITY_NAME,
|
|
1224
|
+
query,
|
|
1310
1225
|
);
|
|
1311
|
-
|
|
1312
|
-
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
export function ${useAdminViewDataName}(view: DataViewsView<${itemTypeName}>) {
|
|
1229
|
+
const { hasResolved, isResolving, records, totalItems, totalPages } =
|
|
1230
|
+
${useEntityRecordsName}(view);
|
|
1231
|
+
const items = useMemo(
|
|
1232
|
+
() => (records ?? []).map((record) => normalizeTaxonomyRecord(record)),
|
|
1233
|
+
[records],
|
|
1234
|
+
);
|
|
1235
|
+
const dataSet = useMemo<${dataSetTypeName}>(
|
|
1236
|
+
() => ({
|
|
1237
|
+
items,
|
|
1238
|
+
paginationInfo: {
|
|
1239
|
+
totalItems: totalItems ?? items.length,
|
|
1240
|
+
totalPages: Math.max(1, totalPages ?? 1),
|
|
1241
|
+
},
|
|
1242
|
+
}),
|
|
1243
|
+
[items, totalItems, totalPages],
|
|
1244
|
+
);
|
|
1245
|
+
const error =
|
|
1246
|
+
!isResolving && hasResolved && records === null
|
|
1247
|
+
? 'Unable to load core-data entity records.'
|
|
1248
|
+
: null;
|
|
1313
1249
|
|
|
1314
1250
|
return {
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
totalPages: Math.max(1, Math.ceil(filteredItems.length / perPage)),
|
|
1319
|
-
},
|
|
1251
|
+
dataSet,
|
|
1252
|
+
error,
|
|
1253
|
+
isLoading: isResolving,
|
|
1320
1254
|
};
|
|
1321
|
-
}
|
|
1255
|
+
}
|
|
1322
1256
|
`;
|
|
1323
|
-
}
|
|
1324
|
-
function buildRestAdminViewDataSource(adminViewSlug, restResource) {
|
|
1325
|
-
const pascalName = toPascalCase(adminViewSlug);
|
|
1326
|
-
const restPascalName = toPascalCase(restResource.slug);
|
|
1327
|
-
const camelName = toCamelCase(adminViewSlug);
|
|
1328
|
-
const itemTypeName = `${pascalName}AdminViewItem`;
|
|
1329
|
-
const dataSetTypeName = `${pascalName}AdminViewDataSet`;
|
|
1330
|
-
const dataViewsName = `${camelName}AdminDataViews`;
|
|
1331
|
-
const fetchName = `fetch${pascalName}AdminViewData`;
|
|
1332
|
-
const restApiModule = getAdminViewRelativeModuleSpecifier(adminViewSlug, restResource.apiFile);
|
|
1333
|
-
const restTypesModule = getAdminViewRelativeModuleSpecifier(adminViewSlug, restResource.typesFile);
|
|
1257
|
+
}
|
|
1334
1258
|
return `import type { DataViewsView } from '@wp-typia/dataviews';
|
|
1259
|
+
import { useEntityRecord, useEntityRecords } from '@wordpress/core-data';
|
|
1260
|
+
import { useMemo } from '@wordpress/element';
|
|
1335
1261
|
|
|
1336
|
-
import { listResource } from ${quoteTsString(restApiModule)};
|
|
1337
|
-
import type { ${restPascalName}ListQuery } from ${quoteTsString(restTypesModule)};
|
|
1338
1262
|
import { ${dataViewsName} } from './config';
|
|
1339
|
-
import type {
|
|
1263
|
+
import type {
|
|
1264
|
+
${coreDataRecordTypeName},
|
|
1265
|
+
${dataSetTypeName},
|
|
1266
|
+
${itemTypeName},
|
|
1267
|
+
} from './types';
|
|
1340
1268
|
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1269
|
+
export interface ${queryTypeName} {
|
|
1270
|
+
page?: number;
|
|
1271
|
+
per_page?: number;
|
|
1272
|
+
search?: string;
|
|
1344
1273
|
}
|
|
1345
1274
|
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
): Promise<${dataSetTypeName}> {
|
|
1349
|
-
const query = ${dataViewsName}.toQueryArgs<${restPascalName}ListQuery>(view, {
|
|
1350
|
-
perPageParam: 'perPage',
|
|
1351
|
-
searchParam: false,
|
|
1352
|
-
});
|
|
1353
|
-
const result = await listResource({
|
|
1354
|
-
page: query.page,
|
|
1355
|
-
perPage: query.perPage,
|
|
1356
|
-
});
|
|
1357
|
-
if (!result.isValid || !result.data) {
|
|
1358
|
-
throw new Error('Unable to load REST resource records.');
|
|
1359
|
-
}
|
|
1360
|
-
|
|
1361
|
-
const response = result.data;
|
|
1275
|
+
const CORE_DATA_ENTITY_KIND = ${quoteTsString(coreDataSource.entityKind)};
|
|
1276
|
+
const CORE_DATA_ENTITY_NAME = ${quoteTsString(coreDataSource.entityName)};
|
|
1362
1277
|
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
paginationInfo: {
|
|
1366
|
-
totalItems: response.total,
|
|
1367
|
-
totalPages: resolveTotalPages(response.total, response.perPage ?? query.perPage),
|
|
1368
|
-
},
|
|
1369
|
-
};
|
|
1370
|
-
}
|
|
1371
|
-
`;
|
|
1278
|
+
function normalizeCoreDataString(value: unknown): string {
|
|
1279
|
+
return typeof value === 'string' ? value : '';
|
|
1372
1280
|
}
|
|
1373
|
-
function buildRestSettingsAdminViewTypesSource(adminViewSlug, restResource) {
|
|
1374
|
-
const pascalName = toPascalCase(adminViewSlug);
|
|
1375
|
-
const restTypesModule = getAdminViewRelativeModuleSpecifier(adminViewSlug, restResource.typesFile);
|
|
1376
|
-
const formStateTypeName = `${pascalName}SettingsFormState`;
|
|
1377
|
-
const loadResultTypeName = `${pascalName}SettingsLoadResult`;
|
|
1378
|
-
return `import type {
|
|
1379
|
-
${restResource.bodyTypeName},
|
|
1380
|
-
${restResource.queryTypeName},
|
|
1381
|
-
${restResource.responseTypeName},
|
|
1382
|
-
} from ${quoteTsString(restTypesModule)};
|
|
1383
1281
|
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1282
|
+
function normalizeCoreDataTitle(record: ${coreDataRecordTypeName}): string {
|
|
1283
|
+
if (typeof record.title === 'string') {
|
|
1284
|
+
return record.title;
|
|
1285
|
+
}
|
|
1286
|
+
if (record.title && typeof record.title === 'object') {
|
|
1287
|
+
if (typeof record.title.rendered === 'string') {
|
|
1288
|
+
return record.title.rendered;
|
|
1289
|
+
}
|
|
1290
|
+
if (typeof record.title.raw === 'string') {
|
|
1291
|
+
return record.title.raw;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1388
1294
|
|
|
1389
|
-
|
|
1390
|
-
form: ${formStateTypeName};
|
|
1391
|
-
response: ${pascalName}SettingsResponse | null;
|
|
1392
|
-
}
|
|
1393
|
-
`;
|
|
1295
|
+
return normalizeCoreDataString(record.name) || normalizeCoreDataString(record.slug);
|
|
1394
1296
|
}
|
|
1395
|
-
function buildRestSettingsAdminViewConfigSource(adminViewSlug, textDomain, restResource) {
|
|
1396
|
-
const pascalName = toPascalCase(adminViewSlug);
|
|
1397
|
-
const camelName = toCamelCase(adminViewSlug);
|
|
1398
|
-
const title = toTitleCase(adminViewSlug);
|
|
1399
|
-
const configName = `${camelName}SettingsConfig`;
|
|
1400
|
-
const formStateTypeName = `${pascalName}SettingsFormState`;
|
|
1401
|
-
const secretFieldSource = restResource.secretFieldName && restResource.secretStateFieldName ? ` {
|
|
1402
|
-
description: __( 'Write-only secret value. Leave blank to keep the existing secret unless your route treats blank values as removal.', ${quoteTsString(textDomain)} ),
|
|
1403
|
-
id: ${quoteTsString(restResource.secretFieldName)},
|
|
1404
|
-
label: __( ${quoteTsString(toTitleCase(restResource.secretFieldName))}, ${quoteTsString(textDomain)} ),
|
|
1405
|
-
secretStateField: ${quoteTsString(restResource.secretStateFieldName)},
|
|
1406
|
-
type: 'secret',
|
|
1407
|
-
},` : "";
|
|
1408
|
-
return `import { __ } from '@wordpress/i18n';
|
|
1409
|
-
|
|
1410
|
-
import type { ${formStateTypeName} } from './types';
|
|
1411
1297
|
|
|
1412
|
-
|
|
1298
|
+
function normalizeCoreDataUpdatedAt(record: ${coreDataRecordTypeName}): string {
|
|
1299
|
+
return normalizeCoreDataString(record.modified) || normalizeCoreDataString(record.date);
|
|
1300
|
+
}
|
|
1413
1301
|
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1302
|
+
function normalizeCoreDataRecord(record: ${coreDataRecordTypeName}): ${itemTypeName} {
|
|
1303
|
+
return {
|
|
1304
|
+
id: record.id,
|
|
1305
|
+
raw: record,
|
|
1306
|
+
slug: normalizeCoreDataString(record.slug),
|
|
1307
|
+
status: normalizeCoreDataString(record.status),
|
|
1308
|
+
title: normalizeCoreDataTitle(record),
|
|
1309
|
+
updatedAt: normalizeCoreDataUpdatedAt(record),
|
|
1310
|
+
};
|
|
1420
1311
|
}
|
|
1421
1312
|
|
|
1422
|
-
export
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
type: 'textarea',
|
|
1430
|
-
},
|
|
1431
|
-
{
|
|
1432
|
-
description: __( 'Optional operator note included with the save request.', ${quoteTsString(textDomain)} ),
|
|
1433
|
-
id: 'comment',
|
|
1434
|
-
label: __( 'Comment', ${quoteTsString(textDomain)} ),
|
|
1435
|
-
type: 'text',
|
|
1436
|
-
},
|
|
1437
|
-
${secretFieldSource}
|
|
1438
|
-
] satisfies ${pascalName}SettingsField[],
|
|
1439
|
-
secretFieldName: ${restResource.secretFieldName ? quoteTsString(restResource.secretFieldName) : "undefined"},
|
|
1440
|
-
secretStateFieldName: ${restResource.secretStateFieldName ? quoteTsString(restResource.secretStateFieldName) : "undefined"},
|
|
1441
|
-
title: __( ${quoteTsString(title)}, ${quoteTsString(textDomain)} ),
|
|
1442
|
-
};
|
|
1443
|
-
`;
|
|
1313
|
+
export function ${useEntityRecordName}(recordId: number | undefined) {
|
|
1314
|
+
return useEntityRecord<${coreDataRecordTypeName}>(
|
|
1315
|
+
CORE_DATA_ENTITY_KIND,
|
|
1316
|
+
CORE_DATA_ENTITY_NAME,
|
|
1317
|
+
recordId ?? 0,
|
|
1318
|
+
{ enabled: typeof recordId === 'number' },
|
|
1319
|
+
);
|
|
1444
1320
|
}
|
|
1445
|
-
function buildRestSettingsAdminViewDataSource(adminViewSlug, restResource) {
|
|
1446
|
-
const pascalName = toPascalCase(adminViewSlug);
|
|
1447
|
-
const restApiModule = getAdminViewRelativeModuleSpecifier(adminViewSlug, restResource.apiFile);
|
|
1448
|
-
const formStateTypeName = `${pascalName}SettingsFormState`;
|
|
1449
|
-
const loadResultTypeName = `${pascalName}SettingsLoadResult`;
|
|
1450
|
-
const loadName = `load${pascalName}Settings`;
|
|
1451
|
-
const saveName = `save${pascalName}Settings`;
|
|
1452
|
-
const initialFields = [
|
|
1453
|
-
"\tpayload: '',",
|
|
1454
|
-
"\tcomment: '',"
|
|
1455
|
-
].join(`
|
|
1456
|
-
`);
|
|
1457
|
-
const requestBodySource = restResource.secretFieldName ? ` const requestBody = { ...form } as Record<string, unknown>;
|
|
1458
|
-
if (requestBody[${quoteTsString(restResource.secretFieldName)}] === '') {
|
|
1459
|
-
delete requestBody[${quoteTsString(restResource.secretFieldName)}];
|
|
1460
|
-
}
|
|
1461
|
-
` : ` const requestBody = form as Record<string, unknown>;
|
|
1462
|
-
`;
|
|
1463
|
-
return `import { callManualRestContract } from ${quoteTsString(restApiModule)};
|
|
1464
|
-
import type {
|
|
1465
|
-
${formStateTypeName},
|
|
1466
|
-
${loadResultTypeName},
|
|
1467
|
-
${pascalName}SettingsQuery,
|
|
1468
|
-
${pascalName}SettingsRequest,
|
|
1469
|
-
${pascalName}SettingsResponse,
|
|
1470
|
-
} from './types';
|
|
1471
1321
|
|
|
1472
|
-
function
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
}
|
|
1322
|
+
export function ${useEntityRecordsName}(view: DataViewsView<${itemTypeName}>) {
|
|
1323
|
+
const query = ${dataViewsName}.toQueryArgs<${queryTypeName}>(view, {
|
|
1324
|
+
perPageParam: 'per_page',
|
|
1325
|
+
});
|
|
1476
1326
|
|
|
1477
|
-
return
|
|
1327
|
+
return useEntityRecords<${coreDataRecordTypeName}>(
|
|
1328
|
+
CORE_DATA_ENTITY_KIND,
|
|
1329
|
+
CORE_DATA_ENTITY_NAME,
|
|
1330
|
+
query,
|
|
1331
|
+
);
|
|
1478
1332
|
}
|
|
1479
1333
|
|
|
1480
|
-
export function
|
|
1481
|
-
|
|
1482
|
-
${
|
|
1483
|
-
|
|
1484
|
-
|
|
1334
|
+
export function ${useAdminViewDataName}(view: DataViewsView<${itemTypeName}>) {
|
|
1335
|
+
const { hasResolved, isResolving, records, totalItems, totalPages } =
|
|
1336
|
+
${useEntityRecordsName}(view);
|
|
1337
|
+
const items = useMemo(
|
|
1338
|
+
() => (records ?? []).map((record) => normalizeCoreDataRecord(record)),
|
|
1339
|
+
[records],
|
|
1340
|
+
);
|
|
1341
|
+
const dataSet = useMemo<${dataSetTypeName}>(
|
|
1342
|
+
() => ({
|
|
1343
|
+
items,
|
|
1344
|
+
paginationInfo: {
|
|
1345
|
+
totalItems: totalItems ?? items.length,
|
|
1346
|
+
totalPages: Math.max(1, totalPages ?? 1),
|
|
1347
|
+
},
|
|
1348
|
+
}),
|
|
1349
|
+
[items, totalItems, totalPages],
|
|
1350
|
+
);
|
|
1351
|
+
const error =
|
|
1352
|
+
!isResolving && hasResolved && records === null
|
|
1353
|
+
? 'Unable to load core-data entity records.'
|
|
1354
|
+
: null;
|
|
1485
1355
|
|
|
1486
|
-
export async function ${loadName}(): Promise<${loadResultTypeName}> {
|
|
1487
1356
|
return {
|
|
1488
|
-
|
|
1489
|
-
|
|
1357
|
+
dataSet,
|
|
1358
|
+
error,
|
|
1359
|
+
isLoading: isResolving,
|
|
1490
1360
|
};
|
|
1491
1361
|
}
|
|
1492
|
-
|
|
1493
|
-
export async function ${saveName}(
|
|
1494
|
-
form: ${formStateTypeName},
|
|
1495
|
-
query: Partial<${pascalName}SettingsQuery> = {},
|
|
1496
|
-
): Promise<${pascalName}SettingsResponse> {
|
|
1497
|
-
${requestBodySource}
|
|
1498
|
-
const result = await callManualRestContract({
|
|
1499
|
-
body: requestBody as unknown as ${pascalName}SettingsRequest,
|
|
1500
|
-
query: query as ${pascalName}SettingsQuery,
|
|
1501
|
-
});
|
|
1502
|
-
if (!result.isValid) {
|
|
1503
|
-
const message =
|
|
1504
|
-
result.validationTarget === 'request'
|
|
1505
|
-
? 'Settings request failed validation.'
|
|
1506
|
-
: 'Settings response failed validation.';
|
|
1507
|
-
throw new Error(formatValidationError(message, result.errors));
|
|
1508
|
-
}
|
|
1509
|
-
|
|
1510
|
-
return result.data as ${pascalName}SettingsResponse;
|
|
1511
|
-
}
|
|
1512
1362
|
`;
|
|
1513
1363
|
}
|
|
1514
|
-
function
|
|
1364
|
+
function buildCoreDataAdminViewScreenSource(adminViewSlug, textDomain) {
|
|
1515
1365
|
const pascalName = toPascalCase(adminViewSlug);
|
|
1516
|
-
const componentName = `${pascalName}AdminViewScreen`;
|
|
1517
|
-
const formStateTypeName = `${pascalName}SettingsFormState`;
|
|
1518
|
-
const responseTypeName = `${pascalName}SettingsResponse`;
|
|
1519
1366
|
const camelName = toCamelCase(adminViewSlug);
|
|
1520
|
-
const
|
|
1521
|
-
const
|
|
1522
|
-
const
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
} from '@wordpress/components';
|
|
1530
|
-
import { useEffect, useState } from '@wordpress/element';
|
|
1367
|
+
const itemTypeName = `${pascalName}AdminViewItem`;
|
|
1368
|
+
const dataSetTypeName = `${pascalName}AdminViewDataSet`;
|
|
1369
|
+
const componentName = `${pascalName}AdminViewScreen`;
|
|
1370
|
+
const dataViewsName = `${camelName}AdminDataViews`;
|
|
1371
|
+
const useAdminViewDataName = `use${pascalName}AdminViewData`;
|
|
1372
|
+
const title = toTitleCase(adminViewSlug);
|
|
1373
|
+
return `import type { DataViewsConfig, DataViewsView } from '@wp-typia/dataviews';
|
|
1374
|
+
import { Notice, Spinner } from '@wordpress/components';
|
|
1375
|
+
import { useState } from '@wordpress/element';
|
|
1531
1376
|
import { __ } from '@wordpress/i18n';
|
|
1377
|
+
import { DataViews } from '@wordpress/dataviews/wp';
|
|
1532
1378
|
|
|
1533
|
-
import { ${
|
|
1534
|
-
import { ${
|
|
1535
|
-
import type { ${
|
|
1379
|
+
import { ${dataViewsName} } from './config';
|
|
1380
|
+
import { ${useAdminViewDataName} } from './data';
|
|
1381
|
+
import type { ${dataSetTypeName}, ${itemTypeName} } from './types';
|
|
1536
1382
|
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
return value;
|
|
1541
|
-
}
|
|
1542
|
-
if (value == null) {
|
|
1543
|
-
return '';
|
|
1544
|
-
}
|
|
1545
|
-
|
|
1546
|
-
return String(value);
|
|
1547
|
-
}
|
|
1548
|
-
|
|
1549
|
-
function getSecretState(response: ${responseTypeName} | null): boolean | null {
|
|
1550
|
-
const stateField = ${configName}.secretStateFieldName;
|
|
1551
|
-
if (!stateField || !response) {
|
|
1552
|
-
return null;
|
|
1553
|
-
}
|
|
1383
|
+
const TypedDataViews = DataViews as unknown as <TItem extends object>(
|
|
1384
|
+
props: DataViewsConfig<TItem>,
|
|
1385
|
+
) => ReturnType<typeof DataViews>;
|
|
1554
1386
|
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1387
|
+
const EMPTY_DATA_SET: ${dataSetTypeName} = {
|
|
1388
|
+
items: [],
|
|
1389
|
+
paginationInfo: {
|
|
1390
|
+
totalItems: 0,
|
|
1391
|
+
totalPages: 1,
|
|
1392
|
+
},
|
|
1393
|
+
};
|
|
1558
1394
|
|
|
1559
1395
|
export function ${componentName}() {
|
|
1560
|
-
const [
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
const
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
setForm(result.form);
|
|
1576
|
-
setResponse(result.response);
|
|
1577
|
-
}
|
|
1578
|
-
})
|
|
1579
|
-
.catch((nextError: unknown) => {
|
|
1580
|
-
if (isCurrent) {
|
|
1581
|
-
setError(
|
|
1582
|
-
nextError instanceof Error
|
|
1583
|
-
? nextError.message
|
|
1584
|
-
: __( 'Unable to prepare settings form.', ${quoteTsString(textDomain)} ),
|
|
1585
|
-
);
|
|
1586
|
-
}
|
|
1587
|
-
})
|
|
1588
|
-
.finally(() => {
|
|
1589
|
-
if (isCurrent) {
|
|
1590
|
-
setIsLoading(false);
|
|
1591
|
-
}
|
|
1592
|
-
});
|
|
1593
|
-
|
|
1594
|
-
return () => {
|
|
1595
|
-
isCurrent = false;
|
|
1596
|
-
};
|
|
1597
|
-
}, []);
|
|
1598
|
-
|
|
1599
|
-
const setFormValue = (fieldId: string, value: string) => {
|
|
1600
|
-
setForm(
|
|
1601
|
-
(current) =>
|
|
1602
|
-
({
|
|
1603
|
-
...current,
|
|
1604
|
-
[fieldId]: value,
|
|
1605
|
-
}) as ${formStateTypeName},
|
|
1606
|
-
);
|
|
1607
|
-
};
|
|
1608
|
-
const secretState = getSecretState(response);
|
|
1609
|
-
|
|
1610
|
-
const handleSubmit = (event: { preventDefault: () => void }) => {
|
|
1611
|
-
event.preventDefault();
|
|
1612
|
-
setError(null);
|
|
1613
|
-
setSuccessMessage(null);
|
|
1614
|
-
setIsSaving(true);
|
|
1615
|
-
|
|
1616
|
-
void ${saveName}(form)
|
|
1617
|
-
.then((nextResponse) => {
|
|
1618
|
-
setResponse(nextResponse);
|
|
1619
|
-
setSuccessMessage(__( 'Settings saved.', ${quoteTsString(textDomain)} ));
|
|
1620
|
-
})
|
|
1621
|
-
.catch((nextError: unknown) => {
|
|
1622
|
-
setError(
|
|
1623
|
-
nextError instanceof Error
|
|
1624
|
-
? nextError.message
|
|
1625
|
-
: __( 'Unable to save settings.', ${quoteTsString(textDomain)} ),
|
|
1626
|
-
);
|
|
1627
|
-
})
|
|
1628
|
-
.finally(() => setIsSaving(false));
|
|
1629
|
-
};
|
|
1396
|
+
const [view, setView] = useState<DataViewsView<${itemTypeName}>>(
|
|
1397
|
+
${dataViewsName}.defaultView,
|
|
1398
|
+
);
|
|
1399
|
+
const {
|
|
1400
|
+
dataSet = EMPTY_DATA_SET,
|
|
1401
|
+
error,
|
|
1402
|
+
isLoading,
|
|
1403
|
+
} = ${useAdminViewDataName}(view);
|
|
1404
|
+
const config = ${dataViewsName}.createConfig({
|
|
1405
|
+
data: dataSet.items,
|
|
1406
|
+
isLoading,
|
|
1407
|
+
onChangeView: setView,
|
|
1408
|
+
paginationInfo: dataSet.paginationInfo,
|
|
1409
|
+
view,
|
|
1410
|
+
});
|
|
1630
1411
|
|
|
1631
1412
|
return (
|
|
1632
|
-
<div className="wp-typia-admin-view-screen
|
|
1413
|
+
<div className="wp-typia-admin-view-screen">
|
|
1633
1414
|
<header className="wp-typia-admin-view-screen__header">
|
|
1634
1415
|
<div>
|
|
1635
1416
|
<p className="wp-typia-admin-view-screen__eyebrow">
|
|
1636
|
-
{ __( '
|
|
1417
|
+
{ __( 'DataViews admin screen', ${quoteTsString(textDomain)} ) }
|
|
1418
|
+
</p>
|
|
1419
|
+
<h1>{ __( ${quoteTsString(title)}, ${quoteTsString(textDomain)} ) }</h1>
|
|
1420
|
+
<p>
|
|
1421
|
+
{ __( 'This screen reads from the WordPress core-data entity store. Extend data.ts when you need entity-specific field mapping or edit flows.', ${quoteTsString(textDomain)} ) }
|
|
1637
1422
|
</p>
|
|
1638
|
-
<h1>{ ${configName}.title }</h1>
|
|
1639
|
-
<p>{ ${configName}.description }</p>
|
|
1640
1423
|
</div>
|
|
1641
1424
|
<div className="wp-typia-admin-view-screen__actions">
|
|
1642
|
-
{ isLoading
|
|
1425
|
+
{ isLoading ? <Spinner /> : null }
|
|
1643
1426
|
</div>
|
|
1644
1427
|
</header>
|
|
1645
1428
|
{ error ? (
|
|
@@ -1647,260 +1430,179 @@ export function ${componentName}() {
|
|
|
1647
1430
|
{ error }
|
|
1648
1431
|
</Notice>
|
|
1649
1432
|
) : null }
|
|
1650
|
-
{
|
|
1651
|
-
<Notice isDismissible={ false } status="success">
|
|
1652
|
-
{ successMessage }
|
|
1653
|
-
</Notice>
|
|
1654
|
-
) : null }
|
|
1655
|
-
{ secretState !== null ? (
|
|
1656
|
-
<Notice isDismissible={ false } status="info">
|
|
1657
|
-
{ secretState
|
|
1658
|
-
? __( 'A secret is currently configured for this integration.', ${quoteTsString(textDomain)} )
|
|
1659
|
-
: __( 'No secret is currently configured for this integration.', ${quoteTsString(textDomain)} ) }
|
|
1660
|
-
</Notice>
|
|
1661
|
-
) : null }
|
|
1662
|
-
<form className="wp-typia-admin-view-screen__settings-form" onSubmit={ handleSubmit }>
|
|
1663
|
-
{ ${configName}.fields.map((field) => (
|
|
1664
|
-
<div className="wp-typia-admin-view-screen__field" key={ field.id }>
|
|
1665
|
-
{ field.type === 'textarea' ? (
|
|
1666
|
-
<TextareaControl
|
|
1667
|
-
help={ field.description }
|
|
1668
|
-
label={ field.label }
|
|
1669
|
-
onChange={ (value) => setFormValue(field.id, value) }
|
|
1670
|
-
value={ getFieldValue(form, field.id) }
|
|
1671
|
-
/>
|
|
1672
|
-
) : (
|
|
1673
|
-
<TextControl
|
|
1674
|
-
help={ field.description }
|
|
1675
|
-
label={ field.label }
|
|
1676
|
-
onChange={ (value) => setFormValue(field.id, value) }
|
|
1677
|
-
type={ field.type === 'secret' ? 'password' : 'text' }
|
|
1678
|
-
value={ getFieldValue(form, field.id) }
|
|
1679
|
-
/>
|
|
1680
|
-
) }
|
|
1681
|
-
</div>
|
|
1682
|
-
)) }
|
|
1683
|
-
<Button
|
|
1684
|
-
disabled={ isLoading || isSaving }
|
|
1685
|
-
isBusy={ isSaving }
|
|
1686
|
-
type="submit"
|
|
1687
|
-
variant="primary"
|
|
1688
|
-
>
|
|
1689
|
-
{ __( 'Save settings', ${quoteTsString(textDomain)} ) }
|
|
1690
|
-
</Button>
|
|
1691
|
-
</form>
|
|
1433
|
+
<TypedDataViews<${itemTypeName}> { ...config } />
|
|
1692
1434
|
</div>
|
|
1693
1435
|
);
|
|
1694
1436
|
}
|
|
1695
1437
|
`;
|
|
1696
1438
|
}
|
|
1697
|
-
|
|
1439
|
+
|
|
1440
|
+
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-admin-view-templates-default.ts
|
|
1441
|
+
function buildDefaultAdminViewTypesSource(adminViewSlug) {
|
|
1698
1442
|
const pascalName = toPascalCase(adminViewSlug);
|
|
1699
|
-
const camelName = toCamelCase(adminViewSlug);
|
|
1700
|
-
const coreDataRecordTypeName = `${pascalName}CoreDataRecord`;
|
|
1701
|
-
const dataSetTypeName = `${pascalName}AdminViewDataSet`;
|
|
1702
1443
|
const itemTypeName = `${pascalName}AdminViewItem`;
|
|
1703
|
-
const
|
|
1704
|
-
|
|
1705
|
-
const useEntityRecordName = `use${pascalName}EntityRecord`;
|
|
1706
|
-
const useEntityRecordsName = `use${pascalName}EntityRecords`;
|
|
1707
|
-
const useAdminViewDataName = `use${pascalName}AdminViewData`;
|
|
1708
|
-
if (coreDataSource.entityKind === "taxonomy") {
|
|
1709
|
-
return `import type { DataViewsView } from '@wp-typia/dataviews';
|
|
1710
|
-
import { useEntityRecord, useEntityRecords } from '@wordpress/core-data';
|
|
1711
|
-
import { useMemo } from '@wordpress/element';
|
|
1712
|
-
|
|
1713
|
-
import { ${dataViewsName} } from './config';
|
|
1714
|
-
import type {
|
|
1715
|
-
${coreDataRecordTypeName},
|
|
1716
|
-
${dataSetTypeName},
|
|
1717
|
-
${itemTypeName},
|
|
1718
|
-
} from './types';
|
|
1444
|
+
const dataSetTypeName = `${pascalName}AdminViewDataSet`;
|
|
1445
|
+
return `export type ${pascalName}AdminViewStatus = 'draft' | 'published';
|
|
1719
1446
|
|
|
1720
|
-
export interface ${
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1447
|
+
export interface ${itemTypeName} {
|
|
1448
|
+
id: number;
|
|
1449
|
+
owner: string;
|
|
1450
|
+
status: ${pascalName}AdminViewStatus;
|
|
1451
|
+
title: string;
|
|
1452
|
+
updatedAt: string;
|
|
1724
1453
|
}
|
|
1725
1454
|
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1455
|
+
export interface ${dataSetTypeName} {
|
|
1456
|
+
items: ${itemTypeName}[];
|
|
1457
|
+
paginationInfo: {
|
|
1458
|
+
totalItems: number;
|
|
1459
|
+
totalPages: number;
|
|
1460
|
+
};
|
|
1731
1461
|
}
|
|
1732
|
-
|
|
1733
|
-
function normalizeCoreDataString(value: unknown): string {
|
|
1734
|
-
return typeof value === 'string' ? value : '';
|
|
1462
|
+
`;
|
|
1735
1463
|
}
|
|
1464
|
+
function buildDefaultAdminViewConfigSource(adminViewSlug, textDomain) {
|
|
1465
|
+
const pascalName = toPascalCase(adminViewSlug);
|
|
1466
|
+
const camelName = toCamelCase(adminViewSlug);
|
|
1467
|
+
const itemTypeName = `${pascalName}AdminViewItem`;
|
|
1468
|
+
const dataViewsName = `${camelName}AdminDataViews`;
|
|
1469
|
+
return `import { defineDataViews } from '@wp-typia/dataviews';
|
|
1470
|
+
import { __ } from '@wordpress/i18n';
|
|
1736
1471
|
|
|
1737
|
-
|
|
1738
|
-
return {
|
|
1739
|
-
count: normalizeCoreDataNumber(record.count),
|
|
1740
|
-
description: normalizeCoreDataString(record.description),
|
|
1741
|
-
id: record.id,
|
|
1742
|
-
link: normalizeCoreDataString(record.link),
|
|
1743
|
-
name: normalizeCoreDataString(record.name) || normalizeCoreDataString(record.slug),
|
|
1744
|
-
parent: normalizeCoreDataNumber(record.parent),
|
|
1745
|
-
raw: record,
|
|
1746
|
-
slug: normalizeCoreDataString(record.slug),
|
|
1747
|
-
taxonomy: normalizeCoreDataString(record.taxonomy),
|
|
1748
|
-
};
|
|
1749
|
-
}
|
|
1750
|
-
|
|
1751
|
-
export function ${useEntityRecordName}(recordId: number | undefined) {
|
|
1752
|
-
return useEntityRecord<${coreDataRecordTypeName}>(
|
|
1753
|
-
CORE_DATA_ENTITY_KIND,
|
|
1754
|
-
CORE_DATA_ENTITY_NAME,
|
|
1755
|
-
recordId ?? 0,
|
|
1756
|
-
{ enabled: typeof recordId === 'number' },
|
|
1757
|
-
);
|
|
1758
|
-
}
|
|
1759
|
-
|
|
1760
|
-
export function ${useEntityRecordsName}(view: DataViewsView<${itemTypeName}>) {
|
|
1761
|
-
const query = ${dataViewsName}.toQueryArgs<${queryTypeName}>(view, {
|
|
1762
|
-
perPageParam: 'per_page',
|
|
1763
|
-
});
|
|
1764
|
-
|
|
1765
|
-
return useEntityRecords<${coreDataRecordTypeName}>(
|
|
1766
|
-
CORE_DATA_ENTITY_KIND,
|
|
1767
|
-
CORE_DATA_ENTITY_NAME,
|
|
1768
|
-
query,
|
|
1769
|
-
);
|
|
1770
|
-
}
|
|
1472
|
+
import type { ${itemTypeName} } from './types';
|
|
1771
1473
|
|
|
1772
|
-
export
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1474
|
+
export const ${dataViewsName} = defineDataViews<${itemTypeName}>({
|
|
1475
|
+
idField: 'id',
|
|
1476
|
+
search: true,
|
|
1477
|
+
searchLabel: __( 'Search records', ${quoteTsString(textDomain)} ),
|
|
1478
|
+
titleField: 'title',
|
|
1479
|
+
defaultView: {
|
|
1480
|
+
fields: ['title', 'status', 'updatedAt'],
|
|
1481
|
+
page: 1,
|
|
1482
|
+
perPage: 10,
|
|
1483
|
+
sort: {
|
|
1484
|
+
direction: 'desc',
|
|
1485
|
+
field: 'updatedAt',
|
|
1486
|
+
},
|
|
1487
|
+
titleField: 'title',
|
|
1488
|
+
type: 'table',
|
|
1489
|
+
},
|
|
1490
|
+
fields: {
|
|
1491
|
+
id: {
|
|
1492
|
+
enableHiding: false,
|
|
1493
|
+
label: __( 'ID', ${quoteTsString(textDomain)} ),
|
|
1494
|
+
readOnly: true,
|
|
1495
|
+
schema: { type: 'integer' },
|
|
1496
|
+
},
|
|
1497
|
+
owner: {
|
|
1498
|
+
label: __( 'Owner', ${quoteTsString(textDomain)} ),
|
|
1499
|
+
schema: { type: 'string' },
|
|
1500
|
+
},
|
|
1501
|
+
status: {
|
|
1502
|
+
filterBy: { operators: ['isAny', 'isNone'] },
|
|
1503
|
+
label: __( 'Status', ${quoteTsString(textDomain)} ),
|
|
1504
|
+
schema: {
|
|
1505
|
+
enum: ['draft', 'published'],
|
|
1506
|
+
enumLabels: {
|
|
1507
|
+
draft: __( 'Draft', ${quoteTsString(textDomain)} ),
|
|
1508
|
+
published: __( 'Published', ${quoteTsString(textDomain)} ),
|
|
1509
|
+
},
|
|
1510
|
+
type: 'string',
|
|
1785
1511
|
},
|
|
1786
|
-
}
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
}
|
|
1512
|
+
},
|
|
1513
|
+
title: {
|
|
1514
|
+
enableGlobalSearch: true,
|
|
1515
|
+
enableSorting: true,
|
|
1516
|
+
label: __( 'Title', ${quoteTsString(textDomain)} ),
|
|
1517
|
+
schema: { type: 'string' },
|
|
1518
|
+
},
|
|
1519
|
+
updatedAt: {
|
|
1520
|
+
enableSorting: true,
|
|
1521
|
+
label: __( 'Updated', ${quoteTsString(textDomain)} ),
|
|
1522
|
+
schema: { format: 'date-time', type: 'string' },
|
|
1523
|
+
type: 'datetime',
|
|
1524
|
+
},
|
|
1525
|
+
},
|
|
1526
|
+
});
|
|
1800
1527
|
`;
|
|
1801
|
-
|
|
1528
|
+
}
|
|
1529
|
+
function buildDefaultAdminViewDataSource(adminViewSlug) {
|
|
1530
|
+
const pascalName = toPascalCase(adminViewSlug);
|
|
1531
|
+
const camelName = toCamelCase(adminViewSlug);
|
|
1532
|
+
const title = toTitleCase(adminViewSlug);
|
|
1533
|
+
const itemTypeName = `${pascalName}AdminViewItem`;
|
|
1534
|
+
const dataSetTypeName = `${pascalName}AdminViewDataSet`;
|
|
1535
|
+
const queryTypeName = `${pascalName}AdminViewQuery`;
|
|
1536
|
+
const dataViewsName = `${camelName}AdminDataViews`;
|
|
1537
|
+
const fetchName = `fetch${pascalName}AdminViewData`;
|
|
1802
1538
|
return `import type { DataViewsView } from '@wp-typia/dataviews';
|
|
1803
|
-
import { useEntityRecord, useEntityRecords } from '@wordpress/core-data';
|
|
1804
|
-
import { useMemo } from '@wordpress/element';
|
|
1805
1539
|
|
|
1806
1540
|
import { ${dataViewsName} } from './config';
|
|
1807
|
-
import type {
|
|
1808
|
-
${coreDataRecordTypeName},
|
|
1809
|
-
${dataSetTypeName},
|
|
1810
|
-
${itemTypeName},
|
|
1811
|
-
} from './types';
|
|
1541
|
+
import type { ${dataSetTypeName}, ${itemTypeName} } from './types';
|
|
1812
1542
|
|
|
1813
1543
|
export interface ${queryTypeName} {
|
|
1814
1544
|
page?: number;
|
|
1815
|
-
|
|
1545
|
+
perPage?: number;
|
|
1816
1546
|
search?: string;
|
|
1817
1547
|
}
|
|
1818
1548
|
|
|
1819
|
-
const
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
}
|
|
1549
|
+
const STARTER_ITEMS: ${itemTypeName}[] = [
|
|
1550
|
+
{
|
|
1551
|
+
id: 1,
|
|
1552
|
+
owner: 'Editorial',
|
|
1553
|
+
status: 'published',
|
|
1554
|
+
title: ${quoteTsString(`${title} launch checklist`)},
|
|
1555
|
+
updatedAt: '2026-04-01T10:30:00Z',
|
|
1556
|
+
},
|
|
1557
|
+
{
|
|
1558
|
+
id: 2,
|
|
1559
|
+
owner: 'Design',
|
|
1560
|
+
status: 'draft',
|
|
1561
|
+
title: ${quoteTsString(`${title} content refresh`)},
|
|
1562
|
+
updatedAt: '2026-04-03T14:15:00Z',
|
|
1563
|
+
},
|
|
1564
|
+
{
|
|
1565
|
+
id: 3,
|
|
1566
|
+
owner: 'Operations',
|
|
1567
|
+
status: 'published',
|
|
1568
|
+
title: ${quoteTsString(`${title} support handoff`)},
|
|
1569
|
+
updatedAt: '2026-04-08T08:45:00Z',
|
|
1570
|
+
},
|
|
1571
|
+
];
|
|
1825
1572
|
|
|
1826
|
-
function
|
|
1827
|
-
if (
|
|
1828
|
-
return
|
|
1829
|
-
}
|
|
1830
|
-
if (record.title && typeof record.title === 'object') {
|
|
1831
|
-
if (typeof record.title.rendered === 'string') {
|
|
1832
|
-
return record.title.rendered;
|
|
1833
|
-
}
|
|
1834
|
-
if (typeof record.title.raw === 'string') {
|
|
1835
|
-
return record.title.raw;
|
|
1836
|
-
}
|
|
1573
|
+
function matchesSearch(item: ${itemTypeName}, search: string | undefined): boolean {
|
|
1574
|
+
if (!search) {
|
|
1575
|
+
return true;
|
|
1837
1576
|
}
|
|
1838
1577
|
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
function normalizeCoreDataUpdatedAt(record: ${coreDataRecordTypeName}): string {
|
|
1843
|
-
return normalizeCoreDataString(record.modified) || normalizeCoreDataString(record.date);
|
|
1844
|
-
}
|
|
1845
|
-
|
|
1846
|
-
function normalizeCoreDataRecord(record: ${coreDataRecordTypeName}): ${itemTypeName} {
|
|
1847
|
-
return {
|
|
1848
|
-
id: record.id,
|
|
1849
|
-
raw: record,
|
|
1850
|
-
slug: normalizeCoreDataString(record.slug),
|
|
1851
|
-
status: normalizeCoreDataString(record.status),
|
|
1852
|
-
title: normalizeCoreDataTitle(record),
|
|
1853
|
-
updatedAt: normalizeCoreDataUpdatedAt(record),
|
|
1854
|
-
};
|
|
1855
|
-
}
|
|
1856
|
-
|
|
1857
|
-
export function ${useEntityRecordName}(recordId: number | undefined) {
|
|
1858
|
-
return useEntityRecord<${coreDataRecordTypeName}>(
|
|
1859
|
-
CORE_DATA_ENTITY_KIND,
|
|
1860
|
-
CORE_DATA_ENTITY_NAME,
|
|
1861
|
-
recordId ?? 0,
|
|
1862
|
-
{ enabled: typeof recordId === 'number' },
|
|
1578
|
+
const needle = search.toLowerCase();
|
|
1579
|
+
return [item.title, item.owner, item.status].some((value) =>
|
|
1580
|
+
value.toLowerCase().includes(needle),
|
|
1863
1581
|
);
|
|
1864
1582
|
}
|
|
1865
1583
|
|
|
1866
|
-
export function ${
|
|
1584
|
+
export async function ${fetchName}(
|
|
1585
|
+
view: DataViewsView<${itemTypeName}>,
|
|
1586
|
+
): Promise<${dataSetTypeName}> {
|
|
1867
1587
|
const query = ${dataViewsName}.toQueryArgs<${queryTypeName}>(view, {
|
|
1868
|
-
perPageParam: '
|
|
1588
|
+
perPageParam: 'perPage',
|
|
1869
1589
|
});
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
}
|
|
1877
|
-
|
|
1878
|
-
export function ${useAdminViewDataName}(view: DataViewsView<${itemTypeName}>) {
|
|
1879
|
-
const { hasResolved, isResolving, records, totalItems, totalPages } =
|
|
1880
|
-
${useEntityRecordsName}(view);
|
|
1881
|
-
const items = useMemo(
|
|
1882
|
-
() => (records ?? []).map((record) => normalizeCoreDataRecord(record)),
|
|
1883
|
-
[records],
|
|
1884
|
-
);
|
|
1885
|
-
const dataSet = useMemo<${dataSetTypeName}>(
|
|
1886
|
-
() => ({
|
|
1887
|
-
items,
|
|
1888
|
-
paginationInfo: {
|
|
1889
|
-
totalItems: totalItems ?? items.length,
|
|
1890
|
-
totalPages: Math.max(1, totalPages ?? 1),
|
|
1891
|
-
},
|
|
1892
|
-
}),
|
|
1893
|
-
[items, totalItems, totalPages],
|
|
1590
|
+
const requestedPage = query.page ?? 1;
|
|
1591
|
+
const page = requestedPage > 0 ? requestedPage : 1;
|
|
1592
|
+
const requestedPerPage = query.perPage ?? view.perPage ?? 10;
|
|
1593
|
+
const perPage = requestedPerPage > 0 ? requestedPerPage : 10;
|
|
1594
|
+
const filteredItems = STARTER_ITEMS.filter((item) =>
|
|
1595
|
+
matchesSearch(item, query.search),
|
|
1894
1596
|
);
|
|
1895
|
-
const
|
|
1896
|
-
|
|
1897
|
-
? 'Unable to load core-data entity records.'
|
|
1898
|
-
: null;
|
|
1597
|
+
const offset = (page - 1) * perPage;
|
|
1598
|
+
const items = filteredItems.slice(offset, offset + perPage);
|
|
1899
1599
|
|
|
1900
1600
|
return {
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1601
|
+
items,
|
|
1602
|
+
paginationInfo: {
|
|
1603
|
+
totalItems: filteredItems.length,
|
|
1604
|
+
totalPages: Math.max(1, Math.ceil(filteredItems.length / perPage)),
|
|
1605
|
+
},
|
|
1904
1606
|
};
|
|
1905
1607
|
}
|
|
1906
1608
|
`;
|
|
@@ -2018,79 +1720,33 @@ export function ${componentName}() {
|
|
|
2018
1720
|
}
|
|
2019
1721
|
`;
|
|
2020
1722
|
}
|
|
2021
|
-
function buildCoreDataAdminViewScreenSource(adminViewSlug, textDomain) {
|
|
2022
|
-
const pascalName = toPascalCase(adminViewSlug);
|
|
2023
|
-
const camelName = toCamelCase(adminViewSlug);
|
|
2024
|
-
const itemTypeName = `${pascalName}AdminViewItem`;
|
|
2025
|
-
const dataSetTypeName = `${pascalName}AdminViewDataSet`;
|
|
2026
|
-
const componentName = `${pascalName}AdminViewScreen`;
|
|
2027
|
-
const dataViewsName = `${camelName}AdminDataViews`;
|
|
2028
|
-
const useAdminViewDataName = `use${pascalName}AdminViewData`;
|
|
2029
|
-
const title = toTitleCase(adminViewSlug);
|
|
2030
|
-
return `import type { DataViewsConfig, DataViewsView } from '@wp-typia/dataviews';
|
|
2031
|
-
import { Notice, Spinner } from '@wordpress/components';
|
|
2032
|
-
import { useState } from '@wordpress/element';
|
|
2033
|
-
import { __ } from '@wordpress/i18n';
|
|
2034
|
-
import { DataViews } from '@wordpress/dataviews/wp';
|
|
2035
1723
|
|
|
2036
|
-
|
|
2037
|
-
import
|
|
2038
|
-
|
|
1724
|
+
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-admin-view-templates-shared.ts
|
|
1725
|
+
import path4 from "path";
|
|
1726
|
+
function getAdminViewRelativeModuleSpecifier(adminViewSlug, workspaceFile) {
|
|
1727
|
+
const adminViewDir = `src/admin-views/${adminViewSlug}`;
|
|
1728
|
+
const normalizedFile = workspaceFile.replace(/\\/gu, "/");
|
|
1729
|
+
const modulePath = normalizedFile.replace(/\.[cm]?[jt]sx?$/u, "");
|
|
1730
|
+
const relativeModulePath = path4.posix.relative(adminViewDir, modulePath);
|
|
1731
|
+
return relativeModulePath.startsWith(".") ? relativeModulePath : `./${relativeModulePath}`;
|
|
1732
|
+
}
|
|
1733
|
+
function buildAdminViewConfigEntry(adminViewSlug, source) {
|
|
1734
|
+
return [
|
|
1735
|
+
"\t{",
|
|
1736
|
+
` file: ${quoteTsString(`src/admin-views/${adminViewSlug}/index.tsx`)},`,
|
|
1737
|
+
` phpFile: ${quoteTsString(`inc/admin-views/${adminViewSlug}.php`)},`,
|
|
1738
|
+
` slug: ${quoteTsString(adminViewSlug)},`,
|
|
1739
|
+
source ? ` source: ${quoteTsString(formatAdminViewSourceLocator(source))},` : null,
|
|
1740
|
+
"\t},"
|
|
1741
|
+
].filter((line) => typeof line === "string").join(`
|
|
1742
|
+
`);
|
|
1743
|
+
}
|
|
1744
|
+
function buildAdminViewRegistrySource(adminViewSlugs) {
|
|
1745
|
+
const importLines = adminViewSlugs.map((adminViewSlug) => `import './${adminViewSlug}';`).join(`
|
|
1746
|
+
`);
|
|
1747
|
+
return `${importLines}${importLines ? `
|
|
2039
1748
|
|
|
2040
|
-
|
|
2041
|
-
props: DataViewsConfig<TItem>,
|
|
2042
|
-
) => ReturnType<typeof DataViews>;
|
|
2043
|
-
|
|
2044
|
-
const EMPTY_DATA_SET: ${dataSetTypeName} = {
|
|
2045
|
-
items: [],
|
|
2046
|
-
paginationInfo: {
|
|
2047
|
-
totalItems: 0,
|
|
2048
|
-
totalPages: 1,
|
|
2049
|
-
},
|
|
2050
|
-
};
|
|
2051
|
-
|
|
2052
|
-
export function ${componentName}() {
|
|
2053
|
-
const [view, setView] = useState<DataViewsView<${itemTypeName}>>(
|
|
2054
|
-
${dataViewsName}.defaultView,
|
|
2055
|
-
);
|
|
2056
|
-
const {
|
|
2057
|
-
dataSet = EMPTY_DATA_SET,
|
|
2058
|
-
error,
|
|
2059
|
-
isLoading,
|
|
2060
|
-
} = ${useAdminViewDataName}(view);
|
|
2061
|
-
const config = ${dataViewsName}.createConfig({
|
|
2062
|
-
data: dataSet.items,
|
|
2063
|
-
isLoading,
|
|
2064
|
-
onChangeView: setView,
|
|
2065
|
-
paginationInfo: dataSet.paginationInfo,
|
|
2066
|
-
view,
|
|
2067
|
-
});
|
|
2068
|
-
|
|
2069
|
-
return (
|
|
2070
|
-
<div className="wp-typia-admin-view-screen">
|
|
2071
|
-
<header className="wp-typia-admin-view-screen__header">
|
|
2072
|
-
<div>
|
|
2073
|
-
<p className="wp-typia-admin-view-screen__eyebrow">
|
|
2074
|
-
{ __( 'DataViews admin screen', ${quoteTsString(textDomain)} ) }
|
|
2075
|
-
</p>
|
|
2076
|
-
<h1>{ __( ${quoteTsString(title)}, ${quoteTsString(textDomain)} ) }</h1>
|
|
2077
|
-
<p>
|
|
2078
|
-
{ __( 'This screen reads from the WordPress core-data entity store. Extend data.ts when you need entity-specific field mapping or edit flows.', ${quoteTsString(textDomain)} ) }
|
|
2079
|
-
</p>
|
|
2080
|
-
</div>
|
|
2081
|
-
<div className="wp-typia-admin-view-screen__actions">
|
|
2082
|
-
{ isLoading ? <Spinner /> : null }
|
|
2083
|
-
</div>
|
|
2084
|
-
</header>
|
|
2085
|
-
{ error ? (
|
|
2086
|
-
<Notice isDismissible={ false } status="error">
|
|
2087
|
-
{ error }
|
|
2088
|
-
</Notice>
|
|
2089
|
-
) : null }
|
|
2090
|
-
<TypedDataViews<${itemTypeName}> { ...config } />
|
|
2091
|
-
</div>
|
|
2092
|
-
);
|
|
2093
|
-
}
|
|
1749
|
+
` : ""}// wp-typia add admin-view entries
|
|
2094
1750
|
`;
|
|
2095
1751
|
}
|
|
2096
1752
|
function buildAdminViewEntrySource(adminViewSlug, options = {}) {
|
|
@@ -2273,12 +1929,473 @@ if ( ! function_exists( '${enqueueFunctionName}' ) ) {
|
|
|
2273
1929
|
}
|
|
2274
1930
|
}
|
|
2275
1931
|
}
|
|
2276
|
-
|
|
2277
|
-
add_action( 'admin_menu', '${registerFunctionName}' );
|
|
2278
|
-
add_action( 'admin_enqueue_scripts', '${enqueueFunctionName}' );
|
|
1932
|
+
|
|
1933
|
+
add_action( 'admin_menu', '${registerFunctionName}' );
|
|
1934
|
+
add_action( 'admin_enqueue_scripts', '${enqueueFunctionName}' );
|
|
1935
|
+
`;
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-admin-view-templates-rest.ts
|
|
1939
|
+
function buildRestAdminViewTypesSource(adminViewSlug, restResource) {
|
|
1940
|
+
const pascalName = toPascalCase(adminViewSlug);
|
|
1941
|
+
const restPascalName = toPascalCase(restResource.slug);
|
|
1942
|
+
const itemTypeName = `${pascalName}AdminViewItem`;
|
|
1943
|
+
const dataSetTypeName = `${pascalName}AdminViewDataSet`;
|
|
1944
|
+
const restTypesModule = getAdminViewRelativeModuleSpecifier(adminViewSlug, restResource.typesFile);
|
|
1945
|
+
return `import type { ${restPascalName}Record } from ${quoteTsString(restTypesModule)};
|
|
1946
|
+
|
|
1947
|
+
export type ${itemTypeName} = ${restPascalName}Record;
|
|
1948
|
+
|
|
1949
|
+
export interface ${dataSetTypeName} {
|
|
1950
|
+
items: ${itemTypeName}[];
|
|
1951
|
+
paginationInfo: {
|
|
1952
|
+
totalItems: number;
|
|
1953
|
+
totalPages: number;
|
|
1954
|
+
};
|
|
1955
|
+
}
|
|
1956
|
+
`;
|
|
1957
|
+
}
|
|
1958
|
+
function buildRestAdminViewConfigSource(adminViewSlug, textDomain) {
|
|
1959
|
+
const pascalName = toPascalCase(adminViewSlug);
|
|
1960
|
+
const camelName = toCamelCase(adminViewSlug);
|
|
1961
|
+
const itemTypeName = `${pascalName}AdminViewItem`;
|
|
1962
|
+
const dataViewsName = `${camelName}AdminDataViews`;
|
|
1963
|
+
return `import { defineDataViews } from '@wp-typia/dataviews';
|
|
1964
|
+
import { __ } from '@wordpress/i18n';
|
|
1965
|
+
|
|
1966
|
+
import type { ${itemTypeName} } from './types';
|
|
1967
|
+
|
|
1968
|
+
export const ${dataViewsName} = defineDataViews<${itemTypeName}>({
|
|
1969
|
+
idField: 'id',
|
|
1970
|
+
search: false,
|
|
1971
|
+
searchLabel: __( 'Search records', ${quoteTsString(textDomain)} ),
|
|
1972
|
+
defaultView: {
|
|
1973
|
+
fields: ['id'],
|
|
1974
|
+
page: 1,
|
|
1975
|
+
perPage: 10,
|
|
1976
|
+
type: 'table',
|
|
1977
|
+
},
|
|
1978
|
+
fields: {
|
|
1979
|
+
id: {
|
|
1980
|
+
enableHiding: false,
|
|
1981
|
+
label: __( 'ID', ${quoteTsString(textDomain)} ),
|
|
1982
|
+
readOnly: true,
|
|
1983
|
+
schema: { type: 'integer' },
|
|
1984
|
+
},
|
|
1985
|
+
// REST-backed screens start with the guaranteed ID column. Add project-owned fields here once they are declared on the REST record type.
|
|
1986
|
+
},
|
|
1987
|
+
});
|
|
1988
|
+
`;
|
|
1989
|
+
}
|
|
1990
|
+
function buildRestAdminViewDataSource(adminViewSlug, restResource) {
|
|
1991
|
+
const pascalName = toPascalCase(adminViewSlug);
|
|
1992
|
+
const restPascalName = toPascalCase(restResource.slug);
|
|
1993
|
+
const camelName = toCamelCase(adminViewSlug);
|
|
1994
|
+
const itemTypeName = `${pascalName}AdminViewItem`;
|
|
1995
|
+
const dataSetTypeName = `${pascalName}AdminViewDataSet`;
|
|
1996
|
+
const dataViewsName = `${camelName}AdminDataViews`;
|
|
1997
|
+
const fetchName = `fetch${pascalName}AdminViewData`;
|
|
1998
|
+
const restApiModule = getAdminViewRelativeModuleSpecifier(adminViewSlug, restResource.apiFile);
|
|
1999
|
+
const restTypesModule = getAdminViewRelativeModuleSpecifier(adminViewSlug, restResource.typesFile);
|
|
2000
|
+
return `import type { DataViewsView } from '@wp-typia/dataviews';
|
|
2001
|
+
|
|
2002
|
+
import { listResource } from ${quoteTsString(restApiModule)};
|
|
2003
|
+
import type { ${restPascalName}ListQuery } from ${quoteTsString(restTypesModule)};
|
|
2004
|
+
import { ${dataViewsName} } from './config';
|
|
2005
|
+
import type { ${dataSetTypeName}, ${itemTypeName} } from './types';
|
|
2006
|
+
|
|
2007
|
+
function resolveTotalPages(total: number, perPage: number | undefined): number {
|
|
2008
|
+
const resolvedPerPage = perPage && perPage > 0 ? perPage : 1;
|
|
2009
|
+
return Math.max(1, Math.ceil(total / resolvedPerPage));
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
export async function ${fetchName}(
|
|
2013
|
+
view: DataViewsView<${itemTypeName}>,
|
|
2014
|
+
): Promise<${dataSetTypeName}> {
|
|
2015
|
+
const query = ${dataViewsName}.toQueryArgs<${restPascalName}ListQuery>(view, {
|
|
2016
|
+
perPageParam: 'perPage',
|
|
2017
|
+
searchParam: false,
|
|
2018
|
+
});
|
|
2019
|
+
const result = await listResource({
|
|
2020
|
+
page: query.page,
|
|
2021
|
+
perPage: query.perPage,
|
|
2022
|
+
});
|
|
2023
|
+
if (!result.isValid || !result.data) {
|
|
2024
|
+
throw new Error('Unable to load REST resource records.');
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
const response = result.data;
|
|
2028
|
+
|
|
2029
|
+
return {
|
|
2030
|
+
items: response.items,
|
|
2031
|
+
paginationInfo: {
|
|
2032
|
+
totalItems: response.total,
|
|
2033
|
+
totalPages: resolveTotalPages(response.total, response.perPage ?? query.perPage),
|
|
2034
|
+
},
|
|
2035
|
+
};
|
|
2036
|
+
}
|
|
2037
|
+
`;
|
|
2038
|
+
}
|
|
2039
|
+
|
|
2040
|
+
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-admin-view-templates-settings.ts
|
|
2041
|
+
function buildRestSettingsAdminViewTypesSource(adminViewSlug, restResource) {
|
|
2042
|
+
const pascalName = toPascalCase(adminViewSlug);
|
|
2043
|
+
const restTypesModule = getAdminViewRelativeModuleSpecifier(adminViewSlug, restResource.typesFile);
|
|
2044
|
+
const formStateTypeName = `${pascalName}SettingsFormState`;
|
|
2045
|
+
const loadResultTypeName = `${pascalName}SettingsLoadResult`;
|
|
2046
|
+
return `import type {
|
|
2047
|
+
${restResource.bodyTypeName},
|
|
2048
|
+
${restResource.queryTypeName},
|
|
2049
|
+
${restResource.responseTypeName},
|
|
2050
|
+
} from ${quoteTsString(restTypesModule)};
|
|
2051
|
+
|
|
2052
|
+
export type ${pascalName}SettingsRequest = ${restResource.bodyTypeName};
|
|
2053
|
+
export type ${pascalName}SettingsQuery = ${restResource.queryTypeName};
|
|
2054
|
+
export type ${pascalName}SettingsResponse = ${restResource.responseTypeName};
|
|
2055
|
+
export type ${formStateTypeName} = Partial<${pascalName}SettingsRequest>;
|
|
2056
|
+
|
|
2057
|
+
export interface ${loadResultTypeName} {
|
|
2058
|
+
form: ${formStateTypeName};
|
|
2059
|
+
response: ${pascalName}SettingsResponse | null;
|
|
2060
|
+
}
|
|
2061
|
+
`;
|
|
2062
|
+
}
|
|
2063
|
+
function buildRestSettingsAdminViewConfigSource(adminViewSlug, textDomain, restResource) {
|
|
2064
|
+
const pascalName = toPascalCase(adminViewSlug);
|
|
2065
|
+
const camelName = toCamelCase(adminViewSlug);
|
|
2066
|
+
const title = toTitleCase(adminViewSlug);
|
|
2067
|
+
const configName = `${camelName}SettingsConfig`;
|
|
2068
|
+
const formStateTypeName = `${pascalName}SettingsFormState`;
|
|
2069
|
+
const secretPreserveOnEmpty = restResource.secretPreserveOnEmpty !== false;
|
|
2070
|
+
const secretFieldSource = restResource.secretFieldName && restResource.secretStateFieldName ? ` {
|
|
2071
|
+
description: __( ${quoteTsString(secretPreserveOnEmpty ? "Write-only secret value. Leave blank to keep the existing secret." : "Write-only secret value. Blank submissions are sent to the REST route.")}, ${quoteTsString(textDomain)} ),
|
|
2072
|
+
id: ${quoteTsString(restResource.secretFieldName)},
|
|
2073
|
+
label: __( ${quoteTsString(toTitleCase(restResource.secretFieldName))}, ${quoteTsString(textDomain)} ),
|
|
2074
|
+
preserveOnEmpty: ${secretPreserveOnEmpty},
|
|
2075
|
+
secretStateField: ${quoteTsString(restResource.secretStateFieldName)},
|
|
2076
|
+
type: 'secret',
|
|
2077
|
+
},` : "";
|
|
2078
|
+
return `import { __ } from '@wordpress/i18n';
|
|
2079
|
+
|
|
2080
|
+
import type { ${formStateTypeName} } from './types';
|
|
2081
|
+
|
|
2082
|
+
export type ${pascalName}SettingsFieldType = 'secret' | 'text' | 'textarea';
|
|
2083
|
+
|
|
2084
|
+
export interface ${pascalName}SettingsField {
|
|
2085
|
+
description?: string;
|
|
2086
|
+
id: Extract<keyof ${formStateTypeName}, string> | string;
|
|
2087
|
+
label: string;
|
|
2088
|
+
preserveOnEmpty?: boolean;
|
|
2089
|
+
secretStateField?: string;
|
|
2090
|
+
type: ${pascalName}SettingsFieldType;
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
export const ${configName} = {
|
|
2094
|
+
description: __( 'This generated settings form is backed by the ${restResource.slug} REST contract. Adjust config.ts and data.ts as the contract becomes product-specific.', ${quoteTsString(textDomain)} ),
|
|
2095
|
+
fields: [
|
|
2096
|
+
{
|
|
2097
|
+
description: __( 'Primary settings payload for this integration.', ${quoteTsString(textDomain)} ),
|
|
2098
|
+
id: 'payload',
|
|
2099
|
+
label: __( 'Payload', ${quoteTsString(textDomain)} ),
|
|
2100
|
+
type: 'textarea',
|
|
2101
|
+
},
|
|
2102
|
+
{
|
|
2103
|
+
description: __( 'Optional operator note included with the save request.', ${quoteTsString(textDomain)} ),
|
|
2104
|
+
id: 'comment',
|
|
2105
|
+
label: __( 'Comment', ${quoteTsString(textDomain)} ),
|
|
2106
|
+
type: 'text',
|
|
2107
|
+
},
|
|
2108
|
+
${secretFieldSource}
|
|
2109
|
+
] satisfies ${pascalName}SettingsField[],
|
|
2110
|
+
secretFieldName: ${restResource.secretFieldName ? quoteTsString(restResource.secretFieldName) : "undefined"},
|
|
2111
|
+
secretPreserveOnEmpty: ${restResource.secretFieldName ? secretPreserveOnEmpty : "undefined"},
|
|
2112
|
+
secretStateFieldName: ${restResource.secretStateFieldName ? quoteTsString(restResource.secretStateFieldName) : "undefined"},
|
|
2113
|
+
title: __( ${quoteTsString(title)}, ${quoteTsString(textDomain)} ),
|
|
2114
|
+
};
|
|
2115
|
+
`;
|
|
2116
|
+
}
|
|
2117
|
+
function buildRestSettingsAdminViewDataSource(adminViewSlug, restResource) {
|
|
2118
|
+
const pascalName = toPascalCase(adminViewSlug);
|
|
2119
|
+
const restApiModule = getAdminViewRelativeModuleSpecifier(adminViewSlug, restResource.apiFile);
|
|
2120
|
+
const formStateTypeName = `${pascalName}SettingsFormState`;
|
|
2121
|
+
const loadResultTypeName = `${pascalName}SettingsLoadResult`;
|
|
2122
|
+
const loadName = `load${pascalName}Settings`;
|
|
2123
|
+
const saveName = `save${pascalName}Settings`;
|
|
2124
|
+
const secretPreserveOnEmpty = restResource.secretPreserveOnEmpty !== false;
|
|
2125
|
+
const initialFieldLines = [
|
|
2126
|
+
"\tpayload: '',",
|
|
2127
|
+
"\tcomment: '',",
|
|
2128
|
+
...restResource.secretFieldName && !secretPreserveOnEmpty ? [` ${quoteTsString(restResource.secretFieldName)}: '',`] : []
|
|
2129
|
+
];
|
|
2130
|
+
const initialFields = initialFieldLines.join(`
|
|
2131
|
+
`);
|
|
2132
|
+
const requestBodySource = restResource.secretFieldName && secretPreserveOnEmpty ? ` const requestBody = { ...form } as Record<string, unknown>;
|
|
2133
|
+
if (requestBody[${quoteTsString(restResource.secretFieldName)}] === '') {
|
|
2134
|
+
delete requestBody[${quoteTsString(restResource.secretFieldName)}];
|
|
2135
|
+
}
|
|
2136
|
+
` : ` const requestBody = form as Record<string, unknown>;
|
|
2137
|
+
`;
|
|
2138
|
+
return `import { callManualRestContract } from ${quoteTsString(restApiModule)};
|
|
2139
|
+
import type {
|
|
2140
|
+
${formStateTypeName},
|
|
2141
|
+
${loadResultTypeName},
|
|
2142
|
+
${pascalName}SettingsQuery,
|
|
2143
|
+
${pascalName}SettingsRequest,
|
|
2144
|
+
${pascalName}SettingsResponse,
|
|
2145
|
+
} from './types';
|
|
2146
|
+
|
|
2147
|
+
function formatValidationError(prefix: string, errors: unknown): string {
|
|
2148
|
+
if (!Array.isArray(errors) || errors.length === 0) {
|
|
2149
|
+
return prefix;
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
return \`\${prefix} \${JSON.stringify(errors)}\`;
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
export function createInitial${pascalName}SettingsFormState(): ${formStateTypeName} {
|
|
2156
|
+
return {
|
|
2157
|
+
${initialFields}
|
|
2158
|
+
} as ${formStateTypeName};
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
export async function ${loadName}(): Promise<${loadResultTypeName}> {
|
|
2162
|
+
return {
|
|
2163
|
+
form: createInitial${pascalName}SettingsFormState(),
|
|
2164
|
+
response: null,
|
|
2165
|
+
};
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
export async function ${saveName}(
|
|
2169
|
+
form: ${formStateTypeName},
|
|
2170
|
+
query: Partial<${pascalName}SettingsQuery> = {},
|
|
2171
|
+
): Promise<${pascalName}SettingsResponse> {
|
|
2172
|
+
${requestBodySource}
|
|
2173
|
+
const result = await callManualRestContract({
|
|
2174
|
+
body: requestBody as unknown as ${pascalName}SettingsRequest,
|
|
2175
|
+
query: query as ${pascalName}SettingsQuery,
|
|
2176
|
+
});
|
|
2177
|
+
if (!result.isValid) {
|
|
2178
|
+
const message =
|
|
2179
|
+
result.validationTarget === 'request'
|
|
2180
|
+
? 'Settings request failed validation.'
|
|
2181
|
+
: 'Settings response failed validation.';
|
|
2182
|
+
throw new Error(formatValidationError(message, result.errors));
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2185
|
+
return result.data as ${pascalName}SettingsResponse;
|
|
2186
|
+
}
|
|
2187
|
+
`;
|
|
2188
|
+
}
|
|
2189
|
+
function buildRestSettingsAdminViewScreenSource(adminViewSlug, textDomain) {
|
|
2190
|
+
const pascalName = toPascalCase(adminViewSlug);
|
|
2191
|
+
const componentName = `${pascalName}AdminViewScreen`;
|
|
2192
|
+
const formStateTypeName = `${pascalName}SettingsFormState`;
|
|
2193
|
+
const responseTypeName = `${pascalName}SettingsResponse`;
|
|
2194
|
+
const camelName = toCamelCase(adminViewSlug);
|
|
2195
|
+
const configName = `${camelName}SettingsConfig`;
|
|
2196
|
+
const loadName = `load${pascalName}Settings`;
|
|
2197
|
+
const saveName = `save${pascalName}Settings`;
|
|
2198
|
+
return `import {
|
|
2199
|
+
Button,
|
|
2200
|
+
Notice,
|
|
2201
|
+
Spinner,
|
|
2202
|
+
TextControl,
|
|
2203
|
+
TextareaControl,
|
|
2204
|
+
} from '@wordpress/components';
|
|
2205
|
+
import { useEffect, useState } from '@wordpress/element';
|
|
2206
|
+
import { __ } from '@wordpress/i18n';
|
|
2207
|
+
|
|
2208
|
+
import { ${configName} } from './config';
|
|
2209
|
+
import { ${loadName}, ${saveName} } from './data';
|
|
2210
|
+
import type { ${formStateTypeName}, ${responseTypeName} } from './types';
|
|
2211
|
+
|
|
2212
|
+
function getFieldValue(form: ${formStateTypeName}, fieldId: string): string {
|
|
2213
|
+
const value = (form as Record<string, unknown>)[fieldId];
|
|
2214
|
+
if (typeof value === 'string') {
|
|
2215
|
+
return value;
|
|
2216
|
+
}
|
|
2217
|
+
if (value == null) {
|
|
2218
|
+
return '';
|
|
2219
|
+
}
|
|
2220
|
+
|
|
2221
|
+
return String(value);
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2224
|
+
function getSecretState(response: ${responseTypeName} | null): boolean | null {
|
|
2225
|
+
const stateField = ${configName}.secretStateFieldName;
|
|
2226
|
+
if (!stateField || !response) {
|
|
2227
|
+
return null;
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
const value = (response as unknown as Record<string, unknown>)[stateField];
|
|
2231
|
+
return typeof value === 'boolean' ? value : null;
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
export function ${componentName}() {
|
|
2235
|
+
const [form, setForm] = useState<${formStateTypeName}>({});
|
|
2236
|
+
const [response, setResponse] = useState<${responseTypeName} | null>(null);
|
|
2237
|
+
const [error, setError] = useState<string | null>(null);
|
|
2238
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
2239
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
2240
|
+
const [successMessage, setSuccessMessage] = useState<string | null>(null);
|
|
2241
|
+
|
|
2242
|
+
useEffect(() => {
|
|
2243
|
+
let isCurrent = true;
|
|
2244
|
+
setIsLoading(true);
|
|
2245
|
+
setError(null);
|
|
2246
|
+
|
|
2247
|
+
void ${loadName}()
|
|
2248
|
+
.then((result) => {
|
|
2249
|
+
if (isCurrent) {
|
|
2250
|
+
setForm(result.form);
|
|
2251
|
+
setResponse(result.response);
|
|
2252
|
+
}
|
|
2253
|
+
})
|
|
2254
|
+
.catch((nextError: unknown) => {
|
|
2255
|
+
if (isCurrent) {
|
|
2256
|
+
setError(
|
|
2257
|
+
nextError instanceof Error
|
|
2258
|
+
? nextError.message
|
|
2259
|
+
: __( 'Unable to prepare settings form.', ${quoteTsString(textDomain)} ),
|
|
2260
|
+
);
|
|
2261
|
+
}
|
|
2262
|
+
})
|
|
2263
|
+
.finally(() => {
|
|
2264
|
+
if (isCurrent) {
|
|
2265
|
+
setIsLoading(false);
|
|
2266
|
+
}
|
|
2267
|
+
});
|
|
2268
|
+
|
|
2269
|
+
return () => {
|
|
2270
|
+
isCurrent = false;
|
|
2271
|
+
};
|
|
2272
|
+
}, []);
|
|
2273
|
+
|
|
2274
|
+
const setFormValue = (fieldId: string, value: string) => {
|
|
2275
|
+
setForm(
|
|
2276
|
+
(current) =>
|
|
2277
|
+
({
|
|
2278
|
+
...current,
|
|
2279
|
+
[fieldId]: value,
|
|
2280
|
+
}) as ${formStateTypeName},
|
|
2281
|
+
);
|
|
2282
|
+
};
|
|
2283
|
+
const secretState = getSecretState(response);
|
|
2284
|
+
|
|
2285
|
+
const handleSubmit = (event: { preventDefault: () => void }) => {
|
|
2286
|
+
event.preventDefault();
|
|
2287
|
+
setError(null);
|
|
2288
|
+
setSuccessMessage(null);
|
|
2289
|
+
setIsSaving(true);
|
|
2290
|
+
|
|
2291
|
+
void ${saveName}(form)
|
|
2292
|
+
.then((nextResponse) => {
|
|
2293
|
+
setResponse(nextResponse);
|
|
2294
|
+
setSuccessMessage(__( 'Settings saved.', ${quoteTsString(textDomain)} ));
|
|
2295
|
+
})
|
|
2296
|
+
.catch((nextError: unknown) => {
|
|
2297
|
+
setError(
|
|
2298
|
+
nextError instanceof Error
|
|
2299
|
+
? nextError.message
|
|
2300
|
+
: __( 'Unable to save settings.', ${quoteTsString(textDomain)} ),
|
|
2301
|
+
);
|
|
2302
|
+
})
|
|
2303
|
+
.finally(() => setIsSaving(false));
|
|
2304
|
+
};
|
|
2305
|
+
|
|
2306
|
+
return (
|
|
2307
|
+
<div className="wp-typia-admin-view-screen wp-typia-admin-view-screen--settings">
|
|
2308
|
+
<header className="wp-typia-admin-view-screen__header">
|
|
2309
|
+
<div>
|
|
2310
|
+
<p className="wp-typia-admin-view-screen__eyebrow">
|
|
2311
|
+
{ __( 'Typed settings screen', ${quoteTsString(textDomain)} ) }
|
|
2312
|
+
</p>
|
|
2313
|
+
<h1>{ ${configName}.title }</h1>
|
|
2314
|
+
<p>{ ${configName}.description }</p>
|
|
2315
|
+
</div>
|
|
2316
|
+
<div className="wp-typia-admin-view-screen__actions">
|
|
2317
|
+
{ isLoading || isSaving ? <Spinner /> : null }
|
|
2318
|
+
</div>
|
|
2319
|
+
</header>
|
|
2320
|
+
{ error ? (
|
|
2321
|
+
<Notice isDismissible={ false } status="error">
|
|
2322
|
+
{ error }
|
|
2323
|
+
</Notice>
|
|
2324
|
+
) : null }
|
|
2325
|
+
{ successMessage ? (
|
|
2326
|
+
<Notice isDismissible={ false } status="success">
|
|
2327
|
+
{ successMessage }
|
|
2328
|
+
</Notice>
|
|
2329
|
+
) : null }
|
|
2330
|
+
{ secretState !== null ? (
|
|
2331
|
+
<Notice isDismissible={ false } status="info">
|
|
2332
|
+
{ secretState
|
|
2333
|
+
? __( 'A secret is currently configured for this integration.', ${quoteTsString(textDomain)} )
|
|
2334
|
+
: __( 'No secret is currently configured for this integration.', ${quoteTsString(textDomain)} ) }
|
|
2335
|
+
</Notice>
|
|
2336
|
+
) : null }
|
|
2337
|
+
<form className="wp-typia-admin-view-screen__settings-form" onSubmit={ handleSubmit }>
|
|
2338
|
+
{ ${configName}.fields.map((field) => (
|
|
2339
|
+
<div className="wp-typia-admin-view-screen__field" key={ field.id }>
|
|
2340
|
+
{ field.type === 'textarea' ? (
|
|
2341
|
+
<TextareaControl
|
|
2342
|
+
help={ field.description }
|
|
2343
|
+
label={ field.label }
|
|
2344
|
+
onChange={ (value) => setFormValue(field.id, value) }
|
|
2345
|
+
value={ getFieldValue(form, field.id) }
|
|
2346
|
+
/>
|
|
2347
|
+
) : (
|
|
2348
|
+
<TextControl
|
|
2349
|
+
help={ field.description }
|
|
2350
|
+
label={ field.label }
|
|
2351
|
+
onChange={ (value) => setFormValue(field.id, value) }
|
|
2352
|
+
type={ field.type === 'secret' ? 'password' : 'text' }
|
|
2353
|
+
value={ getFieldValue(form, field.id) }
|
|
2354
|
+
/>
|
|
2355
|
+
) }
|
|
2356
|
+
</div>
|
|
2357
|
+
)) }
|
|
2358
|
+
<Button
|
|
2359
|
+
disabled={ isLoading || isSaving }
|
|
2360
|
+
isBusy={ isSaving }
|
|
2361
|
+
type="submit"
|
|
2362
|
+
variant="primary"
|
|
2363
|
+
>
|
|
2364
|
+
{ __( 'Save settings', ${quoteTsString(textDomain)} ) }
|
|
2365
|
+
</Button>
|
|
2366
|
+
</form>
|
|
2367
|
+
</div>
|
|
2368
|
+
);
|
|
2369
|
+
}
|
|
2279
2370
|
`;
|
|
2280
2371
|
}
|
|
2281
2372
|
|
|
2373
|
+
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-admin-view-templates.ts
|
|
2374
|
+
function buildAdminViewTypesSource(adminViewSlug, restResource, coreDataSource) {
|
|
2375
|
+
if (restResource) {
|
|
2376
|
+
return buildRestAdminViewTypesSource(adminViewSlug, restResource);
|
|
2377
|
+
}
|
|
2378
|
+
if (coreDataSource) {
|
|
2379
|
+
return buildCoreDataAdminViewTypesSource(adminViewSlug, coreDataSource);
|
|
2380
|
+
}
|
|
2381
|
+
return buildDefaultAdminViewTypesSource(adminViewSlug);
|
|
2382
|
+
}
|
|
2383
|
+
function buildAdminViewConfigSource(adminViewSlug, textDomain, source, restResource) {
|
|
2384
|
+
if (restResource) {
|
|
2385
|
+
return buildRestAdminViewConfigSource(adminViewSlug, textDomain);
|
|
2386
|
+
}
|
|
2387
|
+
if (isAdminViewCoreDataSource(source)) {
|
|
2388
|
+
return buildCoreDataAdminViewConfigSource(adminViewSlug, textDomain, source);
|
|
2389
|
+
}
|
|
2390
|
+
return buildDefaultAdminViewConfigSource(adminViewSlug, textDomain);
|
|
2391
|
+
}
|
|
2392
|
+
function buildAdminViewScreenSource2(adminViewSlug, textDomain) {
|
|
2393
|
+
return buildAdminViewScreenSource(adminViewSlug, textDomain);
|
|
2394
|
+
}
|
|
2395
|
+
function buildAdminViewPhpSource2(adminViewSlug, workspace) {
|
|
2396
|
+
return buildAdminViewPhpSource(adminViewSlug, workspace);
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2282
2399
|
// ../wp-typia-project-tools/src/runtime/rest-resource-artifacts.ts
|
|
2283
2400
|
import path5 from "path";
|
|
2284
2401
|
import {
|
|
@@ -2581,18 +2698,22 @@ function buildManualRestContractConfigEntry(options) {
|
|
|
2581
2698
|
` auth: ${quoteTsString(options.auth)},`,
|
|
2582
2699
|
...options.bodyTypeName ? [` bodyTypeName: ${quoteTsString(options.bodyTypeName)},`] : [],
|
|
2583
2700
|
` clientFile: ${quoteTsString(`src/rest/${options.restResourceSlug}/api-client.ts`)},`,
|
|
2701
|
+
...options.controllerClass ? [` controllerClass: ${quoteTsString(options.controllerClass)},`] : [],
|
|
2702
|
+
...options.controllerExtends ? [` controllerExtends: ${quoteTsString(options.controllerExtends)},`] : [],
|
|
2584
2703
|
` method: ${quoteTsString(options.method)},`,
|
|
2585
2704
|
"\t\tmethods: [],",
|
|
2586
2705
|
"\t\tmode: 'manual',",
|
|
2587
2706
|
` namespace: ${quoteTsString(options.namespace)},`,
|
|
2588
2707
|
` openApiFile: ${quoteTsString(`src/rest/${options.restResourceSlug}/api.openapi.json`)},`,
|
|
2589
2708
|
` pathPattern: ${quoteTsString(options.pathPattern)},`,
|
|
2709
|
+
...options.permissionCallback ? [` permissionCallback: ${quoteTsString(options.permissionCallback)},`] : [],
|
|
2590
2710
|
` queryTypeName: ${quoteTsString(options.queryTypeName)},`,
|
|
2591
2711
|
"\t\trestManifest: defineEndpointManifest(",
|
|
2592
2712
|
indentMultiline(JSON.stringify(manifest, null, "\t"), "\t\t\t"),
|
|
2593
2713
|
"\t\t),",
|
|
2594
2714
|
` responseTypeName: ${quoteTsString(options.responseTypeName)},`,
|
|
2595
2715
|
...options.secretFieldName ? [` secretFieldName: ${quoteTsString(options.secretFieldName)},`] : [],
|
|
2716
|
+
...options.secretPreserveOnEmpty !== undefined ? [` secretPreserveOnEmpty: ${options.secretPreserveOnEmpty},`] : [],
|
|
2596
2717
|
...options.secretStateFieldName ? [` secretStateFieldName: ${quoteTsString(options.secretStateFieldName)},`] : [],
|
|
2597
2718
|
` slug: ${quoteTsString(options.restResourceSlug)},`,
|
|
2598
2719
|
` typesFile: ${quoteTsString(`src/rest/${options.restResourceSlug}/api-types.ts`)},`,
|
|
@@ -2603,18 +2724,21 @@ function buildManualRestContractConfigEntry(options) {
|
|
|
2603
2724
|
}
|
|
2604
2725
|
function buildManualRestContractTypesSource(options) {
|
|
2605
2726
|
const title = toTitleCase(options.restResourceSlug);
|
|
2727
|
+
const pathParameterNames = Array.from(new Set(options.pathParameterNames ?? []));
|
|
2728
|
+
const queryFields = pathParameterNames.length > 0 ? pathParameterNames.map((parameterName) => ` ${parameterName}: string & tags.MinLength< 1 >;`) : ["\tid?: string & tags.MinLength< 1 >;"];
|
|
2606
2729
|
const lines = [
|
|
2607
2730
|
"import type { tags } from '@wp-typia/block-runtime/typia-tags';",
|
|
2608
2731
|
"",
|
|
2609
2732
|
`export interface ${options.queryTypeName} {`,
|
|
2610
|
-
|
|
2611
|
-
"\tpreview?: boolean;",
|
|
2733
|
+
...queryFields,
|
|
2734
|
+
...pathParameterNames.includes("preview") ? [] : ["\tpreview?: boolean;"],
|
|
2612
2735
|
"}"
|
|
2613
2736
|
];
|
|
2614
2737
|
if (options.bodyTypeName) {
|
|
2738
|
+
const secretPreserveOnEmpty = options.secretPreserveOnEmpty ?? true;
|
|
2615
2739
|
const secretLines = options.secretFieldName && options.secretStateFieldName ? [
|
|
2616
|
-
` ${options.secretFieldName}?: string & tags.MinLength< 1 > & tags.MaxLength< 4096 > & tags.Secret< ${quoteTsString(options.secretStateFieldName)}
|
|
2617
|
-
` // ${options.secretFieldName} is write-only: persist it server-side and expose ${options.secretStateFieldName} in responses instead of returning the raw value.`
|
|
2740
|
+
` ${options.secretFieldName}?: string${secretPreserveOnEmpty ? " & tags.MinLength< 1 >" : ""} & tags.MaxLength< 4096 > & tags.Secret< ${quoteTsString(options.secretStateFieldName)} >${secretPreserveOnEmpty ? " & tags.PreserveOnEmpty< true >" : ""};`,
|
|
2741
|
+
secretPreserveOnEmpty ? ` // ${options.secretFieldName} is write-only: omit or submit an empty value to preserve the stored secret, and expose ${options.secretStateFieldName} in responses instead of returning the raw value.` : ` // ${options.secretFieldName} is write-only: persist it server-side and expose ${options.secretStateFieldName} in responses instead of returning the raw value.`
|
|
2618
2742
|
] : [];
|
|
2619
2743
|
lines.push("", `export interface ${options.bodyTypeName} {`, ...secretLines, "\tpayload: string & tags.MinLength< 1 >;", "\tcomment?: string & tags.MaxLength< 500 >;", "}");
|
|
2620
2744
|
}
|
|
@@ -3105,67 +3229,6 @@ ${exportedBindings.join(`
|
|
|
3105
3229
|
`;
|
|
3106
3230
|
}
|
|
3107
3231
|
|
|
3108
|
-
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-mutation.ts
|
|
3109
|
-
var DEFAULT_PHP_SNIPPET_INSERTION_ANCHORS = [
|
|
3110
|
-
/add_action\(\s*["']init["']\s*,\s*["'][^"']+_load_textdomain["']\s*\);\s*\n/u,
|
|
3111
|
-
/\?>\s*$/u
|
|
3112
|
-
];
|
|
3113
|
-
|
|
3114
|
-
class WorkspaceMutationRollbackError extends Error {
|
|
3115
|
-
mutationError;
|
|
3116
|
-
rollbackError;
|
|
3117
|
-
constructor(mutationError, rollbackError) {
|
|
3118
|
-
super("Workspace mutation failed and rollback also failed.");
|
|
3119
|
-
this.name = "WorkspaceMutationRollbackError";
|
|
3120
|
-
this.mutationError = mutationError;
|
|
3121
|
-
this.rollbackError = rollbackError;
|
|
3122
|
-
}
|
|
3123
|
-
}
|
|
3124
|
-
async function executeWorkspaceMutationPlan({
|
|
3125
|
-
filePaths,
|
|
3126
|
-
run,
|
|
3127
|
-
snapshotDirs = [],
|
|
3128
|
-
targetPaths = []
|
|
3129
|
-
}) {
|
|
3130
|
-
const mutationSnapshot = {
|
|
3131
|
-
fileSources: await snapshotWorkspaceFiles(filePaths),
|
|
3132
|
-
snapshotDirs: [...snapshotDirs],
|
|
3133
|
-
targetPaths: [...targetPaths]
|
|
3134
|
-
};
|
|
3135
|
-
try {
|
|
3136
|
-
return await run();
|
|
3137
|
-
} catch (error) {
|
|
3138
|
-
try {
|
|
3139
|
-
await rollbackWorkspaceMutation(mutationSnapshot);
|
|
3140
|
-
} catch (rollbackError) {
|
|
3141
|
-
throw new WorkspaceMutationRollbackError(error, rollbackError);
|
|
3142
|
-
}
|
|
3143
|
-
throw error;
|
|
3144
|
-
}
|
|
3145
|
-
}
|
|
3146
|
-
function insertPhpSnippetBeforeWorkspaceAnchors(source, snippet) {
|
|
3147
|
-
for (const anchor of DEFAULT_PHP_SNIPPET_INSERTION_ANCHORS) {
|
|
3148
|
-
const candidate = source.replace(anchor, (match) => `${snippet}
|
|
3149
|
-
${match}`);
|
|
3150
|
-
if (candidate !== source) {
|
|
3151
|
-
return candidate;
|
|
3152
|
-
}
|
|
3153
|
-
}
|
|
3154
|
-
return `${source.trimEnd()}
|
|
3155
|
-
${snippet}
|
|
3156
|
-
`;
|
|
3157
|
-
}
|
|
3158
|
-
function appendPhpSnippetBeforeClosingTag(source, snippet) {
|
|
3159
|
-
const closingTagPattern = /\?>\s*$/u;
|
|
3160
|
-
if (closingTagPattern.test(source)) {
|
|
3161
|
-
return source.replace(closingTagPattern, `${snippet}
|
|
3162
|
-
?>`);
|
|
3163
|
-
}
|
|
3164
|
-
return `${source.trimEnd()}
|
|
3165
|
-
${snippet}
|
|
3166
|
-
`;
|
|
3167
|
-
}
|
|
3168
|
-
|
|
3169
3232
|
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-admin-view-scaffold.ts
|
|
3170
3233
|
function detectJsonIndent(source) {
|
|
3171
3234
|
const indentMatch = /\n([ \t]+)"/u.exec(source);
|
|
@@ -3218,7 +3281,10 @@ async function ensureAdminViewPackageDependencies(workspace, adminViewSource, re
|
|
|
3218
3281
|
packageName: "@wordpress/data"
|
|
3219
3282
|
});
|
|
3220
3283
|
await patchFile(packageJsonPath, (source) => {
|
|
3221
|
-
const packageJson =
|
|
3284
|
+
const packageJson = safeJsonParse(source, {
|
|
3285
|
+
context: "admin view package manifest",
|
|
3286
|
+
filePath: packageJsonPath
|
|
3287
|
+
});
|
|
3222
3288
|
const needsDataViews = !isAdminViewManualSettingsRestResource(restResource);
|
|
3223
3289
|
const coreDataDependencies = isAdminViewCoreDataSource(adminViewSource) ? {
|
|
3224
3290
|
"@wordpress/core-data": packageJson.dependencies?.["@wordpress/core-data"] ?? wordpressCoreDataVersion,
|
|
@@ -3429,12 +3495,12 @@ async function scaffoldAdminViewWorkspace(options) {
|
|
|
3429
3495
|
await fsp3.writeFile(path6.join(adminViewDir, "types.ts"), manualSettingsRestResource ? buildRestSettingsAdminViewTypesSource(adminViewSlug, manualSettingsRestResource) : buildAdminViewTypesSource(adminViewSlug, restResource, coreDataSource), "utf8");
|
|
3430
3496
|
await fsp3.writeFile(path6.join(adminViewDir, "config.ts"), manualSettingsRestResource ? buildRestSettingsAdminViewConfigSource(adminViewSlug, workspace.workspace.textDomain, manualSettingsRestResource) : buildAdminViewConfigSource(adminViewSlug, workspace.workspace.textDomain, parsedSource, restResource), "utf8");
|
|
3431
3497
|
await fsp3.writeFile(path6.join(adminViewDir, "data.ts"), manualSettingsRestResource ? buildRestSettingsAdminViewDataSource(adminViewSlug, manualSettingsRestResource) : coreDataSource ? buildCoreDataAdminViewDataSource(adminViewSlug, coreDataSource) : restResource ? buildRestAdminViewDataSource(adminViewSlug, restResource) : buildDefaultAdminViewDataSource(adminViewSlug), "utf8");
|
|
3432
|
-
await fsp3.writeFile(path6.join(adminViewDir, "Screen.tsx"), manualSettingsRestResource ? buildRestSettingsAdminViewScreenSource(adminViewSlug, workspace.workspace.textDomain) : coreDataSource ? buildCoreDataAdminViewScreenSource(adminViewSlug, workspace.workspace.textDomain) :
|
|
3498
|
+
await fsp3.writeFile(path6.join(adminViewDir, "Screen.tsx"), manualSettingsRestResource ? buildRestSettingsAdminViewScreenSource(adminViewSlug, workspace.workspace.textDomain) : coreDataSource ? buildCoreDataAdminViewScreenSource(adminViewSlug, workspace.workspace.textDomain) : buildAdminViewScreenSource2(adminViewSlug, workspace.workspace.textDomain), "utf8");
|
|
3433
3499
|
await fsp3.writeFile(path6.join(adminViewDir, "index.tsx"), buildAdminViewEntrySource(adminViewSlug, {
|
|
3434
3500
|
includeDataViewsStyle: !manualSettingsRestResource
|
|
3435
3501
|
}), "utf8");
|
|
3436
3502
|
await fsp3.writeFile(path6.join(adminViewDir, "style.scss"), buildAdminViewStyleSource(), "utf8");
|
|
3437
|
-
await fsp3.writeFile(adminViewPhpPath,
|
|
3503
|
+
await fsp3.writeFile(adminViewPhpPath, buildAdminViewPhpSource2(adminViewSlug, workspace), "utf8");
|
|
3438
3504
|
await writeAdminViewRegistry(workspace.projectDir, adminViewSlug);
|
|
3439
3505
|
await appendWorkspaceInventoryEntries(workspace.projectDir, {
|
|
3440
3506
|
adminViewEntries: [
|
|
@@ -4332,486 +4398,107 @@ async function runAddPatternCommand({
|
|
|
4332
4398
|
throw error;
|
|
4333
4399
|
}
|
|
4334
4400
|
}
|
|
4335
|
-
async function runAddBindingSourceCommand({
|
|
4336
|
-
attributeName,
|
|
4337
|
-
bindingSourceName,
|
|
4338
|
-
blockName,
|
|
4339
|
-
cwd = process.cwd()
|
|
4340
|
-
}) {
|
|
4341
|
-
const workspace = resolveWorkspaceProject(cwd);
|
|
4342
|
-
const bindingSourceSlug = assertValidGeneratedSlug("Binding source name", normalizeBlockSlug(bindingSourceName), "wp-typia add binding-source <name> [--block <block-slug|namespace/block-slug> --attribute <attribute>]");
|
|
4343
|
-
const inventory = await readWorkspaceInventoryAsync(workspace.projectDir);
|
|
4344
|
-
assertBindingSourceDoesNotExist(workspace.projectDir, bindingSourceSlug, inventory);
|
|
4345
|
-
const target = resolveBindingTarget({
|
|
4346
|
-
attributeName,
|
|
4347
|
-
blockName
|
|
4348
|
-
}, workspace.workspace.namespace);
|
|
4349
|
-
const targetBlock = target ? resolveWorkspaceBlock(inventory, target.blockSlug) : undefined;
|
|
4350
|
-
const blockConfigPath = path7.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
4351
|
-
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
4352
|
-
const bindingsIndexPath = await resolveBindingSourceRegistryPath(workspace.projectDir);
|
|
4353
|
-
const bindingSourceDir = path7.join(workspace.projectDir, "src", "bindings", bindingSourceSlug);
|
|
4354
|
-
const serverFilePath = path7.join(bindingSourceDir, "server.php");
|
|
4355
|
-
const editorFilePath = path7.join(bindingSourceDir, "editor.ts");
|
|
4356
|
-
const blockJsonPath = target ? path7.join(workspace.projectDir, "src", "blocks", target.blockSlug, "block.json") : undefined;
|
|
4357
|
-
const targetGeneratedMetadataPaths = target ? [
|
|
4358
|
-
path7.join(workspace.projectDir, "src", "blocks", target.blockSlug, "typia.manifest.json"),
|
|
4359
|
-
path7.join(workspace.projectDir, "src", "blocks", target.blockSlug, "typia.openapi.json"),
|
|
4360
|
-
path7.join(workspace.projectDir, "src", "blocks", target.blockSlug, "typia.schema.json"),
|
|
4361
|
-
path7.join(workspace.projectDir, "src", "blocks", target.blockSlug, "typia-validator.php")
|
|
4362
|
-
] : [];
|
|
4363
|
-
const mutationSnapshot = {
|
|
4364
|
-
fileSources: await snapshotWorkspaceFiles([
|
|
4365
|
-
blockConfigPath,
|
|
4366
|
-
bootstrapPath,
|
|
4367
|
-
bindingsIndexPath,
|
|
4368
|
-
...blockJsonPath ? [blockJsonPath] : [],
|
|
4369
|
-
...targetBlock ? [path7.join(workspace.projectDir, targetBlock.typesFile)] : [],
|
|
4370
|
-
...targetGeneratedMetadataPaths
|
|
4371
|
-
]),
|
|
4372
|
-
snapshotDirs: [],
|
|
4373
|
-
targetPaths: [bindingSourceDir]
|
|
4374
|
-
};
|
|
4375
|
-
try {
|
|
4376
|
-
await fsp4.mkdir(bindingSourceDir, { recursive: true });
|
|
4377
|
-
await ensureBindingSourceBootstrapAnchors(workspace);
|
|
4378
|
-
await fsp4.writeFile(serverFilePath, buildBindingSourceServerSource(bindingSourceSlug, workspace.workspace.phpPrefix, workspace.workspace.namespace, workspace.workspace.textDomain, target), "utf8");
|
|
4379
|
-
await fsp4.writeFile(editorFilePath, buildBindingSourceEditorSource(bindingSourceSlug, workspace.workspace.namespace, workspace.workspace.textDomain, target), "utf8");
|
|
4380
|
-
if (target && targetBlock) {
|
|
4381
|
-
await ensureBindingTargetBlockAttributeType(workspace.projectDir, targetBlock, target);
|
|
4382
|
-
}
|
|
4383
|
-
await writeBindingSourceRegistry(workspace.projectDir, bindingSourceSlug);
|
|
4384
|
-
await appendWorkspaceInventoryEntries(workspace.projectDir, {
|
|
4385
|
-
bindingSourceEntries: [buildBindingSourceConfigEntry(bindingSourceSlug, target)]
|
|
4386
|
-
});
|
|
4387
|
-
return {
|
|
4388
|
-
...target ? { attributeName: target.attributeName, blockSlug: target.blockSlug } : {},
|
|
4389
|
-
bindingSourceSlug,
|
|
4390
|
-
projectDir: workspace.projectDir
|
|
4391
|
-
};
|
|
4392
|
-
} catch (error) {
|
|
4393
|
-
await rollbackWorkspaceMutation(mutationSnapshot);
|
|
4394
|
-
throw error;
|
|
4395
|
-
}
|
|
4396
|
-
}
|
|
4397
|
-
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-integration-env.ts
|
|
4398
|
-
import { promises as fsp5 } from "fs";
|
|
4399
|
-
import path8 from "path";
|
|
4400
|
-
var WP_ENV_PACKAGE_VERSION = "^11.2.0";
|
|
4401
|
-
function buildWpEnvConfigSource() {
|
|
4402
|
-
return `${JSON.stringify({
|
|
4403
|
-
$schema: "https://schemas.wp.org/trunk/wp-env.json",
|
|
4404
|
-
core: null,
|
|
4405
|
-
port: 8888,
|
|
4406
|
-
testsEnvironment: false,
|
|
4407
|
-
plugins: ["."],
|
|
4408
|
-
config: {
|
|
4409
|
-
WP_DEBUG: true,
|
|
4410
|
-
WP_DEBUG_LOG: true,
|
|
4411
|
-
WP_DEBUG_DISPLAY: false,
|
|
4412
|
-
SCRIPT_DEBUG: true,
|
|
4413
|
-
WP_ENVIRONMENT_TYPE: "local"
|
|
4414
|
-
}
|
|
4415
|
-
}, null, 2)}
|
|
4416
|
-
`;
|
|
4417
|
-
}
|
|
4418
|
-
function buildDockerComposeSource() {
|
|
4419
|
-
return `services:
|
|
4420
|
-
integration-service:
|
|
4421
|
-
image: node:22-alpine
|
|
4422
|
-
working_dir: /workspace
|
|
4423
|
-
volumes:
|
|
4424
|
-
- .:/workspace
|
|
4425
|
-
command: >
|
|
4426
|
-
node -e "require('node:http').createServer((request, response) => {
|
|
4427
|
-
response.writeHead(request.url === '/health' ? 200 : 404, {
|
|
4428
|
-
'content-type': 'application/json'
|
|
4429
|
-
});
|
|
4430
|
-
response.end(JSON.stringify({
|
|
4431
|
-
ok: request.url === '/health',
|
|
4432
|
-
service: 'wp-typia-integration-starter'
|
|
4433
|
-
}));
|
|
4434
|
-
}).listen(3000, '0.0.0.0')"
|
|
4435
|
-
ports:
|
|
4436
|
-
- "3000:3000"
|
|
4437
|
-
`;
|
|
4438
|
-
}
|
|
4439
|
-
function buildIntegrationSmokeScriptSource(integrationEnvSlug) {
|
|
4440
|
-
return `import fs from "node:fs";
|
|
4441
|
-
import path from "node:path";
|
|
4442
|
-
import { fileURLToPath } from "node:url";
|
|
4443
|
-
|
|
4444
|
-
const ROOT_DIR = path.resolve(
|
|
4445
|
-
fileURLToPath(new URL("../..", import.meta.url)),
|
|
4446
|
-
);
|
|
4447
|
-
const ENV_FILE = path.join(ROOT_DIR, ".env");
|
|
4448
|
-
|
|
4449
|
-
function parseEnvValue(value) {
|
|
4450
|
-
const trimmed = value.trim();
|
|
4451
|
-
if (
|
|
4452
|
-
(trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
4453
|
-
(trimmed.startsWith("'") && trimmed.endsWith("'"))
|
|
4454
|
-
) {
|
|
4455
|
-
return trimmed.slice(1, -1);
|
|
4456
|
-
}
|
|
4457
|
-
|
|
4458
|
-
return trimmed;
|
|
4459
|
-
}
|
|
4460
|
-
|
|
4461
|
-
function readEnvFile(filePath) {
|
|
4462
|
-
if (!fs.existsSync(filePath)) {
|
|
4463
|
-
return {};
|
|
4464
|
-
}
|
|
4465
|
-
|
|
4466
|
-
return Object.fromEntries(
|
|
4467
|
-
fs
|
|
4468
|
-
.readFileSync(filePath, "utf8")
|
|
4469
|
-
.split(/\\r?\\n/u)
|
|
4470
|
-
.map((line) => line.trim())
|
|
4471
|
-
.filter((line) => line.length > 0 && !line.startsWith("#"))
|
|
4472
|
-
.map((line) => {
|
|
4473
|
-
const separatorIndex = line.indexOf("=");
|
|
4474
|
-
if (separatorIndex === -1) {
|
|
4475
|
-
return null;
|
|
4476
|
-
}
|
|
4477
|
-
|
|
4478
|
-
return [
|
|
4479
|
-
line.slice(0, separatorIndex).trim(),
|
|
4480
|
-
parseEnvValue(line.slice(separatorIndex + 1)),
|
|
4481
|
-
];
|
|
4482
|
-
})
|
|
4483
|
-
.filter(Boolean),
|
|
4484
|
-
);
|
|
4485
|
-
}
|
|
4486
|
-
|
|
4487
|
-
const envFile = readEnvFile(ENV_FILE);
|
|
4488
|
-
|
|
4489
|
-
function getEnv(name, fallback) {
|
|
4490
|
-
return process.env[name] ?? envFile[name] ?? fallback;
|
|
4491
|
-
}
|
|
4492
|
-
|
|
4493
|
-
function resolveEndpointUrl(baseUrl, endpointPath) {
|
|
4494
|
-
const normalizedBaseUrl = new URL(baseUrl);
|
|
4495
|
-
if (!normalizedBaseUrl.pathname.endsWith("/")) {
|
|
4496
|
-
normalizedBaseUrl.pathname = \`\${normalizedBaseUrl.pathname}/\`;
|
|
4497
|
-
}
|
|
4498
|
-
|
|
4499
|
-
const relativePath = endpointPath.replace(/^\\/+/u, "");
|
|
4500
|
-
return new URL(relativePath, normalizedBaseUrl);
|
|
4501
|
-
}
|
|
4502
|
-
|
|
4503
|
-
async function assertJsonEndpoint(label, url) {
|
|
4504
|
-
const response = await fetch(url, {
|
|
4505
|
-
headers: {
|
|
4506
|
-
accept: "application/json",
|
|
4507
|
-
},
|
|
4508
|
-
});
|
|
4509
|
-
|
|
4510
|
-
if (!response.ok) {
|
|
4511
|
-
throw new Error(
|
|
4512
|
-
\`\${label} failed at \${url} with HTTP \${response.status}.\`,
|
|
4513
|
-
);
|
|
4514
|
-
}
|
|
4515
|
-
|
|
4516
|
-
const contentType = response.headers.get("content-type") ?? "";
|
|
4517
|
-
if (!contentType.includes("application/json")) {
|
|
4518
|
-
throw new Error(
|
|
4519
|
-
\`\${label} at \${url} did not return JSON (content-type: \${contentType || "missing"}).\`,
|
|
4520
|
-
);
|
|
4521
|
-
}
|
|
4522
|
-
|
|
4523
|
-
return response.json();
|
|
4524
|
-
}
|
|
4525
|
-
|
|
4526
|
-
const baseUrl = new URL(
|
|
4527
|
-
getEnv("WP_TYPIA_SMOKE_BASE_URL", "http://localhost:8888"),
|
|
4528
|
-
);
|
|
4529
|
-
const serviceUrl = getEnv("WP_TYPIA_SERVICE_URL", "").trim();
|
|
4530
|
-
|
|
4531
|
-
await assertJsonEndpoint(
|
|
4532
|
-
"WordPress REST index",
|
|
4533
|
-
resolveEndpointUrl(baseUrl, "wp-json/"),
|
|
4534
|
-
);
|
|
4535
|
-
|
|
4536
|
-
if (serviceUrl.length > 0) {
|
|
4537
|
-
await assertJsonEndpoint(
|
|
4538
|
-
"Local integration service healthcheck",
|
|
4539
|
-
resolveEndpointUrl(serviceUrl, "health"),
|
|
4540
|
-
);
|
|
4541
|
-
}
|
|
4542
|
-
|
|
4543
|
-
console.log("wp-typia integration smoke passed: ${integrationEnvSlug}");
|
|
4544
|
-
`;
|
|
4545
|
-
}
|
|
4546
|
-
function buildEnvExampleSource(service) {
|
|
4547
|
-
return [
|
|
4548
|
-
"# wp-typia integration smoke settings",
|
|
4549
|
-
"WP_TYPIA_SMOKE_BASE_URL=http://localhost:8888",
|
|
4550
|
-
"WP_TYPIA_SMOKE_USERNAME=admin",
|
|
4551
|
-
"WP_TYPIA_SMOKE_PASSWORD=password",
|
|
4552
|
-
...service === "docker-compose" ? [
|
|
4553
|
-
"",
|
|
4554
|
-
"# Optional docker-compose integration service starter.",
|
|
4555
|
-
"WP_TYPIA_SERVICE_URL=http://localhost:3000"
|
|
4556
|
-
] : [
|
|
4557
|
-
"",
|
|
4558
|
-
"# Set this when your smoke test needs a project-specific service.",
|
|
4559
|
-
"# WP_TYPIA_SERVICE_URL=http://localhost:3000"
|
|
4560
|
-
],
|
|
4561
|
-
""
|
|
4562
|
-
].join(`
|
|
4563
|
-
`);
|
|
4564
|
-
}
|
|
4565
|
-
function buildIntegrationEnvReadmeSource({
|
|
4566
|
-
integrationEnvSlug,
|
|
4567
|
-
service,
|
|
4568
|
-
withWpEnv
|
|
4569
|
-
}) {
|
|
4570
|
-
const title = toTitleCase(integrationEnvSlug);
|
|
4571
|
-
const setupSteps = [
|
|
4572
|
-
"Copy `.env.example` to `.env` and adjust the URLs or credentials for your local project.",
|
|
4573
|
-
...withWpEnv ? [
|
|
4574
|
-
"Run `npm run wp-env:start` to start the generated WordPress environment."
|
|
4575
|
-
] : [
|
|
4576
|
-
"Point `WP_TYPIA_SMOKE_BASE_URL` at the WordPress environment you already run locally."
|
|
4577
|
-
],
|
|
4578
|
-
...service === "docker-compose" ? [
|
|
4579
|
-
"Run `npm run service:start` if you want the placeholder docker-compose service available at `WP_TYPIA_SERVICE_URL`."
|
|
4580
|
-
] : [
|
|
4581
|
-
"Set `WP_TYPIA_SERVICE_URL` only when your integration smoke needs a local service dependency."
|
|
4582
|
-
],
|
|
4583
|
-
`Run \`npm run smoke:${integrationEnvSlug}\` to execute the starter smoke check.`
|
|
4584
|
-
];
|
|
4585
|
-
return `# ${title} Integration Environment
|
|
4586
|
-
|
|
4587
|
-
This starter keeps local WordPress integration smoke checks opt-in and editable.
|
|
4588
|
-
It does not change default block scaffolds or require wp-env unless this add
|
|
4589
|
-
workflow was run with \`--wp-env\`.
|
|
4590
|
-
|
|
4591
|
-
## Setup
|
|
4592
|
-
|
|
4593
|
-
${setupSteps.map((step, index) => `${index + 1}. ${step}`).join(`
|
|
4594
|
-
`)}
|
|
4595
|
-
|
|
4596
|
-
## Adapting the Starter
|
|
4597
|
-
|
|
4598
|
-
- Extend \`scripts/integration-smoke/${integrationEnvSlug}.mjs\` with the REST,
|
|
4599
|
-
editor, or service assertions that matter for this project.
|
|
4600
|
-
- Keep secrets in \`.env\`; \`.env.example\` should document only safe defaults.
|
|
4601
|
-
- If your project uses a real service stack, replace the placeholder
|
|
4602
|
-
\`docker-compose.integration.yml\` service with your database, queue, API, or
|
|
4603
|
-
emulator containers.
|
|
4604
|
-
- Keep the smoke script focused on high-signal integration checks so CI and
|
|
4605
|
-
local debugging stay fast.
|
|
4606
|
-
`;
|
|
4607
|
-
}
|
|
4608
|
-
async function appendMissingLines(filePath, lines) {
|
|
4609
|
-
const current = await readOptionalUtf8File(filePath) ?? "";
|
|
4610
|
-
const missingLines = lines.filter((line) => !current.includes(`${line}
|
|
4611
|
-
`) && !current.endsWith(line));
|
|
4612
|
-
if (missingLines.length === 0) {
|
|
4613
|
-
return;
|
|
4614
|
-
}
|
|
4615
|
-
const separator = current.length === 0 || current.endsWith(`
|
|
4616
|
-
`) ? "" : `
|
|
4617
|
-
`;
|
|
4618
|
-
await fsp5.mkdir(path8.dirname(filePath), { recursive: true });
|
|
4619
|
-
await fsp5.writeFile(filePath, `${current}${separator}${missingLines.join(`
|
|
4620
|
-
`)}
|
|
4621
|
-
`, "utf8");
|
|
4622
|
-
}
|
|
4623
|
-
async function writeFileIfAbsent({
|
|
4624
|
-
filePath,
|
|
4625
|
-
source,
|
|
4626
|
-
warnings
|
|
4627
|
-
}) {
|
|
4628
|
-
if (await pathExists(filePath)) {
|
|
4629
|
-
warnings.push(`Preserved existing ${path8.basename(filePath)}; review it manually if you need different local integration settings.`);
|
|
4630
|
-
return;
|
|
4631
|
-
}
|
|
4632
|
-
await fsp5.mkdir(path8.dirname(filePath), { recursive: true });
|
|
4633
|
-
await fsp5.writeFile(filePath, source, "utf8");
|
|
4634
|
-
}
|
|
4635
|
-
async function writeNewScaffoldFile(filePath, source) {
|
|
4636
|
-
if (await pathExists(filePath)) {
|
|
4637
|
-
throw new Error(`An integration environment scaffold already exists at ${filePath}. Choose a different name.`);
|
|
4638
|
-
}
|
|
4639
|
-
await fsp5.mkdir(path8.dirname(filePath), { recursive: true });
|
|
4640
|
-
await fsp5.writeFile(filePath, source, "utf8");
|
|
4641
|
-
}
|
|
4642
|
-
function addScriptIfMissing({
|
|
4643
|
-
scriptName,
|
|
4644
|
-
scripts,
|
|
4645
|
-
scriptValue,
|
|
4646
|
-
warnings
|
|
4647
|
-
}) {
|
|
4648
|
-
if (scripts[scriptName] === undefined) {
|
|
4649
|
-
scripts[scriptName] = scriptValue;
|
|
4650
|
-
return;
|
|
4651
|
-
}
|
|
4652
|
-
if (scripts[scriptName] !== scriptValue) {
|
|
4653
|
-
warnings.push(`Preserved existing package script "${scriptName}"; add "${scriptValue}" manually if you want the generated integration command.`);
|
|
4654
|
-
}
|
|
4655
|
-
}
|
|
4656
|
-
async function mutatePackageJson(projectDir, mutate) {
|
|
4657
|
-
const packageJsonPath = path8.join(projectDir, "package.json");
|
|
4658
|
-
const packageJson = JSON.parse(await fsp5.readFile(packageJsonPath, "utf8"));
|
|
4659
|
-
mutate(packageJson);
|
|
4660
|
-
await fsp5.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, "\t")}
|
|
4661
|
-
`, "utf8");
|
|
4662
|
-
}
|
|
4663
|
-
function addIntegrationEnvPackageJsonEntries({
|
|
4664
|
-
integrationEnvSlug,
|
|
4665
|
-
packageManager,
|
|
4666
|
-
packageJson,
|
|
4667
|
-
service,
|
|
4668
|
-
warnings,
|
|
4669
|
-
withWpEnv
|
|
4670
|
-
}) {
|
|
4671
|
-
const devDependencies = {
|
|
4672
|
-
...packageJson.devDependencies ?? {}
|
|
4673
|
-
};
|
|
4674
|
-
if (withWpEnv && devDependencies["@wordpress/env"] === undefined) {
|
|
4675
|
-
devDependencies["@wordpress/env"] = WP_ENV_PACKAGE_VERSION;
|
|
4676
|
-
}
|
|
4677
|
-
packageJson.devDependencies = devDependencies;
|
|
4678
|
-
const scripts = {
|
|
4679
|
-
...packageJson.scripts ?? {}
|
|
4680
|
-
};
|
|
4681
|
-
addScriptIfMissing({
|
|
4682
|
-
scriptName: `smoke:${integrationEnvSlug}`,
|
|
4683
|
-
scriptValue: `node scripts/integration-smoke/${integrationEnvSlug}.mjs`,
|
|
4684
|
-
scripts,
|
|
4685
|
-
warnings
|
|
4686
|
-
});
|
|
4687
|
-
addScriptIfMissing({
|
|
4688
|
-
scriptName: "smoke:integration",
|
|
4689
|
-
scriptValue: formatRunScript(packageManager, `smoke:${integrationEnvSlug}`),
|
|
4690
|
-
scripts,
|
|
4691
|
-
warnings
|
|
4692
|
-
});
|
|
4693
|
-
if (withWpEnv) {
|
|
4694
|
-
addScriptIfMissing({
|
|
4695
|
-
scriptName: "wp-env:start",
|
|
4696
|
-
scriptValue: "wp-env start",
|
|
4697
|
-
scripts,
|
|
4698
|
-
warnings
|
|
4699
|
-
});
|
|
4700
|
-
addScriptIfMissing({
|
|
4701
|
-
scriptName: "wp-env:stop",
|
|
4702
|
-
scriptValue: "wp-env stop",
|
|
4703
|
-
scripts,
|
|
4704
|
-
warnings
|
|
4705
|
-
});
|
|
4706
|
-
addScriptIfMissing({
|
|
4707
|
-
scriptName: "wp-env:reset",
|
|
4708
|
-
scriptValue: "wp-env destroy all && wp-env start",
|
|
4709
|
-
scripts,
|
|
4710
|
-
warnings
|
|
4711
|
-
});
|
|
4712
|
-
}
|
|
4713
|
-
if (service === "docker-compose") {
|
|
4714
|
-
addScriptIfMissing({
|
|
4715
|
-
scriptName: "service:start",
|
|
4716
|
-
scriptValue: "docker compose -f docker-compose.integration.yml up -d",
|
|
4717
|
-
scripts,
|
|
4718
|
-
warnings
|
|
4719
|
-
});
|
|
4720
|
-
addScriptIfMissing({
|
|
4721
|
-
scriptName: "service:stop",
|
|
4722
|
-
scriptValue: "docker compose -f docker-compose.integration.yml down",
|
|
4723
|
-
scripts,
|
|
4724
|
-
warnings
|
|
4725
|
-
});
|
|
4726
|
-
}
|
|
4727
|
-
packageJson.scripts = scripts;
|
|
4728
|
-
}
|
|
4729
|
-
async function runAddIntegrationEnvCommand({
|
|
4730
|
-
cwd = process.cwd(),
|
|
4731
|
-
integrationEnvName,
|
|
4732
|
-
service,
|
|
4733
|
-
withWpEnv = false
|
|
4401
|
+
async function runAddBindingSourceCommand({
|
|
4402
|
+
attributeName,
|
|
4403
|
+
bindingSourceName,
|
|
4404
|
+
blockName,
|
|
4405
|
+
cwd = process.cwd()
|
|
4734
4406
|
}) {
|
|
4735
4407
|
const workspace = resolveWorkspaceProject(cwd);
|
|
4736
|
-
const
|
|
4737
|
-
const
|
|
4738
|
-
|
|
4739
|
-
const
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
const
|
|
4744
|
-
const
|
|
4745
|
-
const
|
|
4746
|
-
const
|
|
4747
|
-
const
|
|
4748
|
-
const
|
|
4749
|
-
const
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
...
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
service: serviceId,
|
|
4769
|
-
withWpEnv
|
|
4770
|
-
}));
|
|
4771
|
-
await appendMissingLines(envExamplePath, [
|
|
4772
|
-
...buildEnvExampleSource(serviceId).trimEnd().split(`
|
|
4773
|
-
`)
|
|
4774
|
-
]);
|
|
4775
|
-
await appendMissingLines(gitignorePath, [".env", ".env.local"]);
|
|
4776
|
-
if (withWpEnv) {
|
|
4777
|
-
await writeFileIfAbsent({
|
|
4778
|
-
filePath: wpEnvPath,
|
|
4779
|
-
source: buildWpEnvConfigSource(),
|
|
4780
|
-
warnings
|
|
4781
|
-
});
|
|
4782
|
-
}
|
|
4783
|
-
if (serviceId === "docker-compose") {
|
|
4784
|
-
await writeFileIfAbsent({
|
|
4785
|
-
filePath: dockerComposePath,
|
|
4786
|
-
source: buildDockerComposeSource(),
|
|
4787
|
-
warnings
|
|
4788
|
-
});
|
|
4789
|
-
}
|
|
4790
|
-
await mutatePackageJson(workspace.projectDir, (packageJson) => addIntegrationEnvPackageJsonEntries({
|
|
4791
|
-
integrationEnvSlug,
|
|
4792
|
-
packageManager: workspace.packageManager,
|
|
4793
|
-
packageJson,
|
|
4794
|
-
service: serviceId,
|
|
4795
|
-
warnings,
|
|
4796
|
-
withWpEnv
|
|
4797
|
-
}));
|
|
4798
|
-
}
|
|
4799
|
-
});
|
|
4800
|
-
return {
|
|
4801
|
-
integrationEnvSlug,
|
|
4802
|
-
projectDir: workspace.projectDir,
|
|
4803
|
-
service: serviceId,
|
|
4804
|
-
warnings: warnings.length > 0 ? warnings : undefined,
|
|
4805
|
-
withWpEnv
|
|
4408
|
+
const bindingSourceSlug = assertValidGeneratedSlug("Binding source name", normalizeBlockSlug(bindingSourceName), "wp-typia add binding-source <name> [--block <block-slug|namespace/block-slug> --attribute <attribute>]");
|
|
4409
|
+
const inventory = await readWorkspaceInventoryAsync(workspace.projectDir);
|
|
4410
|
+
assertBindingSourceDoesNotExist(workspace.projectDir, bindingSourceSlug, inventory);
|
|
4411
|
+
const target = resolveBindingTarget({
|
|
4412
|
+
attributeName,
|
|
4413
|
+
blockName
|
|
4414
|
+
}, workspace.workspace.namespace);
|
|
4415
|
+
const targetBlock = target ? resolveWorkspaceBlock(inventory, target.blockSlug) : undefined;
|
|
4416
|
+
const blockConfigPath = path7.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
4417
|
+
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
4418
|
+
const bindingsIndexPath = await resolveBindingSourceRegistryPath(workspace.projectDir);
|
|
4419
|
+
const bindingSourceDir = path7.join(workspace.projectDir, "src", "bindings", bindingSourceSlug);
|
|
4420
|
+
const serverFilePath = path7.join(bindingSourceDir, "server.php");
|
|
4421
|
+
const editorFilePath = path7.join(bindingSourceDir, "editor.ts");
|
|
4422
|
+
const blockJsonPath = target ? path7.join(workspace.projectDir, "src", "blocks", target.blockSlug, "block.json") : undefined;
|
|
4423
|
+
const targetGeneratedMetadataPaths = target ? [
|
|
4424
|
+
path7.join(workspace.projectDir, "src", "blocks", target.blockSlug, "typia.manifest.json"),
|
|
4425
|
+
path7.join(workspace.projectDir, "src", "blocks", target.blockSlug, "typia.openapi.json"),
|
|
4426
|
+
path7.join(workspace.projectDir, "src", "blocks", target.blockSlug, "typia.schema.json"),
|
|
4427
|
+
path7.join(workspace.projectDir, "src", "blocks", target.blockSlug, "typia-validator.php")
|
|
4428
|
+
] : [];
|
|
4429
|
+
const mutationSnapshot = {
|
|
4430
|
+
fileSources: await snapshotWorkspaceFiles([
|
|
4431
|
+
blockConfigPath,
|
|
4432
|
+
bootstrapPath,
|
|
4433
|
+
bindingsIndexPath,
|
|
4434
|
+
...blockJsonPath ? [blockJsonPath] : [],
|
|
4435
|
+
...targetBlock ? [path7.join(workspace.projectDir, targetBlock.typesFile)] : [],
|
|
4436
|
+
...targetGeneratedMetadataPaths
|
|
4437
|
+
]),
|
|
4438
|
+
snapshotDirs: [],
|
|
4439
|
+
targetPaths: [bindingSourceDir]
|
|
4806
4440
|
};
|
|
4441
|
+
try {
|
|
4442
|
+
await fsp4.mkdir(bindingSourceDir, { recursive: true });
|
|
4443
|
+
await ensureBindingSourceBootstrapAnchors(workspace);
|
|
4444
|
+
await fsp4.writeFile(serverFilePath, buildBindingSourceServerSource(bindingSourceSlug, workspace.workspace.phpPrefix, workspace.workspace.namespace, workspace.workspace.textDomain, target), "utf8");
|
|
4445
|
+
await fsp4.writeFile(editorFilePath, buildBindingSourceEditorSource(bindingSourceSlug, workspace.workspace.namespace, workspace.workspace.textDomain, target), "utf8");
|
|
4446
|
+
if (target && targetBlock) {
|
|
4447
|
+
await ensureBindingTargetBlockAttributeType(workspace.projectDir, targetBlock, target);
|
|
4448
|
+
}
|
|
4449
|
+
await writeBindingSourceRegistry(workspace.projectDir, bindingSourceSlug);
|
|
4450
|
+
await appendWorkspaceInventoryEntries(workspace.projectDir, {
|
|
4451
|
+
bindingSourceEntries: [buildBindingSourceConfigEntry(bindingSourceSlug, target)]
|
|
4452
|
+
});
|
|
4453
|
+
return {
|
|
4454
|
+
...target ? { attributeName: target.attributeName, blockSlug: target.blockSlug } : {},
|
|
4455
|
+
bindingSourceSlug,
|
|
4456
|
+
projectDir: workspace.projectDir
|
|
4457
|
+
};
|
|
4458
|
+
} catch (error) {
|
|
4459
|
+
await rollbackWorkspaceMutation(mutationSnapshot);
|
|
4460
|
+
throw error;
|
|
4461
|
+
}
|
|
4807
4462
|
}
|
|
4808
|
-
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-rest.ts
|
|
4809
|
-
import { promises as
|
|
4810
|
-
import
|
|
4463
|
+
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-rest-generated.ts
|
|
4464
|
+
import { promises as fsp5 } from "fs";
|
|
4465
|
+
import path9 from "path";
|
|
4811
4466
|
|
|
4812
4467
|
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-rest-anchors.ts
|
|
4813
|
-
import
|
|
4468
|
+
import path8 from "path";
|
|
4814
4469
|
var REST_RESOURCE_SERVER_GLOB = "/inc/rest/*.php";
|
|
4470
|
+
var REST_SCHEMA_HELPER_PATH = "/inc/rest-schema.php";
|
|
4471
|
+
async function ensureRestSchemaHelperBootstrapAnchors(workspace) {
|
|
4472
|
+
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
4473
|
+
await patchFile(bootstrapPath, (source) => {
|
|
4474
|
+
let nextSource = source;
|
|
4475
|
+
const loadFunctionName = `${workspace.workspace.phpPrefix}_load_rest_schema_helpers`;
|
|
4476
|
+
const loadCall = `${loadFunctionName}();`;
|
|
4477
|
+
const helperFunction = `
|
|
4478
|
+
|
|
4479
|
+
function ${loadFunctionName}() {
|
|
4480
|
+
$helper_path = __DIR__ . '${REST_SCHEMA_HELPER_PATH}';
|
|
4481
|
+
if ( is_readable( $helper_path ) ) {
|
|
4482
|
+
require_once $helper_path;
|
|
4483
|
+
}
|
|
4484
|
+
}
|
|
4485
|
+
|
|
4486
|
+
${loadCall}
|
|
4487
|
+
`;
|
|
4488
|
+
if (!hasPhpFunctionDefinition(nextSource, loadFunctionName)) {
|
|
4489
|
+
nextSource = insertPhpSnippetBeforeWorkspaceAnchors(nextSource, helperFunction);
|
|
4490
|
+
} else if (!nextSource.includes(REST_SCHEMA_HELPER_PATH)) {
|
|
4491
|
+
throw new Error([
|
|
4492
|
+
`Unable to patch ${path8.basename(bootstrapPath)} in ensureRestSchemaHelperBootstrapAnchors.`,
|
|
4493
|
+
`The existing ${loadFunctionName}() definition does not include ${REST_SCHEMA_HELPER_PATH}.`,
|
|
4494
|
+
"Restore the generated bootstrap shape or load inc/rest-schema.php manually before retrying."
|
|
4495
|
+
].join(" "));
|
|
4496
|
+
} else if (!nextSource.includes(loadCall)) {
|
|
4497
|
+
nextSource = appendPhpSnippetBeforeClosingTag(nextSource, loadCall);
|
|
4498
|
+
}
|
|
4499
|
+
return nextSource;
|
|
4500
|
+
});
|
|
4501
|
+
}
|
|
4815
4502
|
async function ensureRestResourceBootstrapAnchors(workspace) {
|
|
4816
4503
|
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
4817
4504
|
await patchFile(bootstrapPath, (source) => {
|
|
@@ -4830,7 +4517,7 @@ function ${registerFunctionName}() {
|
|
|
4830
4517
|
nextSource = insertPhpSnippetBeforeWorkspaceAnchors(nextSource, registerFunction);
|
|
4831
4518
|
} else if (!nextSource.includes(REST_RESOURCE_SERVER_GLOB)) {
|
|
4832
4519
|
throw new Error([
|
|
4833
|
-
`Unable to patch ${
|
|
4520
|
+
`Unable to patch ${path8.basename(bootstrapPath)} in ensureRestResourceBootstrapAnchors.`,
|
|
4834
4521
|
`The existing ${registerFunctionName}() definition does not include ${REST_RESOURCE_SERVER_GLOB}.`,
|
|
4835
4522
|
"Restore the generated bootstrap shape or wire the REST resource loader manually before retrying."
|
|
4836
4523
|
].join(" "));
|
|
@@ -4844,7 +4531,7 @@ function ${registerFunctionName}() {
|
|
|
4844
4531
|
function assertSyncRestAnchor(nextSource, target, anchorDescription, hasAnchor, syncRestScriptPath) {
|
|
4845
4532
|
if (!nextSource.includes(target) && !hasAnchor) {
|
|
4846
4533
|
throw new Error([
|
|
4847
|
-
`ensureRestResourceSyncScriptAnchors could not patch ${
|
|
4534
|
+
`ensureRestResourceSyncScriptAnchors could not patch ${path8.basename(syncRestScriptPath)}.`,
|
|
4848
4535
|
`Missing expected ${anchorDescription} anchor in scripts/sync-rest-contracts.ts.`,
|
|
4849
4536
|
"Restore the generated template or add the REST_RESOURCES wiring manually before retrying."
|
|
4850
4537
|
].join(" "));
|
|
@@ -4860,7 +4547,7 @@ function replaceRequiredSyncRestSource(nextSource, target, anchor, replacement,
|
|
|
4860
4547
|
}
|
|
4861
4548
|
function getSyncRestPatchErrorMessage(functionName, syncRestScriptPath, anchorDescription, subject) {
|
|
4862
4549
|
return [
|
|
4863
|
-
`${functionName} could not patch ${
|
|
4550
|
+
`${functionName} could not patch ${path8.basename(syncRestScriptPath)}.`,
|
|
4864
4551
|
`Missing expected ${anchorDescription} anchor in scripts/sync-rest-contracts.ts.`,
|
|
4865
4552
|
`Restore the generated template or add the ${subject} wiring manually before retrying.`
|
|
4866
4553
|
].join(" ");
|
|
@@ -5123,7 +4810,7 @@ ${resourceLoopAnchor}`);
|
|
|
5123
4810
|
`), "success log insertion point", syncRestScriptPath);
|
|
5124
4811
|
}
|
|
5125
4812
|
async function ensureContractSyncScriptAnchors(workspace) {
|
|
5126
|
-
const syncRestScriptPath =
|
|
4813
|
+
const syncRestScriptPath = path8.join(workspace.projectDir, "scripts", "sync-rest-contracts.ts");
|
|
5127
4814
|
await patchFile(syncRestScriptPath, (source) => {
|
|
5128
4815
|
let nextSource = replaceBlockConfigImportForContracts(source, syncRestScriptPath);
|
|
5129
4816
|
const helperInsertionAnchor = "async function assertTypeArtifactsCurrent";
|
|
@@ -5156,7 +4843,7 @@ async function ensureContractSyncScriptAnchors(workspace) {
|
|
|
5156
4843
|
});
|
|
5157
4844
|
}
|
|
5158
4845
|
async function ensureRestResourceSyncScriptAnchors(workspace) {
|
|
5159
|
-
const syncRestScriptPath =
|
|
4846
|
+
const syncRestScriptPath = path8.join(workspace.projectDir, "scripts", "sync-rest-contracts.ts");
|
|
5160
4847
|
await patchFile(syncRestScriptPath, (source) => {
|
|
5161
4848
|
let nextSource = replaceBlockConfigImportForRestResources(source, syncRestScriptPath);
|
|
5162
4849
|
const helperInsertionAnchor = "async function assertTypeArtifactsCurrent";
|
|
@@ -5252,14 +4939,7 @@ async function ensureRestResourceSyncScriptAnchors(workspace) {
|
|
|
5252
4939
|
});
|
|
5253
4940
|
}
|
|
5254
4941
|
|
|
5255
|
-
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-rest.ts
|
|
5256
|
-
var MANUAL_REST_REQUEST_BODY_FIELD_NAMES = new Set(["payload", "comment"]);
|
|
5257
|
-
var MANUAL_REST_RESPONSE_FIELD_NAMES = new Set([
|
|
5258
|
-
"id",
|
|
5259
|
-
"status",
|
|
5260
|
-
"message",
|
|
5261
|
-
"updatedAt"
|
|
5262
|
-
]);
|
|
4942
|
+
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-rest-php-templates.ts
|
|
5263
4943
|
function buildRestResourceRouteRegistrations(restResourceSlug, methods, functions, options) {
|
|
5264
4944
|
const collectionRoutes = [];
|
|
5265
4945
|
const itemRoutes = [];
|
|
@@ -5370,13 +5050,11 @@ if ( ! class_exists( ${quotePhpString(controllerClassName)} ) ) {
|
|
|
5370
5050
|
}
|
|
5371
5051
|
`;
|
|
5372
5052
|
}
|
|
5373
|
-
function buildRestResourcePhpSource(restResourceSlug, namespace, phpPrefix,
|
|
5053
|
+
function buildRestResourcePhpSource(restResourceSlug, namespace, phpPrefix, methods, options) {
|
|
5374
5054
|
const restResourceTitle = toTitleCase(restResourceSlug);
|
|
5375
5055
|
const restResourcePhpId = restResourceSlug.replace(/-/g, "_");
|
|
5376
5056
|
const canWriteFunctionName = `${phpPrefix}_${restResourcePhpId}_can_manage_rest_resource`;
|
|
5377
5057
|
const getItemsFunctionName = `${phpPrefix}_${restResourcePhpId}_get_rest_resource_items`;
|
|
5378
|
-
const loadSchemaFunctionName = `${phpPrefix}_${restResourcePhpId}_load_rest_resource_schema`;
|
|
5379
|
-
const normalizeSchemaFunctionName = `${phpPrefix}_${restResourcePhpId}_sanitize_rest_resource_schema`;
|
|
5380
5058
|
const validatePayloadFunctionName = `${phpPrefix}_${restResourcePhpId}_validate_rest_resource_payload`;
|
|
5381
5059
|
const normalizeItemFunctionName = `${phpPrefix}_${restResourcePhpId}_normalize_rest_resource_item`;
|
|
5382
5060
|
const saveItemsFunctionName = `${phpPrefix}_${restResourcePhpId}_save_rest_resource_items`;
|
|
@@ -5486,55 +5164,22 @@ if ( ! function_exists( '${saveItemsFunctionName}' ) ) {
|
|
|
5486
5164
|
}
|
|
5487
5165
|
}
|
|
5488
5166
|
|
|
5489
|
-
if ( ! function_exists( '${loadSchemaFunctionName}' ) ) {
|
|
5490
|
-
function ${loadSchemaFunctionName}( $schema_name ) {
|
|
5491
|
-
$project_root = dirname( __DIR__, 2 );
|
|
5492
|
-
$schema_path = $project_root . '/src/rest/${restResourceSlug}/api-schemas/' . $schema_name . '.schema.json';
|
|
5493
|
-
if ( ! file_exists( $schema_path ) ) {
|
|
5494
|
-
return null;
|
|
5495
|
-
}
|
|
5496
|
-
|
|
5497
|
-
$decoded = json_decode( file_get_contents( $schema_path ), true );
|
|
5498
|
-
return is_array( $decoded ) ? $decoded : null;
|
|
5499
|
-
}
|
|
5500
|
-
}
|
|
5501
|
-
|
|
5502
|
-
if ( ! function_exists( '${normalizeSchemaFunctionName}' ) ) {
|
|
5503
|
-
function ${normalizeSchemaFunctionName}( $schema ) {
|
|
5504
|
-
if ( ! is_array( $schema ) ) {
|
|
5505
|
-
return $schema;
|
|
5506
|
-
}
|
|
5507
|
-
|
|
5508
|
-
unset( $schema['$schema'], $schema['title'] );
|
|
5509
|
-
|
|
5510
|
-
if ( isset( $schema['properties'] ) && is_array( $schema['properties'] ) ) {
|
|
5511
|
-
foreach ( $schema['properties'] as $key => $property_schema ) {
|
|
5512
|
-
$schema['properties'][ $key ] = ${normalizeSchemaFunctionName}( $property_schema );
|
|
5513
|
-
}
|
|
5514
|
-
}
|
|
5515
|
-
|
|
5516
|
-
if ( isset( $schema['items'] ) && is_array( $schema['items'] ) ) {
|
|
5517
|
-
$schema['items'] = ${normalizeSchemaFunctionName}( $schema['items'] );
|
|
5518
|
-
}
|
|
5519
|
-
|
|
5520
|
-
return $schema;
|
|
5521
|
-
}
|
|
5522
|
-
}
|
|
5523
|
-
|
|
5524
5167
|
if ( ! function_exists( '${validatePayloadFunctionName}' ) ) {
|
|
5525
5168
|
function ${validatePayloadFunctionName}( $value, $schema_name, $param_name ) {
|
|
5526
|
-
|
|
5527
|
-
|
|
5528
|
-
|
|
5529
|
-
|
|
5530
|
-
|
|
5531
|
-
|
|
5532
|
-
$validation = rest_validate_value_from_schema( $value, $rest_schema, $param_name );
|
|
5533
|
-
if ( is_wp_error( $validation ) ) {
|
|
5534
|
-
return $validation;
|
|
5169
|
+
if ( ! function_exists( '${phpPrefix}_validate_and_sanitize_rest_payload' ) ) {
|
|
5170
|
+
return new WP_Error(
|
|
5171
|
+
'missing_rest_schema_helper',
|
|
5172
|
+
'Missing REST schema helper. Ensure inc/rest-schema.php is loaded before REST resources.',
|
|
5173
|
+
array( 'status' => 500 )
|
|
5174
|
+
);
|
|
5535
5175
|
}
|
|
5536
5176
|
|
|
5537
|
-
return
|
|
5177
|
+
return ${phpPrefix}_validate_and_sanitize_rest_payload(
|
|
5178
|
+
$value,
|
|
5179
|
+
$schema_name,
|
|
5180
|
+
$param_name,
|
|
5181
|
+
array( 'resource' => ${quotePhpString(restResourceSlug)} )
|
|
5182
|
+
);
|
|
5538
5183
|
}
|
|
5539
5184
|
}
|
|
5540
5185
|
|
|
@@ -5742,170 +5387,247 @@ if ( ! function_exists( '${registerRoutesFunctionName}' ) ) {
|
|
|
5742
5387
|
function ${registerRoutesFunctionName}() {
|
|
5743
5388
|
$namespace = ${quotePhpString(namespace)};
|
|
5744
5389
|
|
|
5745
|
-
${controllerBootstrapSource}
|
|
5746
|
-
${routeRegistrations}
|
|
5390
|
+
${controllerBootstrapSource}
|
|
5391
|
+
${routeRegistrations}
|
|
5392
|
+
}
|
|
5393
|
+
}
|
|
5394
|
+
|
|
5395
|
+
add_action( 'rest_api_init', '${registerRoutesFunctionName}' );
|
|
5396
|
+
`;
|
|
5397
|
+
}
|
|
5398
|
+
function buildWorkspaceRestSchemaHelperPhpSource(phpPrefix) {
|
|
5399
|
+
return `<?php
|
|
5400
|
+
if ( ! defined( 'ABSPATH' ) ) {
|
|
5401
|
+
return;
|
|
5402
|
+
}
|
|
5403
|
+
|
|
5404
|
+
if ( ! function_exists( '${phpPrefix}_is_valid_rest_schema_key' ) ) {
|
|
5405
|
+
function ${phpPrefix}_is_valid_rest_schema_key( $value ) {
|
|
5406
|
+
return is_string( $value ) && 1 === preg_match( '/\\A[A-Za-z0-9_-]+\\z/', $value );
|
|
5407
|
+
}
|
|
5408
|
+
}
|
|
5409
|
+
|
|
5410
|
+
if ( ! function_exists( '${phpPrefix}_is_valid_rest_resource_slug' ) ) {
|
|
5411
|
+
function ${phpPrefix}_is_valid_rest_resource_slug( $value ) {
|
|
5412
|
+
return is_string( $value ) && 1 === preg_match( '/\\A[a-z0-9]+(?:-[a-z0-9]+)*\\z/', $value );
|
|
5413
|
+
}
|
|
5414
|
+
}
|
|
5415
|
+
|
|
5416
|
+
if ( ! function_exists( '${phpPrefix}_resolve_rest_schema_paths' ) ) {
|
|
5417
|
+
function ${phpPrefix}_resolve_rest_schema_paths( $schema_name, $options = array() ) {
|
|
5418
|
+
if ( ! ${phpPrefix}_is_valid_rest_schema_key( $schema_name ) ) {
|
|
5419
|
+
return new WP_Error(
|
|
5420
|
+
'invalid_rest_schema_name',
|
|
5421
|
+
'Invalid REST schema name.',
|
|
5422
|
+
array( 'status' => 500 )
|
|
5423
|
+
);
|
|
5424
|
+
}
|
|
5425
|
+
|
|
5426
|
+
$options = is_array( $options ) ? $options : array();
|
|
5427
|
+
$project_root = dirname( __DIR__ );
|
|
5428
|
+
$paths = array();
|
|
5429
|
+
|
|
5430
|
+
if ( isset( $options['resource'] ) && '' !== $options['resource'] ) {
|
|
5431
|
+
if ( ! ${phpPrefix}_is_valid_rest_resource_slug( $options['resource'] ) ) {
|
|
5432
|
+
return new WP_Error(
|
|
5433
|
+
'invalid_rest_schema_resource',
|
|
5434
|
+
'Invalid REST schema resource slug.',
|
|
5435
|
+
array( 'status' => 500 )
|
|
5436
|
+
);
|
|
5437
|
+
}
|
|
5438
|
+
|
|
5439
|
+
$resource_slug = $options['resource'];
|
|
5440
|
+
$paths[] = __DIR__ . '/rest-schemas/rest/' . $resource_slug . '/' . $schema_name . '.schema.json';
|
|
5441
|
+
$paths[] = $project_root . '/src/rest/' . $resource_slug . '/api-schemas/' . $schema_name . '.schema.json';
|
|
5442
|
+
}
|
|
5443
|
+
|
|
5444
|
+
if ( isset( $options['paths'] ) && is_array( $options['paths'] ) ) {
|
|
5445
|
+
foreach ( $options['paths'] as $schema_path ) {
|
|
5446
|
+
if ( is_string( $schema_path ) && '' !== $schema_path && ! in_array( $schema_path, $paths, true ) ) {
|
|
5447
|
+
$paths[] = $schema_path;
|
|
5448
|
+
}
|
|
5449
|
+
}
|
|
5450
|
+
}
|
|
5451
|
+
|
|
5452
|
+
return $paths;
|
|
5453
|
+
}
|
|
5454
|
+
}
|
|
5455
|
+
|
|
5456
|
+
if ( ! function_exists( '${phpPrefix}_load_rest_schema' ) ) {
|
|
5457
|
+
function ${phpPrefix}_load_rest_schema( $schema_name, $options = array() ) {
|
|
5458
|
+
$schema_paths = ${phpPrefix}_resolve_rest_schema_paths( $schema_name, $options );
|
|
5459
|
+
if ( is_wp_error( $schema_paths ) ) {
|
|
5460
|
+
return $schema_paths;
|
|
5461
|
+
}
|
|
5462
|
+
|
|
5463
|
+
foreach ( $schema_paths as $schema_path ) {
|
|
5464
|
+
if ( ! is_file( $schema_path ) ) {
|
|
5465
|
+
continue;
|
|
5466
|
+
}
|
|
5467
|
+
|
|
5468
|
+
if ( ! is_readable( $schema_path ) ) {
|
|
5469
|
+
return new WP_Error(
|
|
5470
|
+
'unreadable_rest_schema',
|
|
5471
|
+
'Generated REST schema is not readable.',
|
|
5472
|
+
array(
|
|
5473
|
+
'path' => $schema_path,
|
|
5474
|
+
'status' => 500,
|
|
5475
|
+
)
|
|
5476
|
+
);
|
|
5477
|
+
}
|
|
5478
|
+
|
|
5479
|
+
$schema_json = file_get_contents( $schema_path );
|
|
5480
|
+
if ( false === $schema_json ) {
|
|
5481
|
+
return new WP_Error(
|
|
5482
|
+
'rest_schema_read_failed',
|
|
5483
|
+
'Generated REST schema could not be read.',
|
|
5484
|
+
array(
|
|
5485
|
+
'path' => $schema_path,
|
|
5486
|
+
'status' => 500,
|
|
5487
|
+
)
|
|
5488
|
+
);
|
|
5489
|
+
}
|
|
5490
|
+
|
|
5491
|
+
$decoded = json_decode( $schema_json, true );
|
|
5492
|
+
if ( ! is_array( $decoded ) ) {
|
|
5493
|
+
return new WP_Error(
|
|
5494
|
+
'malformed_rest_schema',
|
|
5495
|
+
'Generated REST schema contains malformed JSON.',
|
|
5496
|
+
array(
|
|
5497
|
+
'json_error' => json_last_error_msg(),
|
|
5498
|
+
'path' => $schema_path,
|
|
5499
|
+
'status' => 500,
|
|
5500
|
+
)
|
|
5501
|
+
);
|
|
5502
|
+
}
|
|
5503
|
+
|
|
5504
|
+
return $decoded;
|
|
5505
|
+
}
|
|
5506
|
+
|
|
5507
|
+
return new WP_Error(
|
|
5508
|
+
'missing_rest_schema',
|
|
5509
|
+
'Generated REST schema could not be found.',
|
|
5510
|
+
array(
|
|
5511
|
+
'paths' => $schema_paths,
|
|
5512
|
+
'schema' => $schema_name,
|
|
5513
|
+
'status' => 500,
|
|
5514
|
+
)
|
|
5515
|
+
);
|
|
5516
|
+
}
|
|
5517
|
+
}
|
|
5518
|
+
|
|
5519
|
+
if ( ! function_exists( '${phpPrefix}_prepare_rest_schema_for_wordpress' ) ) {
|
|
5520
|
+
function ${phpPrefix}_prepare_rest_schema_for_wordpress( $schema ) {
|
|
5521
|
+
if ( ! is_array( $schema ) ) {
|
|
5522
|
+
return $schema;
|
|
5523
|
+
}
|
|
5524
|
+
|
|
5525
|
+
unset( $schema['$schema'], $schema['title'] );
|
|
5526
|
+
|
|
5527
|
+
foreach ( array( 'properties', 'patternProperties', 'definitions', '$defs' ) as $schema_map_key ) {
|
|
5528
|
+
if ( ! isset( $schema[ $schema_map_key ] ) || ! is_array( $schema[ $schema_map_key ] ) ) {
|
|
5529
|
+
continue;
|
|
5530
|
+
}
|
|
5531
|
+
|
|
5532
|
+
foreach ( $schema[ $schema_map_key ] as $key => $property_schema ) {
|
|
5533
|
+
$schema[ $schema_map_key ][ $key ] = ${phpPrefix}_prepare_rest_schema_for_wordpress( $property_schema );
|
|
5534
|
+
}
|
|
5535
|
+
}
|
|
5536
|
+
|
|
5537
|
+
foreach ( array( 'items', 'additionalProperties', 'contains', 'propertyNames', 'not', 'if', 'then', 'else' ) as $nested_schema_key ) {
|
|
5538
|
+
if ( isset( $schema[ $nested_schema_key ] ) && is_array( $schema[ $nested_schema_key ] ) ) {
|
|
5539
|
+
$schema[ $nested_schema_key ] = ${phpPrefix}_prepare_rest_schema_for_wordpress( $schema[ $nested_schema_key ] );
|
|
5540
|
+
}
|
|
5541
|
+
}
|
|
5542
|
+
|
|
5543
|
+
foreach ( array( 'allOf', 'anyOf', 'oneOf' ) as $schema_list_key ) {
|
|
5544
|
+
if ( ! isset( $schema[ $schema_list_key ] ) || ! is_array( $schema[ $schema_list_key ] ) ) {
|
|
5545
|
+
continue;
|
|
5546
|
+
}
|
|
5547
|
+
|
|
5548
|
+
foreach ( $schema[ $schema_list_key ] as $index => $variant_schema ) {
|
|
5549
|
+
$schema[ $schema_list_key ][ $index ] = ${phpPrefix}_prepare_rest_schema_for_wordpress( $variant_schema );
|
|
5550
|
+
}
|
|
5551
|
+
}
|
|
5552
|
+
|
|
5553
|
+
return $schema;
|
|
5554
|
+
}
|
|
5555
|
+
}
|
|
5556
|
+
|
|
5557
|
+
if ( ! function_exists( '${phpPrefix}_get_wordpress_rest_schema' ) ) {
|
|
5558
|
+
function ${phpPrefix}_get_wordpress_rest_schema( $schema_name, $options = array() ) {
|
|
5559
|
+
$schema = ${phpPrefix}_load_rest_schema( $schema_name, $options );
|
|
5560
|
+
if ( is_wp_error( $schema ) ) {
|
|
5561
|
+
return $schema;
|
|
5562
|
+
}
|
|
5563
|
+
|
|
5564
|
+
return ${phpPrefix}_prepare_rest_schema_for_wordpress( $schema );
|
|
5565
|
+
}
|
|
5566
|
+
}
|
|
5567
|
+
|
|
5568
|
+
if ( ! function_exists( '${phpPrefix}_validate_and_sanitize_rest_payload' ) ) {
|
|
5569
|
+
function ${phpPrefix}_validate_and_sanitize_rest_payload( $value, $schema_name, $param_name, $options = array() ) {
|
|
5570
|
+
$rest_schema = ${phpPrefix}_get_wordpress_rest_schema( $schema_name, $options );
|
|
5571
|
+
if ( is_wp_error( $rest_schema ) ) {
|
|
5572
|
+
return $rest_schema;
|
|
5573
|
+
}
|
|
5574
|
+
|
|
5575
|
+
$validation = rest_validate_value_from_schema( $value, $rest_schema, $param_name );
|
|
5576
|
+
if ( is_wp_error( $validation ) ) {
|
|
5577
|
+
return $validation;
|
|
5578
|
+
}
|
|
5579
|
+
|
|
5580
|
+
return rest_sanitize_value_from_schema( $value, $rest_schema, $param_name );
|
|
5747
5581
|
}
|
|
5748
5582
|
}
|
|
5749
|
-
|
|
5750
|
-
add_action( 'rest_api_init', '${registerRoutesFunctionName}' );
|
|
5751
5583
|
`;
|
|
5752
5584
|
}
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5585
|
+
|
|
5586
|
+
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-rest-generated.ts
|
|
5587
|
+
async function ensureWorkspaceRestSchemaHelperFile(helperFilePath, phpPrefix) {
|
|
5588
|
+
let currentSource = null;
|
|
5589
|
+
try {
|
|
5590
|
+
currentSource = await fsp5.readFile(helperFilePath, "utf8");
|
|
5591
|
+
} catch (error) {
|
|
5592
|
+
if (error.code !== "ENOENT") {
|
|
5593
|
+
throw error;
|
|
5594
|
+
}
|
|
5595
|
+
}
|
|
5596
|
+
if (currentSource === null) {
|
|
5597
|
+
await fsp5.mkdir(path9.dirname(helperFilePath), { recursive: true });
|
|
5598
|
+
await fsp5.writeFile(helperFilePath, buildWorkspaceRestSchemaHelperPhpSource(phpPrefix), "utf8");
|
|
5599
|
+
return;
|
|
5600
|
+
}
|
|
5601
|
+
const requiredFunctions = [
|
|
5602
|
+
`${phpPrefix}_load_rest_schema`,
|
|
5603
|
+
`${phpPrefix}_prepare_rest_schema_for_wordpress`,
|
|
5604
|
+
`${phpPrefix}_get_wordpress_rest_schema`,
|
|
5605
|
+
`${phpPrefix}_validate_and_sanitize_rest_payload`
|
|
5606
|
+
];
|
|
5607
|
+
const missingFunctions = requiredFunctions.filter((functionName) => !hasPhpFunctionDefinition(currentSource, functionName));
|
|
5608
|
+
if (missingFunctions.length > 0) {
|
|
5609
|
+
throw new Error([
|
|
5610
|
+
`Existing ${path9.relative(process.cwd(), helperFilePath)} is missing generated REST schema helper functions: ${missingFunctions.join(", ")}.`,
|
|
5611
|
+
"Restore the generated inc/rest-schema.php helper or add these functions manually before retrying."
|
|
5612
|
+
].join(" "));
|
|
5613
|
+
}
|
|
5614
|
+
}
|
|
5615
|
+
async function scaffoldGeneratedRestResource({
|
|
5756
5616
|
controllerClass,
|
|
5757
5617
|
controllerExtends,
|
|
5758
|
-
cwd = process.cwd(),
|
|
5759
|
-
manual,
|
|
5760
|
-
method,
|
|
5761
5618
|
methods,
|
|
5762
5619
|
namespace,
|
|
5763
5620
|
permissionCallback,
|
|
5764
|
-
|
|
5765
|
-
queryTypeName,
|
|
5766
|
-
restResourceName,
|
|
5767
|
-
responseTypeName,
|
|
5621
|
+
restResourceSlug,
|
|
5768
5622
|
routePattern,
|
|
5769
|
-
|
|
5770
|
-
secretStateFieldName
|
|
5623
|
+
workspace
|
|
5771
5624
|
}) {
|
|
5772
|
-
const
|
|
5773
|
-
const
|
|
5774
|
-
const
|
|
5775
|
-
const
|
|
5776
|
-
|
|
5777
|
-
const
|
|
5778
|
-
const syncRestScriptPath = path10.join(workspace.projectDir, "scripts", "sync-rest-contracts.ts");
|
|
5779
|
-
const restResourceDir = path10.join(workspace.projectDir, "src", "rest", restResourceSlug);
|
|
5780
|
-
const typesFilePath = path10.join(restResourceDir, "api-types.ts");
|
|
5781
|
-
const validatorsFilePath = path10.join(restResourceDir, "api-validators.ts");
|
|
5782
|
-
const apiFilePath = path10.join(restResourceDir, "api.ts");
|
|
5783
|
-
if (manual) {
|
|
5784
|
-
if (controllerClass || controllerExtends || permissionCallback || routePattern) {
|
|
5785
|
-
throw new Error("Manual REST contracts do not generate PHP route glue. Use generated rest-resource mode for --route-pattern, --permission-callback, --controller-class, or --controller-extends.");
|
|
5786
|
-
}
|
|
5787
|
-
const pascalCase = toPascalCase(restResourceSlug);
|
|
5788
|
-
const resolvedAuth = assertValidManualRestContractAuth(auth);
|
|
5789
|
-
const resolvedMethod = assertValidManualRestContractHttpMethod(method);
|
|
5790
|
-
const resolvedPathPattern = resolveManualRestContractPathPattern(restResourceSlug, pathPattern);
|
|
5791
|
-
const resolvedQueryTypeName = assertValidTypeScriptIdentifier("Manual REST contract query type", queryTypeName ?? `${pascalCase}Query`, "wp-typia add rest-resource <name> --manual [--query-type <ExportedQueryType>]");
|
|
5792
|
-
const resolvedResponseTypeName = assertValidTypeScriptIdentifier("Manual REST contract response type", responseTypeName ?? `${pascalCase}Response`, "wp-typia add rest-resource <name> --manual [--response-type <ExportedResponseType>]");
|
|
5793
|
-
const defaultsToBody = bodyTypeName == null && ["PATCH", "POST", "PUT"].includes(resolvedMethod);
|
|
5794
|
-
const resolvedBodyTypeName = bodyTypeName != null || defaultsToBody ? assertValidTypeScriptIdentifier("Manual REST contract body type", bodyTypeName ?? `${pascalCase}Request`, "wp-typia add rest-resource <name> --manual [--body-type <ExportedBodyType>]") : undefined;
|
|
5795
|
-
if (resolvedMethod === "GET" && resolvedBodyTypeName) {
|
|
5796
|
-
throw new Error("Manual REST contract GET routes cannot define a body type. Remove --body-type or use POST, PUT, or PATCH.");
|
|
5797
|
-
}
|
|
5798
|
-
if (secretStateFieldName && !secretFieldName) {
|
|
5799
|
-
throw new Error("Manual REST contract --secret-state-field requires --secret-field.");
|
|
5800
|
-
}
|
|
5801
|
-
if (secretFieldName && !resolvedBodyTypeName) {
|
|
5802
|
-
throw new Error("Manual REST contract secret fields require a request body. Use POST, PUT, or PATCH so a request body is generated.");
|
|
5803
|
-
}
|
|
5804
|
-
const resolvedSecretFieldName = secretFieldName ? assertValidTypeScriptIdentifier("Manual REST contract secret field", secretFieldName, "wp-typia add rest-resource <name> --manual --method POST --secret-field <field>") : undefined;
|
|
5805
|
-
const resolvedSecretStateFieldName = resolvedSecretFieldName ? assertValidTypeScriptIdentifier("Manual REST contract secret state field", secretStateFieldName ?? `has${toPascalCase(resolvedSecretFieldName)}`, "wp-typia add rest-resource <name> --manual --method POST --secret-state-field <field>") : undefined;
|
|
5806
|
-
if (resolvedSecretFieldName && MANUAL_REST_REQUEST_BODY_FIELD_NAMES.has(resolvedSecretFieldName)) {
|
|
5807
|
-
throw new Error(`Manual REST contract secret field must not reuse scaffolded request body fields: ${Array.from(MANUAL_REST_REQUEST_BODY_FIELD_NAMES).join(", ")}.`);
|
|
5808
|
-
}
|
|
5809
|
-
if (resolvedSecretStateFieldName && MANUAL_REST_RESPONSE_FIELD_NAMES.has(resolvedSecretStateFieldName)) {
|
|
5810
|
-
throw new Error(`Manual REST contract secret state field must not reuse scaffolded response fields: ${Array.from(MANUAL_REST_RESPONSE_FIELD_NAMES).join(", ")}.`);
|
|
5811
|
-
}
|
|
5812
|
-
if (resolvedSecretFieldName && resolvedSecretStateFieldName && resolvedSecretFieldName === resolvedSecretStateFieldName) {
|
|
5813
|
-
throw new Error("Manual REST contract secret state field must be different from the raw secret field.");
|
|
5814
|
-
}
|
|
5815
|
-
const manualTypeNames = [
|
|
5816
|
-
resolvedQueryTypeName,
|
|
5817
|
-
resolvedResponseTypeName,
|
|
5818
|
-
resolvedBodyTypeName
|
|
5819
|
-
].filter((value) => value != null);
|
|
5820
|
-
const duplicateManualTypeNames = manualTypeNames.filter((name, index) => manualTypeNames.indexOf(name) !== index);
|
|
5821
|
-
if (duplicateManualTypeNames.length > 0) {
|
|
5822
|
-
throw new Error(`Manual REST contract type names must be unique: ${Array.from(new Set(duplicateManualTypeNames)).join(", ")}. Use distinct --query-type, --body-type, and --response-type values.`);
|
|
5823
|
-
}
|
|
5824
|
-
const mutationSnapshot2 = {
|
|
5825
|
-
fileSources: await snapshotWorkspaceFiles([
|
|
5826
|
-
blockConfigPath,
|
|
5827
|
-
syncRestScriptPath
|
|
5828
|
-
]),
|
|
5829
|
-
snapshotDirs: [],
|
|
5830
|
-
targetPaths: [restResourceDir]
|
|
5831
|
-
};
|
|
5832
|
-
try {
|
|
5833
|
-
await fsp6.mkdir(restResourceDir, { recursive: true });
|
|
5834
|
-
await ensureRestResourceSyncScriptAnchors(workspace);
|
|
5835
|
-
await fsp6.writeFile(typesFilePath, buildManualRestContractTypesSource({
|
|
5836
|
-
...resolvedBodyTypeName ? { bodyTypeName: resolvedBodyTypeName } : {},
|
|
5837
|
-
queryTypeName: resolvedQueryTypeName,
|
|
5838
|
-
responseTypeName: resolvedResponseTypeName,
|
|
5839
|
-
restResourceSlug,
|
|
5840
|
-
...resolvedSecretFieldName ? { secretFieldName: resolvedSecretFieldName } : {},
|
|
5841
|
-
...resolvedSecretStateFieldName ? { secretStateFieldName: resolvedSecretStateFieldName } : {}
|
|
5842
|
-
}), "utf8");
|
|
5843
|
-
await fsp6.writeFile(validatorsFilePath, buildManualRestContractValidatorsSource({
|
|
5844
|
-
...resolvedBodyTypeName ? { bodyTypeName: resolvedBodyTypeName } : {},
|
|
5845
|
-
queryTypeName: resolvedQueryTypeName,
|
|
5846
|
-
responseTypeName: resolvedResponseTypeName
|
|
5847
|
-
}), "utf8");
|
|
5848
|
-
await fsp6.writeFile(apiFilePath, buildManualRestContractApiSource({
|
|
5849
|
-
...resolvedBodyTypeName ? { bodyTypeName: resolvedBodyTypeName } : {},
|
|
5850
|
-
queryTypeName: resolvedQueryTypeName,
|
|
5851
|
-
restResourceSlug
|
|
5852
|
-
}), "utf8");
|
|
5853
|
-
await syncManualRestContractArtifacts({
|
|
5854
|
-
clientFile: `src/rest/${restResourceSlug}/api-client.ts`,
|
|
5855
|
-
outputDir: restResourceDir,
|
|
5856
|
-
projectDir: workspace.projectDir,
|
|
5857
|
-
typesFile: `src/rest/${restResourceSlug}/api-types.ts`,
|
|
5858
|
-
validatorsFile: `src/rest/${restResourceSlug}/api-validators.ts`,
|
|
5859
|
-
variables: {
|
|
5860
|
-
auth: resolvedAuth,
|
|
5861
|
-
...resolvedBodyTypeName ? { bodyTypeName: resolvedBodyTypeName } : {},
|
|
5862
|
-
method: resolvedMethod,
|
|
5863
|
-
namespace: resolvedNamespace,
|
|
5864
|
-
pascalCase,
|
|
5865
|
-
pathPattern: resolvedPathPattern,
|
|
5866
|
-
queryTypeName: resolvedQueryTypeName,
|
|
5867
|
-
responseTypeName: resolvedResponseTypeName,
|
|
5868
|
-
slugKebabCase: restResourceSlug,
|
|
5869
|
-
title: toTitleCase(restResourceSlug)
|
|
5870
|
-
}
|
|
5871
|
-
});
|
|
5872
|
-
await appendWorkspaceInventoryEntries(workspace.projectDir, {
|
|
5873
|
-
restResourceEntries: [
|
|
5874
|
-
buildManualRestContractConfigEntry({
|
|
5875
|
-
auth: resolvedAuth,
|
|
5876
|
-
...resolvedBodyTypeName ? { bodyTypeName: resolvedBodyTypeName } : {},
|
|
5877
|
-
method: resolvedMethod,
|
|
5878
|
-
namespace: resolvedNamespace,
|
|
5879
|
-
pathPattern: resolvedPathPattern,
|
|
5880
|
-
queryTypeName: resolvedQueryTypeName,
|
|
5881
|
-
responseTypeName: resolvedResponseTypeName,
|
|
5882
|
-
restResourceSlug,
|
|
5883
|
-
...resolvedSecretFieldName ? { secretFieldName: resolvedSecretFieldName } : {},
|
|
5884
|
-
...resolvedSecretStateFieldName ? { secretStateFieldName: resolvedSecretStateFieldName } : {}
|
|
5885
|
-
})
|
|
5886
|
-
],
|
|
5887
|
-
transformSource: ensureBlockConfigCanAddRestManifests
|
|
5888
|
-
});
|
|
5889
|
-
return {
|
|
5890
|
-
auth: resolvedAuth,
|
|
5891
|
-
...resolvedBodyTypeName ? { bodyTypeName: resolvedBodyTypeName } : {},
|
|
5892
|
-
method: resolvedMethod,
|
|
5893
|
-
methods: [],
|
|
5894
|
-
mode: "manual",
|
|
5895
|
-
namespace: resolvedNamespace,
|
|
5896
|
-
pathPattern: resolvedPathPattern,
|
|
5897
|
-
projectDir: workspace.projectDir,
|
|
5898
|
-
queryTypeName: resolvedQueryTypeName,
|
|
5899
|
-
restResourceSlug,
|
|
5900
|
-
responseTypeName: resolvedResponseTypeName,
|
|
5901
|
-
...resolvedSecretFieldName ? { secretFieldName: resolvedSecretFieldName } : {},
|
|
5902
|
-
...resolvedSecretStateFieldName ? { secretStateFieldName: resolvedSecretStateFieldName } : {}
|
|
5903
|
-
};
|
|
5904
|
-
} catch (error) {
|
|
5905
|
-
await rollbackWorkspaceMutation(mutationSnapshot2);
|
|
5906
|
-
throw error;
|
|
5907
|
-
}
|
|
5908
|
-
}
|
|
5625
|
+
const blockConfigPath = path9.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
5626
|
+
const syncRestScriptPath = path9.join(workspace.projectDir, "scripts", "sync-rest-contracts.ts");
|
|
5627
|
+
const restResourceDir = path9.join(workspace.projectDir, "src", "rest", restResourceSlug);
|
|
5628
|
+
const typesFilePath = path9.join(restResourceDir, "api-types.ts");
|
|
5629
|
+
const validatorsFilePath = path9.join(restResourceDir, "api-validators.ts");
|
|
5630
|
+
const apiFilePath = path9.join(restResourceDir, "api.ts");
|
|
5909
5631
|
const resolvedMethods = assertValidRestResourceMethods(methods);
|
|
5910
5632
|
const resolvedRoutePattern = resolveGeneratedRestResourceRoutePattern(restResourceSlug, routePattern);
|
|
5911
5633
|
const hasCustomRoutePattern = typeof routePattern === "string" && routePattern.trim().length > 0;
|
|
@@ -5916,27 +5638,31 @@ async function runAddRestResourceCommand({
|
|
|
5916
5638
|
throw new Error("Generated REST resource controller base class requires --controller-class.");
|
|
5917
5639
|
}
|
|
5918
5640
|
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
5919
|
-
const dataFilePath =
|
|
5920
|
-
const
|
|
5641
|
+
const dataFilePath = path9.join(restResourceDir, "data.ts");
|
|
5642
|
+
const restSchemaHelperPath = path9.join(workspace.projectDir, "inc", "rest-schema.php");
|
|
5643
|
+
const phpFilePath = path9.join(workspace.projectDir, "inc", "rest", `${restResourceSlug}.php`);
|
|
5921
5644
|
const mutationSnapshot = {
|
|
5922
5645
|
fileSources: await snapshotWorkspaceFiles([
|
|
5923
5646
|
blockConfigPath,
|
|
5924
5647
|
bootstrapPath,
|
|
5648
|
+
restSchemaHelperPath,
|
|
5925
5649
|
syncRestScriptPath
|
|
5926
5650
|
]),
|
|
5927
5651
|
snapshotDirs: [],
|
|
5928
|
-
targetPaths: [restResourceDir, phpFilePath]
|
|
5652
|
+
targetPaths: [restResourceDir, restSchemaHelperPath, phpFilePath]
|
|
5929
5653
|
};
|
|
5930
5654
|
try {
|
|
5931
|
-
await
|
|
5932
|
-
await
|
|
5655
|
+
await fsp5.mkdir(restResourceDir, { recursive: true });
|
|
5656
|
+
await fsp5.mkdir(path9.dirname(phpFilePath), { recursive: true });
|
|
5657
|
+
await ensureWorkspaceRestSchemaHelperFile(restSchemaHelperPath, workspace.workspace.phpPrefix);
|
|
5658
|
+
await ensureRestSchemaHelperBootstrapAnchors(workspace);
|
|
5933
5659
|
await ensureRestResourceBootstrapAnchors(workspace);
|
|
5934
5660
|
await ensureRestResourceSyncScriptAnchors(workspace);
|
|
5935
|
-
await
|
|
5936
|
-
await
|
|
5937
|
-
await
|
|
5938
|
-
await
|
|
5939
|
-
await
|
|
5661
|
+
await fsp5.writeFile(typesFilePath, buildRestResourceTypesSource(restResourceSlug, resolvedMethods), "utf8");
|
|
5662
|
+
await fsp5.writeFile(validatorsFilePath, buildRestResourceValidatorsSource(restResourceSlug, resolvedMethods), "utf8");
|
|
5663
|
+
await fsp5.writeFile(apiFilePath, buildRestResourceApiSource(restResourceSlug, resolvedMethods), "utf8");
|
|
5664
|
+
await fsp5.writeFile(dataFilePath, buildRestResourceDataSource(restResourceSlug, resolvedMethods), "utf8");
|
|
5665
|
+
await fsp5.writeFile(phpFilePath, buildRestResourcePhpSource(restResourceSlug, namespace, workspace.workspace.phpPrefix, resolvedMethods, {
|
|
5940
5666
|
...resolvedControllerClass ? { controllerClass: resolvedControllerClass } : {},
|
|
5941
5667
|
...resolvedControllerExtends ? { controllerExtends: resolvedControllerExtends } : {},
|
|
5942
5668
|
...resolvedPermissionCallback ? { permissionCallback: resolvedPermissionCallback } : {},
|
|
@@ -5950,7 +5676,7 @@ async function runAddRestResourceCommand({
|
|
|
5950
5676
|
typesFile: `src/rest/${restResourceSlug}/api-types.ts`,
|
|
5951
5677
|
validatorsFile: `src/rest/${restResourceSlug}/api-validators.ts`,
|
|
5952
5678
|
variables: {
|
|
5953
|
-
namespace
|
|
5679
|
+
namespace,
|
|
5954
5680
|
pascalCase: toPascalCase(restResourceSlug),
|
|
5955
5681
|
...hasCustomRoutePattern ? { routePattern: resolvedRoutePattern } : {},
|
|
5956
5682
|
slugKebabCase: restResourceSlug,
|
|
@@ -5963,7 +5689,7 @@ async function runAddRestResourceCommand({
|
|
|
5963
5689
|
...resolvedControllerClass ? { controllerClass: resolvedControllerClass } : {},
|
|
5964
5690
|
...resolvedControllerExtends ? { controllerExtends: resolvedControllerExtends } : {},
|
|
5965
5691
|
methods: resolvedMethods,
|
|
5966
|
-
namespace
|
|
5692
|
+
namespace,
|
|
5967
5693
|
...resolvedPermissionCallback ? { permissionCallback: resolvedPermissionCallback } : {},
|
|
5968
5694
|
restResourceSlug,
|
|
5969
5695
|
...hasCustomRoutePattern ? { routePattern: resolvedRoutePattern } : {}
|
|
@@ -5976,7 +5702,7 @@ async function runAddRestResourceCommand({
|
|
|
5976
5702
|
...resolvedControllerExtends ? { controllerExtends: resolvedControllerExtends } : {},
|
|
5977
5703
|
methods: resolvedMethods,
|
|
5978
5704
|
mode: "generated",
|
|
5979
|
-
namespace
|
|
5705
|
+
namespace,
|
|
5980
5706
|
...resolvedPermissionCallback ? { permissionCallback: resolvedPermissionCallback } : {},
|
|
5981
5707
|
projectDir: workspace.projectDir,
|
|
5982
5708
|
restResourceSlug,
|
|
@@ -5987,6 +5713,293 @@ async function runAddRestResourceCommand({
|
|
|
5987
5713
|
throw error;
|
|
5988
5714
|
}
|
|
5989
5715
|
}
|
|
5716
|
+
|
|
5717
|
+
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-rest-manual.ts
|
|
5718
|
+
import { promises as fsp6 } from "fs";
|
|
5719
|
+
import path10 from "path";
|
|
5720
|
+
var MANUAL_REST_REQUEST_BODY_FIELD_NAMES = new Set(["payload", "comment"]);
|
|
5721
|
+
var MANUAL_REST_RESPONSE_FIELD_NAMES = new Set([
|
|
5722
|
+
"id",
|
|
5723
|
+
"status",
|
|
5724
|
+
"message",
|
|
5725
|
+
"updatedAt"
|
|
5726
|
+
]);
|
|
5727
|
+
function resolveManualRestSecretPreserveOnEmpty(value) {
|
|
5728
|
+
if (value === undefined) {
|
|
5729
|
+
return true;
|
|
5730
|
+
}
|
|
5731
|
+
if (typeof value === "boolean") {
|
|
5732
|
+
return value;
|
|
5733
|
+
}
|
|
5734
|
+
const normalized = value.trim().toLowerCase();
|
|
5735
|
+
if (["1", "true", "yes"].includes(normalized)) {
|
|
5736
|
+
return true;
|
|
5737
|
+
}
|
|
5738
|
+
if (["0", "false", "no"].includes(normalized)) {
|
|
5739
|
+
return false;
|
|
5740
|
+
}
|
|
5741
|
+
throw new Error("Manual REST contract --secret-preserve-on-empty must be true or false.");
|
|
5742
|
+
}
|
|
5743
|
+
function resolveManualRestSecretStateFieldCandidate(options) {
|
|
5744
|
+
const candidates = [
|
|
5745
|
+
options.secretStateFieldName,
|
|
5746
|
+
options.secretHasValueFieldName,
|
|
5747
|
+
options.secretMaskedResponseFieldName
|
|
5748
|
+
].filter((value) => typeof value === "string");
|
|
5749
|
+
const distinct = Array.from(new Set(candidates));
|
|
5750
|
+
if (distinct.length > 1) {
|
|
5751
|
+
throw new Error("Manual REST contract secret state, has-value, and masked response field flags must match when combined.");
|
|
5752
|
+
}
|
|
5753
|
+
return distinct[0];
|
|
5754
|
+
}
|
|
5755
|
+
function resolveManualRestRoutePathPattern(options) {
|
|
5756
|
+
const trimmedPathPattern = typeof options.pathPattern === "string" ? options.pathPattern.trim() : undefined;
|
|
5757
|
+
const trimmedRoutePattern = typeof options.routePattern === "string" ? options.routePattern.trim() : undefined;
|
|
5758
|
+
if (trimmedPathPattern && trimmedRoutePattern && trimmedPathPattern !== trimmedRoutePattern) {
|
|
5759
|
+
throw new Error("Manual REST contract --path and --route-pattern must match when both are provided. Use one route pattern flag for provider routes.");
|
|
5760
|
+
}
|
|
5761
|
+
return resolveManualRestContractPathPattern(options.restResourceSlug, options.pathPattern ?? options.routePattern);
|
|
5762
|
+
}
|
|
5763
|
+
async function scaffoldManualRestContract({
|
|
5764
|
+
auth,
|
|
5765
|
+
bodyTypeName,
|
|
5766
|
+
controllerClass,
|
|
5767
|
+
controllerExtends,
|
|
5768
|
+
method,
|
|
5769
|
+
namespace,
|
|
5770
|
+
pathPattern,
|
|
5771
|
+
permissionCallback,
|
|
5772
|
+
queryTypeName,
|
|
5773
|
+
responseTypeName,
|
|
5774
|
+
restResourceSlug,
|
|
5775
|
+
routePattern,
|
|
5776
|
+
secretFieldName,
|
|
5777
|
+
secretHasValueFieldName,
|
|
5778
|
+
secretMaskedResponseFieldName,
|
|
5779
|
+
secretPreserveOnEmpty,
|
|
5780
|
+
secretStateFieldName,
|
|
5781
|
+
workspace
|
|
5782
|
+
}) {
|
|
5783
|
+
const blockConfigPath = path10.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
5784
|
+
const syncRestScriptPath = path10.join(workspace.projectDir, "scripts", "sync-rest-contracts.ts");
|
|
5785
|
+
const restResourceDir = path10.join(workspace.projectDir, "src", "rest", restResourceSlug);
|
|
5786
|
+
const typesFilePath = path10.join(restResourceDir, "api-types.ts");
|
|
5787
|
+
const validatorsFilePath = path10.join(restResourceDir, "api-validators.ts");
|
|
5788
|
+
const apiFilePath = path10.join(restResourceDir, "api.ts");
|
|
5789
|
+
const pascalCase = toPascalCase(restResourceSlug);
|
|
5790
|
+
const resolvedAuth = assertValidManualRestContractAuth(auth);
|
|
5791
|
+
const resolvedMethod = assertValidManualRestContractHttpMethod(method);
|
|
5792
|
+
const resolvedPathPattern = resolveManualRestRoutePathPattern({
|
|
5793
|
+
pathPattern,
|
|
5794
|
+
restResourceSlug,
|
|
5795
|
+
routePattern
|
|
5796
|
+
});
|
|
5797
|
+
const pathParameterNames = collectRestRouteNamedCaptureNames(resolvedPathPattern);
|
|
5798
|
+
const resolvedPermissionCallback = resolveOptionalPhpCallbackReference("Manual REST contract permission callback", permissionCallback);
|
|
5799
|
+
const resolvedControllerClass = resolveOptionalPhpClassReference("Manual REST contract controller class", controllerClass);
|
|
5800
|
+
const resolvedControllerExtends = resolveOptionalPhpClassReference("Manual REST contract controller base class", controllerExtends);
|
|
5801
|
+
if (resolvedControllerExtends && !resolvedControllerClass) {
|
|
5802
|
+
throw new Error("Manual REST contract controller base class requires --controller-class.");
|
|
5803
|
+
}
|
|
5804
|
+
const resolvedQueryTypeName = assertValidTypeScriptIdentifier("Manual REST contract query type", queryTypeName ?? `${pascalCase}Query`, "wp-typia add rest-resource <name> --manual [--query-type <ExportedQueryType>]");
|
|
5805
|
+
const resolvedResponseTypeName = assertValidTypeScriptIdentifier("Manual REST contract response type", responseTypeName ?? `${pascalCase}Response`, "wp-typia add rest-resource <name> --manual [--response-type <ExportedResponseType>]");
|
|
5806
|
+
const defaultsToBody = bodyTypeName == null && ["PATCH", "POST", "PUT"].includes(resolvedMethod);
|
|
5807
|
+
const resolvedBodyTypeName = bodyTypeName != null || defaultsToBody ? assertValidTypeScriptIdentifier("Manual REST contract body type", bodyTypeName ?? `${pascalCase}Request`, "wp-typia add rest-resource <name> --manual [--body-type <ExportedBodyType>]") : undefined;
|
|
5808
|
+
if (resolvedMethod === "GET" && resolvedBodyTypeName) {
|
|
5809
|
+
throw new Error("Manual REST contract GET routes cannot define a body type. Remove --body-type or use POST, PUT, or PATCH.");
|
|
5810
|
+
}
|
|
5811
|
+
const secretStateFieldCandidate = resolveManualRestSecretStateFieldCandidate({
|
|
5812
|
+
secretHasValueFieldName,
|
|
5813
|
+
secretMaskedResponseFieldName,
|
|
5814
|
+
secretStateFieldName
|
|
5815
|
+
});
|
|
5816
|
+
if (secretPreserveOnEmpty !== undefined && !secretFieldName) {
|
|
5817
|
+
throw new Error("Manual REST contract --secret-preserve-on-empty requires --secret-field.");
|
|
5818
|
+
}
|
|
5819
|
+
if (secretStateFieldCandidate !== undefined && !secretFieldName) {
|
|
5820
|
+
throw new Error("Manual REST contract secret state, has-value, and masked response field flags require --secret-field.");
|
|
5821
|
+
}
|
|
5822
|
+
if (secretFieldName && !resolvedBodyTypeName) {
|
|
5823
|
+
throw new Error("Manual REST contract secret fields require a request body. Use POST, PUT, or PATCH so a request body is generated.");
|
|
5824
|
+
}
|
|
5825
|
+
const resolvedSecretFieldName = secretFieldName ? assertValidTypeScriptIdentifier("Manual REST contract secret field", secretFieldName, "wp-typia add rest-resource <name> --manual --method POST --secret-field <field>") : undefined;
|
|
5826
|
+
const resolvedSecretPreserveOnEmpty = resolvedSecretFieldName ? resolveManualRestSecretPreserveOnEmpty(secretPreserveOnEmpty) : undefined;
|
|
5827
|
+
const resolvedSecretStateFieldName = resolvedSecretFieldName ? assertValidTypeScriptIdentifier("Manual REST contract secret state field", secretStateFieldCandidate ?? `has${toPascalCase(resolvedSecretFieldName)}`, "wp-typia add rest-resource <name> --manual --method POST --secret-state-field <field>") : undefined;
|
|
5828
|
+
if (resolvedSecretFieldName && MANUAL_REST_REQUEST_BODY_FIELD_NAMES.has(resolvedSecretFieldName)) {
|
|
5829
|
+
throw new Error(`Manual REST contract secret field must not reuse scaffolded request body fields: ${Array.from(MANUAL_REST_REQUEST_BODY_FIELD_NAMES).join(", ")}.`);
|
|
5830
|
+
}
|
|
5831
|
+
if (resolvedSecretStateFieldName && MANUAL_REST_RESPONSE_FIELD_NAMES.has(resolvedSecretStateFieldName)) {
|
|
5832
|
+
throw new Error(`Manual REST contract secret state field must not reuse scaffolded response fields: ${Array.from(MANUAL_REST_RESPONSE_FIELD_NAMES).join(", ")}.`);
|
|
5833
|
+
}
|
|
5834
|
+
if (resolvedSecretFieldName && resolvedSecretStateFieldName && resolvedSecretFieldName === resolvedSecretStateFieldName) {
|
|
5835
|
+
throw new Error("Manual REST contract secret state field must be different from the raw secret field.");
|
|
5836
|
+
}
|
|
5837
|
+
const manualTypeNames = [
|
|
5838
|
+
resolvedQueryTypeName,
|
|
5839
|
+
resolvedResponseTypeName,
|
|
5840
|
+
resolvedBodyTypeName
|
|
5841
|
+
].filter((value) => value != null);
|
|
5842
|
+
const duplicateManualTypeNames = manualTypeNames.filter((name, index) => manualTypeNames.indexOf(name) !== index);
|
|
5843
|
+
if (duplicateManualTypeNames.length > 0) {
|
|
5844
|
+
throw new Error(`Manual REST contract type names must be unique: ${Array.from(new Set(duplicateManualTypeNames)).join(", ")}. Use distinct --query-type, --body-type, and --response-type values.`);
|
|
5845
|
+
}
|
|
5846
|
+
const mutationSnapshot = {
|
|
5847
|
+
fileSources: await snapshotWorkspaceFiles([
|
|
5848
|
+
blockConfigPath,
|
|
5849
|
+
syncRestScriptPath
|
|
5850
|
+
]),
|
|
5851
|
+
snapshotDirs: [],
|
|
5852
|
+
targetPaths: [restResourceDir]
|
|
5853
|
+
};
|
|
5854
|
+
try {
|
|
5855
|
+
await fsp6.mkdir(restResourceDir, { recursive: true });
|
|
5856
|
+
await ensureRestResourceSyncScriptAnchors(workspace);
|
|
5857
|
+
await fsp6.writeFile(typesFilePath, buildManualRestContractTypesSource({
|
|
5858
|
+
...resolvedBodyTypeName ? { bodyTypeName: resolvedBodyTypeName } : {},
|
|
5859
|
+
pathParameterNames,
|
|
5860
|
+
queryTypeName: resolvedQueryTypeName,
|
|
5861
|
+
responseTypeName: resolvedResponseTypeName,
|
|
5862
|
+
restResourceSlug,
|
|
5863
|
+
...resolvedSecretFieldName ? { secretFieldName: resolvedSecretFieldName } : {},
|
|
5864
|
+
...resolvedSecretPreserveOnEmpty !== undefined ? { secretPreserveOnEmpty: resolvedSecretPreserveOnEmpty } : {},
|
|
5865
|
+
...resolvedSecretStateFieldName ? { secretStateFieldName: resolvedSecretStateFieldName } : {}
|
|
5866
|
+
}), "utf8");
|
|
5867
|
+
await fsp6.writeFile(validatorsFilePath, buildManualRestContractValidatorsSource({
|
|
5868
|
+
...resolvedBodyTypeName ? { bodyTypeName: resolvedBodyTypeName } : {},
|
|
5869
|
+
queryTypeName: resolvedQueryTypeName,
|
|
5870
|
+
responseTypeName: resolvedResponseTypeName
|
|
5871
|
+
}), "utf8");
|
|
5872
|
+
await fsp6.writeFile(apiFilePath, buildManualRestContractApiSource({
|
|
5873
|
+
...resolvedBodyTypeName ? { bodyTypeName: resolvedBodyTypeName } : {},
|
|
5874
|
+
queryTypeName: resolvedQueryTypeName,
|
|
5875
|
+
restResourceSlug
|
|
5876
|
+
}), "utf8");
|
|
5877
|
+
await syncManualRestContractArtifacts({
|
|
5878
|
+
clientFile: `src/rest/${restResourceSlug}/api-client.ts`,
|
|
5879
|
+
outputDir: restResourceDir,
|
|
5880
|
+
projectDir: workspace.projectDir,
|
|
5881
|
+
typesFile: `src/rest/${restResourceSlug}/api-types.ts`,
|
|
5882
|
+
validatorsFile: `src/rest/${restResourceSlug}/api-validators.ts`,
|
|
5883
|
+
variables: {
|
|
5884
|
+
auth: resolvedAuth,
|
|
5885
|
+
...resolvedBodyTypeName ? { bodyTypeName: resolvedBodyTypeName } : {},
|
|
5886
|
+
method: resolvedMethod,
|
|
5887
|
+
namespace,
|
|
5888
|
+
pascalCase,
|
|
5889
|
+
pathPattern: resolvedPathPattern,
|
|
5890
|
+
queryTypeName: resolvedQueryTypeName,
|
|
5891
|
+
responseTypeName: resolvedResponseTypeName,
|
|
5892
|
+
slugKebabCase: restResourceSlug,
|
|
5893
|
+
title: toTitleCase(restResourceSlug)
|
|
5894
|
+
}
|
|
5895
|
+
});
|
|
5896
|
+
await appendWorkspaceInventoryEntries(workspace.projectDir, {
|
|
5897
|
+
restResourceEntries: [
|
|
5898
|
+
buildManualRestContractConfigEntry({
|
|
5899
|
+
auth: resolvedAuth,
|
|
5900
|
+
...resolvedBodyTypeName ? { bodyTypeName: resolvedBodyTypeName } : {},
|
|
5901
|
+
...resolvedControllerClass ? { controllerClass: resolvedControllerClass } : {},
|
|
5902
|
+
...resolvedControllerExtends ? { controllerExtends: resolvedControllerExtends } : {},
|
|
5903
|
+
method: resolvedMethod,
|
|
5904
|
+
namespace,
|
|
5905
|
+
pathPattern: resolvedPathPattern,
|
|
5906
|
+
...resolvedPermissionCallback ? { permissionCallback: resolvedPermissionCallback } : {},
|
|
5907
|
+
queryTypeName: resolvedQueryTypeName,
|
|
5908
|
+
responseTypeName: resolvedResponseTypeName,
|
|
5909
|
+
restResourceSlug,
|
|
5910
|
+
...resolvedSecretFieldName ? { secretFieldName: resolvedSecretFieldName } : {},
|
|
5911
|
+
...resolvedSecretPreserveOnEmpty !== undefined ? { secretPreserveOnEmpty: resolvedSecretPreserveOnEmpty } : {},
|
|
5912
|
+
...resolvedSecretStateFieldName ? { secretStateFieldName: resolvedSecretStateFieldName } : {}
|
|
5913
|
+
})
|
|
5914
|
+
],
|
|
5915
|
+
transformSource: ensureBlockConfigCanAddRestManifests
|
|
5916
|
+
});
|
|
5917
|
+
return {
|
|
5918
|
+
auth: resolvedAuth,
|
|
5919
|
+
...resolvedBodyTypeName ? { bodyTypeName: resolvedBodyTypeName } : {},
|
|
5920
|
+
...resolvedControllerClass ? { controllerClass: resolvedControllerClass } : {},
|
|
5921
|
+
...resolvedControllerExtends ? { controllerExtends: resolvedControllerExtends } : {},
|
|
5922
|
+
method: resolvedMethod,
|
|
5923
|
+
methods: [],
|
|
5924
|
+
mode: "manual",
|
|
5925
|
+
namespace,
|
|
5926
|
+
pathPattern: resolvedPathPattern,
|
|
5927
|
+
...resolvedPermissionCallback ? { permissionCallback: resolvedPermissionCallback } : {},
|
|
5928
|
+
projectDir: workspace.projectDir,
|
|
5929
|
+
queryTypeName: resolvedQueryTypeName,
|
|
5930
|
+
restResourceSlug,
|
|
5931
|
+
responseTypeName: resolvedResponseTypeName,
|
|
5932
|
+
...resolvedSecretFieldName ? { secretFieldName: resolvedSecretFieldName } : {},
|
|
5933
|
+
...resolvedSecretPreserveOnEmpty !== undefined ? { secretPreserveOnEmpty: resolvedSecretPreserveOnEmpty } : {},
|
|
5934
|
+
...resolvedSecretStateFieldName ? { secretStateFieldName: resolvedSecretStateFieldName } : {}
|
|
5935
|
+
};
|
|
5936
|
+
} catch (error) {
|
|
5937
|
+
await rollbackWorkspaceMutation(mutationSnapshot);
|
|
5938
|
+
throw error;
|
|
5939
|
+
}
|
|
5940
|
+
}
|
|
5941
|
+
|
|
5942
|
+
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-rest.ts
|
|
5943
|
+
async function runAddRestResourceCommand({
|
|
5944
|
+
auth,
|
|
5945
|
+
bodyTypeName,
|
|
5946
|
+
controllerClass,
|
|
5947
|
+
controllerExtends,
|
|
5948
|
+
cwd = process.cwd(),
|
|
5949
|
+
manual,
|
|
5950
|
+
method,
|
|
5951
|
+
methods,
|
|
5952
|
+
namespace,
|
|
5953
|
+
permissionCallback,
|
|
5954
|
+
pathPattern,
|
|
5955
|
+
queryTypeName,
|
|
5956
|
+
restResourceName,
|
|
5957
|
+
responseTypeName,
|
|
5958
|
+
routePattern,
|
|
5959
|
+
secretFieldName,
|
|
5960
|
+
secretHasValueFieldName,
|
|
5961
|
+
secretMaskedResponseFieldName,
|
|
5962
|
+
secretPreserveOnEmpty,
|
|
5963
|
+
secretStateFieldName
|
|
5964
|
+
}) {
|
|
5965
|
+
const workspace = resolveWorkspaceProject(cwd);
|
|
5966
|
+
const restResourceSlug = assertValidGeneratedSlug("REST resource name", normalizeBlockSlug(restResourceName), "wp-typia add rest-resource <name> [--namespace <vendor/v1>] [--methods <list,read,create>]");
|
|
5967
|
+
const resolvedNamespace = resolveRestResourceNamespace(workspace.workspace.namespace, namespace);
|
|
5968
|
+
const inventory = await readWorkspaceInventoryAsync(workspace.projectDir);
|
|
5969
|
+
assertRestResourceDoesNotExist(workspace.projectDir, restResourceSlug, inventory);
|
|
5970
|
+
if (manual) {
|
|
5971
|
+
return scaffoldManualRestContract({
|
|
5972
|
+
auth,
|
|
5973
|
+
bodyTypeName,
|
|
5974
|
+
controllerClass,
|
|
5975
|
+
controllerExtends,
|
|
5976
|
+
method,
|
|
5977
|
+
namespace: resolvedNamespace,
|
|
5978
|
+
pathPattern,
|
|
5979
|
+
permissionCallback,
|
|
5980
|
+
queryTypeName,
|
|
5981
|
+
responseTypeName,
|
|
5982
|
+
restResourceSlug,
|
|
5983
|
+
routePattern,
|
|
5984
|
+
secretFieldName,
|
|
5985
|
+
secretHasValueFieldName,
|
|
5986
|
+
secretMaskedResponseFieldName,
|
|
5987
|
+
secretPreserveOnEmpty,
|
|
5988
|
+
secretStateFieldName,
|
|
5989
|
+
workspace
|
|
5990
|
+
});
|
|
5991
|
+
}
|
|
5992
|
+
return scaffoldGeneratedRestResource({
|
|
5993
|
+
controllerClass,
|
|
5994
|
+
controllerExtends,
|
|
5995
|
+
methods,
|
|
5996
|
+
namespace: resolvedNamespace,
|
|
5997
|
+
permissionCallback,
|
|
5998
|
+
restResourceSlug,
|
|
5999
|
+
routePattern,
|
|
6000
|
+
workspace
|
|
6001
|
+
});
|
|
6002
|
+
}
|
|
5990
6003
|
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-contract.ts
|
|
5991
6004
|
import { promises as fsp7 } from "fs";
|
|
5992
6005
|
import path11 from "path";
|
|
@@ -7189,7 +7202,9 @@ function ${enqueueFunctionName}() {
|
|
|
7189
7202
|
}
|
|
7190
7203
|
async function ensureAbilityPackageScripts(workspace) {
|
|
7191
7204
|
const packageJsonPath = path14.join(workspace.projectDir, "package.json");
|
|
7192
|
-
const packageJson =
|
|
7205
|
+
const packageJson = await readJsonFile(packageJsonPath, {
|
|
7206
|
+
context: "workspace package manifest"
|
|
7207
|
+
});
|
|
7193
7208
|
const nextScripts = {
|
|
7194
7209
|
...packageJson.scripts ?? {},
|
|
7195
7210
|
"sync-abilities": packageJson.scripts?.["sync-abilities"] ?? "tsx scripts/sync-abilities.ts"
|
|
@@ -7568,7 +7583,9 @@ async function syncAiFeatureSchemaArtifact({
|
|
|
7568
7583
|
projectDir
|
|
7569
7584
|
}) {
|
|
7570
7585
|
const sourceSchemaPath = path15.join(projectDir, outputDir, "api-schemas", "feature-result.schema.json");
|
|
7571
|
-
const responseSchema = assertJsonObject(
|
|
7586
|
+
const responseSchema = assertJsonObject(await readJsonFile(sourceSchemaPath, {
|
|
7587
|
+
context: "AI feature response schema"
|
|
7588
|
+
}), sourceSchemaPath);
|
|
7572
7589
|
const aiSchema = projectWordPressAiSchema(responseSchema);
|
|
7573
7590
|
await reconcileGeneratedArtifact({
|
|
7574
7591
|
check,
|
|
@@ -7963,7 +7980,23 @@ async function reconcileGeneratedArtifact( options: {
|
|
|
7963
7980
|
}
|
|
7964
7981
|
|
|
7965
7982
|
async function loadJsonDocument( filePath: string ) {
|
|
7966
|
-
|
|
7983
|
+
let source: string;
|
|
7984
|
+
try {
|
|
7985
|
+
source = await readFile( filePath, 'utf8' );
|
|
7986
|
+
} catch ( error ) {
|
|
7987
|
+
throw new Error(
|
|
7988
|
+
\`Failed to read AI schema document at \${ filePath }: \${ error instanceof Error ? error.message : String( error ) }\`
|
|
7989
|
+
);
|
|
7990
|
+
}
|
|
7991
|
+
|
|
7992
|
+
let decoded: unknown;
|
|
7993
|
+
try {
|
|
7994
|
+
decoded = JSON.parse( source ) as unknown;
|
|
7995
|
+
} catch ( error ) {
|
|
7996
|
+
throw new Error(
|
|
7997
|
+
\`Failed to parse AI schema document at \${ filePath }: \${ error instanceof Error ? error.message : String( error ) }\`
|
|
7998
|
+
);
|
|
7999
|
+
}
|
|
7967
8000
|
if ( ! decoded || typeof decoded !== 'object' || Array.isArray( decoded ) ) {
|
|
7968
8001
|
throw new Error( \`Expected \${ filePath } to decode to a JSON object.\` );
|
|
7969
8002
|
}
|
|
@@ -8054,7 +8087,9 @@ function ${registerFunctionName}() {
|
|
|
8054
8087
|
}
|
|
8055
8088
|
async function ensureAiFeaturePackageScripts(workspace) {
|
|
8056
8089
|
const packageJsonPath = path16.join(workspace.projectDir, "package.json");
|
|
8057
|
-
const packageJson =
|
|
8090
|
+
const packageJson = await readJsonFile(packageJsonPath, {
|
|
8091
|
+
context: "workspace package manifest"
|
|
8092
|
+
});
|
|
8058
8093
|
const nextScripts = {
|
|
8059
8094
|
...packageJson.scripts ?? {},
|
|
8060
8095
|
"sync-ai": packageJson.scripts?.["sync-ai"] ?? "tsx scripts/sync-ai-features.ts"
|
|
@@ -9695,4 +9730,4 @@ export {
|
|
|
9695
9730
|
ADD_BLOCK_TEMPLATE_IDS
|
|
9696
9731
|
};
|
|
9697
9732
|
|
|
9698
|
-
//# debugId=
|
|
9733
|
+
//# debugId=6B83DC9C0043FA6264756E2164756E21
|