@wp-typia/project-tools 0.15.4 → 0.16.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/runtime/cli-help.js +1 -1
- package/dist/runtime/cli-scaffold.d.ts +2 -1
- package/dist/runtime/cli-scaffold.js +23 -2
- package/dist/runtime/scaffold-onboarding.d.ts +2 -1
- package/dist/runtime/scaffold-onboarding.js +37 -8
- package/dist/runtime/scaffold.js +26 -15
- package/package.json +2 -2
- package/templates/_shared/base/package.json.mustache +4 -3
- package/templates/_shared/base/scripts/sync-project.ts.mustache +103 -0
- package/templates/_shared/compound/core/package.json.mustache +4 -3
- package/templates/_shared/compound/core/scripts/add-compound-child.ts.mustache +42 -28
- package/templates/_shared/compound/core/scripts/sync-project.ts.mustache +103 -0
- package/templates/_shared/compound/persistence/package.json.mustache +4 -3
- package/templates/_shared/compound/persistence/scripts/sync-project.ts.mustache +103 -0
- package/templates/_shared/compound/persistence/scripts/sync-rest-contracts.ts.mustache +28 -0
- package/templates/_shared/compound/persistence/src/blocks/{{slugKebabCase}}/interactivity.ts.mustache +8 -0
- package/templates/_shared/compound/persistence-auth/{{slugKebabCase}}.php.mustache +5 -5
- package/templates/_shared/compound/persistence-public/{{slugKebabCase}}.php.mustache +5 -5
- package/templates/_shared/persistence/auth/{{slugKebabCase}}.php.mustache +5 -5
- package/templates/_shared/persistence/core/package.json.mustache +4 -3
- package/templates/_shared/persistence/core/scripts/sync-project.ts.mustache +103 -0
- package/templates/_shared/persistence/core/scripts/sync-rest-contracts.ts.mustache +28 -0
- package/templates/_shared/persistence/core/src/interactivity.ts.mustache +5 -0
- package/templates/_shared/persistence/core/{{slugKebabCase}}.php.mustache +5 -5
- package/templates/_shared/persistence/public/{{slugKebabCase}}.php.mustache +5 -5
- package/templates/_shared/workspace/persistence-auth/server.php.mustache +7 -5
- package/templates/_shared/workspace/persistence-public/server.php.mustache +7 -5
- package/templates/compound/src/blocks/{{slugKebabCase}}/types.ts.mustache +5 -0
- package/templates/compound/src/blocks/{{slugKebabCase}}/validators.ts.mustache +10 -3
- package/templates/compound/src/blocks/{{slugKebabCase}}-item/types.ts.mustache +5 -0
- package/templates/compound/src/blocks/{{slugKebabCase}}-item/validators.ts.mustache +10 -3
- package/templates/interactivity/package.json.mustache +4 -3
- package/templates/interactivity/src/edit.tsx.mustache +1 -1
- package/templates/interactivity/src/save.tsx.mustache +1 -1
- package/templates/persistence/src/edit.tsx.mustache +2 -2
package/dist/runtime/cli-help.js
CHANGED
|
@@ -34,7 +34,7 @@ Package managers: ${PACKAGE_MANAGER_IDS.join(", ")}
|
|
|
34
34
|
Notes:
|
|
35
35
|
\`wp-typia create\` is the canonical scaffold command.
|
|
36
36
|
\`wp-typia <project-dir>\` remains a backward-compatible alias to \`create\`.
|
|
37
|
-
Use \`--template
|
|
37
|
+
Use \`--template workspace\` as shorthand for \`@wp-typia/create-workspace-template\`, the official empty workspace scaffold behind \`wp-typia add ...\`.
|
|
38
38
|
\`add variation\` uses an existing workspace block from \`scripts/block-config.ts\`.
|
|
39
39
|
\`add pattern\` scaffolds a namespaced PHP pattern shell under \`src/patterns/\`.
|
|
40
40
|
\`add binding-source\` scaffolds shared PHP and editor registration under \`src/bindings/\`.
|
|
@@ -10,6 +10,7 @@ interface GetNextStepsOptions {
|
|
|
10
10
|
templateId: string;
|
|
11
11
|
}
|
|
12
12
|
interface GetOptionalOnboardingOptions {
|
|
13
|
+
availableScripts?: string[];
|
|
13
14
|
packageManager: PackageManagerId;
|
|
14
15
|
templateId: string;
|
|
15
16
|
compoundPersistenceEnabled?: boolean;
|
|
@@ -60,7 +61,7 @@ export declare function getNextSteps({ projectInput, projectDir, packageManager,
|
|
|
60
61
|
* @param options Package-manager and template context for optional guidance.
|
|
61
62
|
* @returns Optional onboarding note and step list.
|
|
62
63
|
*/
|
|
63
|
-
export declare function getOptionalOnboarding({ packageManager, templateId, compoundPersistenceEnabled, }: GetOptionalOnboardingOptions): OptionalOnboardingGuidance;
|
|
64
|
+
export declare function getOptionalOnboarding({ availableScripts, packageManager, templateId, compoundPersistenceEnabled, }: GetOptionalOnboardingOptions): OptionalOnboardingGuidance;
|
|
64
65
|
/**
|
|
65
66
|
* Resolve scaffold options, prompts, and follow-up steps for one CLI run.
|
|
66
67
|
*
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
import { collectScaffoldAnswers, DATA_STORAGE_MODES, PERSISTENCE_POLICIES, isDataStorageMode, isPersistencePolicy, resolvePackageManagerId, resolveTemplateId, scaffoldProject, } from "./scaffold.js";
|
|
3
4
|
import { formatInstallCommand, formatRunScript, } from "./package-managers.js";
|
|
@@ -79,10 +80,14 @@ export function getNextSteps({ projectInput, projectDir, packageManager, noInsta
|
|
|
79
80
|
* @param options Package-manager and template context for optional guidance.
|
|
80
81
|
* @returns Optional onboarding note and step list.
|
|
81
82
|
*/
|
|
82
|
-
export function getOptionalOnboarding({ packageManager, templateId, compoundPersistenceEnabled = false, }) {
|
|
83
|
+
export function getOptionalOnboarding({ availableScripts, packageManager, templateId, compoundPersistenceEnabled = false, }) {
|
|
83
84
|
return {
|
|
84
|
-
note: getOptionalOnboardingNote(packageManager, templateId
|
|
85
|
+
note: getOptionalOnboardingNote(packageManager, templateId, {
|
|
86
|
+
availableScripts,
|
|
87
|
+
compoundPersistenceEnabled,
|
|
88
|
+
}),
|
|
85
89
|
steps: getOptionalOnboardingSteps(packageManager, templateId, {
|
|
90
|
+
availableScripts,
|
|
86
91
|
compoundPersistenceEnabled,
|
|
87
92
|
}),
|
|
88
93
|
};
|
|
@@ -185,8 +190,24 @@ export async function runScaffoldFlow({ projectInput, cwd = process.cwd(), templ
|
|
|
185
190
|
withTestPreset: resolvedWithTestPreset,
|
|
186
191
|
withWpEnv: resolvedWithWpEnv,
|
|
187
192
|
});
|
|
193
|
+
let availableScripts;
|
|
194
|
+
try {
|
|
195
|
+
const parsedPackageJson = JSON.parse(fs.readFileSync(path.join(projectDir, "package.json"), "utf8"));
|
|
196
|
+
const scripts = parsedPackageJson.scripts &&
|
|
197
|
+
typeof parsedPackageJson.scripts === "object" &&
|
|
198
|
+
!Array.isArray(parsedPackageJson.scripts)
|
|
199
|
+
? parsedPackageJson.scripts
|
|
200
|
+
: {};
|
|
201
|
+
availableScripts = Object.entries(scripts)
|
|
202
|
+
.filter(([, value]) => typeof value === "string")
|
|
203
|
+
.map(([scriptName]) => scriptName);
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
availableScripts = undefined;
|
|
207
|
+
}
|
|
188
208
|
return {
|
|
189
209
|
optionalOnboarding: getOptionalOnboarding({
|
|
210
|
+
availableScripts,
|
|
190
211
|
packageManager: resolvedPackageManager,
|
|
191
212
|
templateId: resolvedTemplateId,
|
|
192
213
|
compoundPersistenceEnabled: result.variables.compoundPersistenceEnabled === "true",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { PackageManagerId } from "./package-managers.js";
|
|
2
2
|
interface SyncOnboardingOptions {
|
|
3
|
+
availableScripts?: string[];
|
|
3
4
|
compoundPersistenceEnabled?: boolean;
|
|
4
5
|
}
|
|
5
6
|
interface PhpRestExtensionOptions extends SyncOnboardingOptions {
|
|
@@ -16,7 +17,7 @@ export declare function getOptionalOnboardingSteps(packageManager: PackageManage
|
|
|
16
17
|
/**
|
|
17
18
|
* Returns the onboarding note explaining when manual sync is optional.
|
|
18
19
|
*/
|
|
19
|
-
export declare function getOptionalOnboardingNote(packageManager: PackageManagerId, templateId?: string): string;
|
|
20
|
+
export declare function getOptionalOnboardingNote(packageManager: PackageManagerId, templateId?: string, options?: SyncOnboardingOptions): string;
|
|
20
21
|
/**
|
|
21
22
|
* Returns source-of-truth guidance for generated artifacts by template mode.
|
|
22
23
|
*/
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { formatRunScript } from "./package-managers.js";
|
|
2
2
|
import { getPrimaryDevelopmentScript } from "./local-dev-presets.js";
|
|
3
|
+
import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, isBuiltInTemplateId, } from "./template-registry.js";
|
|
3
4
|
function templateHasPersistenceSync(templateId, { compoundPersistenceEnabled = false } = {}) {
|
|
4
5
|
return templateId === "persistence" || (templateId === "compound" && compoundPersistenceEnabled);
|
|
5
6
|
}
|
|
@@ -7,9 +8,19 @@ function templateHasPersistenceSync(templateId, { compoundPersistenceEnabled = f
|
|
|
7
8
|
* Returns the optional sync script names to suggest for a template.
|
|
8
9
|
*/
|
|
9
10
|
export function getOptionalSyncScriptNames(templateId, options = {}) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
const availableScripts = new Set(options.availableScripts ?? []);
|
|
12
|
+
if (availableScripts.has("sync")) {
|
|
13
|
+
return ["sync"];
|
|
14
|
+
}
|
|
15
|
+
const fallbackScripts = ["sync-types", "sync-rest"].filter((scriptName) => availableScripts.has(scriptName));
|
|
16
|
+
if (fallbackScripts.length > 0) {
|
|
17
|
+
return fallbackScripts;
|
|
18
|
+
}
|
|
19
|
+
if (!isBuiltInTemplateId(templateId) &&
|
|
20
|
+
templateId !== OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE) {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
return ["sync"];
|
|
13
24
|
}
|
|
14
25
|
/**
|
|
15
26
|
* Formats optional onboarding sync commands for the selected package manager.
|
|
@@ -20,15 +31,33 @@ export function getOptionalOnboardingSteps(packageManager, templateId, options =
|
|
|
20
31
|
/**
|
|
21
32
|
* Returns the onboarding note explaining when manual sync is optional.
|
|
22
33
|
*/
|
|
23
|
-
export function getOptionalOnboardingNote(packageManager, templateId = "basic") {
|
|
34
|
+
export function getOptionalOnboardingNote(packageManager, templateId = "basic", options = {}) {
|
|
35
|
+
const optionalSyncScripts = getOptionalSyncScriptNames(templateId, options);
|
|
36
|
+
const hasUnifiedSync = optionalSyncScripts.includes("sync");
|
|
37
|
+
const syncSteps = optionalSyncScripts.map((scriptName) => formatRunScript(packageManager, scriptName));
|
|
24
38
|
const developmentScript = getPrimaryDevelopmentScript(templateId);
|
|
39
|
+
const syncCommand = formatRunScript(packageManager, hasUnifiedSync ? "sync" : "sync-types");
|
|
40
|
+
const syncCheckCommand = formatRunScript(packageManager, hasUnifiedSync ? "sync" : "sync-types", "--check");
|
|
25
41
|
const failOnLossySyncCommand = formatRunScript(packageManager, "sync-types", "--fail-on-lossy");
|
|
26
42
|
const syncTypesCommand = formatRunScript(packageManager, "sync-types");
|
|
43
|
+
const syncRestCommand = formatRunScript(packageManager, "sync-rest");
|
|
27
44
|
const typecheckCommand = formatRunScript(packageManager, "typecheck");
|
|
28
45
|
const strictSyncCommand = formatRunScript(packageManager, "sync-types", "--strict --report json");
|
|
46
|
+
const advancedPersistenceNote = templateHasPersistenceSync(templateId, options)
|
|
47
|
+
? ` ${syncRestCommand} remains available for advanced REST-only refreshes, but it now fails fast when type-derived artifacts are stale; run \`${syncCommand}\` or \`${syncTypesCommand}\` first.`
|
|
48
|
+
: "";
|
|
49
|
+
const fallbackCustomTemplateNote = !hasUnifiedSync &&
|
|
50
|
+
syncSteps.length > 0 &&
|
|
51
|
+
!isBuiltInTemplateId(templateId) &&
|
|
52
|
+
templateId !== OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE
|
|
53
|
+
? `Run ${syncSteps.join(" then ")} manually before build, typecheck, or commit. ${syncCheckCommand} verifies the current type-derived artifacts without rewriting them.${optionalSyncScripts.includes("sync-rest") ? ` ${syncRestCommand} remains available for REST-only refreshes after ${syncTypesCommand}.` : ""}`
|
|
54
|
+
: null;
|
|
55
|
+
if (fallbackCustomTemplateNote) {
|
|
56
|
+
return fallbackCustomTemplateNote;
|
|
57
|
+
}
|
|
29
58
|
return `${formatRunScript(packageManager, developmentScript)} ${developmentScript === "dev"
|
|
30
59
|
? "watches the relevant sync scripts during local development."
|
|
31
|
-
: "remains the primary local entry point."} ${formatRunScript(packageManager, "start")} still runs one-shot syncs before starting, while ${formatRunScript(packageManager, "build")} and ${typecheckCommand} verify that generated metadata/schema artifacts are already current
|
|
60
|
+
: "remains the primary local entry point."} ${formatRunScript(packageManager, "start")} still runs one-shot syncs before starting, while ${formatRunScript(packageManager, "build")} and ${typecheckCommand} verify that generated metadata/schema artifacts are already current through ${syncCheckCommand}. Run ${syncCommand} manually when you want to refresh generated artifacts before build, typecheck, or commit. ${syncTypesCommand} stays warn-only by default; use \`${failOnLossySyncCommand}\` to fail only on lossy WordPress projections, or \`${strictSyncCommand}\` for a CI-friendly JSON report that fails on all warnings.${advancedPersistenceNote} They do not create migration history.`;
|
|
32
61
|
}
|
|
33
62
|
/**
|
|
34
63
|
* Returns source-of-truth guidance for generated artifacts by template mode.
|
|
@@ -37,12 +66,12 @@ export function getTemplateSourceOfTruthNote(templateId, { compoundPersistenceEn
|
|
|
37
66
|
if (templateId === "compound") {
|
|
38
67
|
const compoundBase = "`src/blocks/*/types.ts` files remain the source of truth for each block's `block.json`, `typia.manifest.json`, and `typia-validator.php`. Fresh scaffolds include starter `typia.manifest.json` files so editor imports resolve before the first sync.";
|
|
39
68
|
if (compoundPersistenceEnabled) {
|
|
40
|
-
return `${compoundBase} For persistence-enabled parents, \`src/blocks/*/api-types.ts\` files remain the source of truth for \`src/blocks/*/api-schemas/*\` when you run \`sync-rest\`, while \`src/blocks/*/transport.ts\` is the first-class transport seam for editor and frontend requests.`;
|
|
69
|
+
return `${compoundBase} For persistence-enabled parents, \`src/blocks/*/api-types.ts\` files remain the source of truth for \`src/blocks/*/api-schemas/*\` when you run \`sync\` or \`sync-rest\`, while \`src/blocks/*/transport.ts\` is the first-class transport seam for editor and frontend requests.`;
|
|
41
70
|
}
|
|
42
71
|
return compoundBase;
|
|
43
72
|
}
|
|
44
73
|
if (templateId === "persistence") {
|
|
45
|
-
return "`src/types.ts` remains the source of truth for `block.json`, `typia.manifest.json`, and `typia-validator.php`. Fresh scaffolds include a starter `typia.manifest.json` so editor imports resolve before the first sync. `src/api-types.ts` remains the source of truth for `src/api-schemas/*` when you run `sync-rest`, while `src/transport.ts` is the first-class transport seam for editor and frontend requests. This scaffold is intentionally server-rendered: `src/render.php` is the canonical frontend entry, `src/save.tsx` returns `null`, and session-only write data now refreshes through the dedicated `/bootstrap` endpoint after hydration instead of being frozen into markup.";
|
|
74
|
+
return "`src/types.ts` remains the source of truth for `block.json`, `typia.manifest.json`, and `typia-validator.php`. Fresh scaffolds include a starter `typia.manifest.json` so editor imports resolve before the first sync. `src/api-types.ts` remains the source of truth for `src/api-schemas/*` when you run `sync` or `sync-rest`, while `src/transport.ts` is the first-class transport seam for editor and frontend requests. This scaffold is intentionally server-rendered: `src/render.php` is the canonical frontend entry, `src/save.tsx` returns `null`, and session-only write data now refreshes through the dedicated `/bootstrap` endpoint after hydration instead of being frozen into markup.";
|
|
46
75
|
}
|
|
47
76
|
return "`src/types.ts` remains the source of truth for `block.json`, `typia.manifest.json`, and `typia-validator.php`. Fresh scaffolds include a starter `typia.manifest.json` so editor imports resolve before the first sync. The basic scaffold stays static by design: `src/render.php` is only an opt-in server placeholder, `src/save.tsx` remains the canonical frontend output, and the generated webpack config keeps the current `@wordpress/scripts` CommonJS baseline unless you intentionally add `render` to `block.json`.";
|
|
48
77
|
}
|
|
@@ -69,7 +98,7 @@ function formatPhpRestExtensionPointsSection({ apiTypesPath, extraNote, mainPhpP
|
|
|
69
98
|
`- Edit \`${mainPhpPath}\` when you need to ${mainPhpScope}.`,
|
|
70
99
|
"- Edit `inc/rest-auth.php` or `inc/rest-public.php` when you need to customize write permissions or token/request-id/nonce checks for the selected policy.",
|
|
71
100
|
`- Edit \`${transportPath}\` when you need to switch between direct WordPress REST and a contract-compatible proxy or BFF without changing the endpoint contracts.`,
|
|
72
|
-
`- Keep \`${apiTypesPath}\` as the source of truth for request and response contracts, then regenerate \`${schemaJsonGlob}\`, per-contract \`${perContractOpenApiGlob}\`, and \`${aggregateOpenApiPath}\` with \`sync-rest
|
|
101
|
+
`- Keep \`${apiTypesPath}\` as the source of truth for request and response contracts, then regenerate \`${schemaJsonGlob}\`, per-contract \`${perContractOpenApiGlob}\`, and \`${aggregateOpenApiPath}\` with \`sync\` (or \`sync-rest\` after \`sync-types\` when you only need the REST layer).`,
|
|
73
102
|
"- Avoid hand-editing generated schema and OpenAPI artifacts unless you are debugging generated output; they are meant to be regenerated from TypeScript contracts.",
|
|
74
103
|
];
|
|
75
104
|
if (typeof extraNote === "string" && extraNote.length > 0) {
|
package/dist/runtime/scaffold.js
CHANGED
|
@@ -19,6 +19,7 @@ const BLOCK_SLUG_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
|
19
19
|
const PHP_PREFIX_PATTERN = /^[a-z_][a-z0-9_]*$/;
|
|
20
20
|
const PHP_PREFIX_MAX_LENGTH = 50;
|
|
21
21
|
const OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE = "@wp-typia/create-workspace-template";
|
|
22
|
+
const WORKSPACE_TEMPLATE_ALIAS = "workspace";
|
|
22
23
|
const EPHEMERAL_NODE_MODULES_LINK_TYPE = process.platform === "win32" ? "junction" : "dir";
|
|
23
24
|
const LOCKFILES = {
|
|
24
25
|
bun: ["bun.lock", "bun.lockb"],
|
|
@@ -138,15 +139,21 @@ export function getDefaultAnswers(projectName, templateId) {
|
|
|
138
139
|
title: toTitleCase(slugDefault),
|
|
139
140
|
};
|
|
140
141
|
}
|
|
142
|
+
function normalizeTemplateSelection(templateId) {
|
|
143
|
+
return templateId === WORKSPACE_TEMPLATE_ALIAS
|
|
144
|
+
? OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE
|
|
145
|
+
: templateId;
|
|
146
|
+
}
|
|
141
147
|
export async function resolveTemplateId({ templateId, yes = false, isInteractive = false, selectTemplate, }) {
|
|
142
148
|
if (templateId) {
|
|
149
|
+
const normalizedTemplateId = normalizeTemplateSelection(templateId);
|
|
143
150
|
if (isRemovedBuiltInTemplateId(templateId)) {
|
|
144
151
|
throw new Error(getRemovedBuiltInTemplateMessage(templateId));
|
|
145
152
|
}
|
|
146
|
-
if (isBuiltInTemplateId(
|
|
147
|
-
return getTemplateById(
|
|
153
|
+
if (isBuiltInTemplateId(normalizedTemplateId)) {
|
|
154
|
+
return getTemplateById(normalizedTemplateId).id;
|
|
148
155
|
}
|
|
149
|
-
return
|
|
156
|
+
return normalizedTemplateId;
|
|
150
157
|
}
|
|
151
158
|
if (yes) {
|
|
152
159
|
return "basic";
|
|
@@ -154,7 +161,7 @@ export async function resolveTemplateId({ templateId, yes = false, isInteractive
|
|
|
154
161
|
if (!isInteractive || !selectTemplate) {
|
|
155
162
|
throw new Error(`Template is required in non-interactive mode. Use --template <${TEMPLATE_IDS.join("|")}|./path|github:owner/repo/path[#ref]|npm-package>.`);
|
|
156
163
|
}
|
|
157
|
-
return selectTemplate();
|
|
164
|
+
return normalizeTemplateSelection(await selectTemplate());
|
|
158
165
|
}
|
|
159
166
|
export async function resolvePackageManagerId({ packageManager, yes = false, isInteractive = false, selectPackageManager, }) {
|
|
160
167
|
if (packageManager) {
|
|
@@ -317,12 +324,13 @@ function buildReadme(templateId, variables, packageManager, { withMigrationUi =
|
|
|
317
324
|
const sourceOfTruthNote = getTemplateSourceOfTruthNote(templateId, {
|
|
318
325
|
compoundPersistenceEnabled: variables.compoundPersistenceEnabled === "true",
|
|
319
326
|
});
|
|
327
|
+
const compoundPersistenceEnabled = variables.compoundPersistenceEnabled === "true";
|
|
320
328
|
const publicPersistencePolicyNote = variables.isPublicPersistencePolicy === "true"
|
|
321
329
|
? "Public persistence writes use signed short-lived tokens, per-request ids, and coarse rate limiting by default. Add application-specific abuse controls before using the same pattern for high-value metrics or experiments."
|
|
322
330
|
: null;
|
|
323
331
|
const compoundExtensionWorkflowSection = getCompoundExtensionWorkflowSection(packageManager, templateId);
|
|
324
332
|
const phpRestExtensionPointsSection = getPhpRestExtensionPointsSection(templateId, {
|
|
325
|
-
compoundPersistenceEnabled
|
|
333
|
+
compoundPersistenceEnabled,
|
|
326
334
|
slug: variables.slug,
|
|
327
335
|
});
|
|
328
336
|
const developmentScript = getPrimaryDevelopmentScript(templateId);
|
|
@@ -362,7 +370,9 @@ ${formatRunScript(packageManager, "build")}
|
|
|
362
370
|
${optionalOnboardingSteps.join("\n")}
|
|
363
371
|
\`\`\`
|
|
364
372
|
|
|
365
|
-
${getOptionalOnboardingNote(packageManager, templateId
|
|
373
|
+
${getOptionalOnboardingNote(packageManager, templateId, {
|
|
374
|
+
compoundPersistenceEnabled,
|
|
375
|
+
})}
|
|
366
376
|
|
|
367
377
|
${sourceOfTruthNote}${publicPersistencePolicyNote ? `\n\n${publicPersistencePolicyNote}` : ""}${migrationSection ? `\n\n${migrationSection}` : ""}${compoundExtensionWorkflowSection ? `\n\n${compoundExtensionWorkflowSection}` : ""}${wpEnvSection ? `\n\n${wpEnvSection}` : ""}${testPresetSection ? `\n\n${testPresetSection}` : ""}${phpRestExtensionPointsSection ? `\n\n${phpRestExtensionPointsSection}` : ""}
|
|
368
378
|
`;
|
|
@@ -625,14 +635,15 @@ async function applyWorkspaceMigrationCapability(projectDir, packageManager) {
|
|
|
625
635
|
writeInitialMigrationScaffold(projectDir, "v1", []);
|
|
626
636
|
}
|
|
627
637
|
export async function scaffoldProject({ projectDir, templateId, answers, dataStorageMode, persistencePolicy, packageManager, cwd = process.cwd(), allowExistingDir = false, noInstall = false, installDependencies = undefined, variant, withMigrationUi = false, withTestPreset = false, withWpEnv = false, }) {
|
|
638
|
+
const resolvedTemplateId = normalizeTemplateSelection(templateId);
|
|
628
639
|
const resolvedPackageManager = getPackageManager(packageManager).id;
|
|
629
|
-
const isBuiltInTemplate = isBuiltInTemplateId(
|
|
630
|
-
const variables = getTemplateVariables(
|
|
640
|
+
const isBuiltInTemplate = isBuiltInTemplateId(resolvedTemplateId);
|
|
641
|
+
const variables = getTemplateVariables(resolvedTemplateId, {
|
|
631
642
|
...answers,
|
|
632
643
|
dataStorageMode: dataStorageMode ?? answers.dataStorageMode,
|
|
633
644
|
persistencePolicy: persistencePolicy ?? answers.persistencePolicy,
|
|
634
645
|
});
|
|
635
|
-
const templateSource = await resolveTemplateSource(
|
|
646
|
+
const templateSource = await resolveTemplateSource(resolvedTemplateId, cwd, variables, variant);
|
|
636
647
|
const supportsMigrationUi = isBuiltInTemplate || templateSource.isOfficialWorkspaceTemplate === true;
|
|
637
648
|
if (withMigrationUi && !supportsMigrationUi) {
|
|
638
649
|
await templateSource.cleanup?.();
|
|
@@ -649,8 +660,8 @@ export async function scaffoldProject({ projectDir, templateId, answers, dataSto
|
|
|
649
660
|
}
|
|
650
661
|
const isOfficialWorkspace = isOfficialWorkspaceProject(projectDir);
|
|
651
662
|
if (isBuiltInTemplate) {
|
|
652
|
-
await writeStarterManifestFiles(projectDir,
|
|
653
|
-
await seedBuiltInPersistenceArtifacts(projectDir,
|
|
663
|
+
await writeStarterManifestFiles(projectDir, resolvedTemplateId, variables);
|
|
664
|
+
await seedBuiltInPersistenceArtifacts(projectDir, resolvedTemplateId, variables);
|
|
654
665
|
await applyLocalDevPresetFiles({
|
|
655
666
|
projectDir,
|
|
656
667
|
variables,
|
|
@@ -661,7 +672,7 @@ export async function scaffoldProject({ projectDir, templateId, answers, dataSto
|
|
|
661
672
|
await applyMigrationUiCapability({
|
|
662
673
|
packageManager: resolvedPackageManager,
|
|
663
674
|
projectDir,
|
|
664
|
-
templateId,
|
|
675
|
+
templateId: resolvedTemplateId,
|
|
665
676
|
variables,
|
|
666
677
|
});
|
|
667
678
|
}
|
|
@@ -671,7 +682,7 @@ export async function scaffoldProject({ projectDir, templateId, answers, dataSto
|
|
|
671
682
|
}
|
|
672
683
|
const readmePath = path.join(projectDir, "README.md");
|
|
673
684
|
if (!fs.existsSync(readmePath)) {
|
|
674
|
-
await fsp.writeFile(readmePath, buildReadme(
|
|
685
|
+
await fsp.writeFile(readmePath, buildReadme(resolvedTemplateId, variables, resolvedPackageManager, {
|
|
675
686
|
withMigrationUi: isBuiltInTemplate || isOfficialWorkspace ? withMigrationUi : false,
|
|
676
687
|
withTestPreset: isBuiltInTemplate ? withTestPreset : false,
|
|
677
688
|
withWpEnv: isBuiltInTemplate ? withWpEnv : false,
|
|
@@ -688,7 +699,7 @@ export async function scaffoldProject({ projectDir, templateId, answers, dataSto
|
|
|
688
699
|
compoundPersistenceEnabled: variables.compoundPersistenceEnabled === "true",
|
|
689
700
|
packageManager: resolvedPackageManager,
|
|
690
701
|
projectDir,
|
|
691
|
-
templateId,
|
|
702
|
+
templateId: resolvedTemplateId,
|
|
692
703
|
withTestPreset,
|
|
693
704
|
withWpEnv,
|
|
694
705
|
});
|
|
@@ -706,7 +717,7 @@ export async function scaffoldProject({ projectDir, templateId, answers, dataSto
|
|
|
706
717
|
return {
|
|
707
718
|
projectDir,
|
|
708
719
|
selectedVariant: templateSource.selectedVariant ?? null,
|
|
709
|
-
templateId,
|
|
720
|
+
templateId: resolvedTemplateId,
|
|
710
721
|
packageManager: resolvedPackageManager,
|
|
711
722
|
variables,
|
|
712
723
|
warnings: templateSource.warnings ?? [],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wp-typia/project-tools",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.1",
|
|
4
4
|
"description": "Project orchestration and programmatic tooling for wp-typia",
|
|
5
5
|
"packageManager": "bun@1.3.11",
|
|
6
6
|
"type": "module",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
},
|
|
63
63
|
"dependencies": {
|
|
64
64
|
"@wp-typia/api-client": "^0.4.2",
|
|
65
|
-
"@wp-typia/block-runtime": "^0.4.
|
|
65
|
+
"@wp-typia/block-runtime": "^0.4.4",
|
|
66
66
|
"@wp-typia/rest": "^0.3.5",
|
|
67
67
|
"@wp-typia/block-types": "^0.2.1",
|
|
68
68
|
"mustache": "^4.2.0",
|
|
@@ -7,15 +7,16 @@
|
|
|
7
7
|
"license": "GPL-2.0-or-later",
|
|
8
8
|
"main": "build/index.js",
|
|
9
9
|
"scripts": {
|
|
10
|
+
"sync": "tsx scripts/sync-project.ts",
|
|
10
11
|
"sync-types": "tsx scripts/sync-types-to-block-json.ts",
|
|
11
|
-
"build": "bun run sync
|
|
12
|
-
"start": "bun run sync
|
|
12
|
+
"build": "bun run sync --check && wp-scripts build --experimental-modules",
|
|
13
|
+
"start": "bun run sync && wp-scripts start --experimental-modules",
|
|
13
14
|
"dev": "bun run start",
|
|
14
15
|
"lint:js": "wp-scripts lint-js",
|
|
15
16
|
"lint:css": "wp-scripts lint-style",
|
|
16
17
|
"lint": "bun run lint:js && bun run lint:css",
|
|
17
18
|
"format": "wp-scripts format",
|
|
18
|
-
"typecheck": "bun run sync
|
|
19
|
+
"typecheck": "bun run sync --check && tsc --noEmit"
|
|
19
20
|
},
|
|
20
21
|
"devDependencies": {
|
|
21
22
|
"@wp-typia/block-runtime": "{{blockRuntimePackageVersion}}",
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
interface SyncCliOptions {
|
|
7
|
+
check: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function parseCliOptions( argv: string[] ): SyncCliOptions {
|
|
11
|
+
const options: SyncCliOptions = {
|
|
12
|
+
check: false,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
for ( const argument of argv ) {
|
|
16
|
+
if ( argument === '--check' ) {
|
|
17
|
+
options.check = true;
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
throw new Error( `Unknown sync flag: ${ argument }` );
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return options;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getSyncScriptEnv() {
|
|
28
|
+
const binaryDirectory = path.join( process.cwd(), 'node_modules', '.bin' );
|
|
29
|
+
const inheritedPath =
|
|
30
|
+
process.env.PATH ??
|
|
31
|
+
process.env.Path ??
|
|
32
|
+
Object.entries( process.env ).find(
|
|
33
|
+
( [ key ] ) => key.toLowerCase() === 'path'
|
|
34
|
+
)?.[ 1 ] ??
|
|
35
|
+
'';
|
|
36
|
+
const nextPath = fs.existsSync( binaryDirectory )
|
|
37
|
+
? `${ binaryDirectory }${ path.delimiter }${ inheritedPath }`
|
|
38
|
+
: inheritedPath;
|
|
39
|
+
const env: NodeJS.ProcessEnv = {
|
|
40
|
+
...process.env,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
for ( const key of Object.keys( env ) ) {
|
|
44
|
+
if ( key.toLowerCase() === 'path' ) {
|
|
45
|
+
delete env[ key ];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
env.PATH = nextPath;
|
|
50
|
+
|
|
51
|
+
return env;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function runSyncScript( scriptPath: string, options: SyncCliOptions ) {
|
|
55
|
+
const args = [ scriptPath ];
|
|
56
|
+
if ( options.check ) {
|
|
57
|
+
args.push( '--check' );
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const result = spawnSync( 'tsx', args, {
|
|
61
|
+
cwd: process.cwd(),
|
|
62
|
+
env: getSyncScriptEnv(),
|
|
63
|
+
shell: process.platform === 'win32',
|
|
64
|
+
stdio: 'inherit',
|
|
65
|
+
} );
|
|
66
|
+
|
|
67
|
+
if ( result.error ) {
|
|
68
|
+
if ( ( result.error as NodeJS.ErrnoException ).code === 'ENOENT' ) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
'Unable to resolve `tsx` for project sync. Install project dependencies or rerun the command through your package manager.'
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
throw result.error;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if ( result.status !== 0 ) {
|
|
78
|
+
throw new Error( `Sync script failed: ${ scriptPath }` );
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function main() {
|
|
83
|
+
const options = parseCliOptions( process.argv.slice( 2 ) );
|
|
84
|
+
const syncTypesScriptPath = path.join( 'scripts', 'sync-types-to-block-json.ts' );
|
|
85
|
+
const syncRestScriptPath = path.join( 'scripts', 'sync-rest-contracts.ts' );
|
|
86
|
+
|
|
87
|
+
runSyncScript( syncTypesScriptPath, options );
|
|
88
|
+
|
|
89
|
+
if ( fs.existsSync( path.resolve( process.cwd(), syncRestScriptPath ) ) ) {
|
|
90
|
+
runSyncScript( syncRestScriptPath, options );
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
console.log(
|
|
94
|
+
options.check
|
|
95
|
+
? '✅ Generated project metadata and REST artifacts are already synchronized.'
|
|
96
|
+
: '✅ Generated project metadata and REST artifacts were synchronized.'
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
main().catch( ( error ) => {
|
|
101
|
+
console.error( '❌ Project sync failed:', error );
|
|
102
|
+
process.exit( 1 );
|
|
103
|
+
} );
|
|
@@ -8,11 +8,12 @@
|
|
|
8
8
|
"main": "build/index.js",
|
|
9
9
|
"scripts": {
|
|
10
10
|
"add-child": "tsx scripts/add-compound-child.ts",
|
|
11
|
+
"sync": "tsx scripts/sync-project.ts",
|
|
11
12
|
"sync-types": "tsx scripts/sync-types-to-block-json.ts",
|
|
12
|
-
"build": "bun run sync
|
|
13
|
-
"start": "bun run sync
|
|
13
|
+
"build": "bun run sync --check && wp-scripts build --experimental-modules",
|
|
14
|
+
"start": "bun run sync && wp-scripts start --experimental-modules",
|
|
14
15
|
"dev": "bun run start",
|
|
15
|
-
"typecheck": "bun run sync
|
|
16
|
+
"typecheck": "bun run sync --check && tsc --noEmit",
|
|
16
17
|
"lint:js": "wp-scripts lint-js",
|
|
17
18
|
"lint:css": "wp-scripts lint-style",
|
|
18
19
|
"lint": "bun run lint:js && bun run lint:css",
|
|
@@ -346,8 +346,15 @@ function renderBlockJson(
|
|
|
346
346
|
) }\n`;
|
|
347
347
|
}
|
|
348
348
|
|
|
349
|
-
function renderTypesFile(
|
|
350
|
-
|
|
349
|
+
function renderTypesFile(
|
|
350
|
+
childTypeName: string,
|
|
351
|
+
childInterfaceName: string,
|
|
352
|
+
childTitle: string
|
|
353
|
+
): string {
|
|
354
|
+
return `import type { ValidationResult } from '@wp-typia/block-runtime/validation';
|
|
355
|
+
import { tags } from 'typia';
|
|
356
|
+
|
|
357
|
+
export type { ValidationResult } from '@wp-typia/block-runtime/validation';
|
|
351
358
|
|
|
352
359
|
export interface ${ childTypeName } {
|
|
353
360
|
\ttitle: string &
|
|
@@ -359,6 +366,8 @@ export interface ${ childTypeName } {
|
|
|
359
366
|
\t\ttags.MaxLength< 280 > &
|
|
360
367
|
\t\ttags.Default< ${ JSON.stringify( CHILD_PLACEHOLDER ) } >;
|
|
361
368
|
}
|
|
369
|
+
|
|
370
|
+
export type ${ childInterfaceName }ValidationResult = ValidationResult< ${ childTypeName } >;
|
|
362
371
|
`;
|
|
363
372
|
}
|
|
364
373
|
|
|
@@ -407,36 +416,37 @@ function renderValidatorsFile(
|
|
|
407
416
|
): string {
|
|
408
417
|
return `import typia from 'typia';
|
|
409
418
|
import currentManifest from './typia.manifest.json';
|
|
410
|
-
import {
|
|
411
|
-
\
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
\
|
|
427
|
-
\tvalidate,
|
|
428
|
-
\tassert,
|
|
429
|
-
\tis,
|
|
430
|
-
\trandom,
|
|
431
|
-
\tclone,
|
|
432
|
-
\tprune,
|
|
419
|
+
import type {
|
|
420
|
+
\t${ childTypeName },
|
|
421
|
+
\t${ childInterfaceName }ValidationResult,
|
|
422
|
+
} from './types';
|
|
423
|
+
import { createTemplateValidatorToolkit } from '../../validator-toolkit';
|
|
424
|
+
|
|
425
|
+
const scaffoldValidators = createTemplateValidatorToolkit< ${ childTypeName } >( {
|
|
426
|
+
\tassert: typia.createAssert< ${ childTypeName } >(),
|
|
427
|
+
\tclone: typia.misc.createClone< ${ childTypeName } >() as (
|
|
428
|
+
\t\tvalue: ${ childTypeName },
|
|
429
|
+
\t) => ${ childTypeName },
|
|
430
|
+
\tis: typia.createIs< ${ childTypeName } >(),
|
|
431
|
+
\tmanifest: currentManifest,
|
|
432
|
+
\tprune: typia.misc.createPrune< ${ childTypeName } >(),
|
|
433
|
+
\trandom: typia.createRandom< ${ childTypeName } >() as (
|
|
434
|
+
\t\t...args: unknown[]
|
|
435
|
+
\t) => ${ childTypeName },
|
|
436
|
+
\tvalidate: typia.createValidate< ${ childTypeName } >(),
|
|
433
437
|
} );
|
|
434
438
|
|
|
435
|
-
export const validate${ childInterfaceName } =
|
|
439
|
+
export const validate${ childInterfaceName } =
|
|
440
|
+
\tscaffoldValidators.validateAttributes as (
|
|
441
|
+
\t\tattributes: unknown
|
|
442
|
+
\t) => ${ childInterfaceName }ValidationResult;
|
|
436
443
|
|
|
437
444
|
export const validators = scaffoldValidators.validators;
|
|
438
445
|
|
|
439
|
-
export const sanitize${ childInterfaceName } =
|
|
446
|
+
export const sanitize${ childInterfaceName } =
|
|
447
|
+
\tscaffoldValidators.sanitizeAttributes as (
|
|
448
|
+
\t\tattributes: Partial< ${ childTypeName } >
|
|
449
|
+
\t) => ${ childTypeName };
|
|
440
450
|
|
|
441
451
|
export const createAttributeUpdater = scaffoldValidators.createAttributeUpdater;
|
|
442
452
|
`;
|
|
@@ -605,7 +615,11 @@ function main() {
|
|
|
605
615
|
renderBlockJson( childBlockName, childFolderSlug, childTitle ),
|
|
606
616
|
'utf8'
|
|
607
617
|
);
|
|
608
|
-
fs.writeFileSync(
|
|
618
|
+
fs.writeFileSync(
|
|
619
|
+
path.join( childDir, 'types.ts' ),
|
|
620
|
+
renderTypesFile( childTypeName, childInterfaceName, childTitle ),
|
|
621
|
+
'utf8'
|
|
622
|
+
);
|
|
609
623
|
fs.writeFileSync(
|
|
610
624
|
path.join( childDir, 'typia.manifest.json' ),
|
|
611
625
|
renderStarterManifestFile( childTypeName, childTitle ),
|