@format.dev/cli 0.0.0 → 0.1.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.
Files changed (109) hide show
  1. package/README.md +29 -20
  2. package/bin/format.mjs +20 -0
  3. package/dist/config-Dq7O4e8N.mjs +63 -0
  4. package/dist/config.d.mts +21 -0
  5. package/dist/config.mjs +3 -0
  6. package/dist/draw-random-name-BzvSSVqO.d.mts +4 -0
  7. package/dist/draw-random-name-Dpt6YmUk.mjs +13 -0
  8. package/dist/index.d.mts +1 -0
  9. package/dist/index.mjs +1539 -0
  10. package/dist/install-BiDrmMgJ.mjs +1486 -0
  11. package/dist/lockstep.d.mts +27 -0
  12. package/dist/lockstep.mjs +30 -0
  13. package/dist/project-pX62Di_9.mjs +124 -0
  14. package/dist/random-name.d.mts +2 -0
  15. package/dist/random-name.mjs +3 -0
  16. package/dist/scaffold-shared.d.mts +2 -0
  17. package/dist/scaffold-shared.mjs +3 -0
  18. package/dist/scaffold.d.mts +191 -0
  19. package/dist/scaffold.mjs +6 -0
  20. package/dist/shared-BD62UD5L.d.mts +32 -0
  21. package/dist/shared-CM3vyUlL.mjs +143 -0
  22. package/env.d.ts +99 -0
  23. package/package.json +86 -34
  24. package/templates/html/documents/welcome/data/alternative.json +3 -0
  25. package/templates/html/documents/welcome/data/default.json +3 -0
  26. package/templates/html/documents/welcome/index.html +32 -0
  27. package/templates/html/documents/welcome/index.ts +4 -0
  28. package/templates/html/documents/welcome/pages/Close/close.html +9 -0
  29. package/templates/html/documents/welcome/pages/Cover/cover.html +13 -0
  30. package/templates/html/documents/welcome/pages/FeatureShowcase/feature-showcase.css +296 -0
  31. package/templates/html/documents/welcome/pages/FeatureShowcase/feature-showcase.html +296 -0
  32. package/templates/html/documents/welcome/pages/NextSteps/next-steps.css +107 -0
  33. package/templates/html/documents/welcome/pages/NextSteps/next-steps.html +398 -0
  34. package/templates/html/documents/welcome/styles.css +214 -0
  35. package/templates/html/format.config.json +4 -0
  36. package/templates/html/package.json +15 -0
  37. package/templates/html/tsconfig.json +3 -0
  38. package/templates/react/documents/welcome/components/ButtonLink.tsx +29 -0
  39. package/templates/react/documents/welcome/data/alternative.json +3 -0
  40. package/templates/react/documents/welcome/data/default.json +3 -0
  41. package/templates/react/documents/welcome/data/schema.ts +7 -0
  42. package/templates/react/documents/welcome/icons/propeller-asterisk.svg +1 -0
  43. package/templates/react/documents/welcome/index.tsx +59 -0
  44. package/templates/react/documents/welcome/logos/_logo-base.tsx +9 -0
  45. package/templates/react/documents/welcome/logos/aws-lambda.tsx +22 -0
  46. package/templates/react/documents/welcome/logos/bun.tsx +30 -0
  47. package/templates/react/documents/welcome/logos/cloudflare-workers.tsx +52 -0
  48. package/templates/react/documents/welcome/logos/css-modules.tsx +54 -0
  49. package/templates/react/documents/welcome/logos/css-official.tsx +18 -0
  50. package/templates/react/documents/welcome/logos/deno.tsx +17 -0
  51. package/templates/react/documents/welcome/logos/linaria.tsx +18 -0
  52. package/templates/react/documents/welcome/logos/node.tsx +47 -0
  53. package/templates/react/documents/welcome/logos/panda-css.tsx +18 -0
  54. package/templates/react/documents/welcome/logos/sass.tsx +12 -0
  55. package/templates/react/documents/welcome/logos/tailwind.tsx +45 -0
  56. package/templates/react/documents/welcome/logos/vanilla-extract.tsx +74 -0
  57. package/templates/react/documents/welcome/logos/vercel.tsx +17 -0
  58. package/templates/react/documents/welcome/pages/Close/ClosePage.tsx +13 -0
  59. package/templates/react/documents/welcome/pages/Cover/CoverPage.tsx +17 -0
  60. package/templates/react/documents/welcome/pages/FeatureShowcase/FeatureShowcasePage.tsx +221 -0
  61. package/templates/react/documents/welcome/pages/FeatureShowcase/feature-showcase.css +296 -0
  62. package/templates/react/documents/welcome/pages/FeatureShowcase/mountains.ts +27 -0
  63. package/templates/react/documents/welcome/pages/NextSteps/NextStepsPage.tsx +146 -0
  64. package/templates/react/documents/welcome/pages/NextSteps/next-steps.css +107 -0
  65. package/templates/react/documents/welcome/styles.css +214 -0
  66. package/templates/react/documents/welcome/types.ts +4 -0
  67. package/templates/react/format.config.json +4 -0
  68. package/templates/react/package.json +22 -0
  69. package/templates/react/tsconfig.json +6 -0
  70. package/templates/shared/README.md +18 -0
  71. package/templates/shared/_env +1 -0
  72. package/templates/shared/_gitignore +3 -0
  73. package/templates/shared/documents/welcome/assets/deploy.svg +268 -0
  74. package/templates/shared/documents/welcome/assets/logo.svg +22 -0
  75. package/templates/shared/documents/welcome/assets/mona-sans.woff2 +0 -0
  76. package/templates/shared/documents/welcome/assets/styling.svg +59 -0
  77. package/templates/vue/documents/welcome/components/ButtonLink.vue +28 -0
  78. package/templates/vue/documents/welcome/data/alternative.json +3 -0
  79. package/templates/vue/documents/welcome/data/default.json +3 -0
  80. package/templates/vue/documents/welcome/data/schema.ts +7 -0
  81. package/templates/vue/documents/welcome/index.vue +48 -0
  82. package/templates/vue/documents/welcome/logos/aws-lambda.vue +18 -0
  83. package/templates/vue/documents/welcome/logos/bun.vue +26 -0
  84. package/templates/vue/documents/welcome/logos/cloudflare-workers.vue +48 -0
  85. package/templates/vue/documents/welcome/logos/css-modules.vue +49 -0
  86. package/templates/vue/documents/welcome/logos/css-official.vue +14 -0
  87. package/templates/vue/documents/welcome/logos/deno.vue +13 -0
  88. package/templates/vue/documents/welcome/logos/linaria.vue +14 -0
  89. package/templates/vue/documents/welcome/logos/node.vue +43 -0
  90. package/templates/vue/documents/welcome/logos/panda-css.vue +14 -0
  91. package/templates/vue/documents/welcome/logos/sass.vue +8 -0
  92. package/templates/vue/documents/welcome/logos/tailwind.vue +41 -0
  93. package/templates/vue/documents/welcome/logos/vanilla-extract.vue +70 -0
  94. package/templates/vue/documents/welcome/logos/vercel.vue +13 -0
  95. package/templates/vue/documents/welcome/pages/Close/ClosePage.vue +11 -0
  96. package/templates/vue/documents/welcome/pages/Cover/CoverPage.vue +19 -0
  97. package/templates/vue/documents/welcome/pages/FeatureShowcase/FeatureShowcasePage.vue +217 -0
  98. package/templates/vue/documents/welcome/pages/FeatureShowcase/feature-showcase.css +296 -0
  99. package/templates/vue/documents/welcome/pages/FeatureShowcase/mountains.ts +27 -0
  100. package/templates/vue/documents/welcome/pages/NextSteps/NextStepsPage.vue +146 -0
  101. package/templates/vue/documents/welcome/pages/NextSteps/next-steps.css +107 -0
  102. package/templates/vue/documents/welcome/styles.css +214 -0
  103. package/templates/vue/documents/welcome/types.ts +4 -0
  104. package/templates/vue/format.config.json +4 -0
  105. package/templates/vue/package.json +19 -0
  106. package/templates/vue/tsconfig.json +7 -0
  107. package/tsconfig.recommended.json +22 -0
  108. package/index.d.ts +0 -2
  109. package/index.js +0 -3
