@wp-typia/project-tools 0.19.2 → 0.19.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/runtime/cli-diagnostics.d.ts +2 -0
  2. package/dist/runtime/cli-diagnostics.js +10 -1
  3. package/dist/runtime/cli-help.js +3 -0
  4. package/dist/runtime/cli-init.d.ts +49 -0
  5. package/dist/runtime/cli-init.js +354 -0
  6. package/dist/runtime/cli-scaffold.d.ts +1 -0
  7. package/dist/runtime/cli-scaffold.js +5 -1
  8. package/dist/runtime/cli-templates.js +36 -6
  9. package/dist/runtime/external-template-guards.d.ts +31 -0
  10. package/dist/runtime/external-template-guards.js +132 -0
  11. package/dist/runtime/package-managers.d.ts +8 -0
  12. package/dist/runtime/package-managers.js +13 -0
  13. package/dist/runtime/package-versions.d.ts +8 -0
  14. package/dist/runtime/package-versions.js +84 -19
  15. package/dist/runtime/scaffold-documents.js +19 -1
  16. package/dist/runtime/scaffold-onboarding.d.ts +4 -0
  17. package/dist/runtime/scaffold-onboarding.js +25 -1
  18. package/dist/runtime/scaffold.js +2 -5
  19. package/dist/runtime/template-registry.d.ts +23 -1
  20. package/dist/runtime/template-registry.js +37 -3
  21. package/dist/runtime/template-source-external.js +9 -3
  22. package/dist/runtime/template-source-remote.js +5 -0
  23. package/dist/runtime/template-source-seeds.js +40 -6
  24. package/package.json +7 -2
  25. package/templates/_shared/base/package.json.mustache +0 -1
  26. package/templates/_shared/compound/core/package.json.mustache +0 -1
  27. package/templates/_shared/compound/persistence/package.json.mustache +0 -1
  28. package/templates/_shared/persistence/core/package.json.mustache +0 -1
  29. package/templates/interactivity/package.json.mustache +0 -1
@@ -17,6 +17,8 @@ export declare const CLI_DIAGNOSTIC_CODES: {
17
17
  readonly MISSING_ARGUMENT: "missing-argument";
18
18
  readonly MISSING_BUILD_ARTIFACT: "missing-build-artifact";
19
19
  readonly OUTSIDE_PROJECT_ROOT: "outside-project-root";
20
+ readonly TEMPLATE_SOURCE_TIMEOUT: "template-source-timeout";
21
+ readonly TEMPLATE_SOURCE_TOO_LARGE: "template-source-too-large";
20
22
  readonly UNSUPPORTED_COMMAND: "unsupported-command";
21
23
  };
22
24
  export type CliDiagnosticCode = (typeof CLI_DIAGNOSTIC_CODES)[keyof typeof CLI_DIAGNOSTIC_CODES];
@@ -8,12 +8,15 @@ export const CLI_DIAGNOSTIC_CODES = {
8
8
  MISSING_ARGUMENT: "missing-argument",
9
9
  MISSING_BUILD_ARTIFACT: "missing-build-artifact",
10
10
  OUTSIDE_PROJECT_ROOT: "outside-project-root",
11
+ TEMPLATE_SOURCE_TIMEOUT: "template-source-timeout",
12
+ TEMPLATE_SOURCE_TOO_LARGE: "template-source-too-large",
11
13
  UNSUPPORTED_COMMAND: "unsupported-command",
12
14
  };
