wp-typia 0.20.5 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/bin/argv-walker.d.ts +19 -0
- package/bin/argv-walker.js +53 -0
- package/bin/routing-metadata.generated.d.ts +3 -1
- package/bin/routing-metadata.generated.js +57 -37
- package/bin/runtime-routing.d.ts +34 -0
- package/bin/runtime-routing.js +124 -0
- package/bin/wp-typia.js +97 -90
- package/dist-bunli/.bunli/commands.gen.js +5705 -1385
- package/dist-bunli/{cli-j1tyw390.js → cli-1w5vkye4.js} +989 -266
- package/dist-bunli/{cli-tesygdnr.js → cli-39er8888.js} +29 -2
- package/dist-bunli/{cli-add-6byyahb8.js → cli-add-kjhghdqq.js} +2488 -479
- package/dist-bunli/{cli-diagnostics-zecc6w1f.js → cli-diagnostics-5dvztm7q.js} +8 -2
- package/dist-bunli/{cli-doctor-2bc4sq7v.js → cli-doctor-p3jxvn0k.js} +493 -20
- package/dist-bunli/{cli-2rev5hqm.js → cli-e623rs7g.js} +1 -1
- package/dist-bunli/cli-init-djhwr245.js +844 -0
- package/dist-bunli/{cli-3w3qxq9w.js → cli-j180bk07.js} +260 -25
- package/dist-bunli/{cli-xnh2s5kz.js → cli-ktp869eh.js} +350 -41
- package/dist-bunli/{cli-xxzpb58s.js → cli-p95wr1q8.js} +72 -4
- package/dist-bunli/{cli-scaffold-19gyvxxt.js → cli-scaffold-376yw891.js} +6 -5
- package/dist-bunli/cli.js +31 -117
- package/dist-bunli/{command-list-pztcgga5.js → command-list-jt1a1w7r.js} +587 -320
- package/dist-bunli/{migrations-1p6mbkyw.js → migrations-nwas5bwt.js} +2 -2
- package/dist-bunli/node-cli.js +1063 -556
- package/package.json +5 -3
- package/dist-bunli/cli-init-gdyp9enw.js +0 -341
|
@@ -20,13 +20,19 @@ import {
|
|
|
20
20
|
scaffoldProject,
|
|
21
21
|
syncPersistenceRestArtifacts,
|
|
22
22
|
updatePluginHeaderCompatibility
|
|
23
|
-
} from "./cli-
|
|
23
|
+
} from "./cli-1w5vkye4.js";
|
|
24
24
|
import {
|
|
25
|
+
DEFAULT_WORDPRESS_ABILITIES_VERSION,
|
|
26
|
+
DEFAULT_WORDPRESS_CORE_ABILITIES_VERSION,
|
|
27
|
+
DEFAULT_WORDPRESS_CORE_DATA_VERSION,
|
|
28
|
+
DEFAULT_WORDPRESS_DATAVIEWS_VERSION,
|
|
29
|
+
DEFAULT_WORDPRESS_DATA_VERSION,
|
|
30
|
+
DEFAULT_WP_TYPIA_DATAVIEWS_VERSION,
|
|
25
31
|
getPackageVersions
|
|
26
|
-
} from "./cli-
|
|
32
|
+
} from "./cli-39er8888.js";
|
|
27
33
|
import {
|
|
28
34
|
snapshotProjectVersion
|
|
29
|
-
} from "./cli-
|
|
35
|
+
} from "./cli-e623rs7g.js";
|
|
30
36
|
import {
|
|
31
37
|
ensureMigrationDirectories,
|
|
32
38
|
parseMigrationConfig,
|
|
@@ -39,6 +45,7 @@ import {
|
|
|
39
45
|
} from "./cli-sj5mtyzj.js";
|
|
40
46
|
import"./cli-10pe4mf8.js";
|
|
41
47
|
import {
|
|
48
|
+
PROJECT_TOOLS_PACKAGE_ROOT,
|
|
42
49
|
SHARED_WORKSPACE_TEMPLATE_ROOT
|
|
43
50
|
} from "./cli-tke8twkn.js";
|
|
44
51
|
import {
|
|
@@ -47,6 +54,7 @@ import {
|
|
|
47
54
|
EDITOR_PLUGIN_SLOT_IDS,
|
|
48
55
|
appendWorkspaceInventoryEntries,
|
|
49
56
|
assertAbilityDoesNotExist,
|
|
57
|
+
assertAdminViewDoesNotExist,
|
|
50
58
|
assertAiFeatureDoesNotExist,
|
|
51
59
|
assertBindingSourceDoesNotExist,
|
|
52
60
|
assertEditorPluginDoesNotExist,
|
|
@@ -59,28 +67,40 @@ import {
|
|
|
59
67
|
assertValidRestResourceMethods,
|
|
60
68
|
assertVariationDoesNotExist,
|
|
61
69
|
buildWorkspacePhpPrefix,
|
|
70
|
+
escapeRegex,
|
|
71
|
+
findPhpFunctionRange,
|
|
62
72
|
formatAddHelpText,
|
|
63
73
|
getMutableBlockHooks,
|
|
64
74
|
getWorkspaceBlockSelectOptions,
|
|
65
75
|
getWorkspaceBootstrapPath,
|
|
76
|
+
hasPhpFunctionDefinition,
|
|
66
77
|
isAddBlockTemplateId,
|
|
67
78
|
normalizeBlockSlug,
|
|
68
79
|
patchFile,
|
|
80
|
+
quotePhpString,
|
|
69
81
|
quoteTsString,
|
|
70
82
|
readOptionalFile,
|
|
71
83
|
readWorkspaceBlockJson,
|
|
72
84
|
readWorkspaceInventory,
|
|
85
|
+
replacePhpFunctionDefinition,
|
|
86
|
+
require_typescript,
|
|
73
87
|
resolveNonEmptyNormalizedBlockSlug,
|
|
74
88
|
resolveRestResourceNamespace,
|
|
75
89
|
resolveWorkspaceBlock,
|
|
76
90
|
rollbackWorkspaceMutation,
|
|
77
91
|
snapshotWorkspaceFiles,
|
|
78
92
|
toKebabCase,
|
|
93
|
+
toPascalCase,
|
|
94
|
+
toSnakeCase,
|
|
79
95
|
toTitleCase
|
|
80
|
-
} from "./cli-
|
|
96
|
+
} from "./cli-j180bk07.js";
|
|
81
97
|
import {
|
|
82
98
|
createManagedTempRoot
|
|
83
99
|
} from "./cli-t73q5aqz.js";
|
|
100
|
+
import {
|
|
101
|
+
CLI_DIAGNOSTIC_CODES,
|
|
102
|
+
createCliDiagnosticCodeError
|
|
103
|
+
} from "./cli-p95wr1q8.js";
|
|
84
104
|
import {
|
|
85
105
|
resolveWorkspaceProject
|
|
86
106
|
} from "./cli-pd5pqgre.js";
|
|
@@ -669,104 +689,1462 @@ async function runAddBlockCommand({
|
|
|
669
689
|
externalLayerSource: normalizedExternalLayerSource,
|
|
670
690
|
selectExternalLayerId
|
|
671
691
|
});
|
|
672
|
-
let tempRoot = "";
|
|
673
|
-
let cleanupTempRoot;
|
|
692
|
+
let tempRoot = "";
|
|
693
|
+
let cleanupTempRoot;
|
|
694
|
+
try {
|
|
695
|
+
const normalizedSlug = resolveNonEmptyNormalizedBlockSlug({
|
|
696
|
+
input: blockName,
|
|
697
|
+
label: "Block name",
|
|
698
|
+
usage: "wp-typia add block <name> --template <family>"
|
|
699
|
+
});
|
|
700
|
+
const defaults = getDefaultAnswers(normalizedSlug, resolvedTemplateId);
|
|
701
|
+
({
|
|
702
|
+
path: tempRoot,
|
|
703
|
+
cleanup: cleanupTempRoot
|
|
704
|
+
} = await createManagedTempRoot("wp-typia-add-block-"));
|
|
705
|
+
const tempProjectDir = path3.join(tempRoot, normalizedSlug);
|
|
706
|
+
const blockConfigPath = path3.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
707
|
+
const migrationConfigPath = path3.join(workspace.projectDir, "src", "migrations", "config.ts");
|
|
708
|
+
const blockPhpPrefix = buildWorkspacePhpPrefix(workspace.workspace.phpPrefix, normalizedSlug);
|
|
709
|
+
const migrationConfigSource = await readOptionalFile(migrationConfigPath);
|
|
710
|
+
const migrationConfig = migrationConfigSource === null ? null : parseMigrationConfig(migrationConfigSource);
|
|
711
|
+
const compoundSupportPaths = resolvedTemplateId === "compound" ? COMPOUND_SHARED_SUPPORT_FILES.map((fileName) => path3.join(workspace.projectDir, "src", fileName)) : [];
|
|
712
|
+
const legacyCompoundValidatorPaths = resolvedTemplateId === "compound" ? await collectLegacyCompoundValidatorPaths(workspace.projectDir) : [];
|
|
713
|
+
const result = await (async () => {
|
|
714
|
+
const scaffoldResult = await scaffoldProject({
|
|
715
|
+
alternateRenderTargets,
|
|
716
|
+
answers: {
|
|
717
|
+
...defaults,
|
|
718
|
+
author: workspace.author,
|
|
719
|
+
compoundInnerBlocksPreset: resolvedInnerBlocksPreset,
|
|
720
|
+
namespace: workspace.workspace.namespace,
|
|
721
|
+
phpPrefix: blockPhpPrefix,
|
|
722
|
+
slug: normalizedSlug,
|
|
723
|
+
textDomain: workspace.workspace.textDomain,
|
|
724
|
+
title: defaults.title
|
|
725
|
+
},
|
|
726
|
+
cwd: workspace.projectDir,
|
|
727
|
+
dataStorageMode,
|
|
728
|
+
externalLayerId: resolvedExternalLayerSelection.externalLayerId,
|
|
729
|
+
externalLayerSource: resolvedExternalLayerSelection.externalLayerSource,
|
|
730
|
+
externalLayerSourceLabel: normalizedExternalLayerSource,
|
|
731
|
+
noInstall: true,
|
|
732
|
+
packageManager: workspace.packageManager,
|
|
733
|
+
persistencePolicy,
|
|
734
|
+
projectDir: tempProjectDir,
|
|
735
|
+
templateId: resolvedTemplateId
|
|
736
|
+
});
|
|
737
|
+
await assertAddBlockSupportsExternalLayerOutputs({
|
|
738
|
+
callerCwd: cwd,
|
|
739
|
+
externalLayerId: resolvedExternalLayerSelection.externalLayerId,
|
|
740
|
+
externalLayerSource: resolvedExternalLayerSelection.externalLayerSource,
|
|
741
|
+
templateId: resolvedTemplateId,
|
|
742
|
+
variables: scaffoldResult.variables
|
|
743
|
+
});
|
|
744
|
+
return scaffoldResult;
|
|
745
|
+
})();
|
|
746
|
+
assertBlockTargetsDoNotExist(workspace.projectDir, resolvedTemplateId, result.variables);
|
|
747
|
+
const mutationSnapshot = {
|
|
748
|
+
fileSources: await snapshotWorkspaceFiles([
|
|
749
|
+
blockConfigPath,
|
|
750
|
+
migrationConfigPath,
|
|
751
|
+
...compoundSupportPaths,
|
|
752
|
+
...legacyCompoundValidatorPaths
|
|
753
|
+
]),
|
|
754
|
+
snapshotDirs: migrationConfig === null ? [] : buildMigrationBlocks(resolvedTemplateId, result.variables).map((block) => path3.join(workspace.projectDir, ...migrationConfig.snapshotDir.split("/"), migrationConfig.currentMigrationVersion, block.key)),
|
|
755
|
+
targetPaths: collectWorkspaceBlockPaths(workspace.projectDir, resolvedTemplateId, result.variables)
|
|
756
|
+
};
|
|
757
|
+
try {
|
|
758
|
+
await copyScaffoldedBlockSlice(workspace.projectDir, resolvedTemplateId, tempProjectDir, result.variables, legacyCompoundValidatorPaths);
|
|
759
|
+
await addCollectionImportsForTemplate(workspace.projectDir, resolvedTemplateId, result.variables);
|
|
760
|
+
await appendBlockConfigEntries(workspace.projectDir, buildConfigEntries(resolvedTemplateId, result.variables), resolvedTemplateId === "persistence" || resolvedTemplateId === "compound" && result.variables.compoundPersistenceEnabled === "true");
|
|
761
|
+
await syncWorkspaceAddedBlockArtifacts(workspace.projectDir, resolvedTemplateId, result.variables);
|
|
762
|
+
await updateWorkspaceMigrationConfigIfPresent(workspace.projectDir, buildMigrationBlocks(resolvedTemplateId, result.variables));
|
|
763
|
+
return {
|
|
764
|
+
blockSlugs: collectWorkspaceBlockPaths(workspace.projectDir, resolvedTemplateId, result.variables).map((targetPath) => path3.basename(targetPath)),
|
|
765
|
+
projectDir: workspace.projectDir,
|
|
766
|
+
templateId: resolvedTemplateId,
|
|
767
|
+
warnings: result.warnings
|
|
768
|
+
};
|
|
769
|
+
} catch (error) {
|
|
770
|
+
await rollbackWorkspaceMutation(mutationSnapshot);
|
|
771
|
+
throw error;
|
|
772
|
+
}
|
|
773
|
+
} finally {
|
|
774
|
+
try {
|
|
775
|
+
await resolvedExternalLayerSelection.cleanup?.();
|
|
776
|
+
} finally {
|
|
777
|
+
await cleanupTempRoot?.();
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
// ../wp-typia-project-tools/src/runtime/cli-add-workspace.ts
|
|
782
|
+
import fs6 from "fs";
|
|
783
|
+
import { promises as fsp9 } from "fs";
|
|
784
|
+
import path13 from "path";
|
|
785
|
+
|
|
786
|
+
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-admin-view.ts
|
|
787
|
+
import fs3 from "fs";
|
|
788
|
+
import { promises as fsp3 } from "fs";
|
|
789
|
+
import { createRequire } from "module";
|
|
790
|
+
import path4 from "path";
|
|
791
|
+
var ADMIN_VIEW_REST_SOURCE_KIND = "rest-resource";
|
|
792
|
+
var ADMIN_VIEW_CORE_DATA_SOURCE_KIND = "core-data";
|
|
793
|
+
var ADMIN_VIEW_CORE_DATA_ENTITY_KIND_IDS = ["postType", "taxonomy"];
|
|
794
|
+
var ADMIN_VIEW_CORE_DATA_ENTITY_SEGMENT_PATTERN = /^[A-Za-z][A-Za-z0-9_-]*$/u;
|
|
795
|
+
var ADMIN_VIEW_CORE_DATA_ENTITY_NAME_PATTERN = /^[a-z0-9][a-z0-9_-]*$/u;
|
|
796
|
+
var ADMIN_VIEW_SOURCE_USAGE = "wp-typia add admin-view <name> --source <rest-resource:slug|core-data:kind/name>";
|
|
797
|
+
var ADMIN_VIEWS_SCRIPT = "build/admin-views/index.js";
|
|
798
|
+
var ADMIN_VIEWS_ASSET = "build/admin-views/index.asset.php";
|
|
799
|
+
var ADMIN_VIEWS_STYLE = "build/admin-views/style-index.css";
|
|
800
|
+
var ADMIN_VIEWS_STYLE_RTL = "build/admin-views/style-index-rtl.css";
|
|
801
|
+
var ADMIN_VIEWS_PHP_GLOB = "/inc/admin-views/*.php";
|
|
802
|
+
var ADMIN_VIEW_ALLOW_UNPUBLISHED_DATAVIEWS_ENV = "WP_TYPIA_ALLOW_UNPUBLISHED_DATAVIEWS";
|
|
803
|
+
var ADMIN_VIEW_PUBLIC_INSTALLS_ENABLED = false;
|
|
804
|
+
var require2 = createRequire(import.meta.url);
|
|
805
|
+
function toCamelCase(input) {
|
|
806
|
+
const pascalCase = toPascalCase(input);
|
|
807
|
+
return `${pascalCase.charAt(0).toLowerCase()}${pascalCase.slice(1)}`;
|
|
808
|
+
}
|
|
809
|
+
function normalizeVersionRange(value, fallback) {
|
|
810
|
+
const trimmed = value?.trim();
|
|
811
|
+
if (!trimmed || trimmed.startsWith("workspace:")) {
|
|
812
|
+
return fallback;
|
|
813
|
+
}
|
|
814
|
+
return /^[~^<>=]/u.test(trimmed) ? trimmed : `^${trimmed}`;
|
|
815
|
+
}
|
|
816
|
+
function readPackageManifest(packageJsonPath) {
|
|
817
|
+
try {
|
|
818
|
+
return JSON.parse(fs3.readFileSync(packageJsonPath, "utf8"));
|
|
819
|
+
} catch {
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
function readPackageManifestVersion(packageJsonPath) {
|
|
824
|
+
return readPackageManifest(packageJsonPath)?.version;
|
|
825
|
+
}
|
|
826
|
+
function isAdminViewUnpublishedDataViewsOverrideEnabled() {
|
|
827
|
+
return process.env[ADMIN_VIEW_ALLOW_UNPUBLISHED_DATAVIEWS_ENV]?.trim() === "1";
|
|
828
|
+
}
|
|
829
|
+
function assertAdminViewPackageAvailability() {
|
|
830
|
+
if (isAdminViewUnpublishedDataViewsOverrideEnabled() || ADMIN_VIEW_PUBLIC_INSTALLS_ENABLED) {
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
throw createCliDiagnosticCodeError(CLI_DIAGNOSTIC_CODES.INVALID_ARGUMENT, "`wp-typia add admin-view` is temporarily unavailable because `@wp-typia/dataviews` is not published to npm for public installs yet.");
|
|
834
|
+
}
|
|
835
|
+
function detectJsonIndent(source) {
|
|
836
|
+
const indentMatch = /\n([ \t]+)"/u.exec(source);
|
|
837
|
+
return indentMatch?.[1] ?? 2;
|
|
838
|
+
}
|
|
839
|
+
function resolvePackageVersionRange(packageName, fallback, workspacePackageDirName) {
|
|
840
|
+
if (workspacePackageDirName) {
|
|
841
|
+
const workspaceVersion = readPackageManifestVersion(path4.join(PROJECT_TOOLS_PACKAGE_ROOT, "..", workspacePackageDirName, "package.json"));
|
|
842
|
+
if (workspaceVersion) {
|
|
843
|
+
return normalizeVersionRange(workspaceVersion, fallback);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
try {
|
|
847
|
+
return normalizeVersionRange(readPackageManifestVersion(require2.resolve(`${packageName}/package.json`)), fallback);
|
|
848
|
+
} catch {
|
|
849
|
+
return fallback;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
function getAdminViewRelativeModuleSpecifier(adminViewSlug, workspaceFile) {
|
|
853
|
+
const adminViewDir = `src/admin-views/${adminViewSlug}`;
|
|
854
|
+
const normalizedFile = workspaceFile.replace(/\\/gu, "/");
|
|
855
|
+
const modulePath = normalizedFile.replace(/\.[cm]?[jt]sx?$/u, "");
|
|
856
|
+
const relativeModulePath = path4.posix.relative(adminViewDir, modulePath);
|
|
857
|
+
return relativeModulePath.startsWith(".") ? relativeModulePath : `./${relativeModulePath}`;
|
|
858
|
+
}
|
|
859
|
+
function isAdminViewCoreDataSource(source) {
|
|
860
|
+
return source?.kind === ADMIN_VIEW_CORE_DATA_SOURCE_KIND;
|
|
861
|
+
}
|
|
862
|
+
function isAdminViewRestResourceSource(source) {
|
|
863
|
+
return source?.kind === ADMIN_VIEW_REST_SOURCE_KIND;
|
|
864
|
+
}
|
|
865
|
+
function assertValidCoreDataEntitySegment(label, value) {
|
|
866
|
+
const trimmed = value.trim();
|
|
867
|
+
if (!trimmed) {
|
|
868
|
+
throw new Error(`${label} is required. Use \`${ADMIN_VIEW_SOURCE_USAGE}\`.`);
|
|
869
|
+
}
|
|
870
|
+
if (!ADMIN_VIEW_CORE_DATA_ENTITY_SEGMENT_PATTERN.test(trimmed)) {
|
|
871
|
+
throw new Error(`${label} must start with a letter and contain only letters, numbers, underscores, or hyphens.`);
|
|
872
|
+
}
|
|
873
|
+
return trimmed;
|
|
874
|
+
}
|
|
875
|
+
function assertValidCoreDataEntityName(value) {
|
|
876
|
+
const normalized = value.trim();
|
|
877
|
+
if (!normalized) {
|
|
878
|
+
throw new Error(`Admin view source entity name is required. Use \`${ADMIN_VIEW_SOURCE_USAGE}\`.`);
|
|
879
|
+
}
|
|
880
|
+
if (!ADMIN_VIEW_CORE_DATA_ENTITY_NAME_PATTERN.test(normalized)) {
|
|
881
|
+
throw new Error("Admin view source entity name must start with a lowercase letter or number and contain only lowercase letters, numbers, underscores, or hyphens.");
|
|
882
|
+
}
|
|
883
|
+
return normalized;
|
|
884
|
+
}
|
|
885
|
+
function assertValidCoreDataEntityKind(value) {
|
|
886
|
+
const normalized = assertValidCoreDataEntitySegment("Admin view source entity kind", value);
|
|
887
|
+
if (!ADMIN_VIEW_CORE_DATA_ENTITY_KIND_IDS.includes(normalized)) {
|
|
888
|
+
throw new Error(`Admin view core-data sources currently support only: ${ADMIN_VIEW_CORE_DATA_ENTITY_KIND_IDS.join(", ")}.`);
|
|
889
|
+
}
|
|
890
|
+
return normalized;
|
|
891
|
+
}
|
|
892
|
+
function formatAdminViewSourceLocator(source) {
|
|
893
|
+
if (isAdminViewCoreDataSource(source)) {
|
|
894
|
+
return `${source.kind}:${source.entityKind}/${source.entityName}`;
|
|
895
|
+
}
|
|
896
|
+
return `${source.kind}:${source.slug}`;
|
|
897
|
+
}
|
|
898
|
+
function parseAdminViewSource(source) {
|
|
899
|
+
const trimmed = source?.trim();
|
|
900
|
+
if (!trimmed) {
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
const separatorIndex = trimmed.indexOf(":");
|
|
904
|
+
const kind = separatorIndex === -1 ? trimmed : trimmed.slice(0, separatorIndex);
|
|
905
|
+
const locator = separatorIndex === -1 ? "" : trimmed.slice(separatorIndex + 1);
|
|
906
|
+
if (!locator) {
|
|
907
|
+
throw new Error("Admin view source must use `rest-resource:<slug>` or `core-data:<kind>/<name>`.");
|
|
908
|
+
}
|
|
909
|
+
if (kind === ADMIN_VIEW_REST_SOURCE_KIND) {
|
|
910
|
+
return {
|
|
911
|
+
kind,
|
|
912
|
+
slug: assertValidGeneratedSlug("Admin view source slug", locator, ADMIN_VIEW_SOURCE_USAGE)
|
|
913
|
+
};
|
|
914
|
+
}
|
|
915
|
+
if (kind === ADMIN_VIEW_CORE_DATA_SOURCE_KIND) {
|
|
916
|
+
const [entityKind, entityName, extra] = locator.split("/");
|
|
917
|
+
if (!entityKind || !entityName || extra !== undefined) {
|
|
918
|
+
throw new Error("Admin view core-data sources must use `core-data:<kind>/<name>`, for example `core-data:postType/post`.");
|
|
919
|
+
}
|
|
920
|
+
return {
|
|
921
|
+
entityKind: assertValidCoreDataEntityKind(entityKind),
|
|
922
|
+
entityName: assertValidCoreDataEntityName(entityName),
|
|
923
|
+
kind
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
throw new Error("Admin view source must use `rest-resource:<slug>` or `core-data:<kind>/<name>`.");
|
|
927
|
+
}
|
|
928
|
+
function resolveRestResourceSource(restResources, source) {
|
|
929
|
+
if (!isAdminViewRestResourceSource(source)) {
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
const restResource = restResources.find((entry) => entry.slug === source.slug);
|
|
933
|
+
if (!restResource) {
|
|
934
|
+
throw new Error(`Unknown REST resource source "${source.slug}". Choose one of: ${restResources.map((entry) => entry.slug).join(", ") || "<none>"}.`);
|
|
935
|
+
}
|
|
936
|
+
if (!restResource.methods.includes("list")) {
|
|
937
|
+
throw new Error(`REST resource source "${source.slug}" must include the list method for DataViews pagination.`);
|
|
938
|
+
}
|
|
939
|
+
return restResource;
|
|
940
|
+
}
|
|
941
|
+
function buildAdminViewConfigEntry(adminViewSlug, source) {
|
|
942
|
+
return [
|
|
943
|
+
"\t{",
|
|
944
|
+
` file: ${quoteTsString(`src/admin-views/${adminViewSlug}/index.tsx`)},`,
|
|
945
|
+
` phpFile: ${quoteTsString(`inc/admin-views/${adminViewSlug}.php`)},`,
|
|
946
|
+
` slug: ${quoteTsString(adminViewSlug)},`,
|
|
947
|
+
source ? ` source: ${quoteTsString(formatAdminViewSourceLocator(source))},` : null,
|
|
948
|
+
"\t},"
|
|
949
|
+
].filter((line) => typeof line === "string").join(`
|
|
950
|
+
`);
|
|
951
|
+
}
|
|
952
|
+
function buildAdminViewRegistrySource(adminViewSlugs) {
|
|
953
|
+
const importLines = adminViewSlugs.map((adminViewSlug) => `import './${adminViewSlug}';`).join(`
|
|
954
|
+
`);
|
|
955
|
+
return `${importLines}${importLines ? `
|
|
956
|
+
|
|
957
|
+
` : ""}// wp-typia add admin-view entries
|
|
958
|
+
`;
|
|
959
|
+
}
|
|
960
|
+
function buildAdminViewTypesSource(adminViewSlug, restResource, coreDataSource) {
|
|
961
|
+
const pascalName = toPascalCase(adminViewSlug);
|
|
962
|
+
const coreDataRecordTypeName = `${pascalName}CoreDataRecord`;
|
|
963
|
+
const itemTypeName = `${pascalName}AdminViewItem`;
|
|
964
|
+
const dataSetTypeName = `${pascalName}AdminViewDataSet`;
|
|
965
|
+
if (restResource) {
|
|
966
|
+
const restPascalName = toPascalCase(restResource.slug);
|
|
967
|
+
const restTypesModule = getAdminViewRelativeModuleSpecifier(adminViewSlug, restResource.typesFile);
|
|
968
|
+
return `import type { ${restPascalName}Record } from ${quoteTsString(restTypesModule)};
|
|
969
|
+
|
|
970
|
+
export type ${itemTypeName} = ${restPascalName}Record;
|
|
971
|
+
|
|
972
|
+
export interface ${dataSetTypeName} {
|
|
973
|
+
items: ${itemTypeName}[];
|
|
974
|
+
paginationInfo: {
|
|
975
|
+
totalItems: number;
|
|
976
|
+
totalPages: number;
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
`;
|
|
980
|
+
}
|
|
981
|
+
if (coreDataSource) {
|
|
982
|
+
if (coreDataSource.entityKind === "taxonomy") {
|
|
983
|
+
return `export interface ${coreDataRecordTypeName} {
|
|
984
|
+
count?: number;
|
|
985
|
+
description?: string;
|
|
986
|
+
id: number;
|
|
987
|
+
link?: string;
|
|
988
|
+
meta?: Record<string, unknown>;
|
|
989
|
+
name?: string;
|
|
990
|
+
parent?: number;
|
|
991
|
+
slug?: string;
|
|
992
|
+
taxonomy?: string;
|
|
993
|
+
[key: string]: unknown;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
export interface ${itemTypeName} {
|
|
997
|
+
count: number;
|
|
998
|
+
description: string;
|
|
999
|
+
id: number;
|
|
1000
|
+
link: string;
|
|
1001
|
+
name: string;
|
|
1002
|
+
parent: number;
|
|
1003
|
+
raw: ${coreDataRecordTypeName};
|
|
1004
|
+
slug: string;
|
|
1005
|
+
taxonomy: string;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
export interface ${dataSetTypeName} {
|
|
1009
|
+
items: ${itemTypeName}[];
|
|
1010
|
+
paginationInfo: {
|
|
1011
|
+
totalItems: number;
|
|
1012
|
+
totalPages: number;
|
|
1013
|
+
};
|
|
1014
|
+
}
|
|
1015
|
+
`;
|
|
1016
|
+
}
|
|
1017
|
+
return `export interface ${coreDataRecordTypeName} {
|
|
1018
|
+
id: number;
|
|
1019
|
+
date?: string;
|
|
1020
|
+
modified?: string;
|
|
1021
|
+
name?: string;
|
|
1022
|
+
slug?: string;
|
|
1023
|
+
status?: string;
|
|
1024
|
+
title?: string | {
|
|
1025
|
+
raw?: string;
|
|
1026
|
+
rendered?: string;
|
|
1027
|
+
};
|
|
1028
|
+
[key: string]: unknown;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
export interface ${itemTypeName} {
|
|
1032
|
+
id: number;
|
|
1033
|
+
raw: ${coreDataRecordTypeName};
|
|
1034
|
+
slug: string;
|
|
1035
|
+
status: string;
|
|
1036
|
+
title: string;
|
|
1037
|
+
updatedAt: string;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
export interface ${dataSetTypeName} {
|
|
1041
|
+
items: ${itemTypeName}[];
|
|
1042
|
+
paginationInfo: {
|
|
1043
|
+
totalItems: number;
|
|
1044
|
+
totalPages: number;
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
`;
|
|
1048
|
+
}
|
|
1049
|
+
return `export type ${pascalName}AdminViewStatus = 'draft' | 'published';
|
|
1050
|
+
|
|
1051
|
+
export interface ${itemTypeName} {
|
|
1052
|
+
id: number;
|
|
1053
|
+
owner: string;
|
|
1054
|
+
status: ${pascalName}AdminViewStatus;
|
|
1055
|
+
title: string;
|
|
1056
|
+
updatedAt: string;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
export interface ${dataSetTypeName} {
|
|
1060
|
+
items: ${itemTypeName}[];
|
|
1061
|
+
paginationInfo: {
|
|
1062
|
+
totalItems: number;
|
|
1063
|
+
totalPages: number;
|
|
1064
|
+
};
|
|
1065
|
+
}
|
|
1066
|
+
`;
|
|
1067
|
+
}
|
|
1068
|
+
function buildAdminViewConfigSource(adminViewSlug, textDomain, source, restResource) {
|
|
1069
|
+
const pascalName = toPascalCase(adminViewSlug);
|
|
1070
|
+
const camelName = toCamelCase(adminViewSlug);
|
|
1071
|
+
const itemTypeName = `${pascalName}AdminViewItem`;
|
|
1072
|
+
const dataViewsName = `${camelName}AdminDataViews`;
|
|
1073
|
+
const isCoreDataSource = source?.kind === ADMIN_VIEW_CORE_DATA_SOURCE_KIND;
|
|
1074
|
+
const isTaxonomyCoreDataSource = source?.kind === ADMIN_VIEW_CORE_DATA_SOURCE_KIND && source.entityKind === "taxonomy";
|
|
1075
|
+
const defaultViewFields = restResource ? "['id']" : isTaxonomyCoreDataSource ? "['name', 'slug', 'count']" : isCoreDataSource ? "['title', 'slug', 'status', 'updatedAt']" : "['title', 'status', 'updatedAt']";
|
|
1076
|
+
const searchEnabled = restResource ? "false" : "true";
|
|
1077
|
+
const titleFieldSource = restResource ? "" : isTaxonomyCoreDataSource ? ` titleField: 'name',
|
|
1078
|
+
` : ` titleField: 'title',
|
|
1079
|
+
`;
|
|
1080
|
+
const defaultViewEnhancementsSource = restResource ? "" : isTaxonomyCoreDataSource ? ` titleField: 'name',
|
|
1081
|
+
` : isCoreDataSource ? ` titleField: 'title',
|
|
1082
|
+
` : ` sort: {
|
|
1083
|
+
direction: 'desc',
|
|
1084
|
+
field: 'updatedAt',
|
|
1085
|
+
},
|
|
1086
|
+
titleField: 'title',
|
|
1087
|
+
`;
|
|
1088
|
+
const additionalFieldsSource = restResource ? "\t\t// REST-backed screens start with the guaranteed ID column. Add project-owned fields here once they are declared on the REST record type." : isTaxonomyCoreDataSource ? ` count: {
|
|
1089
|
+
label: __( 'Count', ${quoteTsString(textDomain)} ),
|
|
1090
|
+
schema: { type: 'integer' },
|
|
1091
|
+
},
|
|
1092
|
+
description: {
|
|
1093
|
+
label: __( 'Description', ${quoteTsString(textDomain)} ),
|
|
1094
|
+
schema: { type: 'string' },
|
|
1095
|
+
},
|
|
1096
|
+
link: {
|
|
1097
|
+
label: __( 'Link', ${quoteTsString(textDomain)} ),
|
|
1098
|
+
schema: { format: 'uri', type: 'string' },
|
|
1099
|
+
},
|
|
1100
|
+
name: {
|
|
1101
|
+
enableGlobalSearch: true,
|
|
1102
|
+
label: __( 'Name', ${quoteTsString(textDomain)} ),
|
|
1103
|
+
schema: { type: 'string' },
|
|
1104
|
+
},
|
|
1105
|
+
parent: {
|
|
1106
|
+
label: __( 'Parent', ${quoteTsString(textDomain)} ),
|
|
1107
|
+
schema: { type: 'integer' },
|
|
1108
|
+
},
|
|
1109
|
+
slug: {
|
|
1110
|
+
enableGlobalSearch: true,
|
|
1111
|
+
label: __( 'Slug', ${quoteTsString(textDomain)} ),
|
|
1112
|
+
schema: { type: 'string' },
|
|
1113
|
+
},
|
|
1114
|
+
taxonomy: {
|
|
1115
|
+
label: __( 'Taxonomy', ${quoteTsString(textDomain)} ),
|
|
1116
|
+
schema: { type: 'string' },
|
|
1117
|
+
},` : isCoreDataSource ? ` slug: {
|
|
1118
|
+
enableGlobalSearch: true,
|
|
1119
|
+
label: __( 'Slug', ${quoteTsString(textDomain)} ),
|
|
1120
|
+
schema: { type: 'string' },
|
|
1121
|
+
},
|
|
1122
|
+
status: {
|
|
1123
|
+
label: __( 'Status', ${quoteTsString(textDomain)} ),
|
|
1124
|
+
schema: { type: 'string' },
|
|
1125
|
+
},
|
|
1126
|
+
title: {
|
|
1127
|
+
enableGlobalSearch: true,
|
|
1128
|
+
label: __( 'Name', ${quoteTsString(textDomain)} ),
|
|
1129
|
+
schema: { type: 'string' },
|
|
1130
|
+
},
|
|
1131
|
+
updatedAt: {
|
|
1132
|
+
label: __( 'Updated', ${quoteTsString(textDomain)} ),
|
|
1133
|
+
schema: { format: 'date-time', type: 'string' },
|
|
1134
|
+
type: 'datetime',
|
|
1135
|
+
},` : ` owner: {
|
|
1136
|
+
label: __( 'Owner', ${quoteTsString(textDomain)} ),
|
|
1137
|
+
schema: { type: 'string' },
|
|
1138
|
+
},
|
|
1139
|
+
status: {
|
|
1140
|
+
filterBy: { operators: ['isAny', 'isNone'] },
|
|
1141
|
+
label: __( 'Status', ${quoteTsString(textDomain)} ),
|
|
1142
|
+
schema: {
|
|
1143
|
+
enum: ['draft', 'published'],
|
|
1144
|
+
enumLabels: {
|
|
1145
|
+
draft: __( 'Draft', ${quoteTsString(textDomain)} ),
|
|
1146
|
+
published: __( 'Published', ${quoteTsString(textDomain)} ),
|
|
1147
|
+
},
|
|
1148
|
+
type: 'string',
|
|
1149
|
+
},
|
|
1150
|
+
},
|
|
1151
|
+
title: {
|
|
1152
|
+
enableGlobalSearch: true,
|
|
1153
|
+
enableSorting: true,
|
|
1154
|
+
label: __( 'Title', ${quoteTsString(textDomain)} ),
|
|
1155
|
+
schema: { type: 'string' },
|
|
1156
|
+
},
|
|
1157
|
+
updatedAt: {
|
|
1158
|
+
enableSorting: true,
|
|
1159
|
+
label: __( 'Updated', ${quoteTsString(textDomain)} ),
|
|
1160
|
+
schema: { format: 'date-time', type: 'string' },
|
|
1161
|
+
type: 'datetime',
|
|
1162
|
+
},`;
|
|
1163
|
+
return `import { defineDataViews } from '@wp-typia/dataviews';
|
|
1164
|
+
import { __ } from '@wordpress/i18n';
|
|
1165
|
+
|
|
1166
|
+
import type { ${itemTypeName} } from './types';
|
|
1167
|
+
|
|
1168
|
+
export const ${dataViewsName} = defineDataViews<${itemTypeName}>({
|
|
1169
|
+
idField: 'id',
|
|
1170
|
+
search: ${searchEnabled},
|
|
1171
|
+
searchLabel: __( 'Search records', ${quoteTsString(textDomain)} ),
|
|
1172
|
+
${titleFieldSource}
|
|
1173
|
+
defaultView: {
|
|
1174
|
+
fields: ${defaultViewFields},
|
|
1175
|
+
page: 1,
|
|
1176
|
+
perPage: 10,
|
|
1177
|
+
${defaultViewEnhancementsSource}
|
|
1178
|
+
type: 'table',
|
|
1179
|
+
},
|
|
1180
|
+
fields: {
|
|
1181
|
+
id: {
|
|
1182
|
+
enableHiding: false,
|
|
1183
|
+
label: __( 'ID', ${quoteTsString(textDomain)} ),
|
|
1184
|
+
readOnly: true,
|
|
1185
|
+
schema: { type: 'integer' },
|
|
1186
|
+
},
|
|
1187
|
+
${additionalFieldsSource}
|
|
1188
|
+
},
|
|
1189
|
+
});
|
|
1190
|
+
`;
|
|
1191
|
+
}
|
|
1192
|
+
function buildDefaultAdminViewDataSource(adminViewSlug) {
|
|
1193
|
+
const pascalName = toPascalCase(adminViewSlug);
|
|
1194
|
+
const camelName = toCamelCase(adminViewSlug);
|
|
1195
|
+
const title = toTitleCase(adminViewSlug);
|
|
1196
|
+
const itemTypeName = `${pascalName}AdminViewItem`;
|
|
1197
|
+
const dataSetTypeName = `${pascalName}AdminViewDataSet`;
|
|
1198
|
+
const queryTypeName = `${pascalName}AdminViewQuery`;
|
|
1199
|
+
const dataViewsName = `${camelName}AdminDataViews`;
|
|
1200
|
+
const fetchName = `fetch${pascalName}AdminViewData`;
|
|
1201
|
+
return `import type { DataViewsView } from '@wp-typia/dataviews';
|
|
1202
|
+
|
|
1203
|
+
import { ${dataViewsName} } from './config';
|
|
1204
|
+
import type { ${dataSetTypeName}, ${itemTypeName} } from './types';
|
|
1205
|
+
|
|
1206
|
+
export interface ${queryTypeName} {
|
|
1207
|
+
page?: number;
|
|
1208
|
+
perPage?: number;
|
|
1209
|
+
search?: string;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
const STARTER_ITEMS: ${itemTypeName}[] = [
|
|
1213
|
+
{
|
|
1214
|
+
id: 1,
|
|
1215
|
+
owner: 'Editorial',
|
|
1216
|
+
status: 'published',
|
|
1217
|
+
title: ${quoteTsString(`${title} launch checklist`)},
|
|
1218
|
+
updatedAt: '2026-04-01T10:30:00Z',
|
|
1219
|
+
},
|
|
1220
|
+
{
|
|
1221
|
+
id: 2,
|
|
1222
|
+
owner: 'Design',
|
|
1223
|
+
status: 'draft',
|
|
1224
|
+
title: ${quoteTsString(`${title} content refresh`)},
|
|
1225
|
+
updatedAt: '2026-04-03T14:15:00Z',
|
|
1226
|
+
},
|
|
1227
|
+
{
|
|
1228
|
+
id: 3,
|
|
1229
|
+
owner: 'Operations',
|
|
1230
|
+
status: 'published',
|
|
1231
|
+
title: ${quoteTsString(`${title} support handoff`)},
|
|
1232
|
+
updatedAt: '2026-04-08T08:45:00Z',
|
|
1233
|
+
},
|
|
1234
|
+
];
|
|
1235
|
+
|
|
1236
|
+
function matchesSearch(item: ${itemTypeName}, search: string | undefined): boolean {
|
|
1237
|
+
if (!search) {
|
|
1238
|
+
return true;
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
const needle = search.toLowerCase();
|
|
1242
|
+
return [item.title, item.owner, item.status].some((value) =>
|
|
1243
|
+
value.toLowerCase().includes(needle),
|
|
1244
|
+
);
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
export async function ${fetchName}(
|
|
1248
|
+
view: DataViewsView<${itemTypeName}>,
|
|
1249
|
+
): Promise<${dataSetTypeName}> {
|
|
1250
|
+
const query = ${dataViewsName}.toQueryArgs<${queryTypeName}>(view, {
|
|
1251
|
+
perPageParam: 'perPage',
|
|
1252
|
+
});
|
|
1253
|
+
const requestedPage = query.page ?? 1;
|
|
1254
|
+
const page = requestedPage > 0 ? requestedPage : 1;
|
|
1255
|
+
const requestedPerPage = query.perPage ?? view.perPage ?? 10;
|
|
1256
|
+
const perPage = requestedPerPage > 0 ? requestedPerPage : 10;
|
|
1257
|
+
const filteredItems = STARTER_ITEMS.filter((item) =>
|
|
1258
|
+
matchesSearch(item, query.search),
|
|
1259
|
+
);
|
|
1260
|
+
const offset = (page - 1) * perPage;
|
|
1261
|
+
const items = filteredItems.slice(offset, offset + perPage);
|
|
1262
|
+
|
|
1263
|
+
return {
|
|
1264
|
+
items,
|
|
1265
|
+
paginationInfo: {
|
|
1266
|
+
totalItems: filteredItems.length,
|
|
1267
|
+
totalPages: Math.max(1, Math.ceil(filteredItems.length / perPage)),
|
|
1268
|
+
},
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
`;
|
|
1272
|
+
}
|
|
1273
|
+
function buildRestAdminViewDataSource(adminViewSlug, restResource) {
|
|
1274
|
+
const pascalName = toPascalCase(adminViewSlug);
|
|
1275
|
+
const restPascalName = toPascalCase(restResource.slug);
|
|
1276
|
+
const camelName = toCamelCase(adminViewSlug);
|
|
1277
|
+
const itemTypeName = `${pascalName}AdminViewItem`;
|
|
1278
|
+
const dataSetTypeName = `${pascalName}AdminViewDataSet`;
|
|
1279
|
+
const dataViewsName = `${camelName}AdminDataViews`;
|
|
1280
|
+
const fetchName = `fetch${pascalName}AdminViewData`;
|
|
1281
|
+
const restApiModule = getAdminViewRelativeModuleSpecifier(adminViewSlug, restResource.apiFile);
|
|
1282
|
+
const restTypesModule = getAdminViewRelativeModuleSpecifier(adminViewSlug, restResource.typesFile);
|
|
1283
|
+
return `import type { DataViewsView } from '@wp-typia/dataviews';
|
|
1284
|
+
|
|
1285
|
+
import { listResource } from ${quoteTsString(restApiModule)};
|
|
1286
|
+
import type { ${restPascalName}ListQuery } from ${quoteTsString(restTypesModule)};
|
|
1287
|
+
import { ${dataViewsName} } from './config';
|
|
1288
|
+
import type { ${dataSetTypeName}, ${itemTypeName} } from './types';
|
|
1289
|
+
|
|
1290
|
+
function resolveTotalPages(total: number, perPage: number | undefined): number {
|
|
1291
|
+
const resolvedPerPage = perPage && perPage > 0 ? perPage : 1;
|
|
1292
|
+
return Math.max(1, Math.ceil(total / resolvedPerPage));
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
export async function ${fetchName}(
|
|
1296
|
+
view: DataViewsView<${itemTypeName}>,
|
|
1297
|
+
): Promise<${dataSetTypeName}> {
|
|
1298
|
+
const query = ${dataViewsName}.toQueryArgs<${restPascalName}ListQuery>(view, {
|
|
1299
|
+
perPageParam: 'perPage',
|
|
1300
|
+
searchParam: false,
|
|
1301
|
+
});
|
|
1302
|
+
const result = await listResource({
|
|
1303
|
+
page: query.page,
|
|
1304
|
+
perPage: query.perPage,
|
|
1305
|
+
});
|
|
1306
|
+
if (!result.isValid || !result.data) {
|
|
1307
|
+
throw new Error('Unable to load REST resource records.');
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
const response = result.data;
|
|
1311
|
+
|
|
1312
|
+
return {
|
|
1313
|
+
items: response.items,
|
|
1314
|
+
paginationInfo: {
|
|
1315
|
+
totalItems: response.total,
|
|
1316
|
+
totalPages: resolveTotalPages(response.total, response.perPage ?? query.perPage),
|
|
1317
|
+
},
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
`;
|
|
1321
|
+
}
|
|
1322
|
+
function buildCoreDataAdminViewDataSource(adminViewSlug, coreDataSource) {
|
|
1323
|
+
const pascalName = toPascalCase(adminViewSlug);
|
|
1324
|
+
const camelName = toCamelCase(adminViewSlug);
|
|
1325
|
+
const coreDataRecordTypeName = `${pascalName}CoreDataRecord`;
|
|
1326
|
+
const dataSetTypeName = `${pascalName}AdminViewDataSet`;
|
|
1327
|
+
const itemTypeName = `${pascalName}AdminViewItem`;
|
|
1328
|
+
const queryTypeName = `${pascalName}AdminViewQuery`;
|
|
1329
|
+
const dataViewsName = `${camelName}AdminDataViews`;
|
|
1330
|
+
const useEntityRecordName = `use${pascalName}EntityRecord`;
|
|
1331
|
+
const useEntityRecordsName = `use${pascalName}EntityRecords`;
|
|
1332
|
+
const useAdminViewDataName = `use${pascalName}AdminViewData`;
|
|
1333
|
+
if (coreDataSource.entityKind === "taxonomy") {
|
|
1334
|
+
return `import type { DataViewsView } from '@wp-typia/dataviews';
|
|
1335
|
+
import { useEntityRecord, useEntityRecords } from '@wordpress/core-data';
|
|
1336
|
+
import { useMemo } from '@wordpress/element';
|
|
1337
|
+
|
|
1338
|
+
import { ${dataViewsName} } from './config';
|
|
1339
|
+
import type {
|
|
1340
|
+
${coreDataRecordTypeName},
|
|
1341
|
+
${dataSetTypeName},
|
|
1342
|
+
${itemTypeName},
|
|
1343
|
+
} from './types';
|
|
1344
|
+
|
|
1345
|
+
export interface ${queryTypeName} {
|
|
1346
|
+
page?: number;
|
|
1347
|
+
per_page?: number;
|
|
1348
|
+
search?: string;
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
const CORE_DATA_ENTITY_KIND = ${quoteTsString(coreDataSource.entityKind)};
|
|
1352
|
+
const CORE_DATA_ENTITY_NAME = ${quoteTsString(coreDataSource.entityName)};
|
|
1353
|
+
|
|
1354
|
+
function normalizeCoreDataNumber(value: unknown): number {
|
|
1355
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : 0;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
function normalizeCoreDataString(value: unknown): string {
|
|
1359
|
+
return typeof value === 'string' ? value : '';
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
function normalizeTaxonomyRecord(record: ${coreDataRecordTypeName}): ${itemTypeName} {
|
|
1363
|
+
return {
|
|
1364
|
+
count: normalizeCoreDataNumber(record.count),
|
|
1365
|
+
description: normalizeCoreDataString(record.description),
|
|
1366
|
+
id: record.id,
|
|
1367
|
+
link: normalizeCoreDataString(record.link),
|
|
1368
|
+
name: normalizeCoreDataString(record.name) || normalizeCoreDataString(record.slug),
|
|
1369
|
+
parent: normalizeCoreDataNumber(record.parent),
|
|
1370
|
+
raw: record,
|
|
1371
|
+
slug: normalizeCoreDataString(record.slug),
|
|
1372
|
+
taxonomy: normalizeCoreDataString(record.taxonomy),
|
|
1373
|
+
};
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
export function ${useEntityRecordName}(recordId: number | undefined) {
|
|
1377
|
+
return useEntityRecord<${coreDataRecordTypeName}>(
|
|
1378
|
+
CORE_DATA_ENTITY_KIND,
|
|
1379
|
+
CORE_DATA_ENTITY_NAME,
|
|
1380
|
+
recordId ?? 0,
|
|
1381
|
+
{ enabled: typeof recordId === 'number' },
|
|
1382
|
+
);
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
export function ${useEntityRecordsName}(view: DataViewsView<${itemTypeName}>) {
|
|
1386
|
+
const query = ${dataViewsName}.toQueryArgs<${queryTypeName}>(view, {
|
|
1387
|
+
perPageParam: 'per_page',
|
|
1388
|
+
});
|
|
1389
|
+
|
|
1390
|
+
return useEntityRecords<${coreDataRecordTypeName}>(
|
|
1391
|
+
CORE_DATA_ENTITY_KIND,
|
|
1392
|
+
CORE_DATA_ENTITY_NAME,
|
|
1393
|
+
query,
|
|
1394
|
+
);
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
export function ${useAdminViewDataName}(view: DataViewsView<${itemTypeName}>) {
|
|
1398
|
+
const { hasResolved, isResolving, records, totalItems, totalPages } =
|
|
1399
|
+
${useEntityRecordsName}(view);
|
|
1400
|
+
const items = useMemo(
|
|
1401
|
+
() => (records ?? []).map((record) => normalizeTaxonomyRecord(record)),
|
|
1402
|
+
[records],
|
|
1403
|
+
);
|
|
1404
|
+
const dataSet = useMemo<${dataSetTypeName}>(
|
|
1405
|
+
() => ({
|
|
1406
|
+
items,
|
|
1407
|
+
paginationInfo: {
|
|
1408
|
+
totalItems: totalItems ?? items.length,
|
|
1409
|
+
totalPages: Math.max(1, totalPages ?? 1),
|
|
1410
|
+
},
|
|
1411
|
+
}),
|
|
1412
|
+
[items, totalItems, totalPages],
|
|
1413
|
+
);
|
|
1414
|
+
const error =
|
|
1415
|
+
!isResolving && hasResolved && records === null
|
|
1416
|
+
? 'Unable to load core-data entity records.'
|
|
1417
|
+
: null;
|
|
1418
|
+
|
|
1419
|
+
return {
|
|
1420
|
+
dataSet,
|
|
1421
|
+
error,
|
|
1422
|
+
isLoading: isResolving,
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1425
|
+
`;
|
|
1426
|
+
}
|
|
1427
|
+
return `import type { DataViewsView } from '@wp-typia/dataviews';
|
|
1428
|
+
import { useEntityRecord, useEntityRecords } from '@wordpress/core-data';
|
|
1429
|
+
import { useMemo } from '@wordpress/element';
|
|
1430
|
+
|
|
1431
|
+
import { ${dataViewsName} } from './config';
|
|
1432
|
+
import type {
|
|
1433
|
+
${coreDataRecordTypeName},
|
|
1434
|
+
${dataSetTypeName},
|
|
1435
|
+
${itemTypeName},
|
|
1436
|
+
} from './types';
|
|
1437
|
+
|
|
1438
|
+
export interface ${queryTypeName} {
|
|
1439
|
+
page?: number;
|
|
1440
|
+
per_page?: number;
|
|
1441
|
+
search?: string;
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
const CORE_DATA_ENTITY_KIND = ${quoteTsString(coreDataSource.entityKind)};
|
|
1445
|
+
const CORE_DATA_ENTITY_NAME = ${quoteTsString(coreDataSource.entityName)};
|
|
1446
|
+
|
|
1447
|
+
function normalizeCoreDataString(value: unknown): string {
|
|
1448
|
+
return typeof value === 'string' ? value : '';
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
function normalizeCoreDataTitle(record: ${coreDataRecordTypeName}): string {
|
|
1452
|
+
if (typeof record.title === 'string') {
|
|
1453
|
+
return record.title;
|
|
1454
|
+
}
|
|
1455
|
+
if (record.title && typeof record.title === 'object') {
|
|
1456
|
+
if (typeof record.title.rendered === 'string') {
|
|
1457
|
+
return record.title.rendered;
|
|
1458
|
+
}
|
|
1459
|
+
if (typeof record.title.raw === 'string') {
|
|
1460
|
+
return record.title.raw;
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
return normalizeCoreDataString(record.name) || normalizeCoreDataString(record.slug);
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
function normalizeCoreDataUpdatedAt(record: ${coreDataRecordTypeName}): string {
|
|
1468
|
+
return normalizeCoreDataString(record.modified) || normalizeCoreDataString(record.date);
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
function normalizeCoreDataRecord(record: ${coreDataRecordTypeName}): ${itemTypeName} {
|
|
1472
|
+
return {
|
|
1473
|
+
id: record.id,
|
|
1474
|
+
raw: record,
|
|
1475
|
+
slug: normalizeCoreDataString(record.slug),
|
|
1476
|
+
status: normalizeCoreDataString(record.status),
|
|
1477
|
+
title: normalizeCoreDataTitle(record),
|
|
1478
|
+
updatedAt: normalizeCoreDataUpdatedAt(record),
|
|
1479
|
+
};
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
export function ${useEntityRecordName}(recordId: number | undefined) {
|
|
1483
|
+
return useEntityRecord<${coreDataRecordTypeName}>(
|
|
1484
|
+
CORE_DATA_ENTITY_KIND,
|
|
1485
|
+
CORE_DATA_ENTITY_NAME,
|
|
1486
|
+
recordId ?? 0,
|
|
1487
|
+
{ enabled: typeof recordId === 'number' },
|
|
1488
|
+
);
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
export function ${useEntityRecordsName}(view: DataViewsView<${itemTypeName}>) {
|
|
1492
|
+
const query = ${dataViewsName}.toQueryArgs<${queryTypeName}>(view, {
|
|
1493
|
+
perPageParam: 'per_page',
|
|
1494
|
+
});
|
|
1495
|
+
|
|
1496
|
+
return useEntityRecords<${coreDataRecordTypeName}>(
|
|
1497
|
+
CORE_DATA_ENTITY_KIND,
|
|
1498
|
+
CORE_DATA_ENTITY_NAME,
|
|
1499
|
+
query,
|
|
1500
|
+
);
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
export function ${useAdminViewDataName}(view: DataViewsView<${itemTypeName}>) {
|
|
1504
|
+
const { hasResolved, isResolving, records, totalItems, totalPages } =
|
|
1505
|
+
${useEntityRecordsName}(view);
|
|
1506
|
+
const items = useMemo(
|
|
1507
|
+
() => (records ?? []).map((record) => normalizeCoreDataRecord(record)),
|
|
1508
|
+
[records],
|
|
1509
|
+
);
|
|
1510
|
+
const dataSet = useMemo<${dataSetTypeName}>(
|
|
1511
|
+
() => ({
|
|
1512
|
+
items,
|
|
1513
|
+
paginationInfo: {
|
|
1514
|
+
totalItems: totalItems ?? items.length,
|
|
1515
|
+
totalPages: Math.max(1, totalPages ?? 1),
|
|
1516
|
+
},
|
|
1517
|
+
}),
|
|
1518
|
+
[items, totalItems, totalPages],
|
|
1519
|
+
);
|
|
1520
|
+
const error =
|
|
1521
|
+
!isResolving && hasResolved && records === null
|
|
1522
|
+
? 'Unable to load core-data entity records.'
|
|
1523
|
+
: null;
|
|
1524
|
+
|
|
1525
|
+
return {
|
|
1526
|
+
dataSet,
|
|
1527
|
+
error,
|
|
1528
|
+
isLoading: isResolving,
|
|
1529
|
+
};
|
|
1530
|
+
}
|
|
1531
|
+
`;
|
|
1532
|
+
}
|
|
1533
|
+
function buildAdminViewScreenSource(adminViewSlug, textDomain) {
|
|
1534
|
+
const pascalName = toPascalCase(adminViewSlug);
|
|
1535
|
+
const camelName = toCamelCase(adminViewSlug);
|
|
1536
|
+
const itemTypeName = `${pascalName}AdminViewItem`;
|
|
1537
|
+
const dataSetTypeName = `${pascalName}AdminViewDataSet`;
|
|
1538
|
+
const componentName = `${pascalName}AdminViewScreen`;
|
|
1539
|
+
const dataViewsName = `${camelName}AdminDataViews`;
|
|
1540
|
+
const fetchName = `fetch${pascalName}AdminViewData`;
|
|
1541
|
+
const title = toTitleCase(adminViewSlug);
|
|
1542
|
+
return `import type { DataViewsConfig, DataViewsView } from '@wp-typia/dataviews';
|
|
1543
|
+
import { Button, Notice, Spinner } from '@wordpress/components';
|
|
1544
|
+
import { useEffect, useState } from '@wordpress/element';
|
|
1545
|
+
import { __ } from '@wordpress/i18n';
|
|
1546
|
+
import { DataViews } from '@wordpress/dataviews/wp';
|
|
1547
|
+
|
|
1548
|
+
import { ${dataViewsName} } from './config';
|
|
1549
|
+
import { ${fetchName} } from './data';
|
|
1550
|
+
import type { ${dataSetTypeName}, ${itemTypeName} } from './types';
|
|
1551
|
+
|
|
1552
|
+
const TypedDataViews = DataViews as unknown as <TItem extends object>(
|
|
1553
|
+
props: DataViewsConfig<TItem>,
|
|
1554
|
+
) => ReturnType<typeof DataViews>;
|
|
1555
|
+
|
|
1556
|
+
const EMPTY_DATA_SET: ${dataSetTypeName} = {
|
|
1557
|
+
items: [],
|
|
1558
|
+
paginationInfo: {
|
|
1559
|
+
totalItems: 0,
|
|
1560
|
+
totalPages: 1,
|
|
1561
|
+
},
|
|
1562
|
+
};
|
|
1563
|
+
|
|
1564
|
+
export function ${componentName}() {
|
|
1565
|
+
const [view, setView] = useState<DataViewsView<${itemTypeName}>>(
|
|
1566
|
+
${dataViewsName}.defaultView,
|
|
1567
|
+
);
|
|
1568
|
+
const [dataSet, setDataSet] = useState<${dataSetTypeName}>(EMPTY_DATA_SET);
|
|
1569
|
+
const [error, setError] = useState<string | null>(null);
|
|
1570
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
1571
|
+
const [reloadToken, setReloadToken] = useState(0);
|
|
1572
|
+
|
|
1573
|
+
useEffect(() => {
|
|
1574
|
+
let isCurrentRequest = true;
|
|
1575
|
+
setIsLoading(true);
|
|
1576
|
+
setError(null);
|
|
1577
|
+
|
|
1578
|
+
void ${fetchName}(view)
|
|
1579
|
+
.then((nextDataSet) => {
|
|
1580
|
+
if (isCurrentRequest) {
|
|
1581
|
+
setDataSet(nextDataSet);
|
|
1582
|
+
}
|
|
1583
|
+
})
|
|
1584
|
+
.catch((nextError: unknown) => {
|
|
1585
|
+
if (isCurrentRequest) {
|
|
1586
|
+
setError(
|
|
1587
|
+
nextError instanceof Error
|
|
1588
|
+
? nextError.message
|
|
1589
|
+
: __( 'Unable to load records.', ${quoteTsString(textDomain)} ),
|
|
1590
|
+
);
|
|
1591
|
+
}
|
|
1592
|
+
})
|
|
1593
|
+
.finally(() => {
|
|
1594
|
+
if (isCurrentRequest) {
|
|
1595
|
+
setIsLoading(false);
|
|
1596
|
+
}
|
|
1597
|
+
});
|
|
1598
|
+
|
|
1599
|
+
return () => {
|
|
1600
|
+
isCurrentRequest = false;
|
|
1601
|
+
};
|
|
1602
|
+
}, [reloadToken, view]);
|
|
1603
|
+
|
|
1604
|
+
const config = ${dataViewsName}.createConfig({
|
|
1605
|
+
data: dataSet.items,
|
|
1606
|
+
isLoading,
|
|
1607
|
+
onChangeView: setView,
|
|
1608
|
+
paginationInfo: dataSet.paginationInfo,
|
|
1609
|
+
view,
|
|
1610
|
+
});
|
|
1611
|
+
|
|
1612
|
+
return (
|
|
1613
|
+
<div className="wp-typia-admin-view-screen">
|
|
1614
|
+
<header className="wp-typia-admin-view-screen__header">
|
|
1615
|
+
<div>
|
|
1616
|
+
<p className="wp-typia-admin-view-screen__eyebrow">
|
|
1617
|
+
{ __( 'DataViews admin screen', ${quoteTsString(textDomain)} ) }
|
|
1618
|
+
</p>
|
|
1619
|
+
<h1>{ __( ${quoteTsString(title)}, ${quoteTsString(textDomain)} ) }</h1>
|
|
1620
|
+
<p>
|
|
1621
|
+
{ __( 'Replace the fetcher in data.ts with your project data source when this screen graduates from scaffold to product UI.', ${quoteTsString(textDomain)} ) }
|
|
1622
|
+
</p>
|
|
1623
|
+
</div>
|
|
1624
|
+
<div className="wp-typia-admin-view-screen__actions">
|
|
1625
|
+
{ isLoading ? <Spinner /> : null }
|
|
1626
|
+
<Button
|
|
1627
|
+
isBusy={ isLoading }
|
|
1628
|
+
onClick={ () => setReloadToken((token) => token + 1) }
|
|
1629
|
+
variant="secondary"
|
|
1630
|
+
>
|
|
1631
|
+
{ __( 'Reload', ${quoteTsString(textDomain)} ) }
|
|
1632
|
+
</Button>
|
|
1633
|
+
</div>
|
|
1634
|
+
</header>
|
|
1635
|
+
{ error ? (
|
|
1636
|
+
<Notice isDismissible={ false } status="error">
|
|
1637
|
+
{ error }
|
|
1638
|
+
</Notice>
|
|
1639
|
+
) : null }
|
|
1640
|
+
<TypedDataViews<${itemTypeName}> { ...config } />
|
|
1641
|
+
</div>
|
|
1642
|
+
);
|
|
1643
|
+
}
|
|
1644
|
+
`;
|
|
1645
|
+
}
|
|
1646
|
+
function buildCoreDataAdminViewScreenSource(adminViewSlug, textDomain) {
|
|
1647
|
+
const pascalName = toPascalCase(adminViewSlug);
|
|
1648
|
+
const camelName = toCamelCase(adminViewSlug);
|
|
1649
|
+
const itemTypeName = `${pascalName}AdminViewItem`;
|
|
1650
|
+
const dataSetTypeName = `${pascalName}AdminViewDataSet`;
|
|
1651
|
+
const componentName = `${pascalName}AdminViewScreen`;
|
|
1652
|
+
const dataViewsName = `${camelName}AdminDataViews`;
|
|
1653
|
+
const useAdminViewDataName = `use${pascalName}AdminViewData`;
|
|
1654
|
+
const title = toTitleCase(adminViewSlug);
|
|
1655
|
+
return `import type { DataViewsConfig, DataViewsView } from '@wp-typia/dataviews';
|
|
1656
|
+
import { Notice, Spinner } from '@wordpress/components';
|
|
1657
|
+
import { useState } from '@wordpress/element';
|
|
1658
|
+
import { __ } from '@wordpress/i18n';
|
|
1659
|
+
import { DataViews } from '@wordpress/dataviews/wp';
|
|
1660
|
+
|
|
1661
|
+
import { ${dataViewsName} } from './config';
|
|
1662
|
+
import { ${useAdminViewDataName} } from './data';
|
|
1663
|
+
import type { ${dataSetTypeName}, ${itemTypeName} } from './types';
|
|
1664
|
+
|
|
1665
|
+
const TypedDataViews = DataViews as unknown as <TItem extends object>(
|
|
1666
|
+
props: DataViewsConfig<TItem>,
|
|
1667
|
+
) => ReturnType<typeof DataViews>;
|
|
1668
|
+
|
|
1669
|
+
const EMPTY_DATA_SET: ${dataSetTypeName} = {
|
|
1670
|
+
items: [],
|
|
1671
|
+
paginationInfo: {
|
|
1672
|
+
totalItems: 0,
|
|
1673
|
+
totalPages: 1,
|
|
1674
|
+
},
|
|
1675
|
+
};
|
|
1676
|
+
|
|
1677
|
+
export function ${componentName}() {
|
|
1678
|
+
const [view, setView] = useState<DataViewsView<${itemTypeName}>>(
|
|
1679
|
+
${dataViewsName}.defaultView,
|
|
1680
|
+
);
|
|
1681
|
+
const {
|
|
1682
|
+
dataSet = EMPTY_DATA_SET,
|
|
1683
|
+
error,
|
|
1684
|
+
isLoading,
|
|
1685
|
+
} = ${useAdminViewDataName}(view);
|
|
1686
|
+
const config = ${dataViewsName}.createConfig({
|
|
1687
|
+
data: dataSet.items,
|
|
1688
|
+
isLoading,
|
|
1689
|
+
onChangeView: setView,
|
|
1690
|
+
paginationInfo: dataSet.paginationInfo,
|
|
1691
|
+
view,
|
|
1692
|
+
});
|
|
1693
|
+
|
|
1694
|
+
return (
|
|
1695
|
+
<div className="wp-typia-admin-view-screen">
|
|
1696
|
+
<header className="wp-typia-admin-view-screen__header">
|
|
1697
|
+
<div>
|
|
1698
|
+
<p className="wp-typia-admin-view-screen__eyebrow">
|
|
1699
|
+
{ __( 'DataViews admin screen', ${quoteTsString(textDomain)} ) }
|
|
1700
|
+
</p>
|
|
1701
|
+
<h1>{ __( ${quoteTsString(title)}, ${quoteTsString(textDomain)} ) }</h1>
|
|
1702
|
+
<p>
|
|
1703
|
+
{ __( '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)} ) }
|
|
1704
|
+
</p>
|
|
1705
|
+
</div>
|
|
1706
|
+
<div className="wp-typia-admin-view-screen__actions">
|
|
1707
|
+
{ isLoading ? <Spinner /> : null }
|
|
1708
|
+
</div>
|
|
1709
|
+
</header>
|
|
1710
|
+
{ error ? (
|
|
1711
|
+
<Notice isDismissible={ false } status="error">
|
|
1712
|
+
{ error }
|
|
1713
|
+
</Notice>
|
|
1714
|
+
) : null }
|
|
1715
|
+
<TypedDataViews<${itemTypeName}> { ...config } />
|
|
1716
|
+
</div>
|
|
1717
|
+
);
|
|
1718
|
+
}
|
|
1719
|
+
`;
|
|
1720
|
+
}
|
|
1721
|
+
function buildAdminViewEntrySource(adminViewSlug) {
|
|
1722
|
+
const pascalName = toPascalCase(adminViewSlug);
|
|
1723
|
+
const componentName = `${pascalName}AdminViewScreen`;
|
|
1724
|
+
const rootId = `wp-typia-admin-view-${adminViewSlug}`;
|
|
1725
|
+
return `import { createRoot } from '@wordpress/element';
|
|
1726
|
+
|
|
1727
|
+
import '@wordpress/dataviews/build-style/style.css';
|
|
1728
|
+
import { ${componentName} } from './Screen';
|
|
1729
|
+
import './style.scss';
|
|
1730
|
+
|
|
1731
|
+
const ROOT_ELEMENT_ID = ${quoteTsString(rootId)};
|
|
1732
|
+
|
|
1733
|
+
function mountAdminView() {
|
|
1734
|
+
const rootElement = document.getElementById(ROOT_ELEMENT_ID);
|
|
1735
|
+
if (!rootElement) {
|
|
1736
|
+
return;
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
createRoot(rootElement).render(<${componentName} />);
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
if (document.readyState === 'loading') {
|
|
1743
|
+
document.addEventListener('DOMContentLoaded', mountAdminView);
|
|
1744
|
+
} else {
|
|
1745
|
+
mountAdminView();
|
|
1746
|
+
}
|
|
1747
|
+
`;
|
|
1748
|
+
}
|
|
1749
|
+
function buildAdminViewStyleSource() {
|
|
1750
|
+
return `.wp-typia-admin-view-screen {
|
|
1751
|
+
box-sizing: border-box;
|
|
1752
|
+
max-width: 1180px;
|
|
1753
|
+
padding: 24px 24px 48px 0;
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
.wp-typia-admin-view-screen__header {
|
|
1757
|
+
align-items: flex-start;
|
|
1758
|
+
display: flex;
|
|
1759
|
+
gap: 24px;
|
|
1760
|
+
justify-content: space-between;
|
|
1761
|
+
margin-bottom: 24px;
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
.wp-typia-admin-view-screen__header h1 {
|
|
1765
|
+
font-size: 28px;
|
|
1766
|
+
line-height: 1.2;
|
|
1767
|
+
margin: 0 0 8px;
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
.wp-typia-admin-view-screen__header p {
|
|
1771
|
+
max-width: 680px;
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
.wp-typia-admin-view-screen__eyebrow {
|
|
1775
|
+
color: #3858e9;
|
|
1776
|
+
font-size: 11px;
|
|
1777
|
+
font-weight: 600;
|
|
1778
|
+
letter-spacing: 0.08em;
|
|
1779
|
+
margin: 0 0 8px;
|
|
1780
|
+
text-transform: uppercase;
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
.wp-typia-admin-view-screen__actions {
|
|
1784
|
+
align-items: center;
|
|
1785
|
+
display: flex;
|
|
1786
|
+
gap: 12px;
|
|
1787
|
+
}
|
|
1788
|
+
`;
|
|
1789
|
+
}
|
|
1790
|
+
function buildAdminViewPhpSource(adminViewSlug, workspace) {
|
|
1791
|
+
const workspaceBaseName = workspace.packageName.split("/").pop() ?? workspace.packageName;
|
|
1792
|
+
const phpSlug = adminViewSlug.replace(/-/g, "_");
|
|
1793
|
+
const functionPrefix = `${workspace.workspace.phpPrefix}_${phpSlug}`;
|
|
1794
|
+
const menuSlugFunctionName = `${functionPrefix}_admin_view_menu_slug`;
|
|
1795
|
+
const renderFunctionName = `${functionPrefix}_render_admin_view`;
|
|
1796
|
+
const registerFunctionName = `${functionPrefix}_register_admin_view`;
|
|
1797
|
+
const enqueueFunctionName = `${functionPrefix}_enqueue_admin_view`;
|
|
1798
|
+
const hookGlobalName = `${functionPrefix}_admin_view_hook`;
|
|
1799
|
+
const rootId = `wp-typia-admin-view-${adminViewSlug}`;
|
|
1800
|
+
const title = toTitleCase(adminViewSlug);
|
|
1801
|
+
return `<?php
|
|
1802
|
+
if ( ! defined( 'ABSPATH' ) ) {
|
|
1803
|
+
return;
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
if ( ! function_exists( '${menuSlugFunctionName}' ) ) {
|
|
1807
|
+
function ${menuSlugFunctionName}() : string {
|
|
1808
|
+
return '${workspaceBaseName}-${adminViewSlug}';
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
if ( ! function_exists( '${renderFunctionName}' ) ) {
|
|
1813
|
+
function ${renderFunctionName}() : void {
|
|
1814
|
+
?>
|
|
1815
|
+
<div class="wrap">
|
|
1816
|
+
<div id="${rootId}"></div>
|
|
1817
|
+
</div>
|
|
1818
|
+
<?php
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
if ( ! function_exists( '${registerFunctionName}' ) ) {
|
|
1823
|
+
function ${registerFunctionName}() : void {
|
|
1824
|
+
$GLOBALS['${hookGlobalName}'] = add_submenu_page(
|
|
1825
|
+
'tools.php',
|
|
1826
|
+
__( ${quotePhpString(title)}, ${quotePhpString(workspace.workspace.textDomain)} ),
|
|
1827
|
+
__( ${quotePhpString(title)}, ${quotePhpString(workspace.workspace.textDomain)} ),
|
|
1828
|
+
'edit_posts',
|
|
1829
|
+
${menuSlugFunctionName}(),
|
|
1830
|
+
'${renderFunctionName}'
|
|
1831
|
+
);
|
|
1832
|
+
}
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
if ( ! function_exists( '${enqueueFunctionName}' ) ) {
|
|
1836
|
+
function ${enqueueFunctionName}( string $hook_suffix ) : void {
|
|
1837
|
+
$page_hook = isset( $GLOBALS['${hookGlobalName}'] ) && is_string( $GLOBALS['${hookGlobalName}'] )
|
|
1838
|
+
? $GLOBALS['${hookGlobalName}']
|
|
1839
|
+
: '';
|
|
1840
|
+
|
|
1841
|
+
if ( $page_hook !== $hook_suffix ) {
|
|
1842
|
+
return;
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
$plugin_file = dirname( __DIR__, 2 ) . '/${workspaceBaseName}.php';
|
|
1846
|
+
$script_path = dirname( __DIR__, 2 ) . '/${ADMIN_VIEWS_SCRIPT}';
|
|
1847
|
+
$asset_path = dirname( __DIR__, 2 ) . '/${ADMIN_VIEWS_ASSET}';
|
|
1848
|
+
$style_path = dirname( __DIR__, 2 ) . '/${ADMIN_VIEWS_STYLE}';
|
|
1849
|
+
$style_rtl_path = dirname( __DIR__, 2 ) . '/${ADMIN_VIEWS_STYLE_RTL}';
|
|
1850
|
+
|
|
1851
|
+
if ( ! file_exists( $script_path ) || ! file_exists( $asset_path ) ) {
|
|
1852
|
+
return;
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
$asset = require $asset_path;
|
|
1856
|
+
if ( ! is_array( $asset ) ) {
|
|
1857
|
+
$asset = array();
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
$dependencies = isset( $asset['dependencies'] ) && is_array( $asset['dependencies'] )
|
|
1861
|
+
? $asset['dependencies']
|
|
1862
|
+
: array();
|
|
1863
|
+
|
|
1864
|
+
wp_enqueue_script(
|
|
1865
|
+
'${workspaceBaseName}-${adminViewSlug}-admin-view',
|
|
1866
|
+
plugins_url( '${ADMIN_VIEWS_SCRIPT}', $plugin_file ),
|
|
1867
|
+
$dependencies,
|
|
1868
|
+
isset( $asset['version'] ) ? $asset['version'] : filemtime( $script_path ),
|
|
1869
|
+
true
|
|
1870
|
+
);
|
|
1871
|
+
|
|
1872
|
+
if ( file_exists( $style_path ) ) {
|
|
1873
|
+
wp_enqueue_style(
|
|
1874
|
+
'${workspaceBaseName}-${adminViewSlug}-admin-view',
|
|
1875
|
+
plugins_url( '${ADMIN_VIEWS_STYLE}', $plugin_file ),
|
|
1876
|
+
array( 'wp-components' ),
|
|
1877
|
+
isset( $asset['version'] ) ? $asset['version'] : filemtime( $style_path )
|
|
1878
|
+
);
|
|
1879
|
+
if ( file_exists( $style_rtl_path ) ) {
|
|
1880
|
+
wp_style_add_data( '${workspaceBaseName}-${adminViewSlug}-admin-view', 'rtl', 'replace' );
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
add_action( 'admin_menu', '${registerFunctionName}' );
|
|
1887
|
+
add_action( 'admin_enqueue_scripts', '${enqueueFunctionName}' );
|
|
1888
|
+
`;
|
|
1889
|
+
}
|
|
1890
|
+
async function ensureAdminViewPackageDependencies(workspace, adminViewSource) {
|
|
1891
|
+
const packageJsonPath = path4.join(workspace.projectDir, "package.json");
|
|
1892
|
+
const wpTypiaDataViewsVersion = resolvePackageVersionRange("@wp-typia/dataviews", DEFAULT_WP_TYPIA_DATAVIEWS_VERSION, "wp-typia-dataviews");
|
|
1893
|
+
const wordpressDataViewsVersion = resolvePackageVersionRange("@wordpress/dataviews", DEFAULT_WORDPRESS_DATAVIEWS_VERSION);
|
|
1894
|
+
const wordpressCoreDataVersion = resolvePackageVersionRange("@wordpress/core-data", DEFAULT_WORDPRESS_CORE_DATA_VERSION);
|
|
1895
|
+
const wordpressDataVersion = resolvePackageVersionRange("@wordpress/data", DEFAULT_WORDPRESS_DATA_VERSION);
|
|
1896
|
+
await patchFile(packageJsonPath, (source) => {
|
|
1897
|
+
const packageJson = JSON.parse(source);
|
|
1898
|
+
const coreDataDependencies = isAdminViewCoreDataSource(adminViewSource) ? {
|
|
1899
|
+
"@wordpress/core-data": packageJson.dependencies?.["@wordpress/core-data"] ?? wordpressCoreDataVersion,
|
|
1900
|
+
"@wordpress/data": packageJson.dependencies?.["@wordpress/data"] ?? wordpressDataVersion
|
|
1901
|
+
} : {};
|
|
1902
|
+
const nextDependencies = {
|
|
1903
|
+
...packageJson.dependencies ?? {},
|
|
1904
|
+
"@wordpress/dataviews": packageJson.dependencies?.["@wordpress/dataviews"] ?? wordpressDataViewsVersion,
|
|
1905
|
+
...coreDataDependencies
|
|
1906
|
+
};
|
|
1907
|
+
const nextDevDependencies = {
|
|
1908
|
+
...packageJson.devDependencies ?? {},
|
|
1909
|
+
"@wp-typia/dataviews": packageJson.devDependencies?.["@wp-typia/dataviews"] ?? wpTypiaDataViewsVersion
|
|
1910
|
+
};
|
|
1911
|
+
if (JSON.stringify(nextDependencies) === JSON.stringify(packageJson.dependencies ?? {}) && JSON.stringify(nextDevDependencies) === JSON.stringify(packageJson.devDependencies ?? {})) {
|
|
1912
|
+
return source;
|
|
1913
|
+
}
|
|
1914
|
+
packageJson.dependencies = nextDependencies;
|
|
1915
|
+
packageJson.devDependencies = nextDevDependencies;
|
|
1916
|
+
return `${JSON.stringify(packageJson, null, detectJsonIndent(source))}
|
|
1917
|
+
`;
|
|
1918
|
+
});
|
|
1919
|
+
}
|
|
1920
|
+
async function ensureAdminViewBootstrapAnchors(workspace) {
|
|
1921
|
+
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
1922
|
+
await patchFile(bootstrapPath, (source) => {
|
|
1923
|
+
let nextSource = source;
|
|
1924
|
+
const loadFunctionName = `${workspace.workspace.phpPrefix}_load_admin_views`;
|
|
1925
|
+
const loadHook = `add_action( 'plugins_loaded', '${loadFunctionName}' );`;
|
|
1926
|
+
const loadFunction = `
|
|
1927
|
+
|
|
1928
|
+
function ${loadFunctionName}() {
|
|
1929
|
+
foreach ( glob( __DIR__ . '${ADMIN_VIEWS_PHP_GLOB}' ) ?: array() as $admin_view_module ) {
|
|
1930
|
+
require_once $admin_view_module;
|
|
1931
|
+
}
|
|
1932
|
+
}
|
|
1933
|
+
`;
|
|
1934
|
+
const insertionAnchors = [
|
|
1935
|
+
/add_action\(\s*["']init["']\s*,\s*["'][^"']+_load_textdomain["']\s*\);\s*\n/u,
|
|
1936
|
+
/\?>\s*$/u
|
|
1937
|
+
];
|
|
1938
|
+
const insertPhpSnippet = (snippet) => {
|
|
1939
|
+
for (const anchor of insertionAnchors) {
|
|
1940
|
+
const candidate = nextSource.replace(anchor, (match) => `${snippet}
|
|
1941
|
+
${match}`);
|
|
1942
|
+
if (candidate !== nextSource) {
|
|
1943
|
+
nextSource = candidate;
|
|
1944
|
+
return;
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
nextSource = `${nextSource.trimEnd()}
|
|
1948
|
+
${snippet}
|
|
1949
|
+
`;
|
|
1950
|
+
};
|
|
1951
|
+
const appendPhpSnippet = (snippet) => {
|
|
1952
|
+
const closingTagPattern = /\?>\s*$/u;
|
|
1953
|
+
if (closingTagPattern.test(nextSource)) {
|
|
1954
|
+
nextSource = nextSource.replace(closingTagPattern, `${snippet}
|
|
1955
|
+
?>`);
|
|
1956
|
+
return;
|
|
1957
|
+
}
|
|
1958
|
+
nextSource = `${nextSource.trimEnd()}
|
|
1959
|
+
${snippet}
|
|
1960
|
+
`;
|
|
1961
|
+
};
|
|
1962
|
+
if (!hasPhpFunctionDefinition(nextSource, loadFunctionName)) {
|
|
1963
|
+
insertPhpSnippet(loadFunction);
|
|
1964
|
+
} else {
|
|
1965
|
+
const functionRange = findPhpFunctionRange(nextSource, loadFunctionName);
|
|
1966
|
+
const functionSource = functionRange ? nextSource.slice(functionRange.start, functionRange.end) : "";
|
|
1967
|
+
if (!functionSource.includes(ADMIN_VIEWS_PHP_GLOB)) {
|
|
1968
|
+
const replacedSource = replacePhpFunctionDefinition(nextSource, loadFunctionName, loadFunction);
|
|
1969
|
+
if (!replacedSource) {
|
|
1970
|
+
throw new Error(`Unable to repair ${path4.basename(bootstrapPath)} for ${loadFunctionName}.`);
|
|
1971
|
+
}
|
|
1972
|
+
nextSource = replacedSource;
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
if (!nextSource.includes(loadHook)) {
|
|
1976
|
+
appendPhpSnippet(loadHook);
|
|
1977
|
+
}
|
|
1978
|
+
return nextSource;
|
|
1979
|
+
});
|
|
1980
|
+
}
|
|
1981
|
+
async function ensureAdminViewBuildScriptAnchors(workspace) {
|
|
1982
|
+
const buildScriptPath = path4.join(workspace.projectDir, "scripts", "build-workspace.mjs");
|
|
1983
|
+
await patchFile(buildScriptPath, (source) => {
|
|
1984
|
+
if (/['"]src\/admin-views\/index\.(?:ts|js)['"]/u.test(source)) {
|
|
1985
|
+
return source;
|
|
1986
|
+
}
|
|
1987
|
+
const currentSharedEntriesPattern = /(\r?\n\s*['"]src\/editor-plugins\/index\.js['"])\s*,?/u;
|
|
1988
|
+
let nextSource = source.replace(currentSharedEntriesPattern, `$1,
|
|
1989
|
+
'src/admin-views/index.ts',
|
|
1990
|
+
'src/admin-views/index.js',`);
|
|
1991
|
+
if (nextSource !== source) {
|
|
1992
|
+
return nextSource;
|
|
1993
|
+
}
|
|
1994
|
+
const legacySharedEntriesPattern = /\[\s*['"]src\/bindings\/index\.ts['"]\s*,\s*['"]src\/bindings\/index\.js['"]\s*(?:,\s*)?\]/u;
|
|
1995
|
+
nextSource = source.replace(legacySharedEntriesPattern, `[
|
|
1996
|
+
'src/bindings/index.ts',
|
|
1997
|
+
'src/bindings/index.js',
|
|
1998
|
+
'src/editor-plugins/index.ts',
|
|
1999
|
+
'src/editor-plugins/index.js',
|
|
2000
|
+
'src/admin-views/index.ts',
|
|
2001
|
+
'src/admin-views/index.js',
|
|
2002
|
+
]`);
|
|
2003
|
+
if (nextSource !== source) {
|
|
2004
|
+
return nextSource;
|
|
2005
|
+
}
|
|
2006
|
+
throw new Error(`Unable to update ${path4.relative(workspace.projectDir, buildScriptPath)} for admin view shared entries.`);
|
|
2007
|
+
});
|
|
2008
|
+
}
|
|
2009
|
+
async function ensureAdminViewWebpackAnchors(workspace) {
|
|
2010
|
+
const webpackConfigPath = path4.join(workspace.projectDir, "webpack.config.js");
|
|
2011
|
+
await patchFile(webpackConfigPath, (source) => {
|
|
2012
|
+
if (/['"]admin-views\/index['"]/u.test(source)) {
|
|
2013
|
+
return source;
|
|
2014
|
+
}
|
|
2015
|
+
const editorPluginEntryPattern = /(\n\s*\[\s*['"]editor-plugins\/index['"][\s\S]*?['"]src\/editor-plugins\/index\.js['"][\s\S]*?\]\s*\])\s*,?/u;
|
|
2016
|
+
let nextSource = source.replace(editorPluginEntryPattern, `$1,
|
|
2017
|
+
[
|
|
2018
|
+
'admin-views/index',
|
|
2019
|
+
[ 'src/admin-views/index.ts', 'src/admin-views/index.js' ],
|
|
2020
|
+
],`);
|
|
2021
|
+
if (nextSource !== source) {
|
|
2022
|
+
return nextSource;
|
|
2023
|
+
}
|
|
2024
|
+
const legacySharedEntriesBlockPattern = /for\s*\(\s*const\s+relativePath\s+of\s+\[\s*['"]src\/bindings\/index\.ts['"]\s*,\s*['"]src\/bindings\/index\.js['"]\s*(?:,\s*)?\]\s*\)\s*\{[\s\S]*?entries\.push\(\s*\[\s*['"]bindings\/index['"]\s*,\s*entryPath\s*\]\s*\);\s*break;\s*\}/u;
|
|
2025
|
+
const nextSharedEntriesBlock = ` for ( const [ entryName, candidates ] of [
|
|
2026
|
+
[
|
|
2027
|
+
'bindings/index',
|
|
2028
|
+
[ 'src/bindings/index.ts', 'src/bindings/index.js' ],
|
|
2029
|
+
],
|
|
2030
|
+
[
|
|
2031
|
+
'editor-plugins/index',
|
|
2032
|
+
[ 'src/editor-plugins/index.ts', 'src/editor-plugins/index.js' ],
|
|
2033
|
+
],
|
|
2034
|
+
[
|
|
2035
|
+
'admin-views/index',
|
|
2036
|
+
[ 'src/admin-views/index.ts', 'src/admin-views/index.js' ],
|
|
2037
|
+
],
|
|
2038
|
+
] ) {
|
|
2039
|
+
for ( const relativePath of candidates ) {
|
|
2040
|
+
const entryPath = path.resolve( process.cwd(), relativePath );
|
|
2041
|
+
if ( ! fs.existsSync( entryPath ) ) {
|
|
2042
|
+
continue;
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
entries.push( [ entryName, entryPath ] );
|
|
2046
|
+
break;
|
|
2047
|
+
}
|
|
2048
|
+
}`;
|
|
2049
|
+
nextSource = source.replace(legacySharedEntriesBlockPattern, nextSharedEntriesBlock);
|
|
2050
|
+
if (nextSource === source) {
|
|
2051
|
+
throw new Error(`Unable to update ${path4.relative(workspace.projectDir, webpackConfigPath)} for admin view shared entries.`);
|
|
2052
|
+
}
|
|
2053
|
+
return nextSource;
|
|
2054
|
+
});
|
|
2055
|
+
}
|
|
2056
|
+
function resolveAdminViewRegistryPath(projectDir) {
|
|
2057
|
+
const adminViewsDir = path4.join(projectDir, "src", "admin-views");
|
|
2058
|
+
return [
|
|
2059
|
+
path4.join(adminViewsDir, "index.ts"),
|
|
2060
|
+
path4.join(adminViewsDir, "index.js")
|
|
2061
|
+
].find((candidatePath) => fs3.existsSync(candidatePath)) ?? path4.join(adminViewsDir, "index.ts");
|
|
2062
|
+
}
|
|
2063
|
+
function readAdminViewRegistrySlugs(registryPath) {
|
|
2064
|
+
if (!fs3.existsSync(registryPath)) {
|
|
2065
|
+
return [];
|
|
2066
|
+
}
|
|
2067
|
+
const source = fs3.readFileSync(registryPath, "utf8");
|
|
2068
|
+
return Array.from(source.matchAll(/^\s*import\s+['"]\.\/([^/'"]+)(?:\/index(?:\.[cm]?[jt]sx?)?)?['"];?\s*$/gmu)).map((match) => match[1]);
|
|
2069
|
+
}
|
|
2070
|
+
async function writeAdminViewRegistry(projectDir, adminViewSlug) {
|
|
2071
|
+
const adminViewsDir = path4.join(projectDir, "src", "admin-views");
|
|
2072
|
+
const registryPath = resolveAdminViewRegistryPath(projectDir);
|
|
2073
|
+
await fsp3.mkdir(adminViewsDir, { recursive: true });
|
|
2074
|
+
const existingAdminViewSlugs = readWorkspaceInventory(projectDir).adminViews.map((entry) => entry.slug);
|
|
2075
|
+
const existingRegistrySlugs = readAdminViewRegistrySlugs(registryPath);
|
|
2076
|
+
const nextAdminViewSlugs = Array.from(new Set([...existingAdminViewSlugs, ...existingRegistrySlugs, adminViewSlug])).sort();
|
|
2077
|
+
await fsp3.writeFile(registryPath, buildAdminViewRegistrySource(nextAdminViewSlugs), "utf8");
|
|
2078
|
+
}
|
|
2079
|
+
async function runAddAdminViewCommand({
|
|
2080
|
+
adminViewName,
|
|
2081
|
+
cwd = process.cwd(),
|
|
2082
|
+
source
|
|
2083
|
+
}) {
|
|
2084
|
+
const workspace = resolveWorkspaceProject(cwd);
|
|
2085
|
+
assertAdminViewPackageAvailability();
|
|
2086
|
+
const adminViewSlug = assertValidGeneratedSlug("Admin view name", normalizeBlockSlug(adminViewName), "wp-typia add admin-view <name> [--source <rest-resource:slug|core-data:kind/name>]");
|
|
2087
|
+
const parsedSource = parseAdminViewSource(source);
|
|
2088
|
+
const inventory = readWorkspaceInventory(workspace.projectDir);
|
|
2089
|
+
const restResource = resolveRestResourceSource(inventory.restResources, parsedSource);
|
|
2090
|
+
const coreDataSource = isAdminViewCoreDataSource(parsedSource) ? parsedSource : undefined;
|
|
2091
|
+
assertAdminViewDoesNotExist(workspace.projectDir, adminViewSlug, inventory);
|
|
2092
|
+
const blockConfigPath = path4.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
2093
|
+
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
2094
|
+
const buildScriptPath = path4.join(workspace.projectDir, "scripts", "build-workspace.mjs");
|
|
2095
|
+
const packageJsonPath = path4.join(workspace.projectDir, "package.json");
|
|
2096
|
+
const webpackConfigPath = path4.join(workspace.projectDir, "webpack.config.js");
|
|
2097
|
+
const adminViewsIndexPath = resolveAdminViewRegistryPath(workspace.projectDir);
|
|
2098
|
+
const adminViewDir = path4.join(workspace.projectDir, "src", "admin-views", adminViewSlug);
|
|
2099
|
+
const adminViewPhpPath = path4.join(workspace.projectDir, "inc", "admin-views", `${adminViewSlug}.php`);
|
|
2100
|
+
const mutationSnapshot = {
|
|
2101
|
+
fileSources: await snapshotWorkspaceFiles([
|
|
2102
|
+
adminViewsIndexPath,
|
|
2103
|
+
blockConfigPath,
|
|
2104
|
+
bootstrapPath,
|
|
2105
|
+
buildScriptPath,
|
|
2106
|
+
packageJsonPath,
|
|
2107
|
+
webpackConfigPath
|
|
2108
|
+
]),
|
|
2109
|
+
snapshotDirs: [],
|
|
2110
|
+
targetPaths: [adminViewDir, adminViewPhpPath]
|
|
2111
|
+
};
|
|
674
2112
|
try {
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
2113
|
+
await fsp3.mkdir(adminViewDir, { recursive: true });
|
|
2114
|
+
await fsp3.mkdir(path4.dirname(adminViewPhpPath), { recursive: true });
|
|
2115
|
+
await ensureAdminViewPackageDependencies(workspace, parsedSource);
|
|
2116
|
+
await ensureAdminViewBootstrapAnchors(workspace);
|
|
2117
|
+
await ensureAdminViewBuildScriptAnchors(workspace);
|
|
2118
|
+
await ensureAdminViewWebpackAnchors(workspace);
|
|
2119
|
+
await fsp3.writeFile(path4.join(adminViewDir, "types.ts"), buildAdminViewTypesSource(adminViewSlug, restResource, coreDataSource), "utf8");
|
|
2120
|
+
await fsp3.writeFile(path4.join(adminViewDir, "config.ts"), buildAdminViewConfigSource(adminViewSlug, workspace.workspace.textDomain, parsedSource, restResource), "utf8");
|
|
2121
|
+
await fsp3.writeFile(path4.join(adminViewDir, "data.ts"), coreDataSource ? buildCoreDataAdminViewDataSource(adminViewSlug, coreDataSource) : restResource ? buildRestAdminViewDataSource(adminViewSlug, restResource) : buildDefaultAdminViewDataSource(adminViewSlug), "utf8");
|
|
2122
|
+
await fsp3.writeFile(path4.join(adminViewDir, "Screen.tsx"), coreDataSource ? buildCoreDataAdminViewScreenSource(adminViewSlug, workspace.workspace.textDomain) : buildAdminViewScreenSource(adminViewSlug, workspace.workspace.textDomain), "utf8");
|
|
2123
|
+
await fsp3.writeFile(path4.join(adminViewDir, "index.tsx"), buildAdminViewEntrySource(adminViewSlug), "utf8");
|
|
2124
|
+
await fsp3.writeFile(path4.join(adminViewDir, "style.scss"), buildAdminViewStyleSource(), "utf8");
|
|
2125
|
+
await fsp3.writeFile(adminViewPhpPath, buildAdminViewPhpSource(adminViewSlug, workspace), "utf8");
|
|
2126
|
+
await writeAdminViewRegistry(workspace.projectDir, adminViewSlug);
|
|
2127
|
+
await appendWorkspaceInventoryEntries(workspace.projectDir, {
|
|
2128
|
+
adminViewEntries: [buildAdminViewConfigEntry(adminViewSlug, parsedSource)]
|
|
679
2129
|
});
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
} = await createManagedTempRoot("wp-typia-add-block-"));
|
|
685
|
-
const tempProjectDir = path3.join(tempRoot, normalizedSlug);
|
|
686
|
-
const blockConfigPath = path3.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
687
|
-
const migrationConfigPath = path3.join(workspace.projectDir, "src", "migrations", "config.ts");
|
|
688
|
-
const blockPhpPrefix = buildWorkspacePhpPrefix(workspace.workspace.phpPrefix, normalizedSlug);
|
|
689
|
-
const migrationConfigSource = await readOptionalFile(migrationConfigPath);
|
|
690
|
-
const migrationConfig = migrationConfigSource === null ? null : parseMigrationConfig(migrationConfigSource);
|
|
691
|
-
const compoundSupportPaths = resolvedTemplateId === "compound" ? COMPOUND_SHARED_SUPPORT_FILES.map((fileName) => path3.join(workspace.projectDir, "src", fileName)) : [];
|
|
692
|
-
const legacyCompoundValidatorPaths = resolvedTemplateId === "compound" ? await collectLegacyCompoundValidatorPaths(workspace.projectDir) : [];
|
|
693
|
-
const result = await (async () => {
|
|
694
|
-
const scaffoldResult = await scaffoldProject({
|
|
695
|
-
alternateRenderTargets,
|
|
696
|
-
answers: {
|
|
697
|
-
...defaults,
|
|
698
|
-
author: workspace.author,
|
|
699
|
-
compoundInnerBlocksPreset: resolvedInnerBlocksPreset,
|
|
700
|
-
namespace: workspace.workspace.namespace,
|
|
701
|
-
phpPrefix: blockPhpPrefix,
|
|
702
|
-
slug: normalizedSlug,
|
|
703
|
-
textDomain: workspace.workspace.textDomain,
|
|
704
|
-
title: defaults.title
|
|
705
|
-
},
|
|
706
|
-
cwd: workspace.projectDir,
|
|
707
|
-
dataStorageMode,
|
|
708
|
-
externalLayerId: resolvedExternalLayerSelection.externalLayerId,
|
|
709
|
-
externalLayerSource: resolvedExternalLayerSelection.externalLayerSource,
|
|
710
|
-
externalLayerSourceLabel: normalizedExternalLayerSource,
|
|
711
|
-
noInstall: true,
|
|
712
|
-
packageManager: workspace.packageManager,
|
|
713
|
-
persistencePolicy,
|
|
714
|
-
projectDir: tempProjectDir,
|
|
715
|
-
templateId: resolvedTemplateId
|
|
716
|
-
});
|
|
717
|
-
await assertAddBlockSupportsExternalLayerOutputs({
|
|
718
|
-
callerCwd: cwd,
|
|
719
|
-
externalLayerId: resolvedExternalLayerSelection.externalLayerId,
|
|
720
|
-
externalLayerSource: resolvedExternalLayerSelection.externalLayerSource,
|
|
721
|
-
templateId: resolvedTemplateId,
|
|
722
|
-
variables: scaffoldResult.variables
|
|
723
|
-
});
|
|
724
|
-
return scaffoldResult;
|
|
725
|
-
})();
|
|
726
|
-
assertBlockTargetsDoNotExist(workspace.projectDir, resolvedTemplateId, result.variables);
|
|
727
|
-
const mutationSnapshot = {
|
|
728
|
-
fileSources: await snapshotWorkspaceFiles([
|
|
729
|
-
blockConfigPath,
|
|
730
|
-
migrationConfigPath,
|
|
731
|
-
...compoundSupportPaths,
|
|
732
|
-
...legacyCompoundValidatorPaths
|
|
733
|
-
]),
|
|
734
|
-
snapshotDirs: migrationConfig === null ? [] : buildMigrationBlocks(resolvedTemplateId, result.variables).map((block) => path3.join(workspace.projectDir, ...migrationConfig.snapshotDir.split("/"), migrationConfig.currentMigrationVersion, block.key)),
|
|
735
|
-
targetPaths: collectWorkspaceBlockPaths(workspace.projectDir, resolvedTemplateId, result.variables)
|
|
2130
|
+
return {
|
|
2131
|
+
adminViewSlug,
|
|
2132
|
+
projectDir: workspace.projectDir,
|
|
2133
|
+
source: parsedSource ? formatAdminViewSourceLocator(parsedSource) : undefined
|
|
736
2134
|
};
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
await appendBlockConfigEntries(workspace.projectDir, buildConfigEntries(resolvedTemplateId, result.variables), resolvedTemplateId === "persistence" || resolvedTemplateId === "compound" && result.variables.compoundPersistenceEnabled === "true");
|
|
741
|
-
await syncWorkspaceAddedBlockArtifacts(workspace.projectDir, resolvedTemplateId, result.variables);
|
|
742
|
-
await updateWorkspaceMigrationConfigIfPresent(workspace.projectDir, buildMigrationBlocks(resolvedTemplateId, result.variables));
|
|
743
|
-
return {
|
|
744
|
-
blockSlugs: collectWorkspaceBlockPaths(workspace.projectDir, resolvedTemplateId, result.variables).map((targetPath) => path3.basename(targetPath)),
|
|
745
|
-
projectDir: workspace.projectDir,
|
|
746
|
-
templateId: resolvedTemplateId,
|
|
747
|
-
warnings: result.warnings
|
|
748
|
-
};
|
|
749
|
-
} catch (error) {
|
|
750
|
-
await rollbackWorkspaceMutation(mutationSnapshot);
|
|
751
|
-
throw error;
|
|
752
|
-
}
|
|
753
|
-
} finally {
|
|
754
|
-
try {
|
|
755
|
-
await resolvedExternalLayerSelection.cleanup?.();
|
|
756
|
-
} finally {
|
|
757
|
-
await cleanupTempRoot?.();
|
|
758
|
-
}
|
|
2135
|
+
} catch (error) {
|
|
2136
|
+
await rollbackWorkspaceMutation(mutationSnapshot);
|
|
2137
|
+
throw error;
|
|
759
2138
|
}
|
|
760
2139
|
}
|
|
761
|
-
// ../wp-typia-project-tools/src/runtime/cli-add-workspace.ts
|
|
762
|
-
import fs5 from "fs";
|
|
763
|
-
import { promises as fsp8 } from "fs";
|
|
764
|
-
import path12 from "path";
|
|
765
|
-
|
|
766
2140
|
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-assets.ts
|
|
767
|
-
|
|
768
|
-
import
|
|
769
|
-
import
|
|
2141
|
+
var import_typescript = __toESM(require_typescript(), 1);
|
|
2142
|
+
import fs4 from "fs";
|
|
2143
|
+
import { promises as fsp4 } from "fs";
|
|
2144
|
+
import path5 from "path";
|
|
2145
|
+
import {
|
|
2146
|
+
syncBlockMetadata as syncBlockMetadata2
|
|
2147
|
+
} from "@wp-typia/block-runtime/metadata-core";
|
|
770
2148
|
var PATTERN_BOOTSTRAP_CATEGORY = "register_block_pattern_category";
|
|
771
2149
|
var BINDING_SOURCE_SERVER_GLOB = "/src/bindings/*/server.php";
|
|
772
2150
|
var BINDING_SOURCE_EDITOR_SCRIPT = "build/bindings/index.js";
|
|
@@ -775,58 +2153,7 @@ var EDITOR_PLUGIN_EDITOR_SCRIPT = "build/editor-plugins/index.js";
|
|
|
775
2153
|
var EDITOR_PLUGIN_EDITOR_ASSET = "build/editor-plugins/index.asset.php";
|
|
776
2154
|
var EDITOR_PLUGIN_EDITOR_STYLE = "build/editor-plugins/style-index.css";
|
|
777
2155
|
var EDITOR_PLUGIN_EDITOR_STYLE_RTL = "build/editor-plugins/style-index-rtl.css";
|
|
778
|
-
|
|
779
|
-
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
780
|
-
}
|
|
781
|
-
function quotePhpString(value) {
|
|
782
|
-
return `'${value.replace(/\\/gu, "\\\\").replace(/'/gu, "\\'")}'`;
|
|
783
|
-
}
|
|
784
|
-
function findPhpFunctionRange(source, functionName) {
|
|
785
|
-
const signaturePattern = new RegExp(`function\\s+${escapeRegex(functionName)}\\s*\\(`, "u");
|
|
786
|
-
const signatureMatch = signaturePattern.exec(source);
|
|
787
|
-
if (!signatureMatch || signatureMatch.index === undefined) {
|
|
788
|
-
return null;
|
|
789
|
-
}
|
|
790
|
-
const functionStart = signatureMatch.index;
|
|
791
|
-
const openBraceIndex = source.indexOf("{", functionStart);
|
|
792
|
-
if (openBraceIndex === -1) {
|
|
793
|
-
return null;
|
|
794
|
-
}
|
|
795
|
-
let depth = 0;
|
|
796
|
-
for (let index = openBraceIndex;index < source.length; index += 1) {
|
|
797
|
-
const character = source[index];
|
|
798
|
-
if (character === "{") {
|
|
799
|
-
depth += 1;
|
|
800
|
-
continue;
|
|
801
|
-
}
|
|
802
|
-
if (character !== "}") {
|
|
803
|
-
continue;
|
|
804
|
-
}
|
|
805
|
-
depth -= 1;
|
|
806
|
-
if (depth === 0) {
|
|
807
|
-
let functionEnd = index + 1;
|
|
808
|
-
while (functionEnd < source.length && /[\r\n]/u.test(source[functionEnd] ?? "")) {
|
|
809
|
-
functionEnd += 1;
|
|
810
|
-
}
|
|
811
|
-
return {
|
|
812
|
-
end: functionEnd,
|
|
813
|
-
start: functionStart
|
|
814
|
-
};
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
return null;
|
|
818
|
-
}
|
|
819
|
-
function replacePhpFunctionDefinition(source, functionName, replacement) {
|
|
820
|
-
const functionRange = findPhpFunctionRange(source, functionName);
|
|
821
|
-
if (!functionRange) {
|
|
822
|
-
return null;
|
|
823
|
-
}
|
|
824
|
-
return [
|
|
825
|
-
source.slice(0, functionRange.start),
|
|
826
|
-
replacement,
|
|
827
|
-
source.slice(functionRange.end)
|
|
828
|
-
].join("");
|
|
829
|
-
}
|
|
2156
|
+
var BINDING_ATTRIBUTE_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9_-]*$/u;
|
|
830
2157
|
function buildPatternConfigEntry(patternSlug) {
|
|
831
2158
|
return [
|
|
832
2159
|
"\t{",
|
|
@@ -836,9 +2163,11 @@ function buildPatternConfigEntry(patternSlug) {
|
|
|
836
2163
|
].join(`
|
|
837
2164
|
`);
|
|
838
2165
|
}
|
|
839
|
-
function buildBindingSourceConfigEntry(bindingSourceSlug) {
|
|
2166
|
+
function buildBindingSourceConfigEntry(bindingSourceSlug, target) {
|
|
840
2167
|
return [
|
|
841
2168
|
"\t{",
|
|
2169
|
+
...target ? [` attribute: ${quoteTsString(target.attributeName)},`] : [],
|
|
2170
|
+
...target ? [` block: ${quoteTsString(target.blockSlug)},`] : [],
|
|
842
2171
|
` editorFile: ${quoteTsString(`src/bindings/${bindingSourceSlug}/editor.ts`)},`,
|
|
843
2172
|
` serverFile: ${quoteTsString(`src/bindings/${bindingSourceSlug}/server.php`)},`,
|
|
844
2173
|
` slug: ${quoteTsString(bindingSourceSlug)},`,
|
|
@@ -846,6 +2175,34 @@ function buildBindingSourceConfigEntry(bindingSourceSlug) {
|
|
|
846
2175
|
].join(`
|
|
847
2176
|
`);
|
|
848
2177
|
}
|
|
2178
|
+
function assertValidBindingAttributeName(attributeName) {
|
|
2179
|
+
const trimmed = attributeName.trim();
|
|
2180
|
+
if (!trimmed) {
|
|
2181
|
+
throw new Error("`wp-typia add binding-source` requires --attribute <attribute> to include a value when --block is provided.");
|
|
2182
|
+
}
|
|
2183
|
+
if (!BINDING_ATTRIBUTE_NAME_PATTERN.test(trimmed)) {
|
|
2184
|
+
throw new Error(`Binding attribute "${attributeName}" must start with a letter and use only letters, numbers, underscores, or hyphens.`);
|
|
2185
|
+
}
|
|
2186
|
+
return trimmed;
|
|
2187
|
+
}
|
|
2188
|
+
function resolveBindingTargetBlockSlug(blockName, namespace) {
|
|
2189
|
+
const trimmed = blockName.trim();
|
|
2190
|
+
if (!trimmed) {
|
|
2191
|
+
throw new Error("`wp-typia add binding-source` requires --block <block-slug|namespace/block-slug> to include a value when --attribute is provided.");
|
|
2192
|
+
}
|
|
2193
|
+
const blockNameSegments = trimmed.split("/");
|
|
2194
|
+
if (blockNameSegments.length > 2) {
|
|
2195
|
+
throw new Error(`Binding target block "${trimmed}" must use <block-slug> or <namespace/block-slug> format.`);
|
|
2196
|
+
}
|
|
2197
|
+
if (blockNameSegments.some((segment) => segment.trim() === "")) {
|
|
2198
|
+
throw new Error(`Binding target block "${trimmed}" must use <block-slug> or <namespace/block-slug> format without empty path segments.`);
|
|
2199
|
+
}
|
|
2200
|
+
const [maybeNamespace, maybeSlug] = blockNameSegments.length === 2 ? blockNameSegments : [undefined, blockNameSegments[0]];
|
|
2201
|
+
if (maybeNamespace && maybeNamespace !== namespace) {
|
|
2202
|
+
throw new Error(`Binding target block "${trimmed}" uses namespace "${maybeNamespace}". Expected "${namespace}".`);
|
|
2203
|
+
}
|
|
2204
|
+
return normalizeBlockSlug(maybeSlug ?? "");
|
|
2205
|
+
}
|
|
849
2206
|
function buildEditorPluginConfigEntry(editorPluginSlug, slot) {
|
|
850
2207
|
return [
|
|
851
2208
|
"\t{",
|
|
@@ -856,9 +2213,6 @@ function buildEditorPluginConfigEntry(editorPluginSlug, slot) {
|
|
|
856
2213
|
].join(`
|
|
857
2214
|
`);
|
|
858
2215
|
}
|
|
859
|
-
function toPascalCaseFromSlug(slug) {
|
|
860
|
-
return normalizeBlockSlug(slug).split("-").filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join("");
|
|
861
|
-
}
|
|
862
2216
|
function buildPatternSource(patternSlug, namespace, textDomain) {
|
|
863
2217
|
const patternTitle = toTitleCase(patternSlug);
|
|
864
2218
|
return `<?php
|
|
@@ -877,12 +2231,32 @@ register_block_pattern(
|
|
|
877
2231
|
);
|
|
878
2232
|
`;
|
|
879
2233
|
}
|
|
880
|
-
function buildBindingSourceServerSource(bindingSourceSlug, phpPrefix, namespace, textDomain) {
|
|
2234
|
+
function buildBindingSourceServerSource(bindingSourceSlug, phpPrefix, namespace, textDomain, target) {
|
|
881
2235
|
const bindingSourceTitle = toTitleCase(bindingSourceSlug);
|
|
882
2236
|
const bindingSourcePhpId = bindingSourceSlug.replace(/-/g, "_");
|
|
883
2237
|
const bindingSourceValueFunctionName = `${phpPrefix}_${bindingSourcePhpId}_binding_source_values`;
|
|
884
2238
|
const bindingSourceResolveFunctionName = `${phpPrefix}_${bindingSourcePhpId}_resolve_binding_source_value`;
|
|
2239
|
+
const bindingSourceSupportedAttributesFunctionName = `${phpPrefix}_${bindingSourcePhpId}_supported_binding_attributes`;
|
|
885
2240
|
const starterValue = `${bindingSourceTitle} starter value`;
|
|
2241
|
+
const supportedAttributesSource = target ? `
|
|
2242
|
+
if ( ! function_exists( '${bindingSourceSupportedAttributesFunctionName}' ) ) {
|
|
2243
|
+
function ${bindingSourceSupportedAttributesFunctionName}( array $supported_attributes ) : array {
|
|
2244
|
+
if ( ! in_array( ${quotePhpString(target.attributeName)}, $supported_attributes, true ) ) {
|
|
2245
|
+
$supported_attributes[] = ${quotePhpString(target.attributeName)};
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
return $supported_attributes;
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
` : "";
|
|
2252
|
+
const supportedAttributesHook = target ? `
|
|
2253
|
+
if ( function_exists( '${bindingSourceSupportedAttributesFunctionName}' ) ) {
|
|
2254
|
+
add_filter(
|
|
2255
|
+
${quotePhpString(`block_bindings_supported_attributes_${namespace}/${target.blockSlug}`)},
|
|
2256
|
+
${quotePhpString(bindingSourceSupportedAttributesFunctionName)}
|
|
2257
|
+
);
|
|
2258
|
+
}
|
|
2259
|
+
` : "";
|
|
886
2260
|
return `<?php
|
|
887
2261
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
888
2262
|
return;
|
|
@@ -911,6 +2285,7 @@ if ( ! function_exists( '${bindingSourceResolveFunctionName}' ) ) {
|
|
|
911
2285
|
return is_string( $value ) ? $value : '';
|
|
912
2286
|
}
|
|
913
2287
|
}
|
|
2288
|
+
${supportedAttributesSource}
|
|
914
2289
|
|
|
915
2290
|
register_block_bindings_source(
|
|
916
2291
|
${quotePhpString(`${namespace}/${bindingSourceSlug}`)},
|
|
@@ -919,11 +2294,20 @@ register_block_bindings_source(
|
|
|
919
2294
|
'get_value_callback' => ${quotePhpString(bindingSourceResolveFunctionName)},
|
|
920
2295
|
)
|
|
921
2296
|
);
|
|
922
|
-
`;
|
|
2297
|
+
${supportedAttributesHook}`;
|
|
923
2298
|
}
|
|
924
|
-
function buildBindingSourceEditorSource(bindingSourceSlug, namespace, textDomain) {
|
|
2299
|
+
function buildBindingSourceEditorSource(bindingSourceSlug, namespace, textDomain, target) {
|
|
925
2300
|
const bindingSourceTitle = toTitleCase(bindingSourceSlug);
|
|
926
2301
|
const starterValue = `${bindingSourceTitle} starter value`;
|
|
2302
|
+
const bindingSourceName = `${namespace}/${bindingSourceSlug}`;
|
|
2303
|
+
const targetSource = target ? `
|
|
2304
|
+
export const BINDING_SOURCE_TARGET = {
|
|
2305
|
+
attribute: ${quoteTsString(target.attributeName)},
|
|
2306
|
+
block: ${quoteTsString(`${namespace}/${target.blockSlug}`)},
|
|
2307
|
+
field: ${quoteTsString(bindingSourceSlug)},
|
|
2308
|
+
source: ${quoteTsString(bindingSourceName)},
|
|
2309
|
+
} as const;
|
|
2310
|
+
` : "";
|
|
927
2311
|
return `import { registerBlockBindingsSource } from '@wordpress/blocks';
|
|
928
2312
|
import { __ } from '@wordpress/i18n';
|
|
929
2313
|
|
|
@@ -936,13 +2320,14 @@ interface BindingSourceRegistration {
|
|
|
936
2320
|
const BINDING_SOURCE_VALUES: Record<string, string> = {
|
|
937
2321
|
${quoteTsString(bindingSourceSlug)}: ${quoteTsString(starterValue)},
|
|
938
2322
|
};
|
|
2323
|
+
${targetSource}
|
|
939
2324
|
|
|
940
2325
|
function resolveBindingSourceValue( field: string ): string {
|
|
941
2326
|
return BINDING_SOURCE_VALUES[ field ] ?? '';
|
|
942
2327
|
}
|
|
943
2328
|
|
|
944
2329
|
registerBlockBindingsSource( {
|
|
945
|
-
name: ${quoteTsString(
|
|
2330
|
+
name: ${quoteTsString(bindingSourceName)},
|
|
946
2331
|
label: __( ${quoteTsString(bindingSourceTitle)}, ${quoteTsString(textDomain)} ),
|
|
947
2332
|
getFieldsList() {
|
|
948
2333
|
return [
|
|
@@ -971,8 +2356,98 @@ registerBlockBindingsSource( {
|
|
|
971
2356
|
} );
|
|
972
2357
|
`;
|
|
973
2358
|
}
|
|
2359
|
+
function resolveBindingTarget(options, namespace) {
|
|
2360
|
+
const hasBlock = options.blockName !== undefined && options.blockName.trim().length > 0;
|
|
2361
|
+
const hasAttribute = options.attributeName !== undefined && options.attributeName.trim().length > 0;
|
|
2362
|
+
if (!hasBlock && !hasAttribute) {
|
|
2363
|
+
return;
|
|
2364
|
+
}
|
|
2365
|
+
if (!hasBlock || !hasAttribute) {
|
|
2366
|
+
throw new Error("`wp-typia add binding-source` requires --block and --attribute to be provided together.");
|
|
2367
|
+
}
|
|
2368
|
+
return {
|
|
2369
|
+
attributeName: assertValidBindingAttributeName(options.attributeName ?? ""),
|
|
2370
|
+
blockSlug: resolveBindingTargetBlockSlug(options.blockName ?? "", namespace)
|
|
2371
|
+
};
|
|
2372
|
+
}
|
|
2373
|
+
function formatBindingAttributeTypeMember(attributeName) {
|
|
2374
|
+
const propertyName = /^[A-Za-z_$][\w$]*$/u.test(attributeName) ? attributeName : JSON.stringify(attributeName);
|
|
2375
|
+
return [
|
|
2376
|
+
"\t/**",
|
|
2377
|
+
"\t * Starter string attribute declared for WordPress Block Bindings.",
|
|
2378
|
+
"\t */",
|
|
2379
|
+
` ${propertyName}?: string;`
|
|
2380
|
+
].join(`
|
|
2381
|
+
`);
|
|
2382
|
+
}
|
|
2383
|
+
function getInterfaceDeclaration(source, interfaceName) {
|
|
2384
|
+
const sourceFile = import_typescript.default.createSourceFile("types.ts", source, import_typescript.default.ScriptTarget.Latest, true, import_typescript.default.ScriptKind.TS);
|
|
2385
|
+
let declaration;
|
|
2386
|
+
const visit = (node) => {
|
|
2387
|
+
if (import_typescript.default.isInterfaceDeclaration(node) && node.name.text === interfaceName) {
|
|
2388
|
+
declaration = node;
|
|
2389
|
+
return true;
|
|
2390
|
+
}
|
|
2391
|
+
return import_typescript.default.forEachChild(node, (child) => visit(child) ? true : undefined) ?? false;
|
|
2392
|
+
};
|
|
2393
|
+
visit(sourceFile);
|
|
2394
|
+
return declaration ? { declaration, sourceFile } : undefined;
|
|
2395
|
+
}
|
|
2396
|
+
function getPropertyNameText(name) {
|
|
2397
|
+
if (import_typescript.default.isIdentifier(name) || import_typescript.default.isStringLiteral(name) || import_typescript.default.isNumericLiteral(name)) {
|
|
2398
|
+
return name.text;
|
|
2399
|
+
}
|
|
2400
|
+
return;
|
|
2401
|
+
}
|
|
2402
|
+
function interfaceHasAttributeMember(declaration, attributeName) {
|
|
2403
|
+
return declaration.members.some((member) => import_typescript.default.isPropertySignature(member) && member.name !== undefined && getPropertyNameText(member.name) === attributeName);
|
|
2404
|
+
}
|
|
2405
|
+
function insertBindingAttributeTypeMember(source, declaration, attributeName) {
|
|
2406
|
+
let closeBracePosition = declaration.end - 1;
|
|
2407
|
+
while (closeBracePosition > declaration.pos && source[closeBracePosition] !== "}") {
|
|
2408
|
+
closeBracePosition -= 1;
|
|
2409
|
+
}
|
|
2410
|
+
if (source[closeBracePosition] !== "}") {
|
|
2411
|
+
throw new Error("Unable to locate the target interface closing brace.");
|
|
2412
|
+
}
|
|
2413
|
+
const lineEnding = source.includes(`\r
|
|
2414
|
+
`) ? `\r
|
|
2415
|
+
` : `
|
|
2416
|
+
`;
|
|
2417
|
+
const beforeCloseBrace = source.slice(0, closeBracePosition);
|
|
2418
|
+
const afterCloseBrace = source.slice(closeBracePosition);
|
|
2419
|
+
const memberSource = formatBindingAttributeTypeMember(attributeName).split(`
|
|
2420
|
+
`).join(lineEnding);
|
|
2421
|
+
const prefix = beforeCloseBrace.endsWith(lineEnding) ? "" : lineEnding;
|
|
2422
|
+
return `${beforeCloseBrace}${prefix}${memberSource}${lineEnding}${afterCloseBrace}`;
|
|
2423
|
+
}
|
|
2424
|
+
async function ensureBindingTargetBlockAttributeType(projectDir, block, target) {
|
|
2425
|
+
if (!block.attributeTypeName) {
|
|
2426
|
+
throw new Error(`Workspace block "${block.slug}" must include attributeTypeName in scripts/block-config.ts before it can receive binding-source targets.`);
|
|
2427
|
+
}
|
|
2428
|
+
const typesPath = path5.join(projectDir, block.typesFile);
|
|
2429
|
+
const source = await fsp4.readFile(typesPath, "utf8");
|
|
2430
|
+
const targetInterface = getInterfaceDeclaration(source, block.attributeTypeName);
|
|
2431
|
+
if (!targetInterface) {
|
|
2432
|
+
throw new Error(`Unable to locate interface ${block.attributeTypeName} in ${block.typesFile}.`);
|
|
2433
|
+
}
|
|
2434
|
+
let nextSource = source;
|
|
2435
|
+
if (!interfaceHasAttributeMember(targetInterface.declaration, target.attributeName)) {
|
|
2436
|
+
nextSource = insertBindingAttributeTypeMember(source, targetInterface.declaration, target.attributeName);
|
|
2437
|
+
await fsp4.writeFile(typesPath, nextSource, "utf8");
|
|
2438
|
+
}
|
|
2439
|
+
await syncBlockMetadata2({
|
|
2440
|
+
blockJsonFile: path5.join("src", "blocks", block.slug, "block.json"),
|
|
2441
|
+
jsonSchemaFile: path5.join("src", "blocks", block.slug, "typia.schema.json"),
|
|
2442
|
+
manifestFile: path5.join("src", "blocks", block.slug, "typia.manifest.json"),
|
|
2443
|
+
openApiFile: path5.join("src", "blocks", block.slug, "typia.openapi.json"),
|
|
2444
|
+
projectRoot: projectDir,
|
|
2445
|
+
sourceTypeName: block.attributeTypeName,
|
|
2446
|
+
typesFile: block.typesFile
|
|
2447
|
+
});
|
|
2448
|
+
}
|
|
974
2449
|
function buildEditorPluginTypesSource(editorPluginSlug) {
|
|
975
|
-
const typeName = `${
|
|
2450
|
+
const typeName = `${toPascalCase(editorPluginSlug)}EditorPluginModel`;
|
|
976
2451
|
return `export interface ${typeName} {
|
|
977
2452
|
primaryActionLabel: string;
|
|
978
2453
|
summary: string;
|
|
@@ -980,22 +2455,22 @@ function buildEditorPluginTypesSource(editorPluginSlug) {
|
|
|
980
2455
|
`;
|
|
981
2456
|
}
|
|
982
2457
|
function buildEditorPluginDataSource(editorPluginSlug, slot) {
|
|
983
|
-
const typeName = `${
|
|
2458
|
+
const typeName = `${toPascalCase(editorPluginSlug)}EditorPluginModel`;
|
|
984
2459
|
const pluginTitle = toTitleCase(editorPluginSlug);
|
|
985
|
-
const modelFactoryName = `get${
|
|
986
|
-
const enabledFactoryName = `is${
|
|
2460
|
+
const modelFactoryName = `get${toPascalCase(editorPluginSlug)}EditorPluginModel`;
|
|
2461
|
+
const enabledFactoryName = `is${toPascalCase(editorPluginSlug)}Enabled`;
|
|
987
2462
|
return `import type { ${typeName} } from './types';
|
|
988
2463
|
|
|
989
2464
|
export const EDITOR_PLUGIN_SLOT = ${quoteTsString(slot)} as const;
|
|
990
2465
|
export const REQUIRED_CAPABILITY = 'edit_posts' as const;
|
|
991
2466
|
|
|
992
|
-
const
|
|
2467
|
+
const DEFAULT_EDITOR_PLUGIN_MODEL: ${typeName} = {
|
|
993
2468
|
primaryActionLabel: ${quoteTsString(`Review ${pluginTitle}`)},
|
|
994
2469
|
summary: ${quoteTsString(`Replace this summary with your ${pluginTitle} workflow state.`)},
|
|
995
2470
|
};
|
|
996
2471
|
|
|
997
2472
|
export function ${modelFactoryName}(): ${typeName} {
|
|
998
|
-
return
|
|
2473
|
+
return DEFAULT_EDITOR_PLUGIN_MODEL;
|
|
999
2474
|
}
|
|
1000
2475
|
|
|
1001
2476
|
export function ${enabledFactoryName}(): boolean {
|
|
@@ -1003,11 +2478,52 @@ export function ${enabledFactoryName}(): boolean {
|
|
|
1003
2478
|
}
|
|
1004
2479
|
`;
|
|
1005
2480
|
}
|
|
1006
|
-
function
|
|
1007
|
-
const pascalName =
|
|
1008
|
-
const modelFactoryName = `get${pascalName}
|
|
2481
|
+
function buildEditorPluginSurfaceSource(editorPluginSlug, slot, textDomain) {
|
|
2482
|
+
const pascalName = toPascalCase(editorPluginSlug);
|
|
2483
|
+
const modelFactoryName = `get${pascalName}EditorPluginModel`;
|
|
1009
2484
|
const enabledFactoryName = `is${pascalName}Enabled`;
|
|
1010
|
-
const componentName = `${pascalName}
|
|
2485
|
+
const componentName = `${pascalName}Surface`;
|
|
2486
|
+
if (slot === "document-setting-panel") {
|
|
2487
|
+
return `import { Button } from '@wordpress/components';
|
|
2488
|
+
import { PluginDocumentSettingPanel } from '@wordpress/editor';
|
|
2489
|
+
import { __ } from '@wordpress/i18n';
|
|
2490
|
+
|
|
2491
|
+
import { ${modelFactoryName}, ${enabledFactoryName} } from './data';
|
|
2492
|
+
import './style.scss';
|
|
2493
|
+
|
|
2494
|
+
export interface ${componentName}Props {
|
|
2495
|
+
surfaceName: string;
|
|
2496
|
+
title: string;
|
|
2497
|
+
}
|
|
2498
|
+
|
|
2499
|
+
export function ${componentName}( {
|
|
2500
|
+
surfaceName,
|
|
2501
|
+
title,
|
|
2502
|
+
}: ${componentName}Props ) {
|
|
2503
|
+
if ( ! ${enabledFactoryName}() ) {
|
|
2504
|
+
return null;
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2507
|
+
const editorPluginModel = ${modelFactoryName}();
|
|
2508
|
+
|
|
2509
|
+
return (
|
|
2510
|
+
<PluginDocumentSettingPanel
|
|
2511
|
+
className="wp-typia-editor-plugin-shell"
|
|
2512
|
+
name={ surfaceName }
|
|
2513
|
+
title={ title }
|
|
2514
|
+
>
|
|
2515
|
+
<p>{ editorPluginModel.summary }</p>
|
|
2516
|
+
<Button variant="secondary">
|
|
2517
|
+
{ editorPluginModel.primaryActionLabel }
|
|
2518
|
+
</Button>
|
|
2519
|
+
<p className="wp-typia-editor-plugin-shell__hint">
|
|
2520
|
+
{ __( 'Use data.ts to add post type, capability, or editor context guards before showing this panel.', ${quoteTsString(textDomain)} ) }
|
|
2521
|
+
</p>
|
|
2522
|
+
</PluginDocumentSettingPanel>
|
|
2523
|
+
);
|
|
2524
|
+
}
|
|
2525
|
+
`;
|
|
2526
|
+
}
|
|
1011
2527
|
return `import { Button, PanelBody } from '@wordpress/components';
|
|
1012
2528
|
import { PluginSidebar, PluginSidebarMoreMenuItem } from '@wordpress/editor';
|
|
1013
2529
|
import { __ } from '@wordpress/i18n';
|
|
@@ -1016,34 +2532,34 @@ import { ${modelFactoryName}, ${enabledFactoryName} } from './data';
|
|
|
1016
2532
|
import './style.scss';
|
|
1017
2533
|
|
|
1018
2534
|
export interface ${componentName}Props {
|
|
1019
|
-
|
|
2535
|
+
surfaceName: string;
|
|
1020
2536
|
title: string;
|
|
1021
2537
|
}
|
|
1022
2538
|
|
|
1023
2539
|
export function ${componentName}( {
|
|
1024
|
-
|
|
2540
|
+
surfaceName,
|
|
1025
2541
|
title,
|
|
1026
2542
|
}: ${componentName}Props ) {
|
|
1027
2543
|
if ( ! ${enabledFactoryName}() ) {
|
|
1028
2544
|
return null;
|
|
1029
2545
|
}
|
|
1030
2546
|
|
|
1031
|
-
const
|
|
2547
|
+
const editorPluginModel = ${modelFactoryName}();
|
|
1032
2548
|
|
|
1033
2549
|
return (
|
|
1034
2550
|
<>
|
|
1035
|
-
<PluginSidebarMoreMenuItem target={
|
|
2551
|
+
<PluginSidebarMoreMenuItem target={ surfaceName }>
|
|
1036
2552
|
{ title }
|
|
1037
2553
|
</PluginSidebarMoreMenuItem>
|
|
1038
|
-
<PluginSidebar name={
|
|
2554
|
+
<PluginSidebar name={ surfaceName } title={ title }>
|
|
1039
2555
|
<div className="wp-typia-editor-plugin-shell">
|
|
1040
2556
|
<PanelBody
|
|
1041
2557
|
initialOpen
|
|
1042
2558
|
title={ __( 'Document workflow', ${quoteTsString(textDomain)} ) }
|
|
1043
2559
|
>
|
|
1044
|
-
<p>{
|
|
2560
|
+
<p>{ editorPluginModel.summary }</p>
|
|
1045
2561
|
<Button variant="secondary">
|
|
1046
|
-
{
|
|
2562
|
+
{ editorPluginModel.primaryActionLabel }
|
|
1047
2563
|
</Button>
|
|
1048
2564
|
</PanelBody>
|
|
1049
2565
|
</div>
|
|
@@ -1054,24 +2570,26 @@ export function ${componentName}( {
|
|
|
1054
2570
|
`;
|
|
1055
2571
|
}
|
|
1056
2572
|
function buildEditorPluginEntrySource(editorPluginSlug, namespace, textDomain) {
|
|
1057
|
-
const pascalName =
|
|
1058
|
-
const componentName = `${pascalName}
|
|
2573
|
+
const pascalName = toPascalCase(editorPluginSlug);
|
|
2574
|
+
const componentName = `${pascalName}Surface`;
|
|
1059
2575
|
const pluginName = `${namespace}-${editorPluginSlug}`;
|
|
2576
|
+
const surfaceName = `${pluginName}-surface`;
|
|
1060
2577
|
const pluginTitle = toTitleCase(editorPluginSlug);
|
|
1061
2578
|
return `import { registerPlugin } from '@wordpress/plugins';
|
|
1062
2579
|
import { __ } from '@wordpress/i18n';
|
|
1063
2580
|
|
|
1064
2581
|
import { REQUIRED_CAPABILITY } from './data';
|
|
1065
|
-
import { ${componentName} } from './
|
|
2582
|
+
import { ${componentName} } from './Surface';
|
|
1066
2583
|
|
|
1067
2584
|
const EDITOR_PLUGIN_NAME = ${quoteTsString(pluginName)};
|
|
2585
|
+
const EDITOR_PLUGIN_SURFACE_NAME = ${quoteTsString(surfaceName)};
|
|
1068
2586
|
const EDITOR_PLUGIN_TITLE = __( ${quoteTsString(pluginTitle)}, ${quoteTsString(textDomain)} );
|
|
1069
2587
|
|
|
1070
2588
|
registerPlugin( EDITOR_PLUGIN_NAME, {
|
|
1071
2589
|
icon: 'admin-generic',
|
|
1072
2590
|
render: () => (
|
|
1073
2591
|
<${componentName}
|
|
1074
|
-
|
|
2592
|
+
surfaceName={ EDITOR_PLUGIN_SURFACE_NAME }
|
|
1075
2593
|
title={ EDITOR_PLUGIN_TITLE }
|
|
1076
2594
|
/>
|
|
1077
2595
|
),
|
|
@@ -1088,6 +2606,11 @@ function buildEditorPluginStyleSource() {
|
|
|
1088
2606
|
.wp-typia-editor-plugin-shell p {
|
|
1089
2607
|
margin: 0 0 12px;
|
|
1090
2608
|
}
|
|
2609
|
+
|
|
2610
|
+
.wp-typia-editor-plugin-shell__hint {
|
|
2611
|
+
color: #757575;
|
|
2612
|
+
font-size: 12px;
|
|
2613
|
+
}
|
|
1091
2614
|
`;
|
|
1092
2615
|
}
|
|
1093
2616
|
function buildBindingSourceIndexSource(bindingSourceSlugs) {
|
|
@@ -1156,7 +2679,7 @@ ${patternFunctions}
|
|
|
1156
2679
|
}
|
|
1157
2680
|
}
|
|
1158
2681
|
if (!nextSource.includes(patternCategoryFunctionName) || !nextSource.includes(patternRegistrationFunctionName)) {
|
|
1159
|
-
throw new Error(`Unable to inject pattern bootstrap functions into ${
|
|
2682
|
+
throw new Error(`Unable to inject pattern bootstrap functions into ${path5.basename(bootstrapPath)}.`);
|
|
1160
2683
|
}
|
|
1161
2684
|
if (!nextSource.includes(patternCategoryHook)) {
|
|
1162
2685
|
nextSource = `${nextSource.trimEnd()}
|
|
@@ -1216,7 +2739,6 @@ function ${bindingEditorEnqueueFunctionName}() {
|
|
|
1216
2739
|
/add_action\(\s*["']init["']\s*,\s*["'][^"']+_load_textdomain["']\s*\);\s*\n/u,
|
|
1217
2740
|
/\?>\s*$/u
|
|
1218
2741
|
];
|
|
1219
|
-
const hasPhpFunctionDefinition = (functionName) => new RegExp(`function\\s+${escapeRegex(functionName)}\\s*\\(`, "u").test(nextSource);
|
|
1220
2742
|
const insertPhpSnippet = (snippet) => {
|
|
1221
2743
|
for (const anchor of insertionAnchors) {
|
|
1222
2744
|
const candidate = nextSource.replace(anchor, (match) => `${snippet}
|
|
@@ -1241,10 +2763,10 @@ ${snippet}
|
|
|
1241
2763
|
${snippet}
|
|
1242
2764
|
`;
|
|
1243
2765
|
};
|
|
1244
|
-
if (!hasPhpFunctionDefinition(bindingRegistrationFunctionName)) {
|
|
2766
|
+
if (!hasPhpFunctionDefinition(nextSource, bindingRegistrationFunctionName)) {
|
|
1245
2767
|
insertPhpSnippet(bindingRegistrationFunction);
|
|
1246
2768
|
}
|
|
1247
|
-
if (!hasPhpFunctionDefinition(bindingEditorEnqueueFunctionName)) {
|
|
2769
|
+
if (!hasPhpFunctionDefinition(nextSource, bindingEditorEnqueueFunctionName)) {
|
|
1248
2770
|
insertPhpSnippet(bindingEditorEnqueueFunction);
|
|
1249
2771
|
}
|
|
1250
2772
|
if (!nextSource.includes(bindingRegistrationHook)) {
|
|
@@ -1305,7 +2827,6 @@ function ${enqueueFunctionName}() {
|
|
|
1305
2827
|
/add_action\(\s*["']init["']\s*,\s*["'][^"']+_load_textdomain["']\s*\);\s*\n/u,
|
|
1306
2828
|
/\?>\s*$/u
|
|
1307
2829
|
];
|
|
1308
|
-
const hasPhpFunctionDefinition = (functionName) => new RegExp(`function\\s+${escapeRegex(functionName)}\\s*\\(`, "u").test(nextSource);
|
|
1309
2830
|
const insertPhpSnippet = (snippet) => {
|
|
1310
2831
|
for (const anchor of insertionAnchors) {
|
|
1311
2832
|
const candidate = nextSource.replace(anchor, (match) => `${snippet}
|
|
@@ -1330,7 +2851,7 @@ ${snippet}
|
|
|
1330
2851
|
${snippet}
|
|
1331
2852
|
`;
|
|
1332
2853
|
};
|
|
1333
|
-
if (!hasPhpFunctionDefinition(enqueueFunctionName)) {
|
|
2854
|
+
if (!hasPhpFunctionDefinition(nextSource, enqueueFunctionName)) {
|
|
1334
2855
|
insertPhpSnippet(enqueueFunction);
|
|
1335
2856
|
} else {
|
|
1336
2857
|
const requiredReferences = [
|
|
@@ -1346,7 +2867,7 @@ ${snippet}
|
|
|
1346
2867
|
if (missingReferences.length > 0) {
|
|
1347
2868
|
const replacedSource = replacePhpFunctionDefinition(nextSource, enqueueFunctionName, enqueueFunction);
|
|
1348
2869
|
if (!replacedSource) {
|
|
1349
|
-
throw new Error(`Unable to repair ${
|
|
2870
|
+
throw new Error(`Unable to repair ${path5.basename(bootstrapPath)} for ${enqueueFunctionName}.`);
|
|
1350
2871
|
}
|
|
1351
2872
|
nextSource = replacedSource;
|
|
1352
2873
|
}
|
|
@@ -1358,7 +2879,7 @@ ${snippet}
|
|
|
1358
2879
|
});
|
|
1359
2880
|
}
|
|
1360
2881
|
async function ensureEditorPluginBuildScriptAnchors(workspace) {
|
|
1361
|
-
const buildScriptPath =
|
|
2882
|
+
const buildScriptPath = path5.join(workspace.projectDir, "scripts", "build-workspace.mjs");
|
|
1362
2883
|
await patchFile(buildScriptPath, (source) => {
|
|
1363
2884
|
if (/['"]src\/editor-plugins\/index\.(?:ts|js)['"]/u.test(source)) {
|
|
1364
2885
|
return source;
|
|
@@ -1371,13 +2892,13 @@ async function ensureEditorPluginBuildScriptAnchors(workspace) {
|
|
|
1371
2892
|
'src/editor-plugins/index.js',
|
|
1372
2893
|
]`);
|
|
1373
2894
|
if (nextSource === source) {
|
|
1374
|
-
throw new Error(`Unable to update ${
|
|
2895
|
+
throw new Error(`Unable to update ${path5.relative(workspace.projectDir, buildScriptPath)} for editor plugin shared entries.`);
|
|
1375
2896
|
}
|
|
1376
2897
|
return nextSource;
|
|
1377
2898
|
});
|
|
1378
2899
|
}
|
|
1379
2900
|
async function ensureEditorPluginWebpackAnchors(workspace) {
|
|
1380
|
-
const webpackConfigPath =
|
|
2901
|
+
const webpackConfigPath = path5.join(workspace.projectDir, "webpack.config.js");
|
|
1381
2902
|
await patchFile(webpackConfigPath, (source) => {
|
|
1382
2903
|
if (/['"]editor-plugins\/index['"]/u.test(source)) {
|
|
1383
2904
|
return source;
|
|
@@ -1405,45 +2926,45 @@ async function ensureEditorPluginWebpackAnchors(workspace) {
|
|
|
1405
2926
|
}`;
|
|
1406
2927
|
const nextSource = source.replace(legacySharedEntriesBlockPattern, nextSharedEntriesBlock);
|
|
1407
2928
|
if (nextSource === source) {
|
|
1408
|
-
throw new Error(`Unable to update ${
|
|
2929
|
+
throw new Error(`Unable to update ${path5.relative(workspace.projectDir, webpackConfigPath)} for editor plugin shared entries.`);
|
|
1409
2930
|
}
|
|
1410
2931
|
return nextSource;
|
|
1411
2932
|
});
|
|
1412
2933
|
}
|
|
1413
2934
|
function resolveBindingSourceRegistryPath(projectDir) {
|
|
1414
|
-
const bindingsDir =
|
|
1415
|
-
return [
|
|
2935
|
+
const bindingsDir = path5.join(projectDir, "src", "bindings");
|
|
2936
|
+
return [path5.join(bindingsDir, "index.ts"), path5.join(bindingsDir, "index.js")].find((candidatePath) => fs4.existsSync(candidatePath)) ?? path5.join(bindingsDir, "index.ts");
|
|
1416
2937
|
}
|
|
1417
2938
|
async function writeBindingSourceRegistry(projectDir, bindingSourceSlug) {
|
|
1418
|
-
const bindingsDir =
|
|
2939
|
+
const bindingsDir = path5.join(projectDir, "src", "bindings");
|
|
1419
2940
|
const bindingsIndexPath = resolveBindingSourceRegistryPath(projectDir);
|
|
1420
|
-
await
|
|
1421
|
-
const existingBindingSourceSlugs =
|
|
2941
|
+
await fsp4.mkdir(bindingsDir, { recursive: true });
|
|
2942
|
+
const existingBindingSourceSlugs = fs4.readdirSync(bindingsDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
1422
2943
|
const nextBindingSourceSlugs = Array.from(new Set([...existingBindingSourceSlugs, bindingSourceSlug])).sort();
|
|
1423
|
-
await
|
|
2944
|
+
await fsp4.writeFile(bindingsIndexPath, buildBindingSourceIndexSource(nextBindingSourceSlugs), "utf8");
|
|
1424
2945
|
}
|
|
1425
2946
|
function resolveEditorPluginRegistryPath(projectDir) {
|
|
1426
|
-
const editorPluginsDir =
|
|
2947
|
+
const editorPluginsDir = path5.join(projectDir, "src", "editor-plugins");
|
|
1427
2948
|
return [
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
].find((candidatePath) =>
|
|
2949
|
+
path5.join(editorPluginsDir, "index.ts"),
|
|
2950
|
+
path5.join(editorPluginsDir, "index.js")
|
|
2951
|
+
].find((candidatePath) => fs4.existsSync(candidatePath)) ?? path5.join(editorPluginsDir, "index.ts");
|
|
1431
2952
|
}
|
|
1432
2953
|
function readEditorPluginRegistrySlugs(registryPath) {
|
|
1433
|
-
if (!
|
|
2954
|
+
if (!fs4.existsSync(registryPath)) {
|
|
1434
2955
|
return [];
|
|
1435
2956
|
}
|
|
1436
|
-
const source =
|
|
2957
|
+
const source = fs4.readFileSync(registryPath, "utf8");
|
|
1437
2958
|
return Array.from(source.matchAll(/^\s*import\s+['"]\.\/([^/'"]+)(?:\/index(?:\.[cm]?[jt]sx?)?)?['"];?\s*$/gmu)).map((match) => match[1]);
|
|
1438
2959
|
}
|
|
1439
2960
|
async function writeEditorPluginRegistry(projectDir, editorPluginSlug) {
|
|
1440
|
-
const editorPluginsDir =
|
|
2961
|
+
const editorPluginsDir = path5.join(projectDir, "src", "editor-plugins");
|
|
1441
2962
|
const registryPath = resolveEditorPluginRegistryPath(projectDir);
|
|
1442
|
-
await
|
|
2963
|
+
await fsp4.mkdir(editorPluginsDir, { recursive: true });
|
|
1443
2964
|
const existingEditorPluginSlugs = readWorkspaceInventory(projectDir).editorPlugins.map((entry) => entry.slug);
|
|
1444
2965
|
const existingRegistrySlugs = readEditorPluginRegistrySlugs(registryPath);
|
|
1445
2966
|
const nextEditorPluginSlugs = Array.from(new Set([...existingEditorPluginSlugs, ...existingRegistrySlugs, editorPluginSlug])).sort();
|
|
1446
|
-
await
|
|
2967
|
+
await fsp4.writeFile(registryPath, buildEditorPluginRegistrySource(nextEditorPluginSlugs), "utf8");
|
|
1447
2968
|
}
|
|
1448
2969
|
async function runAddEditorPluginCommand({
|
|
1449
2970
|
cwd = process.cwd(),
|
|
@@ -1451,21 +2972,21 @@ async function runAddEditorPluginCommand({
|
|
|
1451
2972
|
slot
|
|
1452
2973
|
}) {
|
|
1453
2974
|
const workspace = resolveWorkspaceProject(cwd);
|
|
1454
|
-
const editorPluginSlug = assertValidGeneratedSlug("Editor plugin name", normalizeBlockSlug(editorPluginName), "wp-typia add editor-plugin <name> [--slot <
|
|
2975
|
+
const editorPluginSlug = assertValidGeneratedSlug("Editor plugin name", normalizeBlockSlug(editorPluginName), "wp-typia add editor-plugin <name> [--slot <sidebar|document-setting-panel>]");
|
|
1455
2976
|
const resolvedSlot = assertValidEditorPluginSlot(slot);
|
|
1456
2977
|
const inventory = readWorkspaceInventory(workspace.projectDir);
|
|
1457
2978
|
assertEditorPluginDoesNotExist(workspace.projectDir, editorPluginSlug, inventory);
|
|
1458
|
-
const blockConfigPath =
|
|
2979
|
+
const blockConfigPath = path5.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
1459
2980
|
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
1460
|
-
const buildScriptPath =
|
|
2981
|
+
const buildScriptPath = path5.join(workspace.projectDir, "scripts", "build-workspace.mjs");
|
|
1461
2982
|
const editorPluginsIndexPath = resolveEditorPluginRegistryPath(workspace.projectDir);
|
|
1462
|
-
const webpackConfigPath =
|
|
1463
|
-
const editorPluginDir =
|
|
1464
|
-
const entryFilePath =
|
|
1465
|
-
const
|
|
1466
|
-
const dataFilePath =
|
|
1467
|
-
const typesFilePath =
|
|
1468
|
-
const styleFilePath =
|
|
2983
|
+
const webpackConfigPath = path5.join(workspace.projectDir, "webpack.config.js");
|
|
2984
|
+
const editorPluginDir = path5.join(workspace.projectDir, "src", "editor-plugins", editorPluginSlug);
|
|
2985
|
+
const entryFilePath = path5.join(editorPluginDir, "index.tsx");
|
|
2986
|
+
const surfaceFilePath = path5.join(editorPluginDir, "Surface.tsx");
|
|
2987
|
+
const dataFilePath = path5.join(editorPluginDir, "data.ts");
|
|
2988
|
+
const typesFilePath = path5.join(editorPluginDir, "types.ts");
|
|
2989
|
+
const styleFilePath = path5.join(editorPluginDir, "style.scss");
|
|
1469
2990
|
const mutationSnapshot = {
|
|
1470
2991
|
fileSources: await snapshotWorkspaceFiles([
|
|
1471
2992
|
blockConfigPath,
|
|
@@ -1478,15 +2999,15 @@ async function runAddEditorPluginCommand({
|
|
|
1478
2999
|
targetPaths: [editorPluginDir]
|
|
1479
3000
|
};
|
|
1480
3001
|
try {
|
|
1481
|
-
await
|
|
3002
|
+
await fsp4.mkdir(editorPluginDir, { recursive: true });
|
|
1482
3003
|
await ensureEditorPluginBootstrapAnchors(workspace);
|
|
1483
3004
|
await ensureEditorPluginBuildScriptAnchors(workspace);
|
|
1484
3005
|
await ensureEditorPluginWebpackAnchors(workspace);
|
|
1485
|
-
await
|
|
1486
|
-
await
|
|
1487
|
-
await
|
|
1488
|
-
await
|
|
1489
|
-
await
|
|
3006
|
+
await fsp4.writeFile(entryFilePath, buildEditorPluginEntrySource(editorPluginSlug, workspace.workspace.namespace, workspace.workspace.textDomain), "utf8");
|
|
3007
|
+
await fsp4.writeFile(surfaceFilePath, buildEditorPluginSurfaceSource(editorPluginSlug, resolvedSlot, workspace.workspace.textDomain), "utf8");
|
|
3008
|
+
await fsp4.writeFile(dataFilePath, buildEditorPluginDataSource(editorPluginSlug, resolvedSlot), "utf8");
|
|
3009
|
+
await fsp4.writeFile(typesFilePath, buildEditorPluginTypesSource(editorPluginSlug), "utf8");
|
|
3010
|
+
await fsp4.writeFile(styleFilePath, buildEditorPluginStyleSource(), "utf8");
|
|
1490
3011
|
await writeEditorPluginRegistry(workspace.projectDir, editorPluginSlug);
|
|
1491
3012
|
await appendWorkspaceInventoryEntries(workspace.projectDir, {
|
|
1492
3013
|
editorPluginEntries: [
|
|
@@ -1511,18 +3032,18 @@ async function runAddPatternCommand({
|
|
|
1511
3032
|
const patternSlug = assertValidGeneratedSlug("Pattern name", normalizeBlockSlug(patternName), "wp-typia add pattern <name>");
|
|
1512
3033
|
const inventory = readWorkspaceInventory(workspace.projectDir);
|
|
1513
3034
|
assertPatternDoesNotExist(workspace.projectDir, patternSlug, inventory);
|
|
1514
|
-
const blockConfigPath =
|
|
3035
|
+
const blockConfigPath = path5.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
1515
3036
|
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
1516
|
-
const patternFilePath =
|
|
3037
|
+
const patternFilePath = path5.join(workspace.projectDir, "src", "patterns", `${patternSlug}.php`);
|
|
1517
3038
|
const mutationSnapshot = {
|
|
1518
3039
|
fileSources: await snapshotWorkspaceFiles([blockConfigPath, bootstrapPath]),
|
|
1519
3040
|
snapshotDirs: [],
|
|
1520
3041
|
targetPaths: [patternFilePath]
|
|
1521
3042
|
};
|
|
1522
3043
|
try {
|
|
1523
|
-
await
|
|
3044
|
+
await fsp4.mkdir(path5.dirname(patternFilePath), { recursive: true });
|
|
1524
3045
|
await ensurePatternBootstrapAnchors(workspace);
|
|
1525
|
-
await
|
|
3046
|
+
await fsp4.writeFile(patternFilePath, buildPatternSource(patternSlug, workspace.workspace.namespace, workspace.workspace.textDomain), "utf8");
|
|
1526
3047
|
await appendWorkspaceInventoryEntries(workspace.projectDir, {
|
|
1527
3048
|
patternEntries: [buildPatternConfigEntry(patternSlug)]
|
|
1528
3049
|
});
|
|
@@ -1536,34 +3057,59 @@ async function runAddPatternCommand({
|
|
|
1536
3057
|
}
|
|
1537
3058
|
}
|
|
1538
3059
|
async function runAddBindingSourceCommand({
|
|
3060
|
+
attributeName,
|
|
1539
3061
|
bindingSourceName,
|
|
3062
|
+
blockName,
|
|
1540
3063
|
cwd = process.cwd()
|
|
1541
3064
|
}) {
|
|
1542
3065
|
const workspace = resolveWorkspaceProject(cwd);
|
|
1543
|
-
const bindingSourceSlug = assertValidGeneratedSlug("Binding source name", normalizeBlockSlug(bindingSourceName), "wp-typia add binding-source <name>");
|
|
3066
|
+
const bindingSourceSlug = assertValidGeneratedSlug("Binding source name", normalizeBlockSlug(bindingSourceName), "wp-typia add binding-source <name> [--block <block-slug|namespace/block-slug> --attribute <attribute>]");
|
|
1544
3067
|
const inventory = readWorkspaceInventory(workspace.projectDir);
|
|
1545
3068
|
assertBindingSourceDoesNotExist(workspace.projectDir, bindingSourceSlug, inventory);
|
|
1546
|
-
const
|
|
3069
|
+
const target = resolveBindingTarget({
|
|
3070
|
+
attributeName,
|
|
3071
|
+
blockName
|
|
3072
|
+
}, workspace.workspace.namespace);
|
|
3073
|
+
const targetBlock = target ? resolveWorkspaceBlock(inventory, target.blockSlug) : undefined;
|
|
3074
|
+
const blockConfigPath = path5.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
1547
3075
|
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
1548
3076
|
const bindingsIndexPath = resolveBindingSourceRegistryPath(workspace.projectDir);
|
|
1549
|
-
const bindingSourceDir =
|
|
1550
|
-
const serverFilePath =
|
|
1551
|
-
const editorFilePath =
|
|
3077
|
+
const bindingSourceDir = path5.join(workspace.projectDir, "src", "bindings", bindingSourceSlug);
|
|
3078
|
+
const serverFilePath = path5.join(bindingSourceDir, "server.php");
|
|
3079
|
+
const editorFilePath = path5.join(bindingSourceDir, "editor.ts");
|
|
3080
|
+
const blockJsonPath = target ? path5.join(workspace.projectDir, "src", "blocks", target.blockSlug, "block.json") : undefined;
|
|
3081
|
+
const targetGeneratedMetadataPaths = target ? [
|
|
3082
|
+
path5.join(workspace.projectDir, "src", "blocks", target.blockSlug, "typia.manifest.json"),
|
|
3083
|
+
path5.join(workspace.projectDir, "src", "blocks", target.blockSlug, "typia.openapi.json"),
|
|
3084
|
+
path5.join(workspace.projectDir, "src", "blocks", target.blockSlug, "typia.schema.json"),
|
|
3085
|
+
path5.join(workspace.projectDir, "src", "blocks", target.blockSlug, "typia-validator.php")
|
|
3086
|
+
] : [];
|
|
1552
3087
|
const mutationSnapshot = {
|
|
1553
|
-
fileSources: await snapshotWorkspaceFiles([
|
|
3088
|
+
fileSources: await snapshotWorkspaceFiles([
|
|
3089
|
+
blockConfigPath,
|
|
3090
|
+
bootstrapPath,
|
|
3091
|
+
bindingsIndexPath,
|
|
3092
|
+
...blockJsonPath ? [blockJsonPath] : [],
|
|
3093
|
+
...targetBlock ? [path5.join(workspace.projectDir, targetBlock.typesFile)] : [],
|
|
3094
|
+
...targetGeneratedMetadataPaths
|
|
3095
|
+
]),
|
|
1554
3096
|
snapshotDirs: [],
|
|
1555
3097
|
targetPaths: [bindingSourceDir]
|
|
1556
3098
|
};
|
|
1557
3099
|
try {
|
|
1558
|
-
await
|
|
3100
|
+
await fsp4.mkdir(bindingSourceDir, { recursive: true });
|
|
1559
3101
|
await ensureBindingSourceBootstrapAnchors(workspace);
|
|
1560
|
-
await
|
|
1561
|
-
await
|
|
3102
|
+
await fsp4.writeFile(serverFilePath, buildBindingSourceServerSource(bindingSourceSlug, workspace.workspace.phpPrefix, workspace.workspace.namespace, workspace.workspace.textDomain, target), "utf8");
|
|
3103
|
+
await fsp4.writeFile(editorFilePath, buildBindingSourceEditorSource(bindingSourceSlug, workspace.workspace.namespace, workspace.workspace.textDomain, target), "utf8");
|
|
3104
|
+
if (target && targetBlock) {
|
|
3105
|
+
await ensureBindingTargetBlockAttributeType(workspace.projectDir, targetBlock, target);
|
|
3106
|
+
}
|
|
1562
3107
|
await writeBindingSourceRegistry(workspace.projectDir, bindingSourceSlug);
|
|
1563
3108
|
await appendWorkspaceInventoryEntries(workspace.projectDir, {
|
|
1564
|
-
bindingSourceEntries: [buildBindingSourceConfigEntry(bindingSourceSlug)]
|
|
3109
|
+
bindingSourceEntries: [buildBindingSourceConfigEntry(bindingSourceSlug, target)]
|
|
1565
3110
|
});
|
|
1566
3111
|
return {
|
|
3112
|
+
...target ? { attributeName: target.attributeName, blockSlug: target.blockSlug } : {},
|
|
1567
3113
|
bindingSourceSlug,
|
|
1568
3114
|
projectDir: workspace.projectDir
|
|
1569
3115
|
};
|
|
@@ -1573,15 +3119,12 @@ async function runAddBindingSourceCommand({
|
|
|
1573
3119
|
}
|
|
1574
3120
|
}
|
|
1575
3121
|
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-rest.ts
|
|
1576
|
-
import { promises as
|
|
1577
|
-
import
|
|
3122
|
+
import { promises as fsp5 } from "fs";
|
|
3123
|
+
import path8 from "path";
|
|
1578
3124
|
|
|
1579
3125
|
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-rest-anchors.ts
|
|
1580
|
-
import
|
|
3126
|
+
import path6 from "path";
|
|
1581
3127
|
var REST_RESOURCE_SERVER_GLOB = "/inc/rest/*.php";
|
|
1582
|
-
function escapeRegex2(value) {
|
|
1583
|
-
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
1584
|
-
}
|
|
1585
3128
|
async function ensureRestResourceBootstrapAnchors(workspace) {
|
|
1586
3129
|
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
1587
3130
|
await patchFile(bootstrapPath, (source) => {
|
|
@@ -1600,7 +3143,6 @@ function ${registerFunctionName}() {
|
|
|
1600
3143
|
/add_action\(\s*["']init["']\s*,\s*["'][^"']+_load_textdomain["']\s*\);\s*\n/u,
|
|
1601
3144
|
/\?>\s*$/u
|
|
1602
3145
|
];
|
|
1603
|
-
const hasPhpFunctionDefinition = (functionName) => new RegExp(`function\\s+${escapeRegex2(functionName)}\\s*\\(`, "u").test(nextSource);
|
|
1604
3146
|
const insertPhpSnippet = (snippet) => {
|
|
1605
3147
|
for (const anchor of insertionAnchors) {
|
|
1606
3148
|
const candidate = nextSource.replace(anchor, (match) => `${snippet}
|
|
@@ -1625,11 +3167,11 @@ ${snippet}
|
|
|
1625
3167
|
${snippet}
|
|
1626
3168
|
`;
|
|
1627
3169
|
};
|
|
1628
|
-
if (!hasPhpFunctionDefinition(registerFunctionName)) {
|
|
3170
|
+
if (!hasPhpFunctionDefinition(nextSource, registerFunctionName)) {
|
|
1629
3171
|
insertPhpSnippet(registerFunction);
|
|
1630
3172
|
} else if (!nextSource.includes(REST_RESOURCE_SERVER_GLOB)) {
|
|
1631
3173
|
throw new Error([
|
|
1632
|
-
`Unable to patch ${
|
|
3174
|
+
`Unable to patch ${path6.basename(bootstrapPath)} in ensureRestResourceBootstrapAnchors.`,
|
|
1633
3175
|
`The existing ${registerFunctionName}() definition does not include ${REST_RESOURCE_SERVER_GLOB}.`,
|
|
1634
3176
|
"Restore the generated bootstrap shape or wire the REST resource loader manually before retrying."
|
|
1635
3177
|
].join(" "));
|
|
@@ -1643,7 +3185,7 @@ ${snippet}
|
|
|
1643
3185
|
function assertSyncRestAnchor(nextSource, target, anchorDescription, hasAnchor, syncRestScriptPath) {
|
|
1644
3186
|
if (!nextSource.includes(target) && !hasAnchor) {
|
|
1645
3187
|
throw new Error([
|
|
1646
|
-
`ensureRestResourceSyncScriptAnchors could not patch ${
|
|
3188
|
+
`ensureRestResourceSyncScriptAnchors could not patch ${path6.basename(syncRestScriptPath)}.`,
|
|
1647
3189
|
`Missing expected ${anchorDescription} anchor in scripts/sync-rest-contracts.ts.`,
|
|
1648
3190
|
"Restore the generated template or add the REST_RESOURCES wiring manually before retrying."
|
|
1649
3191
|
].join(" "));
|
|
@@ -1658,7 +3200,7 @@ function replaceRequiredSyncRestSource(nextSource, target, anchor, replacement,
|
|
|
1658
3200
|
return nextSource.replace(anchor, replacement);
|
|
1659
3201
|
}
|
|
1660
3202
|
async function ensureRestResourceSyncScriptAnchors(workspace) {
|
|
1661
|
-
const syncRestScriptPath =
|
|
3203
|
+
const syncRestScriptPath = path6.join(workspace.projectDir, "scripts", "sync-rest-contracts.ts");
|
|
1662
3204
|
await patchFile(syncRestScriptPath, (source) => {
|
|
1663
3205
|
let nextSource = source;
|
|
1664
3206
|
const importAnchor = "import { BLOCKS, type WorkspaceBlockConfig } from './block-config';";
|
|
@@ -1776,7 +3318,7 @@ async function ensureRestResourceSyncScriptAnchors(workspace) {
|
|
|
1776
3318
|
}
|
|
1777
3319
|
|
|
1778
3320
|
// ../wp-typia-project-tools/src/runtime/rest-resource-artifacts.ts
|
|
1779
|
-
import
|
|
3321
|
+
import path7 from "path";
|
|
1780
3322
|
import {
|
|
1781
3323
|
defineEndpointManifest,
|
|
1782
3324
|
syncEndpointClient,
|
|
@@ -1912,8 +3454,8 @@ async function syncRestResourceArtifacts({
|
|
|
1912
3454
|
const manifest = buildRestResourceEndpointManifest(variables, methods);
|
|
1913
3455
|
for (const [baseName, contract] of Object.entries(manifest.contracts)) {
|
|
1914
3456
|
await syncTypeSchemas({
|
|
1915
|
-
jsonSchemaFile:
|
|
1916
|
-
openApiFile:
|
|
3457
|
+
jsonSchemaFile: path7.join(outputDir, "api-schemas", `${baseName}.schema.json`),
|
|
3458
|
+
openApiFile: path7.join(outputDir, "api-schemas", `${baseName}.openapi.json`),
|
|
1917
3459
|
projectRoot: projectDir,
|
|
1918
3460
|
sourceTypeName: contract.sourceTypeName,
|
|
1919
3461
|
typesFile
|
|
@@ -1921,7 +3463,7 @@ async function syncRestResourceArtifacts({
|
|
|
1921
3463
|
}
|
|
1922
3464
|
await syncRestOpenApi({
|
|
1923
3465
|
manifest,
|
|
1924
|
-
openApiFile:
|
|
3466
|
+
openApiFile: path7.join(outputDir, "api.openapi.json"),
|
|
1925
3467
|
projectRoot: projectDir,
|
|
1926
3468
|
typesFile
|
|
1927
3469
|
});
|
|
@@ -1935,16 +3477,13 @@ async function syncRestResourceArtifacts({
|
|
|
1935
3477
|
}
|
|
1936
3478
|
|
|
1937
3479
|
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-rest-source-emitters.ts
|
|
1938
|
-
function toPascalCaseFromSlug2(slug) {
|
|
1939
|
-
return normalizeBlockSlug(slug).split("-").filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join("");
|
|
1940
|
-
}
|
|
1941
3480
|
function indentMultiline(source, prefix) {
|
|
1942
3481
|
return source.split(`
|
|
1943
3482
|
`).map((line) => `${prefix}${line}`).join(`
|
|
1944
3483
|
`);
|
|
1945
3484
|
}
|
|
1946
3485
|
function buildRestResourceConfigEntry(restResourceSlug, namespace, methods) {
|
|
1947
|
-
const pascalCase =
|
|
3486
|
+
const pascalCase = toPascalCase(restResourceSlug);
|
|
1948
3487
|
const title = toTitleCase(restResourceSlug);
|
|
1949
3488
|
const manifest = buildRestResourceEndpointManifest({
|
|
1950
3489
|
namespace,
|
|
@@ -1972,7 +3511,7 @@ function buildRestResourceConfigEntry(restResourceSlug, namespace, methods) {
|
|
|
1972
3511
|
`);
|
|
1973
3512
|
}
|
|
1974
3513
|
function buildRestResourceTypesSource(restResourceSlug, methods) {
|
|
1975
|
-
const pascalCase =
|
|
3514
|
+
const pascalCase = toPascalCase(restResourceSlug);
|
|
1976
3515
|
const lines = [
|
|
1977
3516
|
"import { tags } from 'typia';",
|
|
1978
3517
|
"",
|
|
@@ -2006,7 +3545,7 @@ function buildRestResourceTypesSource(restResourceSlug, methods) {
|
|
|
2006
3545
|
`;
|
|
2007
3546
|
}
|
|
2008
3547
|
function buildRestResourceValidatorsSource(restResourceSlug, methods) {
|
|
2009
|
-
const pascalCase =
|
|
3548
|
+
const pascalCase = toPascalCase(restResourceSlug);
|
|
2010
3549
|
const importedTypes = new Set;
|
|
2011
3550
|
const validatorDeclarations = [];
|
|
2012
3551
|
const validatorEntries = [];
|
|
@@ -2054,7 +3593,7 @@ ${validatorEntries.join(`
|
|
|
2054
3593
|
`;
|
|
2055
3594
|
}
|
|
2056
3595
|
function buildRestResourceApiSource(restResourceSlug, methods) {
|
|
2057
|
-
const pascalCase =
|
|
3596
|
+
const pascalCase = toPascalCase(restResourceSlug);
|
|
2058
3597
|
const typeImports = new Set;
|
|
2059
3598
|
const clientEndpointImports = [];
|
|
2060
3599
|
const exportedBindings = [];
|
|
@@ -2199,7 +3738,7 @@ ${exportedBindings.join(`
|
|
|
2199
3738
|
`;
|
|
2200
3739
|
}
|
|
2201
3740
|
function buildRestResourceDataSource(restResourceSlug, methods) {
|
|
2202
|
-
const pascalCase =
|
|
3741
|
+
const pascalCase = toPascalCase(restResourceSlug);
|
|
2203
3742
|
const typeImports = new Set;
|
|
2204
3743
|
const endpointImports = [];
|
|
2205
3744
|
const exportedBindings = [];
|
|
@@ -2320,9 +3859,6 @@ ${exportedBindings.join(`
|
|
|
2320
3859
|
}
|
|
2321
3860
|
|
|
2322
3861
|
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-rest.ts
|
|
2323
|
-
function quotePhpString2(value) {
|
|
2324
|
-
return `'${value.replace(/\\/gu, "\\\\").replace(/'/gu, "\\'")}'`;
|
|
2325
|
-
}
|
|
2326
3862
|
function buildRestResourceRouteRegistrations(restResourceSlug, methods, functions) {
|
|
2327
3863
|
const collectionRoutes = [];
|
|
2328
3864
|
const itemRoutes = [];
|
|
@@ -2418,7 +3954,7 @@ if ( ! defined( 'ABSPATH' ) ) {
|
|
|
2418
3954
|
|
|
2419
3955
|
if ( ! function_exists( '${getOptionNameFunctionName}' ) ) {
|
|
2420
3956
|
function ${getOptionNameFunctionName}() {
|
|
2421
|
-
return ${
|
|
3957
|
+
return ${quotePhpString(`${phpPrefix}_${restResourcePhpId}_rest_resource_items`)};
|
|
2422
3958
|
}
|
|
2423
3959
|
}
|
|
2424
3960
|
|
|
@@ -2439,8 +3975,8 @@ if ( ! function_exists( '${getItemsFunctionName}' ) ) {
|
|
|
2439
3975
|
$seed_items = array(
|
|
2440
3976
|
array(
|
|
2441
3977
|
'id' => 1,
|
|
2442
|
-
'title' => ${
|
|
2443
|
-
'content' => ${
|
|
3978
|
+
'title' => ${quotePhpString(`${restResourceTitle} Starter`)},
|
|
3979
|
+
'content' => ${quotePhpString(`Replace this seeded ${restResourceTitle.toLowerCase()} content with your plugin data source.`)},
|
|
2444
3980
|
'status' => 'draft',
|
|
2445
3981
|
'updatedAt' => '2026-01-01T00:00:00Z',
|
|
2446
3982
|
),
|
|
@@ -2731,7 +4267,7 @@ if ( ! function_exists( '${deleteHandlerName}' ) ) {
|
|
|
2731
4267
|
|
|
2732
4268
|
if ( ! function_exists( '${registerRoutesFunctionName}' ) ) {
|
|
2733
4269
|
function ${registerRoutesFunctionName}() {
|
|
2734
|
-
$namespace = ${
|
|
4270
|
+
$namespace = ${quotePhpString(namespace)};
|
|
2735
4271
|
|
|
2736
4272
|
${routeRegistrations}
|
|
2737
4273
|
}
|
|
@@ -2752,15 +4288,15 @@ async function runAddRestResourceCommand({
|
|
|
2752
4288
|
const resolvedNamespace = resolveRestResourceNamespace(workspace.workspace.namespace, namespace);
|
|
2753
4289
|
const inventory = readWorkspaceInventory(workspace.projectDir);
|
|
2754
4290
|
assertRestResourceDoesNotExist(workspace.projectDir, restResourceSlug, inventory);
|
|
2755
|
-
const blockConfigPath =
|
|
4291
|
+
const blockConfigPath = path8.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
2756
4292
|
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
2757
|
-
const syncRestScriptPath =
|
|
2758
|
-
const restResourceDir =
|
|
2759
|
-
const typesFilePath =
|
|
2760
|
-
const validatorsFilePath =
|
|
2761
|
-
const apiFilePath =
|
|
2762
|
-
const dataFilePath =
|
|
2763
|
-
const phpFilePath =
|
|
4293
|
+
const syncRestScriptPath = path8.join(workspace.projectDir, "scripts", "sync-rest-contracts.ts");
|
|
4294
|
+
const restResourceDir = path8.join(workspace.projectDir, "src", "rest", restResourceSlug);
|
|
4295
|
+
const typesFilePath = path8.join(restResourceDir, "api-types.ts");
|
|
4296
|
+
const validatorsFilePath = path8.join(restResourceDir, "api-validators.ts");
|
|
4297
|
+
const apiFilePath = path8.join(restResourceDir, "api.ts");
|
|
4298
|
+
const dataFilePath = path8.join(restResourceDir, "data.ts");
|
|
4299
|
+
const phpFilePath = path8.join(workspace.projectDir, "inc", "rest", `${restResourceSlug}.php`);
|
|
2764
4300
|
const mutationSnapshot = {
|
|
2765
4301
|
fileSources: await snapshotWorkspaceFiles([
|
|
2766
4302
|
blockConfigPath,
|
|
@@ -2771,15 +4307,15 @@ async function runAddRestResourceCommand({
|
|
|
2771
4307
|
targetPaths: [restResourceDir, phpFilePath]
|
|
2772
4308
|
};
|
|
2773
4309
|
try {
|
|
2774
|
-
await
|
|
2775
|
-
await
|
|
4310
|
+
await fsp5.mkdir(restResourceDir, { recursive: true });
|
|
4311
|
+
await fsp5.mkdir(path8.dirname(phpFilePath), { recursive: true });
|
|
2776
4312
|
await ensureRestResourceBootstrapAnchors(workspace);
|
|
2777
4313
|
await ensureRestResourceSyncScriptAnchors(workspace);
|
|
2778
|
-
await
|
|
2779
|
-
await
|
|
2780
|
-
await
|
|
2781
|
-
await
|
|
2782
|
-
await
|
|
4314
|
+
await fsp5.writeFile(typesFilePath, buildRestResourceTypesSource(restResourceSlug, resolvedMethods), "utf8");
|
|
4315
|
+
await fsp5.writeFile(validatorsFilePath, buildRestResourceValidatorsSource(restResourceSlug, resolvedMethods), "utf8");
|
|
4316
|
+
await fsp5.writeFile(apiFilePath, buildRestResourceApiSource(restResourceSlug, resolvedMethods), "utf8");
|
|
4317
|
+
await fsp5.writeFile(dataFilePath, buildRestResourceDataSource(restResourceSlug, resolvedMethods), "utf8");
|
|
4318
|
+
await fsp5.writeFile(phpFilePath, buildRestResourcePhpSource(restResourceSlug, resolvedNamespace, workspace.workspace.phpPrefix, workspace.workspace.textDomain, resolvedMethods), "utf8");
|
|
2783
4319
|
await syncRestResourceArtifacts({
|
|
2784
4320
|
clientFile: `src/rest/${restResourceSlug}/api-client.ts`,
|
|
2785
4321
|
methods: resolvedMethods,
|
|
@@ -2789,7 +4325,7 @@ async function runAddRestResourceCommand({
|
|
|
2789
4325
|
validatorsFile: `src/rest/${restResourceSlug}/api-validators.ts`,
|
|
2790
4326
|
variables: {
|
|
2791
4327
|
namespace: resolvedNamespace,
|
|
2792
|
-
pascalCase:
|
|
4328
|
+
pascalCase: toPascalCase(restResourceSlug),
|
|
2793
4329
|
slugKebabCase: restResourceSlug,
|
|
2794
4330
|
title: toTitleCase(restResourceSlug)
|
|
2795
4331
|
}
|
|
@@ -2813,58 +4349,17 @@ async function runAddRestResourceCommand({
|
|
|
2813
4349
|
}
|
|
2814
4350
|
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-ability.ts
|
|
2815
4351
|
var import_semver = __toESM(require_semver(), 1);
|
|
2816
|
-
import
|
|
2817
|
-
import { promises as
|
|
2818
|
-
import
|
|
4352
|
+
import fs5 from "fs";
|
|
4353
|
+
import { promises as fsp6 } from "fs";
|
|
4354
|
+
import path9 from "path";
|
|
2819
4355
|
import { syncTypeSchemas as syncTypeSchemas2 } from "@wp-typia/block-runtime/metadata-core";
|
|
2820
4356
|
var ABILITY_SERVER_GLOB = "/inc/abilities/*.php";
|
|
2821
4357
|
var ABILITY_EDITOR_SCRIPT = "build/abilities/index.js";
|
|
2822
4358
|
var ABILITY_EDITOR_ASSET = "build/abilities/index.asset.php";
|
|
2823
4359
|
var ABILITY_REGISTRY_END_MARKER = "// wp-typia add ability entries end";
|
|
2824
4360
|
var ABILITY_REGISTRY_START_MARKER = "// wp-typia add ability entries start";
|
|
2825
|
-
var WP_ABILITIES_PACKAGE_VERSION = "^0.10.0";
|
|
2826
|
-
var WP_CORE_ABILITIES_PACKAGE_VERSION = "^0.9.0";
|
|
2827
4361
|
var WP_ABILITIES_SCRIPT_MODULE_ID = "@wordpress/abilities";
|
|
2828
4362
|
var WP_CORE_ABILITIES_SCRIPT_MODULE_ID = "@wordpress/core-abilities";
|
|
2829
|
-
function escapeRegex3(value) {
|
|
2830
|
-
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
2831
|
-
}
|
|
2832
|
-
function quotePhpString3(value) {
|
|
2833
|
-
return `'${value.replace(/\\/gu, "\\\\").replace(/'/gu, "\\'")}'`;
|
|
2834
|
-
}
|
|
2835
|
-
function findPhpFunctionRange2(source, functionName) {
|
|
2836
|
-
const functionPattern = new RegExp(`function\\s+${escapeRegex3(functionName)}\\s*\\([^)]*\\)\\s*\\{`, "u");
|
|
2837
|
-
const match = functionPattern.exec(source);
|
|
2838
|
-
if (!match) {
|
|
2839
|
-
return null;
|
|
2840
|
-
}
|
|
2841
|
-
const openingBraceIndex = match.index + match[0].length - 1;
|
|
2842
|
-
let depth = 0;
|
|
2843
|
-
for (let index = openingBraceIndex;index < source.length; index += 1) {
|
|
2844
|
-
const character = source[index];
|
|
2845
|
-
if (character === "{") {
|
|
2846
|
-
depth += 1;
|
|
2847
|
-
} else if (character === "}") {
|
|
2848
|
-
depth -= 1;
|
|
2849
|
-
if (depth === 0) {
|
|
2850
|
-
const end = index + 1;
|
|
2851
|
-
return {
|
|
2852
|
-
end,
|
|
2853
|
-
source: source.slice(match.index, end),
|
|
2854
|
-
start: match.index
|
|
2855
|
-
};
|
|
2856
|
-
}
|
|
2857
|
-
}
|
|
2858
|
-
}
|
|
2859
|
-
return null;
|
|
2860
|
-
}
|
|
2861
|
-
function replacePhpFunctionDefinition2(source, functionName, replacement) {
|
|
2862
|
-
const functionRange = findPhpFunctionRange2(source, functionName);
|
|
2863
|
-
if (!functionRange) {
|
|
2864
|
-
return source;
|
|
2865
|
-
}
|
|
2866
|
-
return `${source.slice(0, functionRange.start)}${replacement.trimStart()}${source.slice(functionRange.end)}`;
|
|
2867
|
-
}
|
|
2868
4363
|
function resolveManagedDependencyVersion(existingVersion, requiredVersion) {
|
|
2869
4364
|
if (!existingVersion) {
|
|
2870
4365
|
return requiredVersion;
|
|
@@ -2876,15 +4371,12 @@ function resolveManagedDependencyVersion(existingVersion, requiredVersion) {
|
|
|
2876
4371
|
}
|
|
2877
4372
|
return import_semver.default.gte(existingMinimum, requiredMinimum) ? existingVersion : requiredVersion;
|
|
2878
4373
|
}
|
|
2879
|
-
function toPascalCaseFromAbilitySlug(abilitySlug) {
|
|
2880
|
-
return normalizeBlockSlug(abilitySlug).split("-").filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join("");
|
|
2881
|
-
}
|
|
2882
4374
|
function toAbilityCategorySlug(workspaceNamespace) {
|
|
2883
4375
|
const normalizedNamespace = workspaceNamespace.replace(/[^a-z0-9-]+/gu, "-").replace(/-{2,}/gu, "-").replace(/^-|-$/gu, "");
|
|
2884
4376
|
return `${normalizedNamespace || "workspace"}-workflows`;
|
|
2885
4377
|
}
|
|
2886
4378
|
function buildAbilityConfigEntry(abilitySlug, compatibilityPolicy) {
|
|
2887
|
-
const pascalCase =
|
|
4379
|
+
const pascalCase = toPascalCase(abilitySlug);
|
|
2888
4380
|
return [
|
|
2889
4381
|
"\t{",
|
|
2890
4382
|
` clientFile: ${quoteTsString(`src/abilities/${abilitySlug}/client.ts`)},`,
|
|
@@ -2928,7 +4420,7 @@ function buildAbilityConfigSource(abilitySlug, workspaceNamespace) {
|
|
|
2928
4420
|
`;
|
|
2929
4421
|
}
|
|
2930
4422
|
function buildAbilityTypesSource(abilitySlug) {
|
|
2931
|
-
const pascalCase =
|
|
4423
|
+
const pascalCase = toPascalCase(abilitySlug);
|
|
2932
4424
|
return `export interface ${pascalCase}AbilityInput {
|
|
2933
4425
|
contextId: number;
|
|
2934
4426
|
note?: string;
|
|
@@ -2943,7 +4435,7 @@ export interface ${pascalCase}AbilityOutput {
|
|
|
2943
4435
|
`;
|
|
2944
4436
|
}
|
|
2945
4437
|
function buildAbilityDataSource(abilitySlug) {
|
|
2946
|
-
const pascalCase =
|
|
4438
|
+
const pascalCase = toPascalCase(abilitySlug);
|
|
2947
4439
|
const abilityConstBase = abilitySlug.toUpperCase().replace(/[^A-Z0-9]+/gu, "_").replace(/_{2,}/gu, "_").replace(/^_|_$/gu, "");
|
|
2948
4440
|
return `import {
|
|
2949
4441
|
executeAbility,
|
|
@@ -3039,7 +4531,7 @@ export async function run${pascalCase}Ability(
|
|
|
3039
4531
|
`;
|
|
3040
4532
|
}
|
|
3041
4533
|
function buildAbilityClientSource(abilitySlug) {
|
|
3042
|
-
const pascalCase =
|
|
4534
|
+
const pascalCase = toPascalCase(abilitySlug);
|
|
3043
4535
|
return `/**
|
|
3044
4536
|
* Re-export the typed ${pascalCase} ability client helpers.
|
|
3045
4537
|
*
|
|
@@ -3243,8 +4735,8 @@ if ( ! function_exists( '${executeFunctionName}' ) ) {
|
|
|
3243
4735
|
'status' => 'ready',
|
|
3244
4736
|
'summary' => sprintf(
|
|
3245
4737
|
/* translators: 1: workflow title, 2: context id */
|
|
3246
|
-
__( '%1$s processed context %2$d.', ${
|
|
3247
|
-
${
|
|
4738
|
+
__( '%1$s processed context %2$d.', ${quotePhpString(workspace.workspace.textDomain)} ),
|
|
4739
|
+
${quotePhpString(abilityTitle)},
|
|
3248
4740
|
$context_id
|
|
3249
4741
|
),
|
|
3250
4742
|
);
|
|
@@ -3310,11 +4802,11 @@ if ( ! function_exists( '${abilityRegisterFunctionName}' ) ) {
|
|
|
3310
4802
|
$args = array(
|
|
3311
4803
|
'category' => (string) $config['category']['slug'],
|
|
3312
4804
|
'description' => (string) $config['description'],
|
|
3313
|
-
'execute_callback' => ${
|
|
4805
|
+
'execute_callback' => ${quotePhpString(executeFunctionName)},
|
|
3314
4806
|
'label' => (string) $config['label'],
|
|
3315
4807
|
'meta' => ${metaFactoryFunctionName}( $config ),
|
|
3316
4808
|
'output_schema' => $output_schema,
|
|
3317
|
-
'permission_callback' => ${
|
|
4809
|
+
'permission_callback' => ${quotePhpString(permissionFunctionName)},
|
|
3318
4810
|
);
|
|
3319
4811
|
|
|
3320
4812
|
if ( is_array( $input_schema ) ) {
|
|
@@ -3344,30 +4836,30 @@ function buildAbilityRegistrySource(abilitySlugs) {
|
|
|
3344
4836
|
`);
|
|
3345
4837
|
}
|
|
3346
4838
|
function resolveAbilityRegistryPath(projectDir) {
|
|
3347
|
-
const abilitiesDir =
|
|
3348
|
-
return [
|
|
4839
|
+
const abilitiesDir = path9.join(projectDir, "src", "abilities");
|
|
4840
|
+
return [path9.join(abilitiesDir, "index.ts"), path9.join(abilitiesDir, "index.js")].find((candidatePath) => fs5.existsSync(candidatePath)) ?? path9.join(abilitiesDir, "index.ts");
|
|
3349
4841
|
}
|
|
3350
4842
|
function readAbilityRegistrySlugs(registryPath) {
|
|
3351
|
-
if (!
|
|
4843
|
+
if (!fs5.existsSync(registryPath)) {
|
|
3352
4844
|
return [];
|
|
3353
4845
|
}
|
|
3354
|
-
const source =
|
|
4846
|
+
const source = fs5.readFileSync(registryPath, "utf8");
|
|
3355
4847
|
return Array.from(source.matchAll(/^\s*export\s+\*\s+from\s+['"]\.\/([^/'"]+)\/client['"];?\s*$/gmu)).map((match) => match[1]);
|
|
3356
4848
|
}
|
|
3357
4849
|
async function writeAbilityRegistry(projectDir, abilitySlug) {
|
|
3358
|
-
const abilitiesDir =
|
|
4850
|
+
const abilitiesDir = path9.join(projectDir, "src", "abilities");
|
|
3359
4851
|
const registryPath = resolveAbilityRegistryPath(projectDir);
|
|
3360
|
-
await
|
|
4852
|
+
await fsp6.mkdir(abilitiesDir, { recursive: true });
|
|
3361
4853
|
const existingAbilitySlugs = readWorkspaceInventory(projectDir).abilities.map((entry) => entry.slug);
|
|
3362
4854
|
const existingRegistrySlugs = readAbilityRegistrySlugs(registryPath);
|
|
3363
4855
|
const nextAbilitySlugs = Array.from(new Set([...existingAbilitySlugs, ...existingRegistrySlugs, abilitySlug])).sort();
|
|
3364
4856
|
const generatedSection = buildAbilityRegistrySource(nextAbilitySlugs);
|
|
3365
|
-
const existingSource =
|
|
3366
|
-
const generatedSectionPattern = new RegExp(`${
|
|
4857
|
+
const existingSource = fs5.existsSync(registryPath) ? fs5.readFileSync(registryPath, "utf8") : "";
|
|
4858
|
+
const generatedSectionPattern = new RegExp(`${escapeRegex(ABILITY_REGISTRY_START_MARKER)}[\\s\\S]*?${escapeRegex(ABILITY_REGISTRY_END_MARKER)}\\n?`, "u");
|
|
3367
4859
|
const nextSource = existingSource ? generatedSectionPattern.test(existingSource) ? existingSource.replace(generatedSectionPattern, generatedSection) : `${existingSource.trimEnd()}
|
|
3368
4860
|
|
|
3369
4861
|
${generatedSection}` : generatedSection;
|
|
3370
|
-
await
|
|
4862
|
+
await fsp6.writeFile(registryPath, nextSource, "utf8");
|
|
3371
4863
|
}
|
|
3372
4864
|
async function ensureAbilityBootstrapAnchors(workspace) {
|
|
3373
4865
|
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
@@ -3442,7 +4934,6 @@ function ${enqueueFunctionName}() {
|
|
|
3442
4934
|
/add_action\(\s*["']init["']\s*,\s*["'][^"']+_load_textdomain["']\s*\);\s*\n/u,
|
|
3443
4935
|
/\?>\s*$/u
|
|
3444
4936
|
];
|
|
3445
|
-
const hasPhpFunctionDefinition = (functionName) => new RegExp(`function\\s+${escapeRegex3(functionName)}\\s*\\(`, "u").test(nextSource);
|
|
3446
4937
|
const insertPhpSnippet = (snippet) => {
|
|
3447
4938
|
for (const anchor of insertionAnchors) {
|
|
3448
4939
|
const candidate = nextSource.replace(anchor, (match) => `${snippet}
|
|
@@ -3467,13 +4958,13 @@ ${snippet}
|
|
|
3467
4958
|
${snippet}
|
|
3468
4959
|
`;
|
|
3469
4960
|
};
|
|
3470
|
-
if (!hasPhpFunctionDefinition(loadFunctionName)) {
|
|
4961
|
+
if (!hasPhpFunctionDefinition(nextSource, loadFunctionName)) {
|
|
3471
4962
|
insertPhpSnippet(loadFunction);
|
|
3472
4963
|
}
|
|
3473
|
-
if (!hasPhpFunctionDefinition(enqueueFunctionName)) {
|
|
4964
|
+
if (!hasPhpFunctionDefinition(nextSource, enqueueFunctionName)) {
|
|
3474
4965
|
insertPhpSnippet(enqueueFunction);
|
|
3475
|
-
} else if (!
|
|
3476
|
-
nextSource =
|
|
4966
|
+
} else if (!findPhpFunctionRange(nextSource, enqueueFunctionName)?.source.includes("wp_enqueue_script_module")) {
|
|
4967
|
+
nextSource = replacePhpFunctionDefinition(nextSource, enqueueFunctionName, enqueueFunction, { trimReplacementStart: true }) ?? nextSource;
|
|
3477
4968
|
}
|
|
3478
4969
|
if (!nextSource.includes(loadHook)) {
|
|
3479
4970
|
appendPhpSnippet(loadHook);
|
|
@@ -3488,27 +4979,27 @@ ${snippet}
|
|
|
3488
4979
|
});
|
|
3489
4980
|
}
|
|
3490
4981
|
async function ensureAbilityPackageScripts(workspace) {
|
|
3491
|
-
const packageJsonPath =
|
|
3492
|
-
const packageJson = JSON.parse(await
|
|
4982
|
+
const packageJsonPath = path9.join(workspace.projectDir, "package.json");
|
|
4983
|
+
const packageJson = JSON.parse(await fsp6.readFile(packageJsonPath, "utf8"));
|
|
3493
4984
|
const nextScripts = {
|
|
3494
4985
|
...packageJson.scripts ?? {},
|
|
3495
4986
|
"sync-abilities": packageJson.scripts?.["sync-abilities"] ?? "tsx scripts/sync-abilities.ts"
|
|
3496
4987
|
};
|
|
3497
4988
|
const nextDependencies = {
|
|
3498
4989
|
...packageJson.dependencies ?? {},
|
|
3499
|
-
[WP_ABILITIES_SCRIPT_MODULE_ID]: resolveManagedDependencyVersion(packageJson.dependencies?.[WP_ABILITIES_SCRIPT_MODULE_ID],
|
|
3500
|
-
[WP_CORE_ABILITIES_SCRIPT_MODULE_ID]: resolveManagedDependencyVersion(packageJson.dependencies?.[WP_CORE_ABILITIES_SCRIPT_MODULE_ID],
|
|
4990
|
+
[WP_ABILITIES_SCRIPT_MODULE_ID]: resolveManagedDependencyVersion(packageJson.dependencies?.[WP_ABILITIES_SCRIPT_MODULE_ID], DEFAULT_WORDPRESS_ABILITIES_VERSION),
|
|
4991
|
+
[WP_CORE_ABILITIES_SCRIPT_MODULE_ID]: resolveManagedDependencyVersion(packageJson.dependencies?.[WP_CORE_ABILITIES_SCRIPT_MODULE_ID], DEFAULT_WORDPRESS_CORE_ABILITIES_VERSION)
|
|
3501
4992
|
};
|
|
3502
4993
|
if (JSON.stringify(nextScripts) === JSON.stringify(packageJson.scripts ?? {}) && JSON.stringify(nextDependencies) === JSON.stringify(packageJson.dependencies ?? {})) {
|
|
3503
4994
|
return;
|
|
3504
4995
|
}
|
|
3505
4996
|
packageJson.scripts = nextScripts;
|
|
3506
4997
|
packageJson.dependencies = nextDependencies;
|
|
3507
|
-
await
|
|
4998
|
+
await fsp6.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, "\t")}
|
|
3508
4999
|
`, "utf8");
|
|
3509
5000
|
}
|
|
3510
5001
|
async function ensureAbilitySyncProjectAnchors(workspace) {
|
|
3511
|
-
const syncProjectScriptPath =
|
|
5002
|
+
const syncProjectScriptPath = path9.join(workspace.projectDir, "scripts", "sync-project.ts");
|
|
3512
5003
|
await patchFile(syncProjectScriptPath, (source) => {
|
|
3513
5004
|
let nextSource = source;
|
|
3514
5005
|
const syncRestConst = "const syncRestScriptPath = path.join( 'scripts', 'sync-rest-contracts.ts' );";
|
|
@@ -3523,7 +5014,7 @@ async function ensureAbilitySyncProjectAnchors(workspace) {
|
|
|
3523
5014
|
if (!nextSource.includes(syncAbilitiesConst)) {
|
|
3524
5015
|
if (!nextSource.includes(syncRestConst)) {
|
|
3525
5016
|
throw new Error([
|
|
3526
|
-
`ensureAbilitySyncProjectAnchors could not patch ${
|
|
5017
|
+
`ensureAbilitySyncProjectAnchors could not patch ${path9.basename(syncProjectScriptPath)}.`,
|
|
3527
5018
|
"Missing the expected sync-rest script constant in scripts/sync-project.ts.",
|
|
3528
5019
|
"Restore the generated template or wire sync-abilities manually before retrying."
|
|
3529
5020
|
].join(" "));
|
|
@@ -3534,7 +5025,7 @@ ${syncAbilitiesConst}`);
|
|
|
3534
5025
|
if (!nextSource.includes("runSyncScript( syncAbilitiesScriptPath, options );")) {
|
|
3535
5026
|
if (!syncRestBlockPattern.test(nextSource)) {
|
|
3536
5027
|
throw new Error([
|
|
3537
|
-
`ensureAbilitySyncProjectAnchors could not patch ${
|
|
5028
|
+
`ensureAbilitySyncProjectAnchors could not patch ${path9.basename(syncProjectScriptPath)}.`,
|
|
3538
5029
|
"Missing the expected sync-rest invocation block in scripts/sync-project.ts.",
|
|
3539
5030
|
"Restore the generated template or wire sync-abilities manually before retrying."
|
|
3540
5031
|
].join(" "));
|
|
@@ -3547,7 +5038,7 @@ ${syncAbilitiesBlock}`);
|
|
|
3547
5038
|
});
|
|
3548
5039
|
}
|
|
3549
5040
|
async function ensureAbilityBuildScriptAnchors(workspace) {
|
|
3550
|
-
const buildScriptPath =
|
|
5041
|
+
const buildScriptPath = path9.join(workspace.projectDir, "scripts", "build-workspace.mjs");
|
|
3551
5042
|
await patchFile(buildScriptPath, (source) => {
|
|
3552
5043
|
let nextSource = source;
|
|
3553
5044
|
if (/['"]src\/abilities\/index\.(?:ts|js)['"]/u.test(nextSource)) {
|
|
@@ -3557,7 +5048,7 @@ async function ensureAbilityBuildScriptAnchors(workspace) {
|
|
|
3557
5048
|
const match = nextSource.match(sharedEntriesPattern);
|
|
3558
5049
|
if (!match || !match[2].includes("src/bindings/index.ts") || !match[2].includes("src/editor-plugins/index.ts")) {
|
|
3559
5050
|
throw new Error([
|
|
3560
|
-
`ensureAbilityBuildScriptAnchors could not patch ${
|
|
5051
|
+
`ensureAbilityBuildScriptAnchors could not patch ${path9.basename(buildScriptPath)}.`,
|
|
3561
5052
|
"Missing the expected shared editor entries array in scripts/build-workspace.mjs.",
|
|
3562
5053
|
"Restore the generated template or wire abilities/index manually before retrying."
|
|
3563
5054
|
].join(" "));
|
|
@@ -3574,7 +5065,7 @@ async function ensureAbilityBuildScriptAnchors(workspace) {
|
|
|
3574
5065
|
});
|
|
3575
5066
|
}
|
|
3576
5067
|
async function ensureAbilityWebpackAnchors(workspace) {
|
|
3577
|
-
const webpackConfigPath =
|
|
5068
|
+
const webpackConfigPath = path9.join(workspace.projectDir, "webpack.config.js");
|
|
3578
5069
|
await patchFile(webpackConfigPath, (source) => {
|
|
3579
5070
|
if (/['"]abilities\/index['"]/u.test(source)) {
|
|
3580
5071
|
return source;
|
|
@@ -3605,7 +5096,7 @@ $2`);
|
|
|
3605
5096
|
const match = source.match(sharedEntriesPattern);
|
|
3606
5097
|
if (!match || !match[1].includes("bindings/index") || !match[1].includes("editor-plugins/index")) {
|
|
3607
5098
|
throw new Error([
|
|
3608
|
-
`ensureAbilityWebpackAnchors could not patch ${
|
|
5099
|
+
`ensureAbilityWebpackAnchors could not patch ${path9.basename(webpackConfigPath)}.`,
|
|
3609
5100
|
"Missing the expected shared editor entries block in webpack.config.js.",
|
|
3610
5101
|
"Restore the generated template or wire abilities/index manually before retrying."
|
|
3611
5102
|
].join(" "));
|
|
@@ -3635,20 +5126,20 @@ async function runAddAbilityCommand({
|
|
|
3635
5126
|
const inventory = readWorkspaceInventory(workspace.projectDir);
|
|
3636
5127
|
assertAbilityDoesNotExist(workspace.projectDir, abilitySlug, inventory);
|
|
3637
5128
|
const compatibilityPolicy = resolveScaffoldCompatibilityPolicy(REQUIRED_WORKSPACE_ABILITY_COMPATIBILITY);
|
|
3638
|
-
const blockConfigPath =
|
|
5129
|
+
const blockConfigPath = path9.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
3639
5130
|
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
3640
|
-
const buildScriptPath =
|
|
3641
|
-
const packageJsonPath =
|
|
3642
|
-
const syncAbilitiesScriptPath =
|
|
3643
|
-
const syncProjectScriptPath =
|
|
3644
|
-
const webpackConfigPath =
|
|
5131
|
+
const buildScriptPath = path9.join(workspace.projectDir, "scripts", "build-workspace.mjs");
|
|
5132
|
+
const packageJsonPath = path9.join(workspace.projectDir, "package.json");
|
|
5133
|
+
const syncAbilitiesScriptPath = path9.join(workspace.projectDir, "scripts", "sync-abilities.ts");
|
|
5134
|
+
const syncProjectScriptPath = path9.join(workspace.projectDir, "scripts", "sync-project.ts");
|
|
5135
|
+
const webpackConfigPath = path9.join(workspace.projectDir, "webpack.config.js");
|
|
3645
5136
|
const abilitiesIndexPath = resolveAbilityRegistryPath(workspace.projectDir);
|
|
3646
|
-
const abilityDir =
|
|
3647
|
-
const configFilePath =
|
|
3648
|
-
const typesFilePath =
|
|
3649
|
-
const dataFilePath =
|
|
3650
|
-
const clientFilePath =
|
|
3651
|
-
const phpFilePath =
|
|
5137
|
+
const abilityDir = path9.join(workspace.projectDir, "src", "abilities", abilitySlug);
|
|
5138
|
+
const configFilePath = path9.join(abilityDir, "ability.config.json");
|
|
5139
|
+
const typesFilePath = path9.join(abilityDir, "types.ts");
|
|
5140
|
+
const dataFilePath = path9.join(abilityDir, "data.ts");
|
|
5141
|
+
const clientFilePath = path9.join(abilityDir, "client.ts");
|
|
5142
|
+
const phpFilePath = path9.join(workspace.projectDir, "inc", "abilities", `${abilitySlug}.php`);
|
|
3652
5143
|
const mutationSnapshot = {
|
|
3653
5144
|
fileSources: await snapshotWorkspaceFiles([
|
|
3654
5145
|
blockConfigPath,
|
|
@@ -3664,21 +5155,21 @@ async function runAddAbilityCommand({
|
|
|
3664
5155
|
targetPaths: [abilityDir, phpFilePath, syncAbilitiesScriptPath]
|
|
3665
5156
|
};
|
|
3666
5157
|
try {
|
|
3667
|
-
await
|
|
3668
|
-
await
|
|
5158
|
+
await fsp6.mkdir(abilityDir, { recursive: true });
|
|
5159
|
+
await fsp6.mkdir(path9.dirname(phpFilePath), { recursive: true });
|
|
3669
5160
|
await ensureAbilityBootstrapAnchors(workspace);
|
|
3670
5161
|
await patchFile(bootstrapPath, (source) => updatePluginHeaderCompatibility(source, compatibilityPolicy));
|
|
3671
5162
|
await ensureAbilityPackageScripts(workspace);
|
|
3672
5163
|
await ensureAbilitySyncProjectAnchors(workspace);
|
|
3673
5164
|
await ensureAbilityBuildScriptAnchors(workspace);
|
|
3674
5165
|
await ensureAbilityWebpackAnchors(workspace);
|
|
3675
|
-
await
|
|
3676
|
-
await
|
|
3677
|
-
await
|
|
3678
|
-
await
|
|
3679
|
-
await
|
|
3680
|
-
await
|
|
3681
|
-
const pascalCase =
|
|
5166
|
+
await fsp6.writeFile(syncAbilitiesScriptPath, buildAbilitySyncScriptSource(), "utf8");
|
|
5167
|
+
await fsp6.writeFile(configFilePath, buildAbilityConfigSource(abilitySlug, workspace.workspace.namespace), "utf8");
|
|
5168
|
+
await fsp6.writeFile(typesFilePath, buildAbilityTypesSource(abilitySlug), "utf8");
|
|
5169
|
+
await fsp6.writeFile(dataFilePath, buildAbilityDataSource(abilitySlug), "utf8");
|
|
5170
|
+
await fsp6.writeFile(clientFilePath, buildAbilityClientSource(abilitySlug), "utf8");
|
|
5171
|
+
await fsp6.writeFile(phpFilePath, buildAbilityPhpSource(abilitySlug, workspace), "utf8");
|
|
5172
|
+
const pascalCase = toPascalCase(abilitySlug);
|
|
3682
5173
|
await syncTypeSchemas2({
|
|
3683
5174
|
jsonSchemaFile: `src/abilities/${abilitySlug}/input.schema.json`,
|
|
3684
5175
|
projectRoot: workspace.projectDir,
|
|
@@ -3705,12 +5196,12 @@ async function runAddAbilityCommand({
|
|
|
3705
5196
|
}
|
|
3706
5197
|
}
|
|
3707
5198
|
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-ai.ts
|
|
3708
|
-
import { promises as
|
|
3709
|
-
import
|
|
5199
|
+
import { promises as fsp8 } from "fs";
|
|
5200
|
+
import path12 from "path";
|
|
3710
5201
|
|
|
3711
5202
|
// ../wp-typia-project-tools/src/runtime/ai-feature-artifacts.ts
|
|
3712
5203
|
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
3713
|
-
import
|
|
5204
|
+
import path10 from "path";
|
|
3714
5205
|
import {
|
|
3715
5206
|
defineEndpointManifest as defineEndpointManifest2,
|
|
3716
5207
|
syncEndpointClient as syncEndpointClient2,
|
|
@@ -3736,7 +5227,7 @@ function normalizeGeneratedArtifactContent(content) {
|
|
|
3736
5227
|
}
|
|
3737
5228
|
async function reconcileGeneratedArtifact(options) {
|
|
3738
5229
|
if (!options.check) {
|
|
3739
|
-
await mkdir(
|
|
5230
|
+
await mkdir(path10.dirname(options.filePath), {
|
|
3740
5231
|
recursive: true
|
|
3741
5232
|
});
|
|
3742
5233
|
await writeFile(options.filePath, options.content, "utf8");
|
|
@@ -3808,8 +5299,8 @@ async function syncAiFeatureRestArtifacts({
|
|
|
3808
5299
|
const manifest = buildAiFeatureEndpointManifest(variables);
|
|
3809
5300
|
for (const [baseName, contract] of Object.entries(manifest.contracts)) {
|
|
3810
5301
|
await syncTypeSchemas3({
|
|
3811
|
-
jsonSchemaFile:
|
|
3812
|
-
openApiFile:
|
|
5302
|
+
jsonSchemaFile: path10.join(outputDir, "api-schemas", `${baseName}.schema.json`),
|
|
5303
|
+
openApiFile: path10.join(outputDir, "api-schemas", `${baseName}.openapi.json`),
|
|
3813
5304
|
projectRoot: projectDir,
|
|
3814
5305
|
sourceTypeName: contract.sourceTypeName,
|
|
3815
5306
|
typesFile
|
|
@@ -3817,7 +5308,7 @@ async function syncAiFeatureRestArtifacts({
|
|
|
3817
5308
|
}
|
|
3818
5309
|
await syncRestOpenApi2({
|
|
3819
5310
|
manifest,
|
|
3820
|
-
openApiFile:
|
|
5311
|
+
openApiFile: path10.join(outputDir, "api.openapi.json"),
|
|
3821
5312
|
projectRoot: projectDir,
|
|
3822
5313
|
typesFile
|
|
3823
5314
|
}, executionOptions);
|
|
@@ -3835,35 +5326,32 @@ async function syncAiFeatureSchemaArtifact({
|
|
|
3835
5326
|
outputDir,
|
|
3836
5327
|
projectDir
|
|
3837
5328
|
}) {
|
|
3838
|
-
const sourceSchemaPath =
|
|
5329
|
+
const sourceSchemaPath = path10.join(projectDir, outputDir, "api-schemas", "feature-result.schema.json");
|
|
3839
5330
|
const responseSchema = assertJsonObject(JSON.parse(await readFile(sourceSchemaPath, "utf8")), sourceSchemaPath);
|
|
3840
5331
|
const aiSchema = projectWordPressAiSchema(responseSchema);
|
|
3841
5332
|
await reconcileGeneratedArtifact({
|
|
3842
5333
|
check,
|
|
3843
5334
|
content: `${JSON.stringify(aiSchema, null, 2)}
|
|
3844
5335
|
`,
|
|
3845
|
-
filePath:
|
|
5336
|
+
filePath: path10.join(projectDir, aiSchemaFile),
|
|
3846
5337
|
label: "AI feature schema"
|
|
3847
5338
|
});
|
|
3848
5339
|
return {
|
|
3849
5340
|
aiSchema,
|
|
3850
|
-
aiSchemaPath:
|
|
5341
|
+
aiSchemaPath: path10.join(projectDir, aiSchemaFile),
|
|
3851
5342
|
check,
|
|
3852
5343
|
sourceSchemaPath
|
|
3853
5344
|
};
|
|
3854
5345
|
}
|
|
3855
5346
|
|
|
3856
5347
|
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-ai-source-emitters.ts
|
|
3857
|
-
function toPascalCaseFromAiFeatureSlug(slug) {
|
|
3858
|
-
return normalizeBlockSlug(slug).split("-").filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1)).join("");
|
|
3859
|
-
}
|
|
3860
5348
|
function indentMultiline2(source, prefix) {
|
|
3861
5349
|
return source.split(`
|
|
3862
5350
|
`).map((line) => `${prefix}${line}`).join(`
|
|
3863
5351
|
`);
|
|
3864
5352
|
}
|
|
3865
5353
|
function buildAiFeatureConfigEntry(aiFeatureSlug, namespace) {
|
|
3866
|
-
const pascalCase =
|
|
5354
|
+
const pascalCase = toPascalCase(aiFeatureSlug);
|
|
3867
5355
|
const title = toTitleCase(aiFeatureSlug);
|
|
3868
5356
|
const compatibilityPolicy = resolveScaffoldCompatibilityPolicy(OPTIONAL_WORDPRESS_AI_CLIENT_COMPATIBILITY);
|
|
3869
5357
|
const manifest = buildAiFeatureEndpointManifest({
|
|
@@ -3893,7 +5381,7 @@ function buildAiFeatureConfigEntry(aiFeatureSlug, namespace) {
|
|
|
3893
5381
|
`);
|
|
3894
5382
|
}
|
|
3895
5383
|
function buildAiFeatureTypesSource(aiFeatureSlug) {
|
|
3896
|
-
const pascalCase =
|
|
5384
|
+
const pascalCase = toPascalCase(aiFeatureSlug);
|
|
3897
5385
|
return `import { tags } from 'typia';
|
|
3898
5386
|
|
|
3899
5387
|
export interface ${pascalCase}AiFeatureRequest {
|
|
@@ -3931,7 +5419,7 @@ export interface ${pascalCase}AiFeatureResponse {
|
|
|
3931
5419
|
`;
|
|
3932
5420
|
}
|
|
3933
5421
|
function buildAiFeatureValidatorsSource(aiFeatureSlug) {
|
|
3934
|
-
const pascalCase =
|
|
5422
|
+
const pascalCase = toPascalCase(aiFeatureSlug);
|
|
3935
5423
|
return `import typia from 'typia';
|
|
3936
5424
|
|
|
3937
5425
|
import { toValidationResult } from '@wp-typia/rest';
|
|
@@ -3962,7 +5450,7 @@ export const apiValidators = {
|
|
|
3962
5450
|
`;
|
|
3963
5451
|
}
|
|
3964
5452
|
function buildAiFeatureApiSource(aiFeatureSlug) {
|
|
3965
|
-
const pascalCase =
|
|
5453
|
+
const pascalCase = toPascalCase(aiFeatureSlug);
|
|
3966
5454
|
return `import {
|
|
3967
5455
|
callEndpoint,
|
|
3968
5456
|
resolveRestRouteUrl,
|
|
@@ -4017,7 +5505,7 @@ export function runAiFeature( request: ${pascalCase}AiFeatureRequest ) {
|
|
|
4017
5505
|
`;
|
|
4018
5506
|
}
|
|
4019
5507
|
function buildAiFeatureDataSource(aiFeatureSlug) {
|
|
4020
|
-
const pascalCase =
|
|
5508
|
+
const pascalCase = toPascalCase(aiFeatureSlug);
|
|
4021
5509
|
return `import {
|
|
4022
5510
|
useEndpointMutation,
|
|
4023
5511
|
type UseEndpointMutationOptions,
|
|
@@ -4173,12 +5661,9 @@ main().catch( ( error ) => {
|
|
|
4173
5661
|
}
|
|
4174
5662
|
|
|
4175
5663
|
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-ai-anchors.ts
|
|
4176
|
-
import { promises as
|
|
4177
|
-
import
|
|
5664
|
+
import { promises as fsp7 } from "fs";
|
|
5665
|
+
import path11 from "path";
|
|
4178
5666
|
var AI_FEATURE_SERVER_GLOB = "/inc/ai-features/*.php";
|
|
4179
|
-
function escapeRegex4(value) {
|
|
4180
|
-
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
4181
|
-
}
|
|
4182
5667
|
async function ensureAiFeatureBootstrapAnchors(workspace) {
|
|
4183
5668
|
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
4184
5669
|
await patchFile(bootstrapPath, (source) => {
|
|
@@ -4197,7 +5682,6 @@ function ${registerFunctionName}() {
|
|
|
4197
5682
|
/add_action\(\s*["']init["']\s*,\s*["'][^"']+_load_textdomain["']\s*\);\s*\n/u,
|
|
4198
5683
|
/\?>\s*$/u
|
|
4199
5684
|
];
|
|
4200
|
-
const hasPhpFunctionDefinition = (functionName) => new RegExp(`function\\s+${escapeRegex4(functionName)}\\s*\\(`, "u").test(nextSource);
|
|
4201
5685
|
const insertPhpSnippet = (snippet) => {
|
|
4202
5686
|
for (const anchor of insertionAnchors) {
|
|
4203
5687
|
const candidate = nextSource.replace(anchor, (match) => `${snippet}
|
|
@@ -4222,11 +5706,11 @@ ${snippet}
|
|
|
4222
5706
|
${snippet}
|
|
4223
5707
|
`;
|
|
4224
5708
|
};
|
|
4225
|
-
if (!hasPhpFunctionDefinition(registerFunctionName)) {
|
|
5709
|
+
if (!hasPhpFunctionDefinition(nextSource, registerFunctionName)) {
|
|
4226
5710
|
insertPhpSnippet(registerFunction);
|
|
4227
5711
|
} else if (!nextSource.includes(AI_FEATURE_SERVER_GLOB)) {
|
|
4228
5712
|
throw new Error([
|
|
4229
|
-
`Unable to patch ${
|
|
5713
|
+
`Unable to patch ${path11.basename(bootstrapPath)} in ensureAiFeatureBootstrapAnchors.`,
|
|
4230
5714
|
`The existing ${registerFunctionName}() definition does not include ${AI_FEATURE_SERVER_GLOB}.`,
|
|
4231
5715
|
"Restore the generated bootstrap shape or wire the AI feature loader manually before retrying."
|
|
4232
5716
|
].join(" "));
|
|
@@ -4238,8 +5722,8 @@ ${snippet}
|
|
|
4238
5722
|
});
|
|
4239
5723
|
}
|
|
4240
5724
|
async function ensureAiFeaturePackageScripts(workspace) {
|
|
4241
|
-
const packageJsonPath =
|
|
4242
|
-
const packageJson = JSON.parse(await
|
|
5725
|
+
const packageJsonPath = path11.join(workspace.projectDir, "package.json");
|
|
5726
|
+
const packageJson = JSON.parse(await fsp7.readFile(packageJsonPath, "utf8"));
|
|
4243
5727
|
const nextScripts = {
|
|
4244
5728
|
...packageJson.scripts ?? {},
|
|
4245
5729
|
"sync-ai": packageJson.scripts?.["sync-ai"] ?? "tsx scripts/sync-ai-features.ts"
|
|
@@ -4258,7 +5742,7 @@ async function ensureAiFeaturePackageScripts(workspace) {
|
|
|
4258
5742
|
}
|
|
4259
5743
|
packageJson.scripts = nextScripts;
|
|
4260
5744
|
packageJson.devDependencies = nextDevDependencies;
|
|
4261
|
-
await
|
|
5745
|
+
await fsp7.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, "\t")}
|
|
4262
5746
|
`, "utf8");
|
|
4263
5747
|
return {
|
|
4264
5748
|
addedProjectToolsDependency,
|
|
@@ -4266,7 +5750,7 @@ async function ensureAiFeaturePackageScripts(workspace) {
|
|
|
4266
5750
|
};
|
|
4267
5751
|
}
|
|
4268
5752
|
async function ensureAiFeatureSyncProjectAnchors(workspace) {
|
|
4269
|
-
const syncProjectScriptPath =
|
|
5753
|
+
const syncProjectScriptPath = path11.join(workspace.projectDir, "scripts", "sync-project.ts");
|
|
4270
5754
|
await patchFile(syncProjectScriptPath, (source) => {
|
|
4271
5755
|
let nextSource = source;
|
|
4272
5756
|
const syncRestConst = "const syncRestScriptPath = path.join( 'scripts', 'sync-rest-contracts.ts' );";
|
|
@@ -4281,7 +5765,7 @@ async function ensureAiFeatureSyncProjectAnchors(workspace) {
|
|
|
4281
5765
|
if (!nextSource.includes(syncAiConst)) {
|
|
4282
5766
|
if (!nextSource.includes(syncRestConst)) {
|
|
4283
5767
|
throw new Error([
|
|
4284
|
-
`ensureAiFeatureSyncProjectAnchors could not patch ${
|
|
5768
|
+
`ensureAiFeatureSyncProjectAnchors could not patch ${path11.basename(syncProjectScriptPath)}.`,
|
|
4285
5769
|
"Missing the expected sync-rest script constant in scripts/sync-project.ts.",
|
|
4286
5770
|
"Restore the generated template or wire sync-ai manually before retrying."
|
|
4287
5771
|
].join(" "));
|
|
@@ -4292,7 +5776,7 @@ ${syncAiConst}`);
|
|
|
4292
5776
|
if (!nextSource.includes("runSyncScript( syncAiScriptPath, options );")) {
|
|
4293
5777
|
if (!syncRestBlockPattern.test(nextSource)) {
|
|
4294
5778
|
throw new Error([
|
|
4295
|
-
`ensureAiFeatureSyncProjectAnchors could not patch ${
|
|
5779
|
+
`ensureAiFeatureSyncProjectAnchors could not patch ${path11.basename(syncProjectScriptPath)}.`,
|
|
4296
5780
|
"Missing the expected sync-rest invocation block in scripts/sync-project.ts.",
|
|
4297
5781
|
"Restore the generated template or wire sync-ai manually before retrying."
|
|
4298
5782
|
].join(" "));
|
|
@@ -4307,7 +5791,7 @@ ${syncAiBlock}`);
|
|
|
4307
5791
|
function assertSyncRestAnchor2(nextSource, target, anchorDescription, hasAnchor, syncRestScriptPath) {
|
|
4308
5792
|
if (!nextSource.includes(target) && !hasAnchor) {
|
|
4309
5793
|
throw new Error([
|
|
4310
|
-
`ensureAiFeatureSyncRestAnchors could not patch ${
|
|
5794
|
+
`ensureAiFeatureSyncRestAnchors could not patch ${path11.basename(syncRestScriptPath)}.`,
|
|
4311
5795
|
`Missing expected ${anchorDescription} anchor in scripts/sync-rest-contracts.ts.`,
|
|
4312
5796
|
"Restore the generated template or add the AI feature wiring manually before retrying."
|
|
4313
5797
|
].join(" "));
|
|
@@ -4322,7 +5806,7 @@ function replaceRequiredSyncRestSource2(nextSource, target, anchor, replacement,
|
|
|
4322
5806
|
return nextSource.replace(anchor, replacement);
|
|
4323
5807
|
}
|
|
4324
5808
|
async function ensureAiFeatureSyncRestAnchors(workspace) {
|
|
4325
|
-
const syncRestScriptPath =
|
|
5809
|
+
const syncRestScriptPath = path11.join(workspace.projectDir, "scripts", "sync-rest-contracts.ts");
|
|
4326
5810
|
await patchFile(syncRestScriptPath, (source) => {
|
|
4327
5811
|
let nextSource = source;
|
|
4328
5812
|
const importAnchor = [
|
|
@@ -4451,9 +5935,6 @@ async function ensureAiFeatureSyncRestAnchors(workspace) {
|
|
|
4451
5935
|
}
|
|
4452
5936
|
|
|
4453
5937
|
// ../wp-typia-project-tools/src/runtime/cli-add-workspace-ai.ts
|
|
4454
|
-
function quotePhpString4(value) {
|
|
4455
|
-
return `'${value.replace(/\\/gu, "\\\\").replace(/'/gu, "\\'")}'`;
|
|
4456
|
-
}
|
|
4457
5938
|
function buildAiFeaturePhpSource(aiFeatureSlug, namespace, phpPrefix, textDomain) {
|
|
4458
5939
|
const aiFeatureTitle = toTitleCase(aiFeatureSlug);
|
|
4459
5940
|
const aiFeaturePhpId = aiFeatureSlug.replace(/-/g, "_");
|
|
@@ -4549,7 +6030,7 @@ if ( ! function_exists( '${buildPromptFunctionName}' ) ) {
|
|
|
4549
6030
|
function ${buildPromptFunctionName}( array $payload ) {
|
|
4550
6031
|
return sprintf(
|
|
4551
6032
|
'You are helping with the %1$s AI workflow. Read the JSON request payload and return JSON that matches the provided schema. Request payload: %2$s',
|
|
4552
|
-
${
|
|
6033
|
+
${quotePhpString(aiFeatureTitle)},
|
|
4553
6034
|
wp_json_encode( $payload )
|
|
4554
6035
|
);
|
|
4555
6036
|
}
|
|
@@ -4678,8 +6159,8 @@ if ( ! function_exists( '${adminNoticeFunctionName}' ) ) {
|
|
|
4678
6159
|
|
|
4679
6160
|
$message = sprintf(
|
|
4680
6161
|
/* translators: %s: AI feature name. */
|
|
4681
|
-
__( 'The %s AI feature is optional and remains disabled until the WordPress AI Client is available with structured text generation support for the generated schema.', ${
|
|
4682
|
-
${
|
|
6162
|
+
__( 'The %s AI feature is optional and remains disabled until the WordPress AI Client is available with structured text generation support for the generated schema.', ${quotePhpString(textDomain)} ),
|
|
6163
|
+
${quotePhpString(aiFeatureTitle)}
|
|
4683
6164
|
);
|
|
4684
6165
|
printf( '<div class="notice notice-warning"><p>%s</p></div>', esc_html( $message ) );
|
|
4685
6166
|
}
|
|
@@ -4814,7 +6295,7 @@ if ( ! function_exists( '${handlerFunctionName}' ) ) {
|
|
|
4814
6295
|
if ( ! function_exists( '${registerRoutesFunctionName}' ) ) {
|
|
4815
6296
|
function ${registerRoutesFunctionName}() {
|
|
4816
6297
|
register_rest_route(
|
|
4817
|
-
${
|
|
6298
|
+
${quotePhpString(namespace)},
|
|
4818
6299
|
'/ai/${aiFeatureSlug}',
|
|
4819
6300
|
array(
|
|
4820
6301
|
array(
|
|
@@ -4842,18 +6323,18 @@ async function runAddAiFeatureCommand({
|
|
|
4842
6323
|
const compatibilityPolicy = resolveScaffoldCompatibilityPolicy(OPTIONAL_WORDPRESS_AI_CLIENT_COMPATIBILITY);
|
|
4843
6324
|
const inventory = readWorkspaceInventory(workspace.projectDir);
|
|
4844
6325
|
assertAiFeatureDoesNotExist(workspace.projectDir, aiFeatureSlug, inventory);
|
|
4845
|
-
const blockConfigPath =
|
|
6326
|
+
const blockConfigPath = path12.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
4846
6327
|
const bootstrapPath = getWorkspaceBootstrapPath(workspace);
|
|
4847
|
-
const packageJsonPath =
|
|
4848
|
-
const syncAiScriptPath =
|
|
4849
|
-
const syncProjectScriptPath =
|
|
4850
|
-
const syncRestScriptPath =
|
|
4851
|
-
const aiFeatureDir =
|
|
4852
|
-
const typesFilePath =
|
|
4853
|
-
const validatorsFilePath =
|
|
4854
|
-
const apiFilePath =
|
|
4855
|
-
const dataFilePath =
|
|
4856
|
-
const phpFilePath =
|
|
6328
|
+
const packageJsonPath = path12.join(workspace.projectDir, "package.json");
|
|
6329
|
+
const syncAiScriptPath = path12.join(workspace.projectDir, "scripts", "sync-ai-features.ts");
|
|
6330
|
+
const syncProjectScriptPath = path12.join(workspace.projectDir, "scripts", "sync-project.ts");
|
|
6331
|
+
const syncRestScriptPath = path12.join(workspace.projectDir, "scripts", "sync-rest-contracts.ts");
|
|
6332
|
+
const aiFeatureDir = path12.join(workspace.projectDir, "src", "ai-features", aiFeatureSlug);
|
|
6333
|
+
const typesFilePath = path12.join(aiFeatureDir, "api-types.ts");
|
|
6334
|
+
const validatorsFilePath = path12.join(aiFeatureDir, "api-validators.ts");
|
|
6335
|
+
const apiFilePath = path12.join(aiFeatureDir, "api.ts");
|
|
6336
|
+
const dataFilePath = path12.join(aiFeatureDir, "data.ts");
|
|
6337
|
+
const phpFilePath = path12.join(workspace.projectDir, "inc", "ai-features", `${aiFeatureSlug}.php`);
|
|
4857
6338
|
const mutationSnapshot = {
|
|
4858
6339
|
fileSources: await snapshotWorkspaceFiles([
|
|
4859
6340
|
blockConfigPath,
|
|
@@ -4867,23 +6348,23 @@ async function runAddAiFeatureCommand({
|
|
|
4867
6348
|
targetPaths: [aiFeatureDir, phpFilePath, syncAiScriptPath]
|
|
4868
6349
|
};
|
|
4869
6350
|
try {
|
|
4870
|
-
await
|
|
4871
|
-
await
|
|
6351
|
+
await fsp8.mkdir(aiFeatureDir, { recursive: true });
|
|
6352
|
+
await fsp8.mkdir(path12.dirname(phpFilePath), { recursive: true });
|
|
4872
6353
|
await ensureAiFeatureBootstrapAnchors(workspace);
|
|
4873
6354
|
await patchFile(bootstrapPath, (source) => updatePluginHeaderCompatibility(source, compatibilityPolicy));
|
|
4874
6355
|
const packageScriptChanges = await ensureAiFeaturePackageScripts(workspace);
|
|
4875
6356
|
await ensureAiFeatureSyncProjectAnchors(workspace);
|
|
4876
6357
|
await ensureAiFeatureSyncRestAnchors(workspace);
|
|
4877
|
-
await
|
|
4878
|
-
await
|
|
4879
|
-
await
|
|
4880
|
-
await
|
|
4881
|
-
await
|
|
4882
|
-
await
|
|
4883
|
-
const pascalCase =
|
|
6358
|
+
await fsp8.writeFile(syncAiScriptPath, buildAiFeatureSyncScriptSource(), "utf8");
|
|
6359
|
+
await fsp8.writeFile(typesFilePath, buildAiFeatureTypesSource(aiFeatureSlug), "utf8");
|
|
6360
|
+
await fsp8.writeFile(validatorsFilePath, buildAiFeatureValidatorsSource(aiFeatureSlug), "utf8");
|
|
6361
|
+
await fsp8.writeFile(apiFilePath, buildAiFeatureApiSource(aiFeatureSlug), "utf8");
|
|
6362
|
+
await fsp8.writeFile(dataFilePath, buildAiFeatureDataSource(aiFeatureSlug), "utf8");
|
|
6363
|
+
await fsp8.writeFile(phpFilePath, buildAiFeaturePhpSource(aiFeatureSlug, resolvedNamespace, workspace.workspace.phpPrefix, workspace.workspace.textDomain), "utf8");
|
|
6364
|
+
const pascalCase = toPascalCase(aiFeatureSlug);
|
|
4884
6365
|
await syncAiFeatureRestArtifacts({
|
|
4885
6366
|
clientFile: `src/ai-features/${aiFeatureSlug}/api-client.ts`,
|
|
4886
|
-
outputDir:
|
|
6367
|
+
outputDir: path12.join("src", "ai-features", aiFeatureSlug),
|
|
4887
6368
|
projectDir: workspace.projectDir,
|
|
4888
6369
|
typesFile: `src/ai-features/${aiFeatureSlug}/api-types.ts`,
|
|
4889
6370
|
validatorsFile: `src/ai-features/${aiFeatureSlug}/api-validators.ts`,
|
|
@@ -4896,7 +6377,7 @@ async function runAddAiFeatureCommand({
|
|
|
4896
6377
|
});
|
|
4897
6378
|
await syncAiFeatureSchemaArtifact({
|
|
4898
6379
|
aiSchemaFile: `src/ai-features/${aiFeatureSlug}/ai-schemas/feature-result.ai.schema.json`,
|
|
4899
|
-
outputDir:
|
|
6380
|
+
outputDir: path12.join("src", "ai-features", aiFeatureSlug),
|
|
4900
6381
|
projectDir: workspace.projectDir
|
|
4901
6382
|
});
|
|
4902
6383
|
await appendWorkspaceInventoryEntries(workspace.projectDir, {
|
|
@@ -4921,7 +6402,168 @@ async function runAddAiFeatureCommand({
|
|
|
4921
6402
|
|
|
4922
6403
|
// ../wp-typia-project-tools/src/runtime/cli-add-workspace.ts
|
|
4923
6404
|
var VARIATIONS_IMPORT_LINE = "import { registerWorkspaceVariations } from './variations';";
|
|
6405
|
+
var VARIATIONS_IMPORT_PATTERN = /^\s*import\s*\{\s*registerWorkspaceVariations\s*\}\s*from\s*["']\.\/variations["']\s*;?\s*$/mu;
|
|
4924
6406
|
var VARIATIONS_CALL_LINE = "registerWorkspaceVariations();";
|
|
6407
|
+
var VARIATIONS_CALL_PATTERN = /registerWorkspaceVariations\s*\(\s*\)\s*;?/u;
|
|
6408
|
+
var BLOCK_STYLES_IMPORT_LINE = "import { registerWorkspaceBlockStyles } from './styles';";
|
|
6409
|
+
var BLOCK_STYLES_IMPORT_PATTERN = /^\s*import\s*\{\s*registerWorkspaceBlockStyles\s*\}\s*from\s*["']\.\/styles["']\s*;?\s*$/mu;
|
|
6410
|
+
var BLOCK_STYLES_CALL_LINE = "registerWorkspaceBlockStyles();";
|
|
6411
|
+
var BLOCK_STYLES_CALL_PATTERN = /registerWorkspaceBlockStyles\s*\(\s*\)\s*;?/u;
|
|
6412
|
+
var BLOCK_TRANSFORMS_IMPORT_LINE = "import { applyWorkspaceBlockTransforms } from './transforms';";
|
|
6413
|
+
var BLOCK_TRANSFORMS_IMPORT_PATTERN = /^\s*import\s*\{\s*applyWorkspaceBlockTransforms\s*\}\s*from\s*["']\.\/transforms["']\s*;?\s*$/mu;
|
|
6414
|
+
var BLOCK_TRANSFORMS_CALL_LINE = "applyWorkspaceBlockTransforms(registration.settings);";
|
|
6415
|
+
var BLOCK_TRANSFORMS_CALL_PATTERN = /applyWorkspaceBlockTransforms\s*\(\s*registration\s*\.\s*settings\s*\)\s*;?/u;
|
|
6416
|
+
var SCAFFOLD_REGISTRATION_SETTINGS_CALL_PATTERN = /registerScaffoldBlockType\s*\(\s*registration\s*\.\s*name\s*,\s*registration\s*\.\s*settings\s*\)\s*;?/u;
|
|
6417
|
+
var FULL_BLOCK_NAME_PATTERN = /^[a-z0-9-]+\/[a-z0-9-]+$/u;
|
|
6418
|
+
function maskSourceSegment(segment) {
|
|
6419
|
+
return segment.replace(/[^\n\r]/gu, " ");
|
|
6420
|
+
}
|
|
6421
|
+
function maskTypeScriptComments(source) {
|
|
6422
|
+
return source.replace(/\/\*[\s\S]*?\*\//gu, maskSourceSegment).replace(/\/\/[^\n\r]*/gu, maskSourceSegment);
|
|
6423
|
+
}
|
|
6424
|
+
function maskTypeScriptCommentsAndLiterals(source) {
|
|
6425
|
+
let maskedSource = "";
|
|
6426
|
+
let index = 0;
|
|
6427
|
+
while (index < source.length) {
|
|
6428
|
+
const current = source[index];
|
|
6429
|
+
const next = source[index + 1];
|
|
6430
|
+
if (current === "/" && next === "/") {
|
|
6431
|
+
const start = index;
|
|
6432
|
+
index += 2;
|
|
6433
|
+
while (index < source.length && source[index] !== `
|
|
6434
|
+
` && source[index] !== "\r") {
|
|
6435
|
+
index += 1;
|
|
6436
|
+
}
|
|
6437
|
+
maskedSource += maskSourceSegment(source.slice(start, index));
|
|
6438
|
+
continue;
|
|
6439
|
+
}
|
|
6440
|
+
if (current === "/" && next === "*") {
|
|
6441
|
+
const start = index;
|
|
6442
|
+
index += 2;
|
|
6443
|
+
while (index < source.length && !(source[index] === "*" && source[index + 1] === "/")) {
|
|
6444
|
+
index += 1;
|
|
6445
|
+
}
|
|
6446
|
+
index = Math.min(index + 2, source.length);
|
|
6447
|
+
maskedSource += maskSourceSegment(source.slice(start, index));
|
|
6448
|
+
continue;
|
|
6449
|
+
}
|
|
6450
|
+
if (current === "'" || current === '"' || current === "`") {
|
|
6451
|
+
const start = index;
|
|
6452
|
+
const quote = current;
|
|
6453
|
+
index += 1;
|
|
6454
|
+
while (index < source.length) {
|
|
6455
|
+
const char = source[index];
|
|
6456
|
+
if (char === "\\") {
|
|
6457
|
+
index += 2;
|
|
6458
|
+
continue;
|
|
6459
|
+
}
|
|
6460
|
+
index += 1;
|
|
6461
|
+
if (char === quote) {
|
|
6462
|
+
break;
|
|
6463
|
+
}
|
|
6464
|
+
}
|
|
6465
|
+
maskedSource += maskSourceSegment(source.slice(start, index));
|
|
6466
|
+
continue;
|
|
6467
|
+
}
|
|
6468
|
+
maskedSource += current;
|
|
6469
|
+
index += 1;
|
|
6470
|
+
}
|
|
6471
|
+
return maskedSource;
|
|
6472
|
+
}
|
|
6473
|
+
function hasExecutablePattern(source, pattern) {
|
|
6474
|
+
return pattern.test(maskTypeScriptCommentsAndLiterals(source));
|
|
6475
|
+
}
|
|
6476
|
+
function hasUncommentedPattern(source, pattern) {
|
|
6477
|
+
return pattern.test(maskTypeScriptComments(source));
|
|
6478
|
+
}
|
|
6479
|
+
function findExecutablePatternMatch(source, patterns) {
|
|
6480
|
+
const maskedSource = maskTypeScriptCommentsAndLiterals(source);
|
|
6481
|
+
for (const pattern of patterns) {
|
|
6482
|
+
const match = pattern.exec(maskedSource);
|
|
6483
|
+
if (match && match.index !== undefined) {
|
|
6484
|
+
return {
|
|
6485
|
+
end: match.index + match[0].length,
|
|
6486
|
+
start: match.index
|
|
6487
|
+
};
|
|
6488
|
+
}
|
|
6489
|
+
}
|
|
6490
|
+
return;
|
|
6491
|
+
}
|
|
6492
|
+
function isIdentifierBoundary(source, index) {
|
|
6493
|
+
if (index < 0 || index >= source.length) {
|
|
6494
|
+
return true;
|
|
6495
|
+
}
|
|
6496
|
+
return !/[A-Za-z0-9_$]/u.test(source[index] ?? "");
|
|
6497
|
+
}
|
|
6498
|
+
function skipWhitespace(source, index) {
|
|
6499
|
+
let cursor = index;
|
|
6500
|
+
while (cursor < source.length && /\s/u.test(source[cursor] ?? "")) {
|
|
6501
|
+
cursor += 1;
|
|
6502
|
+
}
|
|
6503
|
+
return cursor;
|
|
6504
|
+
}
|
|
6505
|
+
function findMatchingDelimiterEnd(source, openIndex, openDelimiter, closeDelimiter) {
|
|
6506
|
+
let depth = 0;
|
|
6507
|
+
for (let index = openIndex;index < source.length; index += 1) {
|
|
6508
|
+
const char = source[index];
|
|
6509
|
+
if (char === openDelimiter) {
|
|
6510
|
+
depth += 1;
|
|
6511
|
+
continue;
|
|
6512
|
+
}
|
|
6513
|
+
if (char === closeDelimiter) {
|
|
6514
|
+
depth -= 1;
|
|
6515
|
+
if (depth === 0) {
|
|
6516
|
+
return index + 1;
|
|
6517
|
+
}
|
|
6518
|
+
}
|
|
6519
|
+
}
|
|
6520
|
+
return;
|
|
6521
|
+
}
|
|
6522
|
+
function findExecutableCallRange(source, callName) {
|
|
6523
|
+
const maskedSource = maskTypeScriptCommentsAndLiterals(source);
|
|
6524
|
+
let searchIndex = 0;
|
|
6525
|
+
while (searchIndex < maskedSource.length) {
|
|
6526
|
+
const callNameIndex = maskedSource.indexOf(callName, searchIndex);
|
|
6527
|
+
if (callNameIndex === -1) {
|
|
6528
|
+
return;
|
|
6529
|
+
}
|
|
6530
|
+
const callNameEnd = callNameIndex + callName.length;
|
|
6531
|
+
if (!isIdentifierBoundary(maskedSource, callNameIndex - 1) || !isIdentifierBoundary(maskedSource, callNameEnd)) {
|
|
6532
|
+
searchIndex = callNameEnd;
|
|
6533
|
+
continue;
|
|
6534
|
+
}
|
|
6535
|
+
let cursor = skipWhitespace(maskedSource, callNameEnd);
|
|
6536
|
+
if (maskedSource[cursor] === "<") {
|
|
6537
|
+
const genericEnd = findMatchingDelimiterEnd(maskedSource, cursor, "<", ">");
|
|
6538
|
+
if (genericEnd === undefined) {
|
|
6539
|
+
searchIndex = callNameEnd;
|
|
6540
|
+
continue;
|
|
6541
|
+
}
|
|
6542
|
+
cursor = skipWhitespace(maskedSource, genericEnd);
|
|
6543
|
+
}
|
|
6544
|
+
if (maskedSource[cursor] !== "(") {
|
|
6545
|
+
searchIndex = callNameEnd;
|
|
6546
|
+
continue;
|
|
6547
|
+
}
|
|
6548
|
+
const callEnd = findMatchingDelimiterEnd(maskedSource, cursor, "(", ")");
|
|
6549
|
+
if (callEnd === undefined) {
|
|
6550
|
+
searchIndex = callNameEnd;
|
|
6551
|
+
continue;
|
|
6552
|
+
}
|
|
6553
|
+
let end = skipWhitespace(maskedSource, callEnd);
|
|
6554
|
+
if (maskedSource[end] === ";") {
|
|
6555
|
+
end += 1;
|
|
6556
|
+
}
|
|
6557
|
+
return {
|
|
6558
|
+
end,
|
|
6559
|
+
start: callNameIndex
|
|
6560
|
+
};
|
|
6561
|
+
}
|
|
6562
|
+
return;
|
|
6563
|
+
}
|
|
6564
|
+
function findBlockRegistrationCallRange(source) {
|
|
6565
|
+
return findExecutableCallRange(source, "registerScaffoldBlockType") ?? findExecutableCallRange(source, "registerBlockType");
|
|
6566
|
+
}
|
|
4925
6567
|
function buildVariationConfigEntry(blockSlug, variationSlug) {
|
|
4926
6568
|
return [
|
|
4927
6569
|
"\t{",
|
|
@@ -4991,49 +6633,300 @@ export function registerWorkspaceVariations() {
|
|
|
4991
6633
|
}
|
|
4992
6634
|
`;
|
|
4993
6635
|
}
|
|
6636
|
+
function buildWorkspaceConstName(prefix, slug) {
|
|
6637
|
+
return `workspace${prefix}_${toSnakeCase(slug)}`;
|
|
6638
|
+
}
|
|
6639
|
+
function buildBlockStyleConfigEntry(blockSlug, styleSlug) {
|
|
6640
|
+
return [
|
|
6641
|
+
"\t{",
|
|
6642
|
+
` block: ${quoteTsString(blockSlug)},`,
|
|
6643
|
+
` file: ${quoteTsString(`src/blocks/${blockSlug}/styles/${styleSlug}.ts`)},`,
|
|
6644
|
+
` slug: ${quoteTsString(styleSlug)},`,
|
|
6645
|
+
"\t},"
|
|
6646
|
+
].join(`
|
|
6647
|
+
`);
|
|
6648
|
+
}
|
|
6649
|
+
function buildBlockTransformConfigEntry(options) {
|
|
6650
|
+
return [
|
|
6651
|
+
"\t{",
|
|
6652
|
+
` block: ${quoteTsString(options.blockSlug)},`,
|
|
6653
|
+
` file: ${quoteTsString(`src/blocks/${options.blockSlug}/transforms/${options.transformSlug}.ts`)},`,
|
|
6654
|
+
` from: ${quoteTsString(options.fromBlockName)},`,
|
|
6655
|
+
` slug: ${quoteTsString(options.transformSlug)},`,
|
|
6656
|
+
` to: ${quoteTsString(options.toBlockName)},`,
|
|
6657
|
+
"\t},"
|
|
6658
|
+
].join(`
|
|
6659
|
+
`);
|
|
6660
|
+
}
|
|
6661
|
+
function getBlockStyleConstBindings(styleSlugs) {
|
|
6662
|
+
const seenConstNames = new Map;
|
|
6663
|
+
return styleSlugs.map((styleSlug) => {
|
|
6664
|
+
const constName = buildWorkspaceConstName("BlockStyle", styleSlug);
|
|
6665
|
+
const previousSlug = seenConstNames.get(constName);
|
|
6666
|
+
if (previousSlug && previousSlug !== styleSlug) {
|
|
6667
|
+
throw new Error(`Style slugs "${previousSlug}" and "${styleSlug}" generate the same registry identifier "${constName}". Rename one of the styles.`);
|
|
6668
|
+
}
|
|
6669
|
+
seenConstNames.set(constName, styleSlug);
|
|
6670
|
+
return { constName, styleSlug };
|
|
6671
|
+
});
|
|
6672
|
+
}
|
|
6673
|
+
function getBlockTransformConstBindings(transformSlugs) {
|
|
6674
|
+
const seenConstNames = new Map;
|
|
6675
|
+
return transformSlugs.map((transformSlug) => {
|
|
6676
|
+
const constName = buildWorkspaceConstName("BlockTransform", transformSlug);
|
|
6677
|
+
const previousSlug = seenConstNames.get(constName);
|
|
6678
|
+
if (previousSlug && previousSlug !== transformSlug) {
|
|
6679
|
+
throw new Error(`Transform slugs "${previousSlug}" and "${transformSlug}" generate the same registry identifier "${constName}". Rename one of the transforms.`);
|
|
6680
|
+
}
|
|
6681
|
+
seenConstNames.set(constName, transformSlug);
|
|
6682
|
+
return { constName, transformSlug };
|
|
6683
|
+
});
|
|
6684
|
+
}
|
|
6685
|
+
function buildBlockStyleSource(styleSlug, textDomain) {
|
|
6686
|
+
const styleTitle = toTitleCase(styleSlug);
|
|
6687
|
+
const styleConstName = buildWorkspaceConstName("BlockStyle", styleSlug);
|
|
6688
|
+
return `import { __ } from '@wordpress/i18n';
|
|
6689
|
+
|
|
6690
|
+
export const ${styleConstName} = {
|
|
6691
|
+
name: ${quoteTsString(styleSlug)},
|
|
6692
|
+
label: __( ${quoteTsString(styleTitle)}, ${quoteTsString(textDomain)} ),
|
|
6693
|
+
} as const;
|
|
6694
|
+
`;
|
|
6695
|
+
}
|
|
6696
|
+
function buildBlockStyleIndexSource(styleSlugs) {
|
|
6697
|
+
const styleBindings = getBlockStyleConstBindings(styleSlugs);
|
|
6698
|
+
const importLines = styleBindings.map(({ constName, styleSlug }) => `import { ${constName} } from './${styleSlug}';`).join(`
|
|
6699
|
+
`);
|
|
6700
|
+
const styleConstNames = styleBindings.map(({ constName }) => constName).join(`,
|
|
6701
|
+
`);
|
|
6702
|
+
return `import { registerBlockStyle } from '@wordpress/blocks';
|
|
6703
|
+
import metadata from '../block.json';
|
|
6704
|
+
${importLines ? `
|
|
6705
|
+
${importLines}` : ""}
|
|
6706
|
+
|
|
6707
|
+
const WORKSPACE_BLOCK_STYLES = [
|
|
6708
|
+
${styleConstNames}
|
|
6709
|
+
// wp-typia add style entries
|
|
6710
|
+
] as const;
|
|
6711
|
+
|
|
6712
|
+
export function registerWorkspaceBlockStyles() {
|
|
6713
|
+
for (const style of WORKSPACE_BLOCK_STYLES) {
|
|
6714
|
+
registerBlockStyle(metadata.name, style);
|
|
6715
|
+
}
|
|
6716
|
+
}
|
|
6717
|
+
`;
|
|
6718
|
+
}
|
|
6719
|
+
function buildBlockTransformSource(options) {
|
|
6720
|
+
const transformTitle = toTitleCase(options.transformSlug);
|
|
6721
|
+
const transformConstName = buildWorkspaceConstName("BlockTransform", options.transformSlug);
|
|
6722
|
+
return `import { createBlock } from '@wordpress/blocks';
|
|
6723
|
+
import { __ } from '@wordpress/i18n';
|
|
6724
|
+
import metadata from '../block.json';
|
|
6725
|
+
|
|
6726
|
+
type TransformAttributes = Record<string, unknown>;
|
|
6727
|
+
type TransformInnerBlock = ReturnType<typeof createBlock>;
|
|
6728
|
+
|
|
6729
|
+
function mapTransformAttributes(attributes: TransformAttributes): TransformAttributes {
|
|
6730
|
+
const content = attributes.content;
|
|
6731
|
+
|
|
6732
|
+
return typeof content === 'string' ? { content } : {};
|
|
6733
|
+
}
|
|
6734
|
+
|
|
6735
|
+
export const ${transformConstName} = {
|
|
6736
|
+
type: 'block',
|
|
6737
|
+
blocks: [${quoteTsString(options.fromBlockName)}],
|
|
6738
|
+
title: __( ${quoteTsString(transformTitle)}, ${quoteTsString(options.textDomain)} ),
|
|
6739
|
+
transform: (
|
|
6740
|
+
attributes: TransformAttributes,
|
|
6741
|
+
innerBlocks: TransformInnerBlock[] = [],
|
|
6742
|
+
) => createBlock(metadata.name, mapTransformAttributes(attributes), innerBlocks),
|
|
6743
|
+
} as const;
|
|
6744
|
+
`;
|
|
6745
|
+
}
|
|
6746
|
+
function buildBlockTransformIndexSource(transformSlugs) {
|
|
6747
|
+
const transformBindings = getBlockTransformConstBindings(transformSlugs);
|
|
6748
|
+
const importLines = transformBindings.map(({ constName, transformSlug }) => `import { ${constName} } from './${transformSlug}';`).join(`
|
|
6749
|
+
`);
|
|
6750
|
+
const transformConstNames = transformBindings.map(({ constName }) => constName).join(`,
|
|
6751
|
+
`);
|
|
6752
|
+
return `${importLines ? `${importLines}
|
|
6753
|
+
|
|
6754
|
+
` : ""}type BlockSettingsWithTransforms = {
|
|
6755
|
+
transforms?: {
|
|
6756
|
+
from?: unknown[];
|
|
6757
|
+
to?: unknown[];
|
|
6758
|
+
};
|
|
6759
|
+
};
|
|
6760
|
+
|
|
6761
|
+
const WORKSPACE_BLOCK_TRANSFORMS = [
|
|
6762
|
+
${transformConstNames}
|
|
6763
|
+
// wp-typia add transform entries
|
|
6764
|
+
] as const;
|
|
6765
|
+
|
|
6766
|
+
export function applyWorkspaceBlockTransforms(settings: BlockSettingsWithTransforms) {
|
|
6767
|
+
const transforms = settings.transforms ?? {};
|
|
6768
|
+
|
|
6769
|
+
settings.transforms = {
|
|
6770
|
+
...transforms,
|
|
6771
|
+
from: [...(transforms.from ?? []), ...WORKSPACE_BLOCK_TRANSFORMS],
|
|
6772
|
+
};
|
|
6773
|
+
}
|
|
6774
|
+
`;
|
|
6775
|
+
}
|
|
4994
6776
|
async function ensureVariationRegistrationHook(blockIndexPath) {
|
|
4995
6777
|
await patchFile(blockIndexPath, (source) => {
|
|
4996
6778
|
let nextSource = source;
|
|
4997
|
-
if (!nextSource
|
|
6779
|
+
if (!hasUncommentedPattern(nextSource, VARIATIONS_IMPORT_PATTERN)) {
|
|
4998
6780
|
nextSource = `${VARIATIONS_IMPORT_LINE}
|
|
4999
6781
|
${nextSource}`;
|
|
5000
6782
|
}
|
|
5001
|
-
if (!nextSource
|
|
5002
|
-
const
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
for (const pattern of callInsertionPatterns) {
|
|
5008
|
-
const candidate = nextSource.replace(pattern, (match) => `${match}
|
|
6783
|
+
if (!hasExecutablePattern(nextSource, VARIATIONS_CALL_PATTERN)) {
|
|
6784
|
+
const callRange = findBlockRegistrationCallRange(nextSource);
|
|
6785
|
+
if (callRange) {
|
|
6786
|
+
nextSource = [
|
|
6787
|
+
nextSource.slice(0, callRange.end),
|
|
6788
|
+
`
|
|
5009
6789
|
${VARIATIONS_CALL_LINE}
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
break;
|
|
5015
|
-
}
|
|
5016
|
-
}
|
|
5017
|
-
if (!inserted) {
|
|
6790
|
+
`,
|
|
6791
|
+
nextSource.slice(callRange.end)
|
|
6792
|
+
].join("");
|
|
6793
|
+
} else {
|
|
5018
6794
|
nextSource = `${nextSource.trimEnd()}
|
|
5019
6795
|
|
|
5020
6796
|
${VARIATIONS_CALL_LINE}
|
|
5021
6797
|
`;
|
|
5022
6798
|
}
|
|
5023
6799
|
}
|
|
5024
|
-
if (!nextSource
|
|
5025
|
-
throw new Error(`Unable to inject ${VARIATIONS_CALL_LINE} into ${
|
|
6800
|
+
if (!hasExecutablePattern(nextSource, VARIATIONS_CALL_PATTERN)) {
|
|
6801
|
+
throw new Error(`Unable to inject ${VARIATIONS_CALL_LINE} into ${path13.basename(blockIndexPath)}.`);
|
|
6802
|
+
}
|
|
6803
|
+
return nextSource;
|
|
6804
|
+
});
|
|
6805
|
+
}
|
|
6806
|
+
async function ensureBlockStyleRegistrationHook(blockIndexPath) {
|
|
6807
|
+
await patchFile(blockIndexPath, (source) => {
|
|
6808
|
+
let nextSource = source;
|
|
6809
|
+
if (!hasUncommentedPattern(nextSource, BLOCK_STYLES_IMPORT_PATTERN)) {
|
|
6810
|
+
nextSource = `${BLOCK_STYLES_IMPORT_LINE}
|
|
6811
|
+
${nextSource}`;
|
|
6812
|
+
}
|
|
6813
|
+
if (!hasExecutablePattern(nextSource, BLOCK_STYLES_CALL_PATTERN)) {
|
|
6814
|
+
const callRange = findBlockRegistrationCallRange(nextSource);
|
|
6815
|
+
if (callRange) {
|
|
6816
|
+
nextSource = [
|
|
6817
|
+
nextSource.slice(0, callRange.end),
|
|
6818
|
+
`
|
|
6819
|
+
${BLOCK_STYLES_CALL_LINE}
|
|
6820
|
+
`,
|
|
6821
|
+
nextSource.slice(callRange.end)
|
|
6822
|
+
].join("");
|
|
6823
|
+
} else {
|
|
6824
|
+
nextSource = `${nextSource.trimEnd()}
|
|
6825
|
+
|
|
6826
|
+
${BLOCK_STYLES_CALL_LINE}
|
|
6827
|
+
`;
|
|
6828
|
+
}
|
|
6829
|
+
}
|
|
6830
|
+
if (!hasExecutablePattern(nextSource, BLOCK_STYLES_CALL_PATTERN)) {
|
|
6831
|
+
throw new Error(`Unable to inject ${BLOCK_STYLES_CALL_LINE} into ${path13.basename(blockIndexPath)}.`);
|
|
6832
|
+
}
|
|
6833
|
+
return nextSource;
|
|
6834
|
+
});
|
|
6835
|
+
}
|
|
6836
|
+
async function ensureBlockTransformRegistrationHook(blockIndexPath) {
|
|
6837
|
+
await patchFile(blockIndexPath, (source) => {
|
|
6838
|
+
let nextSource = source;
|
|
6839
|
+
if (!hasUncommentedPattern(nextSource, BLOCK_TRANSFORMS_IMPORT_PATTERN)) {
|
|
6840
|
+
nextSource = `${BLOCK_TRANSFORMS_IMPORT_LINE}
|
|
6841
|
+
${nextSource}`;
|
|
6842
|
+
}
|
|
6843
|
+
if (!hasExecutablePattern(nextSource, BLOCK_TRANSFORMS_CALL_PATTERN)) {
|
|
6844
|
+
const callRange = findExecutablePatternMatch(nextSource, [
|
|
6845
|
+
SCAFFOLD_REGISTRATION_SETTINGS_CALL_PATTERN
|
|
6846
|
+
]);
|
|
6847
|
+
if (!callRange) {
|
|
6848
|
+
throw new Error(`Unable to inject ${BLOCK_TRANSFORMS_CALL_LINE} into ${path13.basename(blockIndexPath)} because it does not expose a scaffold registration settings object.`);
|
|
6849
|
+
}
|
|
6850
|
+
nextSource = [
|
|
6851
|
+
nextSource.slice(0, callRange.start),
|
|
6852
|
+
`${BLOCK_TRANSFORMS_CALL_LINE}
|
|
6853
|
+
`,
|
|
6854
|
+
nextSource.slice(callRange.start)
|
|
6855
|
+
].join("");
|
|
5026
6856
|
}
|
|
5027
6857
|
return nextSource;
|
|
5028
6858
|
});
|
|
5029
6859
|
}
|
|
5030
6860
|
async function writeVariationRegistry(projectDir, blockSlug, variationSlug) {
|
|
5031
|
-
const variationsDir =
|
|
5032
|
-
const variationsIndexPath =
|
|
5033
|
-
await
|
|
5034
|
-
const existingVariationSlugs =
|
|
6861
|
+
const variationsDir = path13.join(projectDir, "src", "blocks", blockSlug, "variations");
|
|
6862
|
+
const variationsIndexPath = path13.join(variationsDir, "index.ts");
|
|
6863
|
+
await fsp9.mkdir(variationsDir, { recursive: true });
|
|
6864
|
+
const existingVariationSlugs = fs6.readdirSync(variationsDir).filter((entry) => entry.endsWith(".ts") && entry !== "index.ts").map((entry) => entry.replace(/\.ts$/u, ""));
|
|
5035
6865
|
const nextVariationSlugs = Array.from(new Set([...existingVariationSlugs, variationSlug])).sort();
|
|
5036
|
-
await
|
|
6866
|
+
await fsp9.writeFile(variationsIndexPath, buildVariationIndexSource(nextVariationSlugs), "utf8");
|
|
6867
|
+
}
|
|
6868
|
+
async function writeBlockStyleRegistry(projectDir, blockSlug, styleSlug) {
|
|
6869
|
+
const stylesDir = path13.join(projectDir, "src", "blocks", blockSlug, "styles");
|
|
6870
|
+
const stylesIndexPath = path13.join(stylesDir, "index.ts");
|
|
6871
|
+
await fsp9.mkdir(stylesDir, { recursive: true });
|
|
6872
|
+
const existingStyleSlugs = fs6.readdirSync(stylesDir).filter((entry) => entry.endsWith(".ts") && entry !== "index.ts").map((entry) => entry.replace(/\.ts$/u, ""));
|
|
6873
|
+
const nextStyleSlugs = Array.from(new Set([...existingStyleSlugs, styleSlug])).sort();
|
|
6874
|
+
await fsp9.writeFile(stylesIndexPath, buildBlockStyleIndexSource(nextStyleSlugs), "utf8");
|
|
6875
|
+
}
|
|
6876
|
+
async function writeBlockTransformRegistry(projectDir, blockSlug, transformSlug) {
|
|
6877
|
+
const transformsDir = path13.join(projectDir, "src", "blocks", blockSlug, "transforms");
|
|
6878
|
+
const transformsIndexPath = path13.join(transformsDir, "index.ts");
|
|
6879
|
+
await fsp9.mkdir(transformsDir, { recursive: true });
|
|
6880
|
+
const existingTransformSlugs = fs6.readdirSync(transformsDir).filter((entry) => entry.endsWith(".ts") && entry !== "index.ts").map((entry) => entry.replace(/\.ts$/u, ""));
|
|
6881
|
+
const nextTransformSlugs = Array.from(new Set([...existingTransformSlugs, transformSlug])).sort();
|
|
6882
|
+
await fsp9.writeFile(transformsIndexPath, buildBlockTransformIndexSource(nextTransformSlugs), "utf8");
|
|
6883
|
+
}
|
|
6884
|
+
function assertBlockStyleDoesNotExist(projectDir, blockSlug, styleSlug, inventory) {
|
|
6885
|
+
const stylePath = path13.join(projectDir, "src", "blocks", blockSlug, "styles", `${styleSlug}.ts`);
|
|
6886
|
+
if (fs6.existsSync(stylePath)) {
|
|
6887
|
+
throw new Error(`A block style already exists at ${path13.relative(projectDir, stylePath)}. Choose a different name.`);
|
|
6888
|
+
}
|
|
6889
|
+
if (inventory.blockStyles.some((entry) => entry.block === blockSlug && entry.slug === styleSlug)) {
|
|
6890
|
+
throw new Error(`A block style inventory entry already exists for ${blockSlug}/${styleSlug}. Choose a different name.`);
|
|
6891
|
+
}
|
|
6892
|
+
}
|
|
6893
|
+
function assertBlockTransformDoesNotExist(projectDir, blockSlug, transformSlug, inventory) {
|
|
6894
|
+
const transformPath = path13.join(projectDir, "src", "blocks", blockSlug, "transforms", `${transformSlug}.ts`);
|
|
6895
|
+
if (fs6.existsSync(transformPath)) {
|
|
6896
|
+
throw new Error(`A block transform already exists at ${path13.relative(projectDir, transformPath)}. Choose a different name.`);
|
|
6897
|
+
}
|
|
6898
|
+
if (inventory.blockTransforms.some((entry) => entry.block === blockSlug && entry.slug === transformSlug)) {
|
|
6899
|
+
throw new Error(`A block transform inventory entry already exists for ${blockSlug}/${transformSlug}. Choose a different name.`);
|
|
6900
|
+
}
|
|
6901
|
+
}
|
|
6902
|
+
function assertFullBlockName(blockName, flagName) {
|
|
6903
|
+
const trimmed = blockName.trim();
|
|
6904
|
+
if (!trimmed) {
|
|
6905
|
+
throw new Error(`\`${flagName}\` requires a block name.`);
|
|
6906
|
+
}
|
|
6907
|
+
if (!FULL_BLOCK_NAME_PATTERN.test(trimmed)) {
|
|
6908
|
+
throw new Error(`\`${flagName}\` must use <namespace/block-slug> format.`);
|
|
6909
|
+
}
|
|
6910
|
+
return trimmed;
|
|
6911
|
+
}
|
|
6912
|
+
function resolveWorkspaceTargetBlockName(blockName, namespace, flagName) {
|
|
6913
|
+
const trimmed = blockName.trim();
|
|
6914
|
+
if (!trimmed) {
|
|
6915
|
+
throw new Error(`\`${flagName}\` requires <block-slug|namespace/block-slug>.`);
|
|
6916
|
+
}
|
|
6917
|
+
const blockNameSegments = trimmed.split("/");
|
|
6918
|
+
if (blockNameSegments.length > 2 || blockNameSegments.some((segment) => segment.trim() === "")) {
|
|
6919
|
+
throw new Error(`\`${flagName}\` must use <block-slug|namespace/block-slug> format.`);
|
|
6920
|
+
}
|
|
6921
|
+
const [maybeNamespace, maybeSlug] = blockNameSegments.length === 2 ? blockNameSegments : [undefined, blockNameSegments[0]];
|
|
6922
|
+
if (maybeNamespace && maybeNamespace !== namespace) {
|
|
6923
|
+
throw new Error(`\`${flagName}\` references namespace "${maybeNamespace}". Expected "${namespace}".`);
|
|
6924
|
+
}
|
|
6925
|
+
const blockSlug = normalizeBlockSlug(maybeSlug ?? "");
|
|
6926
|
+
return {
|
|
6927
|
+
blockName: `${namespace}/${blockSlug}`,
|
|
6928
|
+
blockSlug
|
|
6929
|
+
};
|
|
5037
6930
|
}
|
|
5038
6931
|
async function runAddVariationCommand({
|
|
5039
6932
|
blockName,
|
|
@@ -5046,11 +6939,12 @@ async function runAddVariationCommand({
|
|
|
5046
6939
|
const inventory = readWorkspaceInventory(workspace.projectDir);
|
|
5047
6940
|
resolveWorkspaceBlock(inventory, blockSlug);
|
|
5048
6941
|
assertVariationDoesNotExist(workspace.projectDir, blockSlug, variationSlug, inventory);
|
|
5049
|
-
const blockConfigPath =
|
|
5050
|
-
const blockIndexPath =
|
|
5051
|
-
const variationsDir =
|
|
5052
|
-
const variationFilePath =
|
|
5053
|
-
const variationsIndexPath =
|
|
6942
|
+
const blockConfigPath = path13.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
6943
|
+
const blockIndexPath = path13.join(workspace.projectDir, "src", "blocks", blockSlug, "index.tsx");
|
|
6944
|
+
const variationsDir = path13.join(workspace.projectDir, "src", "blocks", blockSlug, "variations");
|
|
6945
|
+
const variationFilePath = path13.join(variationsDir, `${variationSlug}.ts`);
|
|
6946
|
+
const variationsIndexPath = path13.join(variationsDir, "index.ts");
|
|
6947
|
+
const shouldRemoveVariationsDirOnRollback = !fs6.existsSync(variationsDir);
|
|
5054
6948
|
const mutationSnapshot = {
|
|
5055
6949
|
fileSources: await snapshotWorkspaceFiles([
|
|
5056
6950
|
blockConfigPath,
|
|
@@ -5058,11 +6952,14 @@ async function runAddVariationCommand({
|
|
|
5058
6952
|
variationsIndexPath
|
|
5059
6953
|
]),
|
|
5060
6954
|
snapshotDirs: [],
|
|
5061
|
-
targetPaths: [
|
|
6955
|
+
targetPaths: [
|
|
6956
|
+
variationFilePath,
|
|
6957
|
+
...shouldRemoveVariationsDirOnRollback ? [variationsDir] : []
|
|
6958
|
+
]
|
|
5062
6959
|
};
|
|
5063
6960
|
try {
|
|
5064
|
-
await
|
|
5065
|
-
await
|
|
6961
|
+
await fsp9.mkdir(variationsDir, { recursive: true });
|
|
6962
|
+
await fsp9.writeFile(variationFilePath, buildVariationSource(variationSlug, workspace.workspace.textDomain), "utf8");
|
|
5066
6963
|
await writeVariationRegistry(workspace.projectDir, blockSlug, variationSlug);
|
|
5067
6964
|
await ensureVariationRegistrationHook(blockIndexPath);
|
|
5068
6965
|
await appendWorkspaceInventoryEntries(workspace.projectDir, {
|
|
@@ -5078,6 +6975,115 @@ async function runAddVariationCommand({
|
|
|
5078
6975
|
throw error;
|
|
5079
6976
|
}
|
|
5080
6977
|
}
|
|
6978
|
+
async function runAddBlockStyleCommand({
|
|
6979
|
+
blockName,
|
|
6980
|
+
cwd = process.cwd(),
|
|
6981
|
+
styleName
|
|
6982
|
+
}) {
|
|
6983
|
+
const workspace = resolveWorkspaceProject(cwd);
|
|
6984
|
+
const blockSlug = normalizeBlockSlug(blockName);
|
|
6985
|
+
const styleSlug = assertValidGeneratedSlug("Style name", normalizeBlockSlug(styleName), "wp-typia add style <name> --block <block-slug>");
|
|
6986
|
+
const inventory = readWorkspaceInventory(workspace.projectDir);
|
|
6987
|
+
resolveWorkspaceBlock(inventory, blockSlug);
|
|
6988
|
+
assertBlockStyleDoesNotExist(workspace.projectDir, blockSlug, styleSlug, inventory);
|
|
6989
|
+
const blockConfigPath = path13.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
6990
|
+
const blockIndexPath = path13.join(workspace.projectDir, "src", "blocks", blockSlug, "index.tsx");
|
|
6991
|
+
const stylesDir = path13.join(workspace.projectDir, "src", "blocks", blockSlug, "styles");
|
|
6992
|
+
const styleFilePath = path13.join(stylesDir, `${styleSlug}.ts`);
|
|
6993
|
+
const stylesIndexPath = path13.join(stylesDir, "index.ts");
|
|
6994
|
+
const shouldRemoveStylesDirOnRollback = !fs6.existsSync(stylesDir);
|
|
6995
|
+
const mutationSnapshot = {
|
|
6996
|
+
fileSources: await snapshotWorkspaceFiles([
|
|
6997
|
+
blockConfigPath,
|
|
6998
|
+
blockIndexPath,
|
|
6999
|
+
stylesIndexPath
|
|
7000
|
+
]),
|
|
7001
|
+
snapshotDirs: [],
|
|
7002
|
+
targetPaths: [
|
|
7003
|
+
styleFilePath,
|
|
7004
|
+
...shouldRemoveStylesDirOnRollback ? [stylesDir] : []
|
|
7005
|
+
]
|
|
7006
|
+
};
|
|
7007
|
+
try {
|
|
7008
|
+
await fsp9.mkdir(stylesDir, { recursive: true });
|
|
7009
|
+
await fsp9.writeFile(styleFilePath, buildBlockStyleSource(styleSlug, workspace.workspace.textDomain), "utf8");
|
|
7010
|
+
await writeBlockStyleRegistry(workspace.projectDir, blockSlug, styleSlug);
|
|
7011
|
+
await ensureBlockStyleRegistrationHook(blockIndexPath);
|
|
7012
|
+
await appendWorkspaceInventoryEntries(workspace.projectDir, {
|
|
7013
|
+
blockStyleEntries: [buildBlockStyleConfigEntry(blockSlug, styleSlug)]
|
|
7014
|
+
});
|
|
7015
|
+
return {
|
|
7016
|
+
blockSlug,
|
|
7017
|
+
projectDir: workspace.projectDir,
|
|
7018
|
+
styleSlug
|
|
7019
|
+
};
|
|
7020
|
+
} catch (error) {
|
|
7021
|
+
await rollbackWorkspaceMutation(mutationSnapshot);
|
|
7022
|
+
throw error;
|
|
7023
|
+
}
|
|
7024
|
+
}
|
|
7025
|
+
async function runAddBlockTransformCommand({
|
|
7026
|
+
cwd = process.cwd(),
|
|
7027
|
+
fromBlockName,
|
|
7028
|
+
toBlockName,
|
|
7029
|
+
transformName
|
|
7030
|
+
}) {
|
|
7031
|
+
const workspace = resolveWorkspaceProject(cwd);
|
|
7032
|
+
const transformSlug = assertValidGeneratedSlug("Transform name", normalizeBlockSlug(transformName), "wp-typia add transform <name> --from <namespace/block> --to <block-slug|namespace/block-slug>");
|
|
7033
|
+
const resolvedFromBlockName = assertFullBlockName(fromBlockName, "--from");
|
|
7034
|
+
const target = resolveWorkspaceTargetBlockName(toBlockName, workspace.workspace.namespace, "--to");
|
|
7035
|
+
const inventory = readWorkspaceInventory(workspace.projectDir);
|
|
7036
|
+
resolveWorkspaceBlock(inventory, target.blockSlug);
|
|
7037
|
+
assertBlockTransformDoesNotExist(workspace.projectDir, target.blockSlug, transformSlug, inventory);
|
|
7038
|
+
const blockConfigPath = path13.join(workspace.projectDir, "scripts", "block-config.ts");
|
|
7039
|
+
const blockIndexPath = path13.join(workspace.projectDir, "src", "blocks", target.blockSlug, "index.tsx");
|
|
7040
|
+
const transformsDir = path13.join(workspace.projectDir, "src", "blocks", target.blockSlug, "transforms");
|
|
7041
|
+
const transformFilePath = path13.join(transformsDir, `${transformSlug}.ts`);
|
|
7042
|
+
const transformsIndexPath = path13.join(transformsDir, "index.ts");
|
|
7043
|
+
const shouldRemoveTransformsDirOnRollback = !fs6.existsSync(transformsDir);
|
|
7044
|
+
const mutationSnapshot = {
|
|
7045
|
+
fileSources: await snapshotWorkspaceFiles([
|
|
7046
|
+
blockConfigPath,
|
|
7047
|
+
blockIndexPath,
|
|
7048
|
+
transformsIndexPath
|
|
7049
|
+
]),
|
|
7050
|
+
snapshotDirs: [],
|
|
7051
|
+
targetPaths: [
|
|
7052
|
+
transformFilePath,
|
|
7053
|
+
...shouldRemoveTransformsDirOnRollback ? [transformsDir] : []
|
|
7054
|
+
]
|
|
7055
|
+
};
|
|
7056
|
+
try {
|
|
7057
|
+
await fsp9.mkdir(transformsDir, { recursive: true });
|
|
7058
|
+
await fsp9.writeFile(transformFilePath, buildBlockTransformSource({
|
|
7059
|
+
fromBlockName: resolvedFromBlockName,
|
|
7060
|
+
textDomain: workspace.workspace.textDomain,
|
|
7061
|
+
transformSlug
|
|
7062
|
+
}), "utf8");
|
|
7063
|
+
await writeBlockTransformRegistry(workspace.projectDir, target.blockSlug, transformSlug);
|
|
7064
|
+
await ensureBlockTransformRegistrationHook(blockIndexPath);
|
|
7065
|
+
await appendWorkspaceInventoryEntries(workspace.projectDir, {
|
|
7066
|
+
blockTransformEntries: [
|
|
7067
|
+
buildBlockTransformConfigEntry({
|
|
7068
|
+
blockSlug: target.blockSlug,
|
|
7069
|
+
fromBlockName: resolvedFromBlockName,
|
|
7070
|
+
toBlockName: target.blockName,
|
|
7071
|
+
transformSlug
|
|
7072
|
+
})
|
|
7073
|
+
]
|
|
7074
|
+
});
|
|
7075
|
+
return {
|
|
7076
|
+
blockSlug: target.blockSlug,
|
|
7077
|
+
fromBlockName: resolvedFromBlockName,
|
|
7078
|
+
projectDir: workspace.projectDir,
|
|
7079
|
+
toBlockName: target.blockName,
|
|
7080
|
+
transformSlug
|
|
7081
|
+
};
|
|
7082
|
+
} catch (error) {
|
|
7083
|
+
await rollbackWorkspaceMutation(mutationSnapshot);
|
|
7084
|
+
throw error;
|
|
7085
|
+
}
|
|
7086
|
+
}
|
|
5081
7087
|
async function runAddHookedBlockCommand({
|
|
5082
7088
|
anchorBlockName,
|
|
5083
7089
|
blockName,
|
|
@@ -5095,7 +7101,7 @@ async function runAddHookedBlockCommand({
|
|
|
5095
7101
|
throw new Error("`wp-typia add hooked-block` cannot hook a block relative to its own block name.");
|
|
5096
7102
|
}
|
|
5097
7103
|
const { blockJson, blockJsonPath } = readWorkspaceBlockJson(workspace.projectDir, blockSlug);
|
|
5098
|
-
const blockJsonRelativePath =
|
|
7104
|
+
const blockJsonRelativePath = path13.relative(workspace.projectDir, blockJsonPath);
|
|
5099
7105
|
const blockHooks = getMutableBlockHooks(blockJson, blockJsonRelativePath);
|
|
5100
7106
|
if (Object.prototype.hasOwnProperty.call(blockHooks, resolvedAnchorBlockName)) {
|
|
5101
7107
|
throw new Error(`${blockJsonRelativePath} already defines a blockHooks entry for "${resolvedAnchorBlockName}".`);
|
|
@@ -5107,7 +7113,7 @@ async function runAddHookedBlockCommand({
|
|
|
5107
7113
|
};
|
|
5108
7114
|
try {
|
|
5109
7115
|
blockHooks[resolvedAnchorBlockName] = resolvedPosition;
|
|
5110
|
-
await
|
|
7116
|
+
await fsp9.writeFile(blockJsonPath, JSON.stringify(blockJson, null, "\t"), "utf8");
|
|
5111
7117
|
return {
|
|
5112
7118
|
anchorBlockName: resolvedAnchorBlockName,
|
|
5113
7119
|
blockSlug,
|
|
@@ -5126,9 +7132,12 @@ export {
|
|
|
5126
7132
|
runAddPatternCommand,
|
|
5127
7133
|
runAddHookedBlockCommand,
|
|
5128
7134
|
runAddEditorPluginCommand,
|
|
7135
|
+
runAddBlockTransformCommand,
|
|
7136
|
+
runAddBlockStyleCommand,
|
|
5129
7137
|
runAddBlockCommand,
|
|
5130
7138
|
runAddBindingSourceCommand,
|
|
5131
7139
|
runAddAiFeatureCommand,
|
|
7140
|
+
runAddAdminViewCommand,
|
|
5132
7141
|
runAddAbilityCommand,
|
|
5133
7142
|
getWorkspaceBlockSelectOptions,
|
|
5134
7143
|
formatAddHelpText,
|
|
@@ -5137,4 +7146,4 @@ export {
|
|
|
5137
7146
|
ADD_BLOCK_TEMPLATE_IDS
|
|
5138
7147
|
};
|
|
5139
7148
|
|
|
5140
|
-
//# debugId=
|
|
7149
|
+
//# debugId=8932DC6C8A5BAD5564756E2164756E21
|