@@ -0,0 +1,1486 @@
1
+ import { r as detectIndent$1, s as CliError } from "./config-Dq7O4e8N.mjs";
2
+ import { a as DEFAULT_STYLING, c as SCHEMA_DEPENDENCIES, d as STYLING_DEPENDENCIES, i as DEFAULT_SCHEMA_KIND, m as STYLING_SUPPORT, r as DEFAULT_PAGE_WIDTH, t as DEFAULT_PAGE_HEIGHT, y as methodsForFramework } from "./shared-CM3vyUlL.mjs";
3
+ import { t as drawRandomName } from "./draw-random-name-Dpt6YmUk.mjs";
4
+ import { createRequire } from "node:module";
5
+ import { spawn } from "node:child_process";
6
+ import fs from "node:fs/promises";
7
+ import { existsSync } from "node:fs";
8
+ import { dirname, extname, join, relative, resolve } from "node:path";
9
+ import { applyEdits, modify } from "jsonc-parser";
10
+ import { pathToFileURL } from "node:url";
11
+
12
+ //#region src/package-manager.ts
13
+ const LOCKFILES = [
14
+ ["pnpm-lock.yaml", "pnpm"],
15
+ ["yarn.lock", "yarn"],
16
+ ["bun.lock", "bun"],
17
+ ["bun.lockb", "bun"],
18
+ ["package-lock.json", "npm"]
19
+ ];
20
+ function findLockfileManager(startDir) {
21
+ let dir = resolve(startDir);
22
+ for (;;) {
23
+ const match = LOCKFILES.find(([lockfile]) => existsSync(join(dir, lockfile)));
24
+ if (match) return match[1];
25
+ const parent = dirname(dir);
26
+ if (parent === dir) return null;
27
+ dir = parent;
28
+ }
29
+ }
30
+ async function detectPackageManager(projectDir) {
31
+ const packageJsonPath = join(projectDir, "package.json");
32
+ if (existsSync(packageJsonPath)) {
33
+ const raw = await fs.readFile(packageJsonPath, "utf8");
34
+ const declared = JSON.parse(raw).packageManager?.split("@")[0];
35
+ if (declared === "pnpm" || declared === "yarn" || declared === "bun" || declared === "npm") return declared;
36
+ }
37
+ const userAgent = process.env.npm_config_user_agent ?? "";
38
+ const fromUserAgent = [
39
+ "pnpm",
40
+ "yarn",
41
+ "bun"
42
+ ].find((name) => userAgent.includes(`${name}/`));
43
+ if (fromUserAgent) return fromUserAgent;
44
+ return findLockfileManager(projectDir) ?? "npm";
45
+ }
46
+ function installCommand(packageManager) {
47
+ if (packageManager === "npm") return {
48
+ command: "npm",
49
+ args: ["install"]
50
+ };
51
+ if (packageManager === "pnpm") return {
52
+ command: "pnpm",
53
+ args: ["install", "--no-frozen-lockfile"]
54
+ };
55
+ if (packageManager === "yarn") return {
56
+ command: "yarn",
57
+ args: ["install", "--no-immutable"]
58
+ };
59
+ return {
60
+ command: packageManager,
61
+ args: ["install"]
62
+ };
63
+ }
64
+ function commandForPlatform(command) {
65
+ if (process.platform !== "win32") return command;
66
+ if (command === "npm" || command === "pnpm" || command === "yarn") return `${command}.cmd`;
67
+ return command;
68
+ }
69
+
70
+ //#endregion
71
+ //#region src/scaffold/run-install.ts
72
+ const MAX_OUTPUT_LINES = 40;
73
+ function truncateForSpinner(line) {
74
+ const available = Math.max(20, (process.stdout.columns ?? 80) - 10);
75
+ if (line.length <= available) return line;
76
+ return `${line.slice(0, available - 1)}…`;
77
+ }
78
+ async function runInstall(args) {
79
+ const { command, args: commandArgs, cwd, onProgress } = args;
80
+ return new Promise((resolvePromise) => {
81
+ const child = spawn(commandForPlatform(command), commandArgs, {
82
+ cwd,
83
+ stdio: [
84
+ "ignore",
85
+ "pipe",
86
+ "pipe"
87
+ ],
88
+ shell: false
89
+ });
90
+ const lines = [];
91
+ const collect = (chunk) => {
92
+ chunk.toString().split("\n").map((line) => line.trim()).filter(Boolean).forEach((line) => {
93
+ lines.push(line);
94
+ onProgress?.(line);
95
+ });
96
+ };
97
+ child.stdout?.on("data", collect);
98
+ child.stderr?.on("data", collect);
99
+ const output = () => lines.slice(-MAX_OUTPUT_LINES).join("\n");
100
+ child.on("error", (error) => {
101
+ resolvePromise({
102
+ ok: false,
103
+ code: null,
104
+ error,
105
+ output: output()
106
+ });
107
+ });
108
+ child.on("exit", (code) => {
109
+ resolvePromise({
110
+ ok: code === 0,
111
+ code,
112
+ output: output()
113
+ });
114
+ });
115
+ });
116
+ }
117
+
118
+ //#endregion
119
+ //#region src/scaffold/dependencies.ts
120
+ async function writeDependenciesToManifest(args) {
121
+ const { projectDir, dependencies } = args;
122
+ if (dependencies.length === 0) return [];
123
+ const packageJsonPath = join(projectDir, "package.json");
124
+ const raw = await fs.readFile(packageJsonPath, "utf8");
125
+ const packageJson = JSON.parse(raw);
126
+ const runtime = { ...packageJson.dependencies ?? {} };
127
+ const development = { ...packageJson.devDependencies ?? {} };
128
+ const present = new Set([...Object.keys(runtime), ...Object.keys(development)]);
129
+ const added = [];
130
+ for (const dependency of dependencies) {
131
+ if (present.has(dependency.name)) continue;
132
+ const target = dependency.dev ? development : runtime;
133
+ target[dependency.name] = dependency.version;
134
+ present.add(dependency.name);
135
+ added.push(dependency.name);
136
+ }
137
+ if (added.length === 0) return [];
138
+ if (Object.keys(runtime).length > 0) packageJson.dependencies = runtime;
139
+ if (Object.keys(development).length > 0) packageJson.devDependencies = development;
140
+ const indent$2 = detectIndent$1(raw);
141
+ await fs.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, indent$2)}\n`, "utf8");
142
+ return added;
143
+ }
144
+ async function installManifest(args) {
145
+ const { projectDir, onProgress } = args;
146
+ const packageManager = await detectPackageManager(projectDir);
147
+ const { command, args: commandArgs } = installCommand(packageManager);
148
+ const result = await runInstall({
149
+ command,
150
+ args: commandArgs,
151
+ cwd: projectDir,
152
+ onProgress
153
+ });
154
+ if (result.ok) return {
155
+ ok: true,
156
+ packageManager
157
+ };
158
+ return {
159
+ ok: false,
160
+ packageManager,
161
+ message: result.error?.message ?? `${packageManager} exited with code ${result.code}`,
162
+ output: result.output
163
+ };
164
+ }
165
+ function joinNames(names) {
166
+ if (names.length <= 1) return names[0] ?? "";
167
+ return `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}`;
168
+ }
169
+
170
+ //#endregion
171
+ //#region src/scaffold/document/types.ts
172
+ const DEFAULT_TEMPLATE = "blank";
173
+ const EMPTY_STYLESHEET_COMMENT = "/* Your styles go here */\n";
174
+
175
+ //#endregion
176
+ //#region src/scaffold/document/document-names.ts
177
+ const RESERVED_WORDS = new Set([
178
+ "await",
179
+ "break",
180
+ "case",
181
+ "catch",
182
+ "class",
183
+ "const",
184
+ "continue",
185
+ "debugger",
186
+ "default",
187
+ "delete",
188
+ "do",
189
+ "else",
190
+ "enum",
191
+ "export",
192
+ "extends",
193
+ "false",
194
+ "finally",
195
+ "for",
196
+ "function",
197
+ "if",
198
+ "import",
199
+ "in",
200
+ "instanceof",
201
+ "new",
202
+ "null",
203
+ "return",
204
+ "super",
205
+ "switch",
206
+ "this",
207
+ "throw",
208
+ "true",
209
+ "try",
210
+ "typeof",
211
+ "var",
212
+ "void",
213
+ "while",
214
+ "with",
215
+ "yield",
216
+ "implements",
217
+ "interface",
218
+ "let",
219
+ "package",
220
+ "private",
221
+ "protected",
222
+ "public",
223
+ "static",
224
+ "arguments",
225
+ "eval",
226
+ "globalthis",
227
+ "infinity",
228
+ "nan",
229
+ "undefined",
230
+ "any",
231
+ "bigint",
232
+ "boolean",
233
+ "never",
234
+ "number",
235
+ "object",
236
+ "string",
237
+ "symbol",
238
+ "unknown"
239
+ ]);
240
+ const IDENTIFIER_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
241
+ const isReserved = (value) => RESERVED_WORDS.has(value.toLowerCase());
242
+ function capitalize$1(value) {
243
+ return value ? value[0].toUpperCase() + value.slice(1) : "";
244
+ }
245
+ function toCamelCase(input) {
246
+ const parts = input.trim().replace(/([a-z0-9])([A-Z])/g, "$1-$2").split(/[^A-Za-z0-9]+/).filter(Boolean);
247
+ if (!parts.length) return "document";
248
+ return `${parts[0].toLowerCase()}${parts.slice(1).map((part) => capitalize$1(part.toLowerCase())).join("")}` || "document";
249
+ }
250
+ function toSafeIdentifier(name) {
251
+ let safe = name;
252
+ if (!/^[A-Za-z_$]/.test(safe)) safe = `doc${capitalize$1(safe)}`;
253
+ if (!IDENTIFIER_RE.test(safe) || isReserved(safe)) safe = `doc${capitalize$1(safe.replace(/[^A-Za-z0-9_$]/g, ""))}`;
254
+ if (!IDENTIFIER_RE.test(safe) || isReserved(safe)) safe = "document";
255
+ return safe;
256
+ }
257
+ function getDocumentExportName(documentName) {
258
+ return toSafeIdentifier(toCamelCase(documentName));
259
+ }
260
+ function buildDocumentExportMap(documentNames) {
261
+ const map = /* @__PURE__ */ new Map();
262
+ const used = /* @__PURE__ */ new Map();
263
+ for (const documentName of documentNames) {
264
+ const exportName = getDocumentExportName(documentName);
265
+ const existing = used.get(exportName);
266
+ if (existing) throw new Error(`Document names must resolve to unique exports. "${existing}" and "${documentName}" both map to "${exportName}".`);
267
+ used.set(exportName, documentName);
268
+ map.set(documentName, exportName);
269
+ }
270
+ return map;
271
+ }
272
+
273
+ //#endregion
274
+ //#region src/scaffold/document/validate-name.ts
275
+ const RESERVED_DOCUMENT_NAMES = ["shared-assets", "chunks"];
276
+ const ALLOWED_NAME = /^[A-Za-z0-9-]+$/;
277
+ function validateDocumentName(args) {
278
+ const { name, existingNames } = args;
279
+ const trimmed = name.trim();
280
+ if (!trimmed) return {
281
+ ok: false,
282
+ reason: "Enter a document name."
283
+ };
284
+ if (!ALLOWED_NAME.test(trimmed)) return {
285
+ ok: false,
286
+ reason: "Document names shouldn't use spaces or non-alphanumeric characters. Use letters, numbers and dashes only."
287
+ };
288
+ if (RESERVED_DOCUMENT_NAMES.includes(trimmed)) return {
289
+ ok: false,
290
+ reason: `"${trimmed}" is a reserved name. Reserved names: ${RESERVED_DOCUMENT_NAMES.join(", ")}.`
291
+ };
292
+ const exportName = getDocumentExportName(trimmed);
293
+ if (RESERVED_WORDS.has(exportName.toLowerCase())) return {
294
+ ok: false,
295
+ reason: `"${trimmed}" maps to the reserved identifier "${exportName}". Pick a different name.`
296
+ };
297
+ if (existingNames.includes(trimmed)) return {
298
+ ok: false,
299
+ reason: `A document named "${trimmed}" already exists.`,
300
+ suggestion: suggestAvailableName({
301
+ name: trimmed,
302
+ existingNames
303
+ })
304
+ };
305
+ const exportCollision = existingNames.find((existing) => getDocumentExportName(existing) === exportName);
306
+ if (exportCollision) return {
307
+ ok: false,
308
+ reason: `"${trimmed}" and the existing document "${exportCollision}" both map to the export "${exportName}". Document names must resolve to unique exports.`,
309
+ suggestion: suggestAvailableName({
310
+ name: trimmed,
311
+ existingNames
312
+ })
313
+ };
314
+ return { ok: true };
315
+ }
316
+ function suggestAvailableName(args) {
317
+ const { name, existingNames } = args;
318
+ const takenNames = new Set(existingNames);
319
+ const takenExports = new Set(existingNames.map(getDocumentExportName));
320
+ const isAvailable = (candidate) => !takenNames.has(candidate) && !takenExports.has(getDocumentExportName(candidate));
321
+ if (isAvailable(name)) return name;
322
+ const base = name.replace(/-\d+$/, "");
323
+ let counter = 2;
324
+ while (!isAvailable(`${base}-${counter}`)) counter += 1;
325
+ return `${base}-${counter}`;
326
+ }
327
+
328
+ //#endregion
329
+ //#region src/scaffold/document/styling/prerequisites.ts
330
+ async function readProjectDependencies(projectRoot) {
331
+ try {
332
+ const raw = await fs.readFile(join(projectRoot, "package.json"), "utf8");
333
+ const pkg = JSON.parse(raw);
334
+ return new Set([...Object.keys(pkg.dependencies ?? {}), ...Object.keys(pkg.devDependencies ?? {})]);
335
+ } catch {
336
+ return /* @__PURE__ */ new Set();
337
+ }
338
+ }
339
+ function buildReport(args) {
340
+ const { method, missingDependencies, missingConfig } = args;
341
+ const steps = [];
342
+ if (missingDependencies.length > 0) {
343
+ const names = missingDependencies.map((dependency) => dependency.name).join(" ");
344
+ const flag = missingDependencies.every((dependency) => dependency.dev) ? "-D " : "";
345
+ steps.push(`Install the required package: \`pnpm add ${flag}${names}\``);
346
+ }
347
+ for (const config of missingConfig) steps.push(config.description);
348
+ return {
349
+ method,
350
+ satisfied: missingDependencies.length === 0 && missingConfig.length === 0,
351
+ missingDependencies,
352
+ missingConfig,
353
+ steps
354
+ };
355
+ }
356
+ function satisfied(method) {
357
+ return buildReport({
358
+ method,
359
+ missingDependencies: [],
360
+ missingConfig: []
361
+ });
362
+ }
363
+ function requireDependency(args) {
364
+ const { method, dependency, installed, missingConfig = [] } = args;
365
+ return buildReport({
366
+ method,
367
+ missingDependencies: installed.has(dependency) ? [] : [{
368
+ name: dependency,
369
+ dev: true
370
+ }],
371
+ missingConfig
372
+ });
373
+ }
374
+
375
+ //#endregion
376
+ //#region src/scaffold/document/panda-setup.ts
377
+ const PANDA_CONFIG_NAMES = [
378
+ "panda.config.ts",
379
+ "panda.config.mts",
380
+ "panda.config.cts",
381
+ "panda.config.js",
382
+ "panda.config.mjs",
383
+ "panda.config.cjs"
384
+ ];
385
+ const DEFAULT_PANDA_CONFIG_NAME = "panda.config.ts";
386
+ const PANDA_CONFIG_TEMPLATE = (documentsDirName) => `import { defineConfig } from '@pandacss/dev'
387
+
388
+ export default defineConfig({
389
+ include: ['./${documentsDirName}/**/*.{js,jsx,ts,tsx,vue}'],
390
+ outdir: './styles/styled-system'
391
+ })
392
+ `;
393
+ function findExistingPandaConfig(projectRoot) {
394
+ let dir = resolve(projectRoot);
395
+ for (;;) {
396
+ for (const name of PANDA_CONFIG_NAMES) {
397
+ const candidate = join(dir, name);
398
+ if (existsSync(candidate)) return candidate;
399
+ }
400
+ const parent = dirname(dir);
401
+ if (parent === dir) return null;
402
+ dir = parent;
403
+ }
404
+ }
405
+ async function writePandaConfig(args) {
406
+ const { projectRoot, documentsDirName } = args;
407
+ const configPath = join(projectRoot, DEFAULT_PANDA_CONFIG_NAME);
408
+ await fs.writeFile(configPath, PANDA_CONFIG_TEMPLATE(documentsDirName), "utf8");
409
+ return configPath;
410
+ }
411
+ const PANDA_LAYER_CSS = `@layer reset, base, tokens, recipes, utilities;
412
+
413
+ :host {
414
+ display: flex;
415
+ flex-direction: column;
416
+ justify-content: center;
417
+ text-align: center;
418
+ }
419
+ `;
420
+ async function ensurePandaLayerCss(projectRoot) {
421
+ const stylesDir = join(projectRoot, "styles");
422
+ const layerPath = join(stylesDir, "styles.css");
423
+ if (existsSync(layerPath)) return "exists";
424
+ await fs.mkdir(stylesDir, { recursive: true });
425
+ await fs.writeFile(layerPath, PANDA_LAYER_CSS, "utf8");
426
+ return "created";
427
+ }
428
+ function detectIndent(raw) {
429
+ const match = raw.match(/^[ \t]+/m);
430
+ return match ? match[0] : " ";
431
+ }
432
+ async function addPrepareScript(projectRoot) {
433
+ const packageJsonPath = join(projectRoot, "package.json");
434
+ let raw;
435
+ try {
436
+ raw = await fs.readFile(packageJsonPath, "utf8");
437
+ } catch {
438
+ return "no-package-json";
439
+ }
440
+ const packageJson = JSON.parse(raw);
441
+ if (packageJson.scripts?.prepare) return "exists";
442
+ packageJson.scripts = {
443
+ ...packageJson.scripts,
444
+ prepare: "panda codegen"
445
+ };
446
+ const indent$2 = detectIndent(raw);
447
+ await fs.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, indent$2)}\n`, "utf8");
448
+ return "added";
449
+ }
450
+ async function enablePandaInFormatConfig(args) {
451
+ const { formatConfigPath, projectRoot, pandaConfigPath } = args;
452
+ const raw = await fs.readFile(formatConfigPath, "utf8");
453
+ const indent$2 = detectIndent(raw);
454
+ const formattingOptions = {
455
+ insertSpaces: !indent$2.includes(" "),
456
+ tabSize: indent$2.includes(" ") ? 4 : indent$2.length
457
+ };
458
+ const applyConfigEdit = (content, path, value) => {
459
+ return applyEdits(content, modify(content, path, value, { formattingOptions }));
460
+ };
461
+ let updated = applyConfigEdit(raw, ["pandaCss", "enabled"], true);
462
+ if (!(resolve(pandaConfigPath) === resolve(projectRoot, DEFAULT_PANDA_CONFIG_NAME))) {
463
+ const relativeConfig = `./${relative(projectRoot, pandaConfigPath)}`;
464
+ const relativeCwd = `./${relative(projectRoot, dirname(pandaConfigPath))}`;
465
+ updated = applyConfigEdit(updated, ["pandaCss", "postCssConfig"], {
466
+ cwd: relativeCwd,
467
+ configPath: relativeConfig
468
+ });
469
+ }
470
+ await fs.writeFile(formatConfigPath, updated, "utf8");
471
+ }
472
+ function execArgs(packageManager, binArgs) {
473
+ switch (packageManager) {
474
+ case "pnpm": return {
475
+ command: "pnpm",
476
+ args: ["exec", ...binArgs]
477
+ };
478
+ case "yarn": return {
479
+ command: "yarn",
480
+ args: ["exec", ...binArgs]
481
+ };
482
+ case "bun": return {
483
+ command: "bun",
484
+ args: ["x", ...binArgs]
485
+ };
486
+ default: return {
487
+ command: "npm",
488
+ args: [
489
+ "exec",
490
+ "--",
491
+ ...binArgs
492
+ ]
493
+ };
494
+ }
495
+ }
496
+ async function runPandaCodegen(projectRoot) {
497
+ const { command, args } = execArgs(await detectPackageManager(projectRoot), ["panda", "codegen"]);
498
+ return new Promise((resolvePromise) => {
499
+ const child = spawn(commandForPlatform(command), args, {
500
+ cwd: projectRoot,
501
+ stdio: "inherit",
502
+ shell: false
503
+ });
504
+ child.on("error", (error) => {
505
+ resolvePromise({
506
+ ok: false,
507
+ message: error.message
508
+ });
509
+ });
510
+ child.on("exit", (code) => {
511
+ resolvePromise(code === 0 ? { ok: true } : {
512
+ ok: false,
513
+ message: `panda codegen exited with code ${code}`
514
+ });
515
+ });
516
+ });
517
+ }
518
+ async function setupPanda(args) {
519
+ const { projectRoot, documentsDirName, formatConfigPath, runCodegen = runPandaCodegen } = args;
520
+ const existing = findExistingPandaConfig(projectRoot);
521
+ const pandaConfigCreated = existing === null;
522
+ const pandaConfigPath = existing ?? await writePandaConfig({
523
+ projectRoot,
524
+ documentsDirName
525
+ });
526
+ const layerCss = await ensurePandaLayerCss(projectRoot);
527
+ const prepareScript = await addPrepareScript(projectRoot);
528
+ let formatConfigUpdated = false;
529
+ try {
530
+ await enablePandaInFormatConfig({
531
+ formatConfigPath,
532
+ projectRoot,
533
+ pandaConfigPath
534
+ });
535
+ formatConfigUpdated = true;
536
+ } catch {
537
+ formatConfigUpdated = false;
538
+ }
539
+ const codegen = await runCodegen(projectRoot);
540
+ return {
541
+ pandaConfigPath,
542
+ pandaConfigCreated,
543
+ layerCss,
544
+ prepareScript,
545
+ formatConfigUpdated,
546
+ codegen
547
+ };
548
+ }
549
+
550
+ //#endregion
551
+ //#region src/scaffold/document/styling/strategies.ts
552
+ const STARTER_CSS = `:host {
553
+ display: flex;
554
+ flex-direction: column;
555
+ justify-content: center;
556
+ text-align: center;
557
+ }
558
+
559
+ h1 {
560
+ margin-block: 0.5rem;
561
+ font-size: 1.5rem;
562
+ }
563
+
564
+ .hint {
565
+ margin: 0;
566
+ opacity: 0.5;
567
+ font-family: var(--font-mono);
568
+ }
569
+ `;
570
+ const TAILWIND_CSS = `@import 'tailwindcss';
571
+
572
+ @theme {
573
+ --font-sans: var(--font-sans-system);
574
+ --font-serif: var(--font-serif-system);
575
+ --font-mono: var(--font-mono-system);
576
+ }
577
+
578
+ :host {
579
+ display: flex;
580
+ flex-direction: column;
581
+ justify-content: center;
582
+ text-align: center;
583
+ }
584
+ `;
585
+ const TAILWIND_EMPTY_CSS = `@import 'tailwindcss';
586
+
587
+ @theme {
588
+ --font-sans: var(--font-sans-system);
589
+ --font-serif: var(--font-serif-system);
590
+ --font-mono: var(--font-mono-system);
591
+ }
592
+
593
+ ${EMPTY_STYLESHEET_COMMENT}`;
594
+ const TAILWIND_HEADING_CLASS = "my-2 text-2xl font-bold";
595
+ const TAILWIND_HINT_CLASS = "m-0 opacity-50 font-mono";
596
+ const STARTER_VANILLA_EXTRACT = `import { globalStyle, style } from '@vanilla-extract/css'
597
+
598
+ globalStyle(':host', {
599
+ display: 'flex',
600
+ flexDirection: 'column',
601
+ justifyContent: 'center',
602
+ textAlign: 'center'
603
+ })
604
+
605
+ export const title = style({
606
+ marginBlock: '0.5rem',
607
+ fontSize: '1.5rem'
608
+ })
609
+
610
+ export const hint = style({
611
+ margin: 0,
612
+ opacity: 0.5,
613
+ fontFamily: 'var(--font-mono)'
614
+ })
615
+ `;
616
+ const LINARIA_HOST_STYLES = `export const hostStyles = css\`
617
+ :global() {
618
+ :host {
619
+ display: flex;
620
+ flex-direction: column;
621
+ justify-content: center;
622
+ text-align: center;
623
+ }
624
+ }
625
+ \``;
626
+ const LINARIA_TITLE = `const Title = styled.h1\`
627
+ margin-block: 0.5rem;
628
+ font-size: 1.5rem;
629
+ \``;
630
+ const LINARIA_HINT = `const Hint = styled.p\`
631
+ margin: 0;
632
+ opacity: 0.5;
633
+ font-family: var(--font-mono);
634
+ \``;
635
+ function elementApplication(imports, vueStyleBlock) {
636
+ return {
637
+ imports,
638
+ declarations: [],
639
+ headingTag: "h1",
640
+ hintTag: "p",
641
+ hintClass: "hint",
642
+ vueStyleBlock
643
+ };
644
+ }
645
+ function tailwindApplication(imports, vueStyleBlock) {
646
+ return {
647
+ imports,
648
+ declarations: [],
649
+ headingTag: "h1",
650
+ hintTag: "p",
651
+ headingClass: TAILWIND_HEADING_CLASS,
652
+ hintClass: TAILWIND_HINT_CLASS,
653
+ vueStyleBlock
654
+ };
655
+ }
656
+ const cssStrategy = {
657
+ method: "css",
658
+ frameworks: STYLING_SUPPORT["css"],
659
+ files() {
660
+ return [{
661
+ path: "styles.css",
662
+ contents: STARTER_CSS
663
+ }];
664
+ },
665
+ apply({ framework }) {
666
+ if (framework === "vue") return elementApplication([], "<style>\n@import './styles.css';\n</style>");
667
+ return elementApplication(["import './styles.css'"]);
668
+ },
669
+ async checkPrerequisites() {
670
+ return satisfied("css");
671
+ }
672
+ };
673
+ const cssModulesStrategy = {
674
+ method: "css-modules",
675
+ frameworks: STYLING_SUPPORT["css-modules"],
676
+ files() {
677
+ return [{
678
+ path: "styles.module.css",
679
+ contents: STARTER_CSS
680
+ }];
681
+ },
682
+ apply() {
683
+ return {
684
+ imports: ["import styles from './styles.module.css'"],
685
+ declarations: [],
686
+ headingTag: "h1",
687
+ hintTag: "p",
688
+ hintClassExpr: "styles.hint"
689
+ };
690
+ },
691
+ async checkPrerequisites() {
692
+ return satisfied("css-modules");
693
+ }
694
+ };
695
+ const scopedStrategy = {
696
+ method: "vue-sfc-scoped",
697
+ frameworks: STYLING_SUPPORT["vue-sfc-scoped"],
698
+ files() {
699
+ return [];
700
+ },
701
+ apply() {
702
+ return elementApplication([], `<style scoped>\n${STARTER_CSS}</style>`);
703
+ },
704
+ async checkPrerequisites() {
705
+ return satisfied("vue-sfc-scoped");
706
+ }
707
+ };
708
+ const scssStrategy = {
709
+ method: "scss",
710
+ frameworks: STYLING_SUPPORT["scss"],
711
+ files() {
712
+ return [{
713
+ path: "styles.scss",
714
+ contents: STARTER_CSS
715
+ }];
716
+ },
717
+ apply({ framework }) {
718
+ if (framework === "vue") return elementApplication([], "<style lang=\"scss\">\n@use './styles.scss';\n</style>");
719
+ return elementApplication(["import './styles.scss'"]);
720
+ },
721
+ async checkPrerequisites() {
722
+ return satisfied("scss");
723
+ }
724
+ };
725
+ const tailwindStrategy = {
726
+ method: "tailwind",
727
+ frameworks: STYLING_SUPPORT["tailwind"],
728
+ files() {
729
+ return [{
730
+ path: "styles.css",
731
+ contents: TAILWIND_CSS
732
+ }];
733
+ },
734
+ emptyFiles() {
735
+ return [{
736
+ path: "styles.css",
737
+ contents: TAILWIND_EMPTY_CSS
738
+ }];
739
+ },
740
+ apply({ framework }) {
741
+ if (framework === "vue") return tailwindApplication([], "<style>\n@import './styles.css';\n</style>");
742
+ return tailwindApplication(["import './styles.css'"]);
743
+ },
744
+ async checkPrerequisites({ projectRoot }) {
745
+ return requireDependency({
746
+ method: "tailwind",
747
+ dependency: "tailwindcss",
748
+ installed: await readProjectDependencies(projectRoot)
749
+ });
750
+ }
751
+ };
752
+ const linariaStrategy = {
753
+ method: "linaria",
754
+ frameworks: STYLING_SUPPORT["linaria"],
755
+ files() {
756
+ return [];
757
+ },
758
+ apply() {
759
+ return {
760
+ imports: ["import { css } from '@linaria/core'", "import { styled } from '@linaria/react'"],
761
+ declarations: [
762
+ LINARIA_HOST_STYLES,
763
+ LINARIA_TITLE,
764
+ LINARIA_HINT
765
+ ],
766
+ headingTag: "Title",
767
+ hintTag: "Hint"
768
+ };
769
+ },
770
+ async checkPrerequisites({ projectRoot }) {
771
+ const installed = await readProjectDependencies(projectRoot);
772
+ return buildReport({
773
+ method: "linaria",
774
+ missingDependencies: STYLING_DEPENDENCIES.linaria.filter((dependency) => !installed.has(dependency)).map((name) => ({
775
+ name,
776
+ dev: true
777
+ })),
778
+ missingConfig: []
779
+ });
780
+ }
781
+ };
782
+ const vanillaExtractStrategy = {
783
+ method: "vanilla-extract",
784
+ frameworks: STYLING_SUPPORT["vanilla-extract"],
785
+ files() {
786
+ return [{
787
+ path: "styles.css.ts",
788
+ contents: STARTER_VANILLA_EXTRACT
789
+ }];
790
+ },
791
+ apply() {
792
+ return {
793
+ imports: ["import { title, hint } from './styles.css'"],
794
+ declarations: [],
795
+ headingTag: "h1",
796
+ hintTag: "p",
797
+ headingClassExpr: "title",
798
+ hintClassExpr: "hint"
799
+ };
800
+ },
801
+ async checkPrerequisites({ projectRoot }) {
802
+ return requireDependency({
803
+ method: "vanilla-extract",
804
+ dependency: "@vanilla-extract/css",
805
+ installed: await readProjectDependencies(projectRoot)
806
+ });
807
+ }
808
+ };
809
+ const PANDA_TITLE = `const title = css({
810
+ marginBlock: '0.5rem',
811
+ fontSize: '1.5rem'
812
+ })`;
813
+ const PANDA_HINT = `const hint = css({
814
+ margin: 0,
815
+ opacity: 0.5,
816
+ fontFamily: 'var(--font-mono)'
817
+ })`;
818
+ const pandaStrategy = {
819
+ method: "panda-css",
820
+ frameworks: STYLING_SUPPORT["panda-css"],
821
+ files() {
822
+ return [];
823
+ },
824
+ apply() {
825
+ return {
826
+ imports: ["import { css } from '../../styles/styled-system/css'", "import '../../styles/styles.css'"],
827
+ declarations: [PANDA_TITLE, PANDA_HINT],
828
+ headingTag: "h1",
829
+ hintTag: "p",
830
+ headingClassExpr: "title",
831
+ hintClassExpr: "hint"
832
+ };
833
+ },
834
+ async checkPrerequisites({ projectRoot }) {
835
+ return requireDependency({
836
+ method: "panda-css",
837
+ dependency: "@pandacss/dev",
838
+ installed: await readProjectDependencies(projectRoot)
839
+ });
840
+ },
841
+ async setupProject(args) {
842
+ const { projectRoot, formatConfigPath, documentsGlobBase } = args;
843
+ const result = await setupPanda({
844
+ projectRoot,
845
+ documentsDirName: documentsGlobBase,
846
+ formatConfigPath
847
+ });
848
+ const summary = [
849
+ result.pandaConfigCreated ? "Created panda.config.ts" : "Reused existing Panda config",
850
+ `Panda layer stylesheet ${result.layerCss}`,
851
+ `prepare script ${result.prepareScript}`,
852
+ result.formatConfigUpdated ? "Enabled pandaCss in the Format config" : "Left the Format config unchanged",
853
+ result.codegen.ok ? "Ran panda codegen" : `panda codegen failed: ${result.codegen.message ?? "unknown error"}`
854
+ ];
855
+ return {
856
+ method: "panda-css",
857
+ ok: result.codegen.ok,
858
+ summary
859
+ };
860
+ }
861
+ };
862
+ const STYLING_STRATEGIES = [
863
+ cssStrategy,
864
+ cssModulesStrategy,
865
+ scopedStrategy,
866
+ scssStrategy,
867
+ tailwindStrategy,
868
+ linariaStrategy,
869
+ vanillaExtractStrategy,
870
+ pandaStrategy
871
+ ];
872
+
873
+ //#endregion
874
+ //#region src/scaffold/document/styling/index.ts
875
+ function resolveStyling(args) {
876
+ const { framework, method } = args;
877
+ const strategy = STYLING_STRATEGIES.find((candidate) => candidate.method === method);
878
+ if (!strategy) throw new CliError(`Unknown styling method "${method}". Available methods: ${STYLING_STRATEGIES.map((candidate) => candidate.method).join(", ")}.`);
879
+ if (!STYLING_SUPPORT[method].includes(framework)) throw new CliError(`The "${method}" styling method isn't supported for ${framework} projects. Supported methods: ${methodsForFramework(framework).join(", ")}.`);
880
+ return strategy;
881
+ }
882
+
883
+ //#endregion
884
+ //#region src/scaffold/document/data-schema.ts
885
+ function kindOf(value) {
886
+ if (value === null) return "null";
887
+ if (Array.isArray(value)) return "array";
888
+ if (typeof value === "object") return "object";
889
+ if (typeof value === "number") return "number";
890
+ if (typeof value === "boolean") return "boolean";
891
+ return "string";
892
+ }
893
+ function isPlainObject(value) {
894
+ return typeof value === "object" && value !== null && !Array.isArray(value);
895
+ }
896
+ function indent$1(text, tabs) {
897
+ const pad = " ".repeat(tabs);
898
+ return text.split("\n").map((line) => line ? pad + line : line).join("\n");
899
+ }
900
+ function keyLiteral(key) {
901
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
902
+ }
903
+ function jsonToTsType(value, depth = 0) {
904
+ const kind = kindOf(value);
905
+ if (kind === "string") return "string";
906
+ if (kind === "number") return "number";
907
+ if (kind === "boolean") return "boolean";
908
+ if (kind === "null") return "null";
909
+ if (kind === "array") {
910
+ const items = value;
911
+ return `${items.length > 0 ? jsonToTsType(items[0], depth) : "unknown"}[]`;
912
+ }
913
+ const entries = Object.entries(value);
914
+ if (entries.length === 0) return "Record<string, unknown>";
915
+ return `{\n${indent$1(entries.map(([key, child]) => `${keyLiteral(key)}: ${jsonToTsType(child, depth + 1)}`).join("\n"), 1)}\n}`;
916
+ }
917
+ const DIALECTS = {
918
+ zod: {
919
+ string: "z.string()",
920
+ number: "z.number()",
921
+ boolean: "z.boolean()",
922
+ null: "z.null()",
923
+ unknown: "z.unknown()",
924
+ object: (body) => `z.object(${body})`,
925
+ array: (element) => `z.array(${element})`
926
+ },
927
+ valibot: {
928
+ string: "v.string()",
929
+ number: "v.number()",
930
+ boolean: "v.boolean()",
931
+ null: "v.null()",
932
+ unknown: "v.unknown()",
933
+ object: (body) => `v.object(${body})`,
934
+ array: (element) => `v.array(${element})`
935
+ },
936
+ yup: {
937
+ string: "yup.string()",
938
+ number: "yup.number()",
939
+ boolean: "yup.boolean()",
940
+ null: "yup.mixed()",
941
+ unknown: "yup.mixed()",
942
+ object: (body) => `yup.object(${body})`,
943
+ array: (element) => `yup.array(${element})`
944
+ },
945
+ joi: {
946
+ string: "Joi.string()",
947
+ number: "Joi.number()",
948
+ boolean: "Joi.boolean()",
949
+ null: "Joi.any()",
950
+ unknown: "Joi.any()",
951
+ object: (body) => `Joi.object(${body})`,
952
+ array: (element) => `Joi.array().items(${element})`
953
+ }
954
+ };
955
+ function jsonToSchemaExpr(value, dialect) {
956
+ const syntax = DIALECTS[dialect];
957
+ const kind = kindOf(value);
958
+ if (kind === "string") return syntax.string;
959
+ if (kind === "number") return syntax.number;
960
+ if (kind === "boolean") return syntax.boolean;
961
+ if (kind === "null") return syntax.null;
962
+ if (kind === "array") {
963
+ const items = value;
964
+ const element = items.length > 0 ? jsonToSchemaExpr(items[0], dialect) : syntax.unknown;
965
+ return syntax.array(element);
966
+ }
967
+ const entries = Object.entries(value);
968
+ if (entries.length === 0) return syntax.object("{}");
969
+ const lines = entries.map(([key, child]) => `${keyLiteral(key)}: ${jsonToSchemaExpr(child, dialect)}`);
970
+ return syntax.object(`{\n${indent$1(lines.join(",\n"), 1)}\n}`);
971
+ }
972
+ function firstStringField(value) {
973
+ if (!isPlainObject(value)) return null;
974
+ for (const [key, child] of Object.entries(value)) if (typeof child === "string") return key;
975
+ return null;
976
+ }
977
+
978
+ //#endregion
979
+ //#region src/scaffold/document/templates/utils.ts
980
+ function starterHint(documentName, entryFile) {
981
+ return `Edit documents/${documentName}/${entryFile} to get started`;
982
+ }
983
+ function indent(text, tabs) {
984
+ const pad = " ".repeat(tabs);
985
+ return text.split("\n").map((line) => line ? pad + line : line).join("\n");
986
+ }
987
+ function capitalize(word) {
988
+ return word ? word[0].toUpperCase() + word.slice(1) : "";
989
+ }
990
+ function words(name) {
991
+ return name.replace(/([a-z0-9])([A-Z])/g, "$1-$2").split(/[^A-Za-z0-9]+/).filter(Boolean);
992
+ }
993
+ function pascalCase(name) {
994
+ return words(name).map((word) => capitalize(word.toLowerCase())).join("");
995
+ }
996
+ function titleize(name) {
997
+ return words(name).map((word) => capitalize(word.toLowerCase())).join(" ");
998
+ }
999
+ function dataTypeName(name) {
1000
+ return `${pascalCase(name)}Data`;
1001
+ }
1002
+ function block(lines) {
1003
+ return lines.filter(Boolean).join("\n");
1004
+ }
1005
+ function buildClassAttr(args) {
1006
+ const { literal, expr, classAttr, classExprAttr } = args;
1007
+ if (expr && classExprAttr) return classExprAttr(expr);
1008
+ if (literal) return classAttr(literal);
1009
+ return "";
1010
+ }
1011
+ function buildBody(args) {
1012
+ const { application, title, hint, classAttr, classExprAttr } = args;
1013
+ const headingClassAttr = buildClassAttr({
1014
+ literal: application.headingClass,
1015
+ expr: application.headingClassExpr,
1016
+ classAttr,
1017
+ classExprAttr
1018
+ });
1019
+ const hintClassAttr = buildClassAttr({
1020
+ literal: application.hintClass,
1021
+ expr: application.hintClassExpr,
1022
+ classAttr,
1023
+ classExprAttr
1024
+ });
1025
+ const lines = `${`<${application.headingTag}${headingClassAttr}>${title}</${application.headingTag}>`}\n${`<${application.hintTag}${hintClassAttr}>${hint}</${application.hintTag}>`}`;
1026
+ if (!application.wrapperTag) return lines;
1027
+ return `<${application.wrapperTag}>\n${indent(lines, 1)}\n</${application.wrapperTag}>`;
1028
+ }
1029
+
1030
+ //#endregion
1031
+ //#region src/scaffold/document/templates/react.ts
1032
+ function buildReactEntry(args) {
1033
+ const { documentName, application, width, height, emitSchema, data, empty } = args;
1034
+ const component = `${pascalCase(documentName)}Document`;
1035
+ const dataType = dataTypeName(documentName);
1036
+ if (empty) return {
1037
+ path: "index.tsx",
1038
+ contents: `${block(["import { Document, Layout } from '@format.dev/react'", ...application.imports])}
1039
+
1040
+ export default function ${component}() {
1041
+ return (
1042
+ <Document title="${titleize(documentName)}">
1043
+ <Layout width={${width}} height={${height}}>
1044
+ {/* Your content goes here */}
1045
+ </Layout>
1046
+ </Document>
1047
+ )
1048
+ }
1049
+ `
1050
+ };
1051
+ const head = block([
1052
+ "import { Document, Layout, type RenderProps } from '@format.dev/react'",
1053
+ emitSchema ? `import type { ${dataType} } from './data/schema'` : "",
1054
+ ...application.imports
1055
+ ]);
1056
+ const field = firstStringField(data);
1057
+ return {
1058
+ path: "index.tsx",
1059
+ contents: `${head}
1060
+ ${emitSchema ? "" : `\ntype ${dataType} = ${jsonToTsType(data)}\n`}${application.declarations.length > 0 ? `\n${application.declarations.join("\n\n")}\n` : ""}
1061
+ export default function ${component}({ data }: RenderProps<${dataType}>) {
1062
+ return (
1063
+ <Document title=${field ? `{data.${field}}` : `'${titleize(documentName)}'`}>
1064
+ <Layout width={${width}} height={${height}}>
1065
+ ${indent(buildBody({
1066
+ application,
1067
+ title: field ? `{data.${field}}` : titleize(documentName),
1068
+ hint: starterHint(documentName, "index.tsx"),
1069
+ classAttr: (cls) => ` className="${cls}"`,
1070
+ classExprAttr: (expr) => ` className={${expr}}`
1071
+ }), 4)}
1072
+ </Layout>
1073
+ </Document>
1074
+ )
1075
+ }
1076
+ `
1077
+ };
1078
+ }
1079
+
1080
+ //#endregion
1081
+ //#region src/scaffold/document/templates/vue.ts
1082
+ function buildVueEntry(args) {
1083
+ const { documentName, application, width, height, emitSchema, data, empty } = args;
1084
+ const dataType = dataTypeName(documentName);
1085
+ const trailingStyles = application.vueStyleBlock ? `\n${application.vueStyleBlock}\n` : "\n";
1086
+ if (empty) return {
1087
+ path: "index.vue",
1088
+ contents: `<script setup lang="ts">
1089
+ ${block(["import { Document, Layout } from '@format.dev/vue'", ...application.imports])}
1090
+ <\/script>
1091
+
1092
+ <template>
1093
+ <Document title="${titleize(documentName)}">
1094
+ <Layout :width="${width}" :height="${height}">
1095
+ <!-- Your content goes here -->
1096
+ </Layout>
1097
+ </Document>
1098
+ </template>
1099
+ ${trailingStyles}`
1100
+ };
1101
+ const scriptImports = block([
1102
+ "import { Document, Layout, type RenderProps } from '@format.dev/vue'",
1103
+ emitSchema ? `import type { ${dataType} } from './data/schema'` : "",
1104
+ ...application.imports
1105
+ ]);
1106
+ const field = firstStringField(data);
1107
+ return {
1108
+ path: "index.vue",
1109
+ contents: `<script setup lang="ts">
1110
+ ${scriptImports}
1111
+ ${emitSchema ? "" : `\ntype ${dataType} = ${jsonToTsType(data)}\n`}${application.declarations.length > 0 ? `\n${application.declarations.join("\n\n")}\n` : ""}
1112
+ const props = defineProps<RenderProps<${dataType}>>()
1113
+ <\/script>
1114
+
1115
+ <template>
1116
+ <Document ${field ? `:title="props.data.${field}"` : `title="${titleize(documentName)}"`}>
1117
+ <Layout :width="${width}" :height="${height}">
1118
+ ${indent(buildBody({
1119
+ application,
1120
+ title: field ? `{{ props.data.${field} }}` : titleize(documentName),
1121
+ hint: starterHint(documentName, "index.vue"),
1122
+ classAttr: (cls) => ` class="${cls}"`,
1123
+ classExprAttr: (expr) => ` :class="${expr}"`
1124
+ }), 3)}
1125
+ </Layout>
1126
+ </Document>
1127
+ </template>
1128
+ ${trailingStyles}`
1129
+ };
1130
+ }
1131
+
1132
+ //#endregion
1133
+ //#region src/scaffold/document/templates/html.ts
1134
+ function buildHtmlEntry(args) {
1135
+ const { documentName, application, width, height, data, empty } = args;
1136
+ const field = firstStringField(data);
1137
+ const title = empty || !field ? titleize(documentName) : `<%= data.${field} %>`;
1138
+ const html = `<template data-type="document" data-title="${title}">
1139
+ <template data-type="layout" data-width="${width}px" data-height="${height}px">
1140
+ ${indent(empty ? "<!-- Your content goes here -->" : buildBody({
1141
+ application,
1142
+ title,
1143
+ hint: starterHint(documentName, "index.html"),
1144
+ classAttr: (cls) => ` class="${cls}"`
1145
+ }), 2)}
1146
+ </template>
1147
+ </template>
1148
+ `;
1149
+ const indexTs = block(["export { default } from './index.html'", ...application.imports]) + "\n";
1150
+ return [{
1151
+ path: "index.html",
1152
+ contents: html
1153
+ }, {
1154
+ path: "index.ts",
1155
+ contents: indexTs
1156
+ }];
1157
+ }
1158
+
1159
+ //#endregion
1160
+ //#region src/scaffold/document/templates/index.ts
1161
+ function buildEntryFiles(args) {
1162
+ const { framework } = args;
1163
+ if (framework === "vue") return [buildVueEntry(args)];
1164
+ if (framework === "html") return buildHtmlEntry(args);
1165
+ return [buildReactEntry(args)];
1166
+ }
1167
+ function buildDataFile(data) {
1168
+ return {
1169
+ path: "data/default.json",
1170
+ contents: `${JSON.stringify(data, null, " ")}\n`
1171
+ };
1172
+ }
1173
+ const SCHEMA_HEADER = {
1174
+ zod: "import { z } from 'zod'",
1175
+ valibot: "import * as v from 'valibot'",
1176
+ yup: "import * as yup from 'yup'",
1177
+ joi: "import Joi from 'joi'"
1178
+ };
1179
+ function schemaTypeExport(kind, typeName, data) {
1180
+ if (kind === "zod") return `export type ${typeName} = z.infer<typeof schema>`;
1181
+ if (kind === "valibot") return `export type ${typeName} = v.InferOutput<typeof schema>`;
1182
+ if (kind === "yup") return `export type ${typeName} = yup.InferType<typeof schema>`;
1183
+ return `export type ${typeName} = ${jsonToTsType(data)}`;
1184
+ }
1185
+ function buildSchemaFile(documentName, kind = DEFAULT_SCHEMA_KIND, data) {
1186
+ const typeName = dataTypeName(documentName);
1187
+ const schemaExpr = jsonToSchemaExpr(data, kind);
1188
+ return {
1189
+ path: "data/schema.ts",
1190
+ contents: `${SCHEMA_HEADER[kind]}
1191
+
1192
+ const schema = ${schemaExpr}
1193
+
1194
+ export default schema
1195
+
1196
+ ${schemaTypeExport(kind, typeName, data)}
1197
+ `
1198
+ };
1199
+ }
1200
+ function buildGitkeepFile() {
1201
+ return {
1202
+ path: "assets/.gitkeep",
1203
+ contents: ""
1204
+ };
1205
+ }
1206
+
1207
+ //#endregion
1208
+ //#region src/scaffold/document/format.ts
1209
+ const PARSER_BY_EXTENSION = {
1210
+ ".tsx": "typescript",
1211
+ ".ts": "typescript",
1212
+ ".jsx": "babel",
1213
+ ".js": "babel",
1214
+ ".vue": "vue",
1215
+ ".css": "css",
1216
+ ".scss": "scss",
1217
+ ".json": "json"
1218
+ };
1219
+ const FALLBACK_OPTIONS = {
1220
+ useTabs: true,
1221
+ singleQuote: true,
1222
+ semi: false
1223
+ };
1224
+ const prettierByProject = /* @__PURE__ */ new Map();
1225
+ function loadProjectPrettier(projectRoot) {
1226
+ const cached = prettierByProject.get(projectRoot);
1227
+ if (cached) return cached;
1228
+ const loading = (async () => {
1229
+ try {
1230
+ const module = await import(pathToFileURL(createRequire(join(projectRoot, "package.json")).resolve("prettier")).href);
1231
+ return "format" in module ? module : module.default;
1232
+ } catch {
1233
+ return null;
1234
+ }
1235
+ })();
1236
+ prettierByProject.set(projectRoot, loading);
1237
+ return loading;
1238
+ }
1239
+ async function formatGeneratedFile(args) {
1240
+ const { contents, filePath, projectRoot } = args;
1241
+ const parser = PARSER_BY_EXTENSION[extname(filePath)];
1242
+ if (!parser || !contents.trim()) return contents;
1243
+ const prettier = await loadProjectPrettier(projectRoot);
1244
+ if (!prettier) return contents;
1245
+ try {
1246
+ const projectConfig = await prettier.resolveConfig(filePath);
1247
+ return await prettier.format(contents, {
1248
+ ...FALLBACK_OPTIONS,
1249
+ ...projectConfig,
1250
+ parser
1251
+ });
1252
+ } catch {
1253
+ return contents;
1254
+ }
1255
+ }
1256
+
1257
+ //#endregion
1258
+ //#region src/scaffold/document/create-document.ts
1259
+ const DOCUMENTS_DIR_NAME = "documents";
1260
+ function getDocumentsDir(args) {
1261
+ const { projectRoot, config } = args;
1262
+ return join(resolve(projectRoot, config.rootDir || "./"), DOCUMENTS_DIR_NAME);
1263
+ }
1264
+ async function readExistingDocumentNames(documentsDir) {
1265
+ try {
1266
+ return (await fs.readdir(documentsDir, { withFileTypes: true })).filter((entry) => entry.isDirectory() || entry.isSymbolicLink()).map((entry) => entry.name);
1267
+ } catch {
1268
+ return [];
1269
+ }
1270
+ }
1271
+ async function writeFiles(args) {
1272
+ const { documentDir, files, projectRoot } = args;
1273
+ await Promise.all(files.map(async (file) => {
1274
+ const fullPath = join(documentDir, file.path);
1275
+ await fs.mkdir(dirname(fullPath), { recursive: true });
1276
+ const contents = await formatGeneratedFile({
1277
+ contents: file.contents,
1278
+ filePath: fullPath,
1279
+ projectRoot
1280
+ });
1281
+ await fs.writeFile(fullPath, contents, "utf8");
1282
+ }));
1283
+ }
1284
+ function isSideEffectImport(statement) {
1285
+ return /^\s*import\s+['"]/.test(statement);
1286
+ }
1287
+ function toEmptyApplication(application) {
1288
+ const vueStyleBlockPullsInFile = application.vueStyleBlock ? /@(import|use)/.test(application.vueStyleBlock) : false;
1289
+ return {
1290
+ imports: application.imports.filter(isSideEffectImport),
1291
+ declarations: [],
1292
+ headingTag: application.headingTag,
1293
+ hintTag: application.hintTag,
1294
+ vueStyleBlock: vueStyleBlockPullsInFile ? application.vueStyleBlock : void 0
1295
+ };
1296
+ }
1297
+ function emptyStylingFiles(strategy, args) {
1298
+ if (strategy.emptyFiles) return strategy.emptyFiles(args);
1299
+ return strategy.files(args).map((file) => ({
1300
+ ...file,
1301
+ contents: EMPTY_STYLESHEET_COMMENT
1302
+ }));
1303
+ }
1304
+ async function buildDocumentPlan(args) {
1305
+ const { config, projectRoot, name, template = DEFAULT_TEMPLATE, schemaKind, width = DEFAULT_PAGE_WIDTH, height = DEFAULT_PAGE_HEIGHT, empty = false } = args;
1306
+ const styling = args.styling ?? DEFAULT_STYLING;
1307
+ const emitSchema = empty ? false : args.emitSchema ?? false;
1308
+ const data = empty ? {} : args.data !== void 0 ? args.data : { title: name };
1309
+ if (template !== "blank") throw new CliError(`Unknown template "${template}". Only "blank" is supported.`);
1310
+ const documentsDir = getDocumentsDir({
1311
+ projectRoot,
1312
+ config
1313
+ });
1314
+ const validation = validateDocumentName({
1315
+ name,
1316
+ existingNames: await readExistingDocumentNames(documentsDir)
1317
+ });
1318
+ if (!validation.ok) {
1319
+ const suffix = validation.suggestion ? ` Try "${validation.suggestion}".` : "";
1320
+ throw new CliError(`${validation.reason}${suffix}`);
1321
+ }
1322
+ const documentDir = join(documentsDir, name);
1323
+ const strategy = resolveStyling({
1324
+ framework: config.framework,
1325
+ method: styling
1326
+ });
1327
+ const exportName = getDocumentExportName(name);
1328
+ const applyArgs = {
1329
+ framework: config.framework,
1330
+ documentName: name,
1331
+ exportName
1332
+ };
1333
+ const fullApplication = strategy.apply(applyArgs);
1334
+ const application = empty ? toEmptyApplication(fullApplication) : fullApplication;
1335
+ const stylingFiles = empty ? emptyStylingFiles(strategy, applyArgs) : strategy.files(applyArgs);
1336
+ const files = [
1337
+ ...buildEntryFiles({
1338
+ framework: config.framework,
1339
+ documentName: name,
1340
+ application,
1341
+ width,
1342
+ height,
1343
+ emitSchema,
1344
+ data,
1345
+ empty
1346
+ }),
1347
+ ...stylingFiles,
1348
+ buildDataFile(data),
1349
+ buildGitkeepFile()
1350
+ ];
1351
+ if (emitSchema) files.push(buildSchemaFile(name, schemaKind, data));
1352
+ return {
1353
+ documentName: name,
1354
+ documentDir,
1355
+ files,
1356
+ prerequisites: await strategy.checkPrerequisites({
1357
+ config,
1358
+ projectRoot
1359
+ }),
1360
+ projectRoot
1361
+ };
1362
+ }
1363
+ async function writeDocument(plan) {
1364
+ try {
1365
+ await writeFiles({
1366
+ documentDir: plan.documentDir,
1367
+ files: plan.files,
1368
+ projectRoot: plan.projectRoot
1369
+ });
1370
+ } catch (error) {
1371
+ await fs.rm(plan.documentDir, {
1372
+ recursive: true,
1373
+ force: true
1374
+ });
1375
+ const message = error instanceof Error ? error.message : "Unknown error";
1376
+ throw new CliError(`Failed to write document "${plan.documentName}": ${message}`);
1377
+ }
1378
+ }
1379
+ async function createDocument(args) {
1380
+ const plan = await buildDocumentPlan(args);
1381
+ await writeDocument(plan);
1382
+ return {
1383
+ documentName: plan.documentName,
1384
+ documentDir: plan.documentDir,
1385
+ filesWritten: plan.files.map((file) => file.path),
1386
+ prerequisites: plan.prerequisites
1387
+ };
1388
+ }
1389
+
1390
+ //#endregion
1391
+ //#region src/scaffold/document/random-name.ts
1392
+ function generateRandomName(args) {
1393
+ const { existingNames, maxAttempts = 10 } = args;
1394
+ for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
1395
+ const candidate = drawRandomName();
1396
+ if (validateDocumentName({
1397
+ name: candidate,
1398
+ existingNames
1399
+ }).ok) return candidate;
1400
+ }
1401
+ return suggestAvailableName({
1402
+ name: drawRandomName(),
1403
+ existingNames
1404
+ });
1405
+ }
1406
+
1407
+ //#endregion
1408
+ //#region src/scaffold/recommended-versions.ts
1409
+ const RECOMMENDED_DEPENDENCY_VERSIONS = {
1410
+ zod: "^4.4.3",
1411
+ valibot: "^1.4.2",
1412
+ tailwindcss: "^4.3.3",
1413
+ "@linaria/react": "^7.0.1",
1414
+ "@linaria/core": "^7.0.0",
1415
+ "@vanilla-extract/css": "^1.21.1",
1416
+ "@pandacss/dev": "^1.11.4",
1417
+ joi: "^18.2.3",
1418
+ yup: "^1.7.1"
1419
+ };
1420
+ function recommendedVersion(name) {
1421
+ return RECOMMENDED_DEPENDENCY_VERSIONS[name] ?? "latest";
1422
+ }
1423
+
1424
+ //#endregion
1425
+ //#region src/scaffold/document/install.ts
1426
+ async function writeDocumentDependencies(args) {
1427
+ const { projectRoot, emitSchema, schemaKind, missingStylingDependencies } = args;
1428
+ const installed = await readProjectDependencies(projectRoot);
1429
+ const candidates = [...missingStylingDependencies];
1430
+ if (emitSchema) candidates.push(SCHEMA_DEPENDENCIES[schemaKind ?? DEFAULT_SCHEMA_KIND]);
1431
+ return writeDependenciesToManifest({
1432
+ projectDir: projectRoot,
1433
+ dependencies: [...new Set(candidates)].filter((name) => !installed.has(name)).map((name) => ({
1434
+ name,
1435
+ version: recommendedVersion(name),
1436
+ dev: true
1437
+ }))
1438
+ });
1439
+ }
1440
+ async function maybeInstallDependencies(args) {
1441
+ const { wanted, projectRoot, emitSchema, schemaKind, missingStylingDependencies, onInstallStart } = args;
1442
+ const packages = await writeDocumentDependencies({
1443
+ projectRoot,
1444
+ emitSchema,
1445
+ schemaKind,
1446
+ missingStylingDependencies
1447
+ });
1448
+ if (!wanted || packages.length === 0) return {
1449
+ requested: wanted,
1450
+ attempted: false,
1451
+ ok: true,
1452
+ packages
1453
+ };
1454
+ onInstallStart?.(packages);
1455
+ const result = await installManifest({
1456
+ projectDir: projectRoot,
1457
+ onProgress: args.onProgress
1458
+ });
1459
+ return {
1460
+ requested: true,
1461
+ attempted: true,
1462
+ ok: result.ok,
1463
+ packages,
1464
+ packageManager: result.packageManager,
1465
+ message: result.message,
1466
+ output: result.output
1467
+ };
1468
+ }
1469
+ async function maybeSetupProject(args) {
1470
+ const { wanted, styling, config, projectRoot, formatConfigPath, documentsGlobBase } = args;
1471
+ if (!wanted || styling === void 0) return null;
1472
+ const strategy = resolveStyling({
1473
+ framework: config.framework,
1474
+ method: styling
1475
+ });
1476
+ if (!strategy.setupProject) return null;
1477
+ return strategy.setupProject({
1478
+ config,
1479
+ projectRoot,
1480
+ formatConfigPath,
1481
+ documentsGlobBase
1482
+ });
1483
+ }
1484
+
1485
+ //#endregion
1486
+ export { joinNames as C, commandForPlatform as D, truncateForSpinner as E, detectPackageManager as O, installManifest as S, runInstall as T, RESERVED_WORDS as _, DOCUMENTS_DIR_NAME as a, DEFAULT_TEMPLATE as b, getDocumentsDir as c, resolveStyling as d, STYLING_STRATEGIES as f, validateDocumentName as g, suggestAvailableName as h, generateRandomName as i, installCommand as k, readExistingDocumentNames as l, RESERVED_DOCUMENT_NAMES as m, maybeSetupProject as n, buildDocumentPlan as o, readProjectDependencies as p, writeDocumentDependencies as r, createDocument as s, maybeInstallDependencies as t, writeDocument as u, buildDocumentExportMap as v, writeDependenciesToManifest as w, EMPTY_STYLESHEET_COMMENT as x, getDocumentExportName as y };