13
15
  const DEFAULT_CLI_FAILURE_SUMMARIES = {
14
16
  add: "Unable to complete the requested add workflow.",
15
17
  create: "Unable to complete the requested create workflow.",
16
18
  doctor: "One or more doctor checks failed.",
19
+ init: "Unable to preview the requested retrofit init plan.",
17
20
  mcp: "Unable to inspect or sync MCP metadata.",
18
21
  migrate: "Unable to complete the requested migration command.",
19
22
  sync: "Unable to complete the requested sync workflow.",
@@ -168,10 +171,16 @@ function inferCliDiagnosticCode(options) {
168
171
  if (/dependencies have not been installed yet/u.test(haystack)) {
169
172
  return CLI_DIAGNOSTIC_CODES.DEPENDENCIES_NOT_INSTALLED;
170
173
  }
174
+ if (/Timed out while .*external template|Timed out while .*npm template|Timed out while .*GitHub template/u.test(haystack)) {
175
+ return CLI_DIAGNOSTIC_CODES.TEMPLATE_SOURCE_TIMEOUT;
176
+ }
177
+ if (/external template size limit/u.test(haystack)) {
178
+ return CLI_DIAGNOSTIC_CODES.TEMPLATE_SOURCE_TOO_LARGE;
179
+ }
171
180
  if (options.command === "doctor") {
172
181
  return CLI_DIAGNOSTIC_CODES.DOCTOR_CHECK_FAILED;
173
182
  }
174
- if (/requires <|requires --/u.test(haystack)) {
183
+ if (/requires <|requires --|requires a value/u.test(haystack)) {
175
184
  return CLI_DIAGNOSTIC_CODES.MISSING_ARGUMENT;
176
185
  }
177
186
  if (/Unknown .*subcommand|Unknown add kind|Unknown template|removed in favor|does not support|The Bun-free fallback runtime does not support|The positional alias only accepts/u.test(haystack)) {
@@ -17,6 +17,7 @@ export function formatHelpText() {
17
17
  wp-typia create <project-dir> [--template persistence] [--external-layer-source <./path|github:owner/repo/path[#ref]|npm-package>] [--external-layer-id <layer-id>] [--alternate-render-targets <email,mjml,plain-text>] [--data-storage <post-meta|custom-table>] [--persistence-policy <authenticated|public>] [--namespace <value>] [--text-domain <value>] [--php-prefix <value>] [--with-migration-ui] [--with-wp-env] [--with-test-preset] [--yes] [--dry-run] [--no-install] [--package-manager <id>]
18
18
  wp-typia create <project-dir> [--template compound] [--external-layer-source <./path|github:owner/repo/path[#ref]|npm-package>] [--external-layer-id <layer-id>] [--inner-blocks-preset <freeform|ordered|horizontal|locked-structure>] [--alternate-render-targets <email,mjml,plain-text>] [--data-storage <post-meta|custom-table>] [--persistence-policy <authenticated|public>] [--namespace <value>] [--text-domain <value>] [--php-prefix <value>] [--with-migration-ui] [--with-wp-env] [--with-test-preset] [--yes] [--dry-run] [--no-install] [--package-manager <id>]
19
19
  wp-typia <project-dir> [create flags...]
20
+ wp-typia init [project-dir]
20
21
  wp-typia add block <name> --template <basic|interactivity|persistence|compound> [--external-layer-source <./path|github:owner/repo/path[#ref]|npm-package>] [--external-layer-id <layer-id>] [--inner-blocks-preset <freeform|ordered|horizontal|locked-structure>] [--alternate-render-targets <email,mjml,plain-text>] [--data-storage <post-meta|custom-table>] [--persistence-policy <authenticated|public>]
21
22
  wp-typia add variation <name> --block <block-slug>
22
23
  wp-typia add pattern <name>
@@ -37,6 +38,7 @@ Package managers: ${PACKAGE_MANAGER_IDS.join(", ")}
37
38
  Notes:
38
39
  \`wp-typia create\` is the canonical scaffold command.
39
40
  \`wp-typia <project-dir>\` remains a backward-compatible alias to \`create\` when \`<project-dir>\` is the only positional argument.
41
+ \`wp-typia init\` is a preview-only retrofit planner for existing projects. It does not write files yet.
40
42
  Use \`--template workspace\` as shorthand for \`@wp-typia/create-workspace-template\`, the official empty workspace scaffold behind \`wp-typia add ...\`.
41
43
  \`query-loop\` is create-only. Use \`wp-typia create <project-dir> --template query-loop\`; \`wp-typia add block\` accepts only basic, interactivity, persistence, and compound families.
42
44
  \`add variation\` uses an existing workspace block from \`scripts/block-config.ts\`.
@@ -46,6 +48,7 @@ Notes:
46
48
  \`add editor-plugin\` scaffolds a document-level editor extension under \`src/editor-plugins/\`.
47
49
  \`add hooked-block\` patches an existing workspace block's \`block.json\` \`blockHooks\` metadata.
48
50
  \`wp-typia doctor\` always checks environment readiness and reports when it only ran environment-level diagnostics; official workspace roots also get inventory and source-tree drift checks.
51
+ \`wp-typia init\` previews the minimum sync/doctor/migration adoption layer for supported existing layouts before a future write mode exists.
49
52
  \`wp-typia migrate doctor --all\` checks migration target alignment, snapshots, fixtures, and generated migration artifacts.
50
53
  \`migrate\` is the canonical migration command; \`migrations\` is no longer supported.`;
51
54
  }
@@ -0,0 +1,49 @@
1
+ import { type PackageManagerId } from "./package-managers.js";
2
+ type InitPlanAction = "add" | "update";
3
+ type InitPlanStatus = "already-initialized" | "preview";
4
+ type InitPlanLayoutKind = "generated-project" | "multi-block" | "official-workspace" | "single-block" | "unsupported";
5
+ interface InitDependencyChange {
6
+ action: InitPlanAction;
7
+ currentValue?: string;
8
+ name: string;
9
+ requiredValue: string;
10
+ }
11
+ interface InitScriptChange {
12
+ action: InitPlanAction;
13
+ currentValue?: string;
14
+ name: string;
15
+ requiredValue: string;
16
+ }
17
+ interface InitPackageManagerFieldChange {
18
+ action: InitPlanAction;
19
+ currentValue?: string;
20
+ requiredValue: string;
21
+ }
22
+ interface InitFilePlan {
23
+ path: string;
24
+ purpose: string;
25
+ }
26
+ export interface RetrofitInitPlan {
27
+ commandMode: "preview-only";
28
+ detectedLayout: {
29
+ blockNames: string[];
30
+ description: string;
31
+ kind: InitPlanLayoutKind;
32
+ };
33
+ generatedArtifacts: string[];
34
+ nextSteps: string[];
35
+ notes: string[];
36
+ packageChanges: {
37
+ addDevDependencies: InitDependencyChange[];
38
+ packageManagerField?: InitPackageManagerFieldChange;
39
+ scripts: InitScriptChange[];
40
+ };
41
+ plannedFiles: InitFilePlan[];
42
+ packageManager: PackageManagerId;
43
+ projectDir: string;
44
+ projectName: string;
45
+ status: InitPlanStatus;
46
+ summary: string;
47
+ }
48
+ export declare function getInitPlan(projectDir: string): RetrofitInitPlan;
49
+ export {};
@@ -0,0 +1,354 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { discoverMigrationInitLayout } from "./migration-project.js";
4
+ import { formatAddDevDependenciesCommand, formatPackageExecCommand, formatRunScript, getPackageManager, transformPackageManagerText, } from "./package-managers.js";
5
+ import { getPackageVersions } from "./package-versions.js";
6
+ import { parseWorkspacePackageManagerId, tryResolveWorkspaceProject, } from "./workspace-project.js";
7
+ const SUPPORTED_RETROFIT_LAYOUT_NOTE = "Supported retrofit layouts currently mirror the migration bootstrap detector: `src/block.json` + `src/types.ts` + `src/save.tsx`, legacy root `block.json` + `src/types.ts` + `src/save.tsx`, or multi-block `src/blocks/*/block.json` workspaces.";
8
+ const BASE_RETROFIT_SCRIPTS = {
9
+ sync: "tsx scripts/sync-project.ts",
10
+ "sync-types": "tsx scripts/sync-types-to-block-json.ts",
11
+ typecheck: "bun run sync --check && tsc --noEmit",
12
+ };
13
+ const BASE_RETROFIT_DEV_DEPENDENCIES = [
14
+ "@typia/unplugin",
15
+ "@wp-typia/block-runtime",
16
+ "@wp-typia/block-types",
17
+ "tsx",
18
+ "typescript",
19
+ "typia",
20
+ ];
21
+ function normalizeRelativePath(value) {
22
+ return value.replace(/\\/gu, "/");
23
+ }
24
+ function readProjectPackageJson(projectDir) {
25
+ const packageJsonPath = path.join(projectDir, "package.json");
26
+ if (!fs.existsSync(packageJsonPath)) {
27
+ return null;
28
+ }
29
+ return JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
30
+ }
31
+ function inferInitPackageManager(projectDir, packageJson) {
32
+ if (packageJson?.packageManager) {
33
+ return parseWorkspacePackageManagerId(packageJson.packageManager);
34
+ }
35
+ if (fs.existsSync(path.join(projectDir, "bun.lock")) ||
36
+ fs.existsSync(path.join(projectDir, "bun.lockb"))) {
37
+ return "bun";
38
+ }
39
+ if (fs.existsSync(path.join(projectDir, "pnpm-lock.yaml"))) {
40
+ return "pnpm";
41
+ }
42
+ if (fs.existsSync(path.join(projectDir, "yarn.lock")) ||
43
+ fs.existsSync(path.join(projectDir, ".yarnrc.yml"))) {
44
+ return "yarn";
45
+ }
46
+ return "npm";
47
+ }
48
+ function getWpTypiaCliSpecifier() {
49
+ const versions = getPackageVersions();
50
+ return versions.wpTypiaPackageExactVersion === "0.0.0"
51
+ ? "wp-typia"
52
+ : `wp-typia@${versions.wpTypiaPackageExactVersion}`;
53
+ }
54
+ function buildRequiredDevDependencyMap() {
55
+ const versions = getPackageVersions();
56
+ return {
57
+ "@typia/unplugin": "^12.0.1",
58
+ "@wp-typia/block-runtime": versions.blockRuntimePackageVersion,
59
+ "@wp-typia/block-types": versions.blockTypesPackageVersion,
60
+ tsx: "^4.20.5",
61
+ typescript: "^5.9.2",
62
+ typia: "^12.0.1",
63
+ };
64
+ }
65
+ function getExistingDependencyVersion(packageJson, name) {
66
+ return packageJson?.devDependencies?.[name] ?? packageJson?.dependencies?.[name];
67
+ }
68
+ function buildDependencyChanges(packageJson) {
69
+ const requiredDependencies = buildRequiredDevDependencyMap();
70
+ return BASE_RETROFIT_DEV_DEPENDENCIES.flatMap((name) => {
71
+ const requiredValue = requiredDependencies[name];
72
+ const currentValue = getExistingDependencyVersion(packageJson, name);
73
+ if (currentValue === requiredValue) {
74
+ return [];
75
+ }
76
+ return [
77
+ {
78
+ action: currentValue ? "update" : "add",
79
+ ...(currentValue ? { currentValue } : {}),
80
+ name,
81
+ requiredValue,
82
+ },
83
+ ];
84
+ });
85
+ }
86
+ function buildScriptChanges(packageJson, packageManager) {
87
+ const scripts = packageJson?.scripts ?? {};
88
+ return Object.entries(BASE_RETROFIT_SCRIPTS).flatMap(([name, commandSource]) => {
89
+ const requiredValue = transformPackageManagerText(commandSource, packageManager);
90
+ const currentValue = scripts[name];
91
+ if (currentValue === requiredValue) {
92
+ return [];
93
+ }
94
+ return [
95
+ {
96
+ action: typeof currentValue === "string" ? "update" : "add",
97
+ ...(typeof currentValue === "string" ? { currentValue } : {}),
98
+ name,
99
+ requiredValue,
100
+ },
101
+ ];
102
+ });
103
+ }
104
+ function buildPackageManagerFieldChange(packageJson, packageManager) {
105
+ if (packageManager === "npm") {
106
+ return undefined;
107
+ }
108
+ const requiredValue = getPackageManager(packageManager).packageManagerField;
109
+ const currentValue = packageJson?.packageManager;
110
+ if (currentValue === requiredValue) {
111
+ return undefined;
112
+ }
113
+ return {
114
+ action: typeof currentValue === "string" ? "update" : "add",
115
+ ...(typeof currentValue === "string" ? { currentValue } : {}),
116
+ requiredValue,
117
+ };
118
+ }
119
+ function buildGeneratedArtifactPaths(blockJsonFile, manifestFile) {
120
+ const manifestDir = path.dirname(manifestFile);
121
+ const artifactPaths = [
122
+ blockJsonFile,
123
+ manifestFile,
124
+ path.join(manifestDir, "typia.schema.json"),
125
+ path.join(manifestDir, "typia-validator.php"),
126
+ path.join(manifestDir, "typia.openapi.json"),
127
+ ];
128
+ return Array.from(new Set(artifactPaths.map((filePath) => normalizeRelativePath(filePath))));
129
+ }
130
+ function buildLayoutDetails(projectDir) {
131
+ try {
132
+ const discoveredLayout = discoverMigrationInitLayout(projectDir);
133
+ if (discoveredLayout.mode === "multi") {
134
+ return {
135
+ blockNames: discoveredLayout.blocks.map((block) => block.blockName),
136
+ description: `Detected a supported multi-block retrofit candidate (${discoveredLayout.blocks.length} targets).`,
137
+ generatedArtifacts: discoveredLayout.blocks.flatMap((block) => buildGeneratedArtifactPaths(block.blockJsonFile, block.manifestFile)),
138
+ kind: "multi-block",
139
+ notes: [
140
+ "Migration bootstrap can stay optional. Add it later with `wp-typia migrate init --current-migration-version v1` once the typed sync surface is in place.",
141
+ ],
142
+ };
143
+ }
144
+ return {
145
+ blockNames: [discoveredLayout.block.blockName],
146
+ description: "Detected a supported single-block retrofit candidate.",
147
+ generatedArtifacts: buildGeneratedArtifactPaths(discoveredLayout.block.blockJsonFile, discoveredLayout.block.manifestFile),
148
+ kind: "single-block",
149
+ notes: discoveredLayout.block.blockJsonFile === "block.json"
150
+ ? [
151
+ "Legacy root `block.json` layouts are still supported for retrofit planning, but newer scaffolds keep generated block metadata under `src/`.",
152
+ ]
153
+ : [],
154
+ };
155
+ }
156
+ catch (error) {
157
+ const message = error instanceof Error ? error.message : String(error);
158
+ return {
159
+ blockNames: [],
160
+ description: "No supported retrofit layout was auto-detected yet.",
161
+ generatedArtifacts: [],
162
+ kind: "unsupported",
163
+ notes: [message, SUPPORTED_RETROFIT_LAYOUT_NOTE],
164
+ };
165
+ }
166
+ }
167
+ function hasExistingWpTypiaProjectSurface(packageJson) {
168
+ const scripts = packageJson?.scripts ?? {};
169
+ const hasSyncSurface = typeof scripts.sync === "string" || typeof scripts["sync-types"] === "string";
170
+ const hasRuntimeDeps = typeof getExistingDependencyVersion(packageJson, "@wp-typia/block-runtime") ===
171
+ "string" &&
172
+ typeof getExistingDependencyVersion(packageJson, "@wp-typia/block-types") ===
173
+ "string";
174
+ return hasSyncSurface && hasRuntimeDeps;
175
+ }
176
+ function buildPlannedFiles(layoutKind) {
177
+ const plannedFiles = [
178
+ {
179
+ path: "scripts/sync-types-to-block-json.ts",
180
+ purpose: "Generate block.json and Typia metadata artifacts from the current TypeScript source of truth.",
181
+ },
182
+ {
183
+ path: "scripts/sync-project.ts",
184
+ purpose: "Provide one shared sync entrypoint that can grow into sync-rest or workspace-aware refresh steps later.",
185
+ },
186
+ ];
187
+ if (layoutKind === "unsupported") {
188
+ plannedFiles.unshift({
189
+ path: "package.json",
190
+ purpose: "Add the minimum wp-typia devDependencies and scripts once the project matches a supported retrofit layout.",
191
+ });
192
+ }
193
+ return plannedFiles;
194
+ }
195
+ function buildChangeSummary(changes) {
196
+ const lines = [];
197
+ for (const dependencyChange of changes.packageChanges.addDevDependencies) {
198
+ lines.push(`devDependency ${dependencyChange.action} ${dependencyChange.name} -> ${dependencyChange.requiredValue}`);
199
+ }
200
+ if (changes.packageChanges.packageManagerField) {
201
+ lines.push(`packageManager ${changes.packageChanges.packageManagerField.action} -> ${changes.packageChanges.packageManagerField.requiredValue}`);
202
+ }
203
+ for (const scriptChange of changes.packageChanges.scripts) {
204
+ lines.push(`script ${scriptChange.action} ${scriptChange.name} -> ${scriptChange.requiredValue}`);
205
+ }
206
+ for (const filePlan of changes.plannedFiles) {
207
+ lines.push(`file add ${filePlan.path} (${filePlan.purpose})`);
208
+ }
209
+ for (const artifactPath of changes.generatedArtifacts) {
210
+ lines.push(`generated artifact ${artifactPath}`);
211
+ }
212
+ return lines;
213
+ }
214
+ function buildNextSteps(options) {
215
+ const cliSpecifier = getWpTypiaCliSpecifier();
216
+ const syncTypesRun = formatRunScript(options.packageManager, "sync-types");
217
+ const syncRun = formatRunScript(options.packageManager, "sync");
218
+ const doctorRun = formatPackageExecCommand(options.packageManager, cliSpecifier, "doctor");
219
+ const migrationInitRun = formatPackageExecCommand(options.packageManager, cliSpecifier, "migrate init --current-migration-version v1");
220
+ const dependencyInstallCommand = formatAddDevDependenciesCommand(options.packageManager, buildRequiredDevDependencyMapEntries());
221
+ if (options.layoutKind === "unsupported") {
222
+ return [
223
+ "Align the project to one of the supported retrofit layouts listed below, then rerun `wp-typia init`.",
224
+ dependencyInstallCommand,
225
+ syncTypesRun,
226
+ doctorRun,
227
+ ];
228
+ }
229
+ const steps = [
230
+ ...(options.changeSummaryLines.length > 0
231
+ ? [
232
+ "Apply the planned package.json changes and helper files listed below.",
233
+ dependencyInstallCommand,
234
+ ]
235
+ : []),
236
+ syncRun,
237
+ doctorRun,
238
+ `Optional migration bootstrap: ${migrationInitRun}`,
239
+ ];
240
+ return steps;
241
+ }
242
+ function buildRequiredDevDependencyMapEntries() {
243
+ return Object.entries(buildRequiredDevDependencyMap()).map(([name, version]) => `${name}@${version.replace(/^workspace:/u, "")}`);
244
+ }
245
+ export function getInitPlan(projectDir) {
246
+ const resolvedProjectDir = path.resolve(projectDir);
247
+ const packageJson = readProjectPackageJson(resolvedProjectDir);
248
+ const packageManager = inferInitPackageManager(resolvedProjectDir, packageJson);
249
+ const workspace = tryResolveWorkspaceProject(resolvedProjectDir);
250
+ if (workspace) {
251
+ return {
252
+ commandMode: "preview-only",
253
+ detectedLayout: {
254
+ blockNames: [],
255
+ description: "Already an official wp-typia workspace.",
256
+ kind: "official-workspace",
257
+ },
258
+ generatedArtifacts: [],
259
+ nextSteps: [
260
+ formatPackageExecCommand(packageManager, getWpTypiaCliSpecifier(), "doctor"),
261
+ "Use `wp-typia add ...` to extend this workspace instead of rerunning init.",
262
+ ],
263
+ notes: [
264
+ "The official workspace template already owns inventory, doctor, and add-command workflows.",
265
+ ],
266
+ packageChanges: {
267
+ addDevDependencies: [],
268
+ scripts: [],
269
+ },
270
+ plannedFiles: [],
271
+ packageManager,
272
+ projectDir: workspace.projectDir,
273
+ projectName: workspace.packageName,
274
+ status: "already-initialized",
275
+ summary: "This directory is already an official wp-typia workspace. No retrofit bootstrap is needed.",
276
+ };
277
+ }
278
+ const projectName = typeof packageJson?.name === "string" && packageJson.name.length > 0
279
+ ? packageJson.name
280
+ : path.basename(resolvedProjectDir);
281
+ const layout = buildLayoutDetails(resolvedProjectDir);
282
+ const dependencyChanges = buildDependencyChanges(packageJson);
283
+ const scriptChanges = buildScriptChanges(packageJson, packageManager);
284
+ const packageManagerFieldChange = buildPackageManagerFieldChange(packageJson, packageManager);
285
+ const rawPlannedFiles = layout.kind === "generated-project" || layout.kind === "official-workspace"
286
+ ? []
287
+ : buildPlannedFiles(layout.kind);
288
+ const hasExistingSurface = hasExistingWpTypiaProjectSurface(packageJson);
289
+ const status = hasExistingSurface &&
290
+ dependencyChanges.length === 0 &&
291
+ scriptChanges.length === 0 &&
292
+ packageManagerFieldChange === undefined
293
+ ? "already-initialized"
294
+ : "preview";
295
+ const plannedFiles = status === "already-initialized" ? [] : rawPlannedFiles;
296
+ const detectedLayout = status === "already-initialized" && hasExistingSurface
297
+ ? {
298
+ blockNames: layout.blockNames,
299
+ description: layout.kind === "unsupported"
300
+ ? "Already exposes the minimum wp-typia sync surface."
301
+ : `Already exposes the minimum wp-typia sync surface for ${layout.kind === "multi-block" ? "a multi-block project" : "a single-block project"}.`,
302
+ kind: "generated-project",
303
+ }
304
+ : {
305
+ blockNames: layout.blockNames,
306
+ description: layout.description,
307
+ kind: layout.kind,
308
+ };
309
+ const plan = {
310
+ commandMode: "preview-only",
311
+ detectedLayout,
312
+ generatedArtifacts: status === "already-initialized" && detectedLayout.kind === "generated-project"
313
+ ? []
314
+ : layout.generatedArtifacts,
315
+ nextSteps: buildNextSteps({
316
+ changeSummaryLines: buildChangeSummary({
317
+ generatedArtifacts: status === "already-initialized" &&
318
+ detectedLayout.kind === "generated-project"
319
+ ? []
320
+ : layout.generatedArtifacts,
321
+ packageChanges: {
322
+ addDevDependencies: dependencyChanges,
323
+ ...(packageManagerFieldChange
324
+ ? { packageManagerField: packageManagerFieldChange }
325
+ : {}),
326
+ scripts: scriptChanges,
327
+ },
328
+ plannedFiles,
329
+ }),
330
+ layoutKind: detectedLayout.kind,
331
+ packageManager,
332
+ }),
333
+ notes: Array.from(new Set([
334
+ "Preview only: `wp-typia init` does not write files yet.",
335
+ ...layout.notes,
336
+ ])),
337
+ packageChanges: {
338
+ addDevDependencies: dependencyChanges,
339
+ ...(packageManagerFieldChange
340
+ ? { packageManagerField: packageManagerFieldChange }
341
+ : {}),
342
+ scripts: scriptChanges,
343
+ },
344
+ plannedFiles,
345
+ packageManager,
346
+ projectDir: resolvedProjectDir,
347
+ projectName,
348
+ status,
349
+ summary: status === "already-initialized"
350
+ ? "This project already exposes the minimum wp-typia retrofit surface."
351
+ : "This command previews the minimum wp-typia adoption layer for the current project without rewriting it into a full scaffold.",
352
+ };
353
+ return plan;
354
+ }
@@ -18,6 +18,7 @@ interface GetOptionalOnboardingOptions {
18
18
  }
19
19
  interface OptionalOnboardingGuidance {
20
20
  note: string;
21
+ shortNote: string;
21
22
  steps: string[];
22
23
  }
23
24
  export interface ScaffoldDryRunPlan {
@@ -7,7 +7,7 @@ import { parseCompoundInnerBlocksPreset } from "./compound-inner-blocks.js";
7
7
  import { formatInstallCommand, formatRunScript, } from "./package-managers.js";
8
8
  import { getPrimaryDevelopmentScript } from "./local-dev-presets.js";
9
9
  import { createManagedTempRoot } from "./temp-roots.js";
10
- import { getOptionalOnboardingNote, getOptionalOnboardingSteps, } from "./scaffold-onboarding.js";
10
+ import { getOptionalOnboardingNote, getOptionalOnboardingShortNote, getOptionalOnboardingSteps, } from "./scaffold-onboarding.js";
11
11
  import { formatNonEmptyTargetDirectoryError } from "./scaffold-bootstrap.js";
12
12
  import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, isBuiltInTemplateId, } from "./template-registry.js";
13
13
  import { resolveOptionalInteractiveExternalLayerId, } from "./external-layer-selection.js";
@@ -254,6 +254,10 @@ export function getOptionalOnboarding({ availableScripts, packageManager, templa
254
254
  availableScripts,
255
255
  compoundPersistenceEnabled,
256
256
  }),
257
+ shortNote: getOptionalOnboardingShortNote(packageManager, templateId, {
258
+ availableScripts,
259
+ compoundPersistenceEnabled,
260
+ }),
257
261
  steps: getOptionalOnboardingSteps(packageManager, templateId, {
258
262
  availableScripts,
259
263
  compoundPersistenceEnabled,
@@ -1,5 +1,4 @@
1
- import { OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, getTemplateById, getTemplateSelectOptions, isBuiltInTemplateId, listTemplates, } from "./template-registry.js";
2
- const WORKSPACE_TEMPLATE_ALIAS = "workspace";
1
+ import { OFFICIAL_WORKSPACE_TEMPLATE_ALIAS, OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE, getUserFacingTemplateId, getTemplateById, getTemplateSelectOptions, isBuiltInTemplateId, listTemplates, } from "./template-registry.js";
3
2
  /**
4
3
  * Format one line of template list output for a built-in template.
5
4
  *
@@ -7,7 +6,7 @@ const WORKSPACE_TEMPLATE_ALIAS = "workspace";
7
6
  * @returns One-line summary text for `templates list`.
8
7
  */
9
8
  export function formatTemplateSummary(template) {
10
- return `${template.id.padEnd(14)} ${template.description}`;
9
+ return `${getUserFacingTemplateId(template.id).padEnd(14)} ${template.description}`;
11
10
  }
12
11
  /**
13
12
  * Format the feature and capability hint lines shown under a template summary.
@@ -17,6 +16,10 @@ export function formatTemplateSummary(template) {
17
16
  */
18
17
  export function formatTemplateFeatures(template) {
19
18
  const lines = [` Features: ${template.features.join(" • ")}`];
19
+ const bestForHint = getTemplateBestForHint(template);
20
+ if (bestForHint) {
21
+ lines.push(` Best for: ${bestForHint}`);
22
+ }
20
23
  const capabilityHints = getTemplateCapabilityHints(template);
21
24
  if (capabilityHints.length > 0) {
22
25
  lines.push(` Supports: ${capabilityHints.join(" • ")}`);
@@ -26,7 +29,7 @@ export function formatTemplateFeatures(template) {
26
29
  lines.push(` Notes: ${specialNotes.join(" • ")}`);
27
30
  }
28
31
  if (template.id === OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE) {
29
- lines.push(` Alias: ${WORKSPACE_TEMPLATE_ALIAS} (\`--template ${WORKSPACE_TEMPLATE_ALIAS}\`)`);
32
+ lines.push(` Alias: ${OFFICIAL_WORKSPACE_TEMPLATE_ALIAS} (\`--template ${OFFICIAL_WORKSPACE_TEMPLATE_ALIAS}\`)`);
30
33
  }
31
34
  return lines.join("\n");
32
35
  }
@@ -43,8 +46,9 @@ export function formatTemplateFeatures(template) {
43
46
  */
44
47
  export function formatTemplateDetails(template) {
45
48
  const detailLines = [
46
- template.id,
49
+ getUserFacingTemplateId(template.id),
47
50
  `Summary: ${template.description}`,
51
+ `Best for: ${getTemplateBestForHint(template)}`,
48
52
  ...getTemplateIdentityLines(template),
49
53
  `Category: ${template.defaultCategory}`,
50
54
  ];
@@ -69,6 +73,24 @@ export function formatTemplateDetails(template) {
69
73
  detailLines.push(`Features: ${template.features.join(", ")}`);
70
74
  return detailLines.join("\n");
71
75
  }
76
+ function getTemplateBestForHint(template) {
77
+ if (template.id === "basic") {
78
+ return "minimal static-first block scaffolds with Typia validation and the lightest default surface";
79
+ }
80
+ if (template.id === "interactivity") {
81
+ return "interactive single-block experiences that keep client-side state and actions inside one scaffold";
82
+ }
83
+ if (template.id === "persistence") {
84
+ return "typed REST-backed blocks that need persistence-aware reads, writes, and schema refresh workflows";
85
+ }
86
+ if (template.id === "compound") {
87
+ return "parent-and-child block families that own nested authoring conventions and optional persistence wiring";
88
+ }
89
+ if (template.id === "query-loop") {
90
+ return "create-time `core/query` variations with connected starter patterns instead of `add block` families";
91
+ }
92
+ return "official multi-block workspaces that extend through `wp-typia add ...` and workspace doctor flows";
93
+ }
72
94
  function getTemplateCapabilityHints(template) {
73
95
  if (template.id === "persistence" || template.id === "compound") {
74
96
  return [
@@ -99,11 +121,19 @@ function getTemplateIdentityLines(template) {
99
121
  if (template.id === OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE) {
100
122
  return [
101
123
  "Identity:",
124
+ ` - User-facing alias: ${OFFICIAL_WORKSPACE_TEMPLATE_ALIAS} (\`--template ${OFFICIAL_WORKSPACE_TEMPLATE_ALIAS}\`)`,
102
125
  ` - Official package: ${OFFICIAL_WORKSPACE_TEMPLATE_PACKAGE}`,
103
- ` - Alias: ${WORKSPACE_TEMPLATE_ALIAS} (\`--template ${WORKSPACE_TEMPLATE_ALIAS}\`)`,
104
126
  "Type: official workspace scaffold",
105
127
  ];
106
128
  }
129
+ if (template.id === "query-loop") {
130
+ return [
131
+ "Identity:",
132
+ " - Built-in template id: query-loop",
133
+ "Type: create-time core/query variation scaffold",
134
+ "Output model: variation-only scaffold; does not generate block.json or Typia manifests",
135
+ ];
136
+ }
107
137
  return [
108
138
  "Identity:",
109
139
  ` - Built-in template id: ${template.id}`,
@@ -0,0 +1,31 @@
1
+ export declare const TEMPLATE_SOURCE_TIMEOUT_CODE: "template-source-timeout";
2
+ export declare const TEMPLATE_SOURCE_TOO_LARGE_CODE: "template-source-too-large";
3
+ export declare function getExternalTemplateTimeoutMs(): number;
4
+ export declare function getExternalTemplateConfigMaxBytes(): number;
5
+ export declare function getExternalTemplatePackageJsonMaxBytes(): number;
6
+ export declare function getExternalTemplateMetadataMaxBytes(): number;
7
+ export declare function getExternalTemplateTarballMaxBytes(): number;
8
+ export declare function createExternalTemplateTimeoutError(label: string, timeoutMs: number): Error & {
9
+ code: typeof TEMPLATE_SOURCE_TIMEOUT_CODE;
10
+ };
11
+ export declare function createExternalTemplateTooLargeError(label: string, maxBytes: number): Error & {
12
+ code: typeof TEMPLATE_SOURCE_TOO_LARGE_CODE;
13
+ };
14
+ export declare function assertExternalTemplateFileSize(filePath: string, options: {
15
+ label: string;
16
+ maxBytes: number;
17
+ }): void;
18
+ export declare function withExternalTemplateTimeout<T>(label: string, task: Promise<T> | (() => Promise<T>), timeoutMs?: number): Promise<T>;
19
+ export declare function fetchWithExternalTemplateTimeout(input: string, options: {
20
+ init?: RequestInit;
21
+ label: string;
22
+ timeoutMs?: number;
23
+ }): Promise<Response>;
24
+ export declare function readJsonResponseWithLimit(response: Response, options: {
25
+ label: string;
26
+ maxBytes: number;
27
+ }): Promise<Record<string, unknown>>;
28
+ export declare function readBufferResponseWithLimit(response: Response, options: {
29
+ label: string;
30
+ maxBytes: number;
31
+ }): Promise<Buffer>;