@format.dev/cli 0.0.0 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
package/dist/index.mjs ADDED
@@ -0,0 +1,1539 @@
1
+ #!/usr/bin/env node
2
+ import { a as readConfigFile, i as findConfigPath, o as writeConfigVersion, r as detectIndent, s as CliError } from "./config-Dq7O4e8N.mjs";
3
+ import { C as joinNames, D as commandForPlatform, E as truncateForSpinner, O as detectPackageManager, S as installManifest, T as runInstall$2, c as getDocumentsDir, g as validateDocumentName, i as generateRandomName, k as installCommand, l as readExistingDocumentNames, n as maybeSetupProject, r as writeDocumentDependencies, s as createDocument, w as writeDependenciesToManifest } from "./install-BiDrmMgJ.mjs";
4
+ import { a as rewriteFormatDependencies, i as mayHaveBreakingChanges, n as isExactVersion, r as isNewer, t as loadProjectState } from "./project-pX62Di_9.mjs";
5
+ import { a as DEFAULT_STYLING, f as STYLING_LABELS, h as SUPPORTED_FRAMEWORKS, l as SCHEMA_KINDS, n as DEFAULT_PAGE_SIZE_ID, s as PAGE_SIZES, u as SCHEMA_LABELS, v as isValidDimension, y as methodsForFramework } from "./shared-CM3vyUlL.mjs";
6
+ import "./draw-random-name-Dpt6YmUk.mjs";
7
+ import { Command, Option } from "commander";
8
+ import { spawn } from "node:child_process";
9
+ import fs, { readFile } from "node:fs/promises";
10
+ import { existsSync } from "node:fs";
11
+ import { basename, dirname, join, relative, resolve } from "node:path";
12
+ import { fileURLToPath } from "node:url";
13
+ import os from "node:os";
14
+ import pc from "picocolors";
15
+ import { cancel, confirm, intro, isCancel, log, outro, select, spinner, text } from "@clack/prompts";
16
+
17
+ //#region src/cli/cli-spec.json
18
+ var cli_spec_default = { commands: [
19
+ {
20
+ "name": "new",
21
+ "description": "Create a new Format project or document.",
22
+ "options": [],
23
+ "commands": [{
24
+ "name": "project",
25
+ "description": "Scaffold a new Format project. This works best for a completely empty codebase. For existing codebases, use `format init`.",
26
+ "arguments": [{
27
+ "name": "directory",
28
+ "description": "Where to create the project. Prompted if omitted.",
29
+ "required": false
30
+ }],
31
+ "options": [
32
+ {
33
+ "field": "framework",
34
+ "flags": "--framework <framework>",
35
+ "description": "The framework to build your documents with.",
36
+ "type": "string",
37
+ "choices": [
38
+ "react",
39
+ "vue",
40
+ "html"
41
+ ]
42
+ },
43
+ {
44
+ "field": "install",
45
+ "flags": "--no-install",
46
+ "description": "Don't install dependencies after creating the project.",
47
+ "type": "boolean",
48
+ "defaultValue": true
49
+ },
50
+ {
51
+ "field": "yes",
52
+ "flags": "-y, --yes",
53
+ "description": "Skip all prompts and use the defaults for every option that isn't provided.",
54
+ "type": "boolean",
55
+ "defaultValue": false
56
+ }
57
+ ]
58
+ }, {
59
+ "name": "document",
60
+ "description": "Scaffold a new document directory with an entry file, a stylesheet, and a default data variant.",
61
+ "arguments": [{
62
+ "name": "name",
63
+ "description": "The document directory name (kebab-case recommended). Omit it to be prompted with a random suggestion.",
64
+ "required": false
65
+ }],
66
+ "options": [
67
+ {
68
+ "field": "styling",
69
+ "flags": "--styling <method>",
70
+ "description": "Styling method for the document. Choices are filtered to your project framework. See the [styling docs](/docs/studio/styling) for more information.",
71
+ "defaultValue": "css",
72
+ "type": "string",
73
+ "choices": [
74
+ "css",
75
+ "css-modules",
76
+ "vue-sfc-scoped",
77
+ "scss",
78
+ "tailwind",
79
+ "linaria",
80
+ "vanilla-extract",
81
+ "panda-css"
82
+ ]
83
+ },
84
+ {
85
+ "field": "schema",
86
+ "flags": "--schema <library>",
87
+ "description": "Scaffold a `data/schema.ts` with this validation library, or \"none\" for no schema. Format validates any Standard Schema compliant library, so you can also wire up others by hand.",
88
+ "type": "string",
89
+ "choices": [
90
+ "none",
91
+ "zod",
92
+ "valibot",
93
+ "joi",
94
+ "yup"
95
+ ],
96
+ "defaultValue": "none"
97
+ },
98
+ {
99
+ "field": "data",
100
+ "flags": "--data <path>",
101
+ "description": "Path to a JSON file to seed the default data variant. Its shape is used to infer the schema and the entry's data type.",
102
+ "type": "string"
103
+ },
104
+ {
105
+ "field": "size",
106
+ "flags": "--size <id>",
107
+ "description": "Page size for the document. A4 is the default; pass \"custom\" with --width and --height for an exact size.",
108
+ "type": "string",
109
+ "defaultValue": "a4",
110
+ "choices": [
111
+ "a3",
112
+ "a4",
113
+ "a5",
114
+ "letter",
115
+ "slide-16-9",
116
+ "slide-4-3",
117
+ "custom"
118
+ ]
119
+ },
120
+ {
121
+ "field": "width",
122
+ "flags": "--width <px>",
123
+ "description": "Custom page width in CSS pixels. Requires --height; ignored unless --size is \"custom\" or omitted.",
124
+ "type": "string"
125
+ },
126
+ {
127
+ "field": "height",
128
+ "flags": "--height <px>",
129
+ "description": "Custom page height in CSS pixels. Requires --width; ignored unless --size is \"custom\" or omitted.",
130
+ "type": "string"
131
+ },
132
+ {
133
+ "field": "install",
134
+ "flags": "--no-install",
135
+ "description": "Don't install missing styling/schema dependencies after creating the document.",
136
+ "type": "boolean",
137
+ "defaultValue": true
138
+ },
139
+ {
140
+ "field": "yes",
141
+ "flags": "-y, --yes",
142
+ "description": "Skip all prompts and use the defaults for every option that isn't provided.",
143
+ "type": "boolean",
144
+ "defaultValue": false
145
+ },
146
+ {
147
+ "field": "empty",
148
+ "flags": "--empty",
149
+ "description": "Scaffold the most minimal document. Can be combined with other flags.",
150
+ "type": "boolean",
151
+ "defaultValue": false
152
+ }
153
+ ]
154
+ }]
155
+ },
156
+ {
157
+ "name": "init",
158
+ "description": "Set up Format in an existing codebase. This script writes a Format config file, adds npm scripts, gitignore entries, the Format packages, and optionally a first document. Each step will confirm before editing anything.",
159
+ "options": [
160
+ {
161
+ "field": "framework",
162
+ "flags": "--framework <framework>",
163
+ "description": "The framework to build your documents with — React, Vue or HTML. Detected from your dependencies when omitted.",
164
+ "type": "string",
165
+ "choices": [
166
+ "react",
167
+ "vue",
168
+ "html"
169
+ ]
170
+ },
171
+ {
172
+ "field": "rootDir",
173
+ "flags": "--root-dir <dir>",
174
+ "description": "Where to install the Format project into. The path you give will be set in your Format config's `rootDir`.",
175
+ "type": "string"
176
+ },
177
+ {
178
+ "field": "install",
179
+ "flags": "--no-install",
180
+ "description": "Don't automatically add or install any Format packages.",
181
+ "type": "boolean",
182
+ "defaultValue": true
183
+ },
184
+ {
185
+ "field": "scripts",
186
+ "flags": "--no-scripts",
187
+ "description": "Don't add the npm dev and npm compile scripts to your package.json. Existing dev and compile script names are never overwritten regardless, If there's a name clash, they get a `format:` prefix.",
188
+ "type": "boolean",
189
+ "defaultValue": true
190
+ },
191
+ {
192
+ "field": "gitignore",
193
+ "flags": "--no-gitignore",
194
+ "description": "Don't append the `_generated` and `.env` entries to .gitignore. Existing lines are never touched.",
195
+ "type": "boolean",
196
+ "defaultValue": true
197
+ },
198
+ {
199
+ "field": "document",
200
+ "flags": "--no-document",
201
+ "description": "Don't offer to create a first document.",
202
+ "type": "boolean",
203
+ "defaultValue": true
204
+ },
205
+ {
206
+ "field": "yes",
207
+ "flags": "-y, --yes",
208
+ "description": "Skip all prompts and use the defaults for every option that isn't provided.",
209
+ "type": "boolean",
210
+ "defaultValue": false
211
+ }
212
+ ]
213
+ },
214
+ {
215
+ "name": "add",
216
+ "description": "Adds and installs Format packages. Packages will be installed at the version defined in your config file.",
217
+ "arguments": [{
218
+ "name": "packages...",
219
+ "description": "One or more Format packages. Shorthands map to the scope: `zip` means `@format.dev/zip`.",
220
+ "required": true
221
+ }],
222
+ "options": []
223
+ },
224
+ {
225
+ "name": "remove",
226
+ "description": "Remove Format packages from your project. The counterpart to `format add`.",
227
+ "arguments": [{
228
+ "name": "packages...",
229
+ "description": "One or more Format packages. Shorthands map to the scope: `zip` means `@format.dev/zip`.",
230
+ "required": true
231
+ }],
232
+ "options": []
233
+ },
234
+ {
235
+ "name": "update",
236
+ "description": "Move your project to a different Format version. With no arguments, the version set in your Format config file will be used. The argument \"latest\" updates to the latest available version and pins it.",
237
+ "arguments": [{
238
+ "name": "version",
239
+ "description": "An exact release like `0.1.2`, or `latest` for the newest. Omit it to apply the release already pinned in your config. The version field is the source of truth, and editing it by hand then running `format update` is a supported flow.",
240
+ "required": false
241
+ }],
242
+ "options": []
243
+ },
244
+ {
245
+ "name": "version",
246
+ "description": "Print the current pinned Format version for your project, check every installed Format package matches it exactly, and mention when a newer version is available. Exits non-zero on any drift, so can be used in CI as a gate.",
247
+ "options": []
248
+ }
249
+ ] };
250
+
251
+ //#endregion
252
+ //#region src/versioning/js-version.ts
253
+ const jsVersion = "0.1.0";
254
+
255
+ //#endregion
256
+ //#region src/versioning/registry.ts
257
+ const REFERENCE_PACKAGE = "@format.dev/studio";
258
+ function registryBaseUrl() {
259
+ return (process.env.npm_config_registry || "https://registry.npmjs.org").replace(/\/$/, "");
260
+ }
261
+ async function fetchLatestVersion() {
262
+ const url = `${registryBaseUrl()}/${encodeURIComponent(REFERENCE_PACKAGE)}/latest`;
263
+ let response;
264
+ try {
265
+ response = await fetch(url, { headers: { accept: "application/json" } });
266
+ } catch {
267
+ throw new CliError("Could not reach the npm registry. Check your connection and try again.");
268
+ }
269
+ if (!response.ok) throw new CliError(`npm registry request failed (${response.status}) while resolving the latest Format version.`);
270
+ const data = await response.json();
271
+ if (!data.version) throw new CliError("Unexpected npm registry response while resolving the latest Format version.");
272
+ return data.version;
273
+ }
274
+
275
+ //#endregion
276
+ //#region src/versioning/update-check.ts
277
+ const CACHE_FILE_NAME = "update-check.json";
278
+ const CACHE_MAX_AGE_MS = 1440 * 60 * 1e3;
279
+ const CHANGELOG_URL = "https://format.dev/changelog";
280
+ function cacheDir() {
281
+ const home = os.homedir();
282
+ if (process.env.XDG_CACHE_HOME) return join(process.env.XDG_CACHE_HOME, "format");
283
+ if (process.platform === "darwin") return join(home, "Library", "Caches", "format");
284
+ if (process.platform === "win32") return join(process.env.LOCALAPPDATA ?? join(home, "AppData", "Local"), "format");
285
+ return join(home, ".cache", "format");
286
+ }
287
+ function cacheFilePath() {
288
+ return join(cacheDir(), CACHE_FILE_NAME);
289
+ }
290
+ async function readCache() {
291
+ try {
292
+ const raw = await fs.readFile(cacheFilePath(), "utf8");
293
+ const parsed = JSON.parse(raw);
294
+ if (typeof parsed.checkedAt !== "number" || typeof parsed.latest !== "string") return null;
295
+ return {
296
+ checkedAt: parsed.checkedAt,
297
+ latest: parsed.latest
298
+ };
299
+ } catch {
300
+ return null;
301
+ }
302
+ }
303
+ async function writeCache(latest) {
304
+ try {
305
+ await fs.mkdir(cacheDir(), { recursive: true });
306
+ const cache = {
307
+ checkedAt: Date.now(),
308
+ latest
309
+ };
310
+ await fs.writeFile(cacheFilePath(), `${JSON.stringify(cache)}\n`, "utf8");
311
+ } catch {}
312
+ }
313
+ async function resolveLatest() {
314
+ const cached = await readCache();
315
+ if (cached && Date.now() - cached.checkedAt < CACHE_MAX_AGE_MS) return cached.latest;
316
+ try {
317
+ const latest = await fetchLatestVersion();
318
+ await writeCache(latest);
319
+ return latest;
320
+ } catch {
321
+ return cached?.latest ?? null;
322
+ }
323
+ }
324
+ function printNudge(pinnedVersion, latest) {
325
+ console.log("");
326
+ console.log(`A newer Format release is available: ${latest} (you're on ${pinnedVersion}).`);
327
+ if (mayHaveBreakingChanges(pinnedVersion, latest)) console.log(`This release may include breaking changes. See ${CHANGELOG_URL} before updating.`);
328
+ console.log("Run `format update latest` to move to it.");
329
+ }
330
+ async function reportNewerRelease(pinnedVersion) {
331
+ const latest = await resolveLatest();
332
+ if (latest && isNewer(latest, pinnedVersion)) printNudge(pinnedVersion, latest);
333
+ }
334
+ function startUpdateCheck(pinnedVersion) {
335
+ const pending = resolveLatest().catch(() => null);
336
+ return { report: async () => {
337
+ const latest = await pending;
338
+ if (latest && isNewer(latest, pinnedVersion)) printNudge(pinnedVersion, latest);
339
+ } };
340
+ }
341
+
342
+ //#endregion
343
+ //#region src/studio.ts
344
+ const STUDIO_PACKAGE = "@format.dev/studio";
345
+ const COMPILE_PACKAGE = "@format.dev/compile";
346
+ const STUDIO_BIN_NAME = "format-studio";
347
+ const COMPILE_BIN_NAME = "format-compile";
348
+ function targetForCommand(commandName) {
349
+ if (commandName === "compile") return {
350
+ packageName: COMPILE_PACKAGE,
351
+ binName: COMPILE_BIN_NAME
352
+ };
353
+ return {
354
+ packageName: STUDIO_PACKAGE,
355
+ binName: STUDIO_BIN_NAME
356
+ };
357
+ }
358
+ function findStudioPackageJson(projectDir, packageName) {
359
+ let dir = resolve(projectDir);
360
+ for (;;) {
361
+ const candidate = join(dir, "node_modules", packageName, "package.json");
362
+ if (existsSync(candidate)) return candidate;
363
+ const parent = dirname(dir);
364
+ if (parent === dir) return null;
365
+ dir = parent;
366
+ }
367
+ }
368
+ async function findLocalStudio(projectDir, target) {
369
+ const packageJsonPath = findStudioPackageJson(projectDir, target.packageName);
370
+ if (!packageJsonPath) return null;
371
+ const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf8"));
372
+ const binRelativePath = packageJson.bin?.[target.binName];
373
+ if (!binRelativePath) throw new CliError(`The installed ${target.packageName} (${packageJson.version}) does not expose a "${target.binName}" bin. Run \`format update\` to align your packages.`);
374
+ return {
375
+ binPath: resolve(dirname(packageJsonPath), binRelativePath),
376
+ version: packageJson.version
377
+ };
378
+ }
379
+ async function runStudioCommand(cwd, argv) {
380
+ const state = await loadProjectState(cwd);
381
+ const updateCheck = argv[0] === "dev" && state.pinnedVersion ? startUpdateCheck(state.pinnedVersion) : null;
382
+ const exitCode = await spawnStudioForProject(state, argv);
383
+ if (updateCheck) await updateCheck.report();
384
+ return exitCode;
385
+ }
386
+ async function spawnStudioForProject(state, argv) {
387
+ const target = targetForCommand(argv[0]);
388
+ const localStudio = await findLocalStudio(state.projectDir, target);
389
+ const keepAliveOnSignal = argv[0] === "dev";
390
+ if (localStudio) {
391
+ if (state.pinnedVersion !== null && localStudio.version !== state.pinnedVersion) throw new CliError(`Installed ${target.packageName} is ${localStudio.version} but format.config.json pins ${state.pinnedVersion}. Run \`format update\` to bring them in line.`);
392
+ return spawnStudio(process.execPath, [localStudio.binPath, ...argv], state.projectDir, keepAliveOnSignal);
393
+ }
394
+ if (!state.pinnedVersion) throw new CliError(`No "version" field in ${state.configFile.filepath} and no local ${target.packageName} install. Run \`format update\` to pin the latest Format release.`);
395
+ return spawnStudio("npm", [
396
+ "exec",
397
+ "-y",
398
+ "--prefer-offline",
399
+ "--",
400
+ `${target.packageName}@${state.pinnedVersion}`,
401
+ ...argv
402
+ ], state.projectDir, keepAliveOnSignal);
403
+ }
404
+ async function spawnStudio(command, args, cwd, keepAliveOnSignal = false) {
405
+ return new Promise((resolvePromise, rejectPromise) => {
406
+ const child = spawn(command, args, {
407
+ cwd,
408
+ stdio: "inherit",
409
+ shell: process.platform === "win32"
410
+ });
411
+ let interrupts = 0;
412
+ const onSignal = () => {
413
+ interrupts += 1;
414
+ if (interrupts >= 2) process.exit(130);
415
+ };
416
+ if (keepAliveOnSignal) process.on("SIGINT", onSignal);
417
+ const cleanup = () => {
418
+ if (keepAliveOnSignal) process.off("SIGINT", onSignal);
419
+ };
420
+ child.on("error", (error) => {
421
+ cleanup();
422
+ rejectPromise(new CliError(`Failed to launch Studio: ${error.message}`));
423
+ });
424
+ child.on("exit", (code, signal) => {
425
+ cleanup();
426
+ if (signal) {
427
+ resolvePromise(1);
428
+ return;
429
+ }
430
+ resolvePromise(code ?? 0);
431
+ });
432
+ });
433
+ }
434
+
435
+ //#endregion
436
+ //#region src/scaffold/project.ts
437
+ function getBuildMode() {
438
+ return "prod";
439
+ }
440
+ const WORKSPACE_VERSION = "workspace:*";
441
+ function formatDependencyVersion(version) {
442
+ return getBuildMode() === "prod" ? version : WORKSPACE_VERSION;
443
+ }
444
+ function devOnlyWorkspacePackages() {
445
+ if (getBuildMode() === "prod") return [];
446
+ return [STUDIO_PACKAGE, COMPILE_PACKAGE];
447
+ }
448
+ const _dirname = dirname(fileURLToPath(import.meta.url));
449
+ function findCliPackageRoot() {
450
+ let dir = _dirname;
451
+ for (;;) {
452
+ if (existsSync(join(dir, "package.json"))) return dir;
453
+ const parent = dirname(dir);
454
+ if (parent === dir) throw new CliError("Could not locate the @format.dev/cli package root.");
455
+ dir = parent;
456
+ }
457
+ }
458
+ const DOTFILE_TEMPLATES = {
459
+ _gitignore: ".gitignore",
460
+ _env: ".env"
461
+ };
462
+ async function restoreDotfiles(dirPath) {
463
+ for (const [templateName, dotfileName] of Object.entries(DOTFILE_TEMPLATES)) {
464
+ const templateFile = join(dirPath, templateName);
465
+ if (existsSync(templateFile)) await fs.rename(templateFile, join(dirPath, dotfileName));
466
+ }
467
+ }
468
+ function resolveTemplatePath(name) {
469
+ const templatePath = join(findCliPackageRoot(), "templates", name);
470
+ if (!existsSync(templatePath)) throw new CliError(`Unknown project template "${name}".`);
471
+ return templatePath;
472
+ }
473
+ function deriveProjectName(dirInput) {
474
+ const name = basename(dirInput.trim().replace(/[\\/]+$/, ""));
475
+ return name === "" || name === "." ? "my-format-project" : name;
476
+ }
477
+ function replaceWorkspaceVersions(deps, version) {
478
+ if (!deps) return deps;
479
+ for (const [depName, depVersion] of Object.entries(deps)) if (depVersion === "workspace:*" && depName.startsWith("@format.dev/")) deps[depName] = version;
480
+ return deps;
481
+ }
482
+ async function updateGeneratedPackageJson(args) {
483
+ const { dirPath, projectName, buildMode, version } = args;
484
+ const packageJsonPath = resolve(dirPath, "package.json");
485
+ const raw = await fs.readFile(packageJsonPath, "utf8");
486
+ const packageJson = JSON.parse(raw);
487
+ packageJson.name = projectName;
488
+ for (const name of devOnlyWorkspacePackages()) packageJson.devDependencies = {
489
+ ...packageJson.devDependencies,
490
+ [name]: WORKSPACE_VERSION
491
+ };
492
+ if (buildMode === "prod") {
493
+ packageJson.dependencies = replaceWorkspaceVersions(packageJson.dependencies, version);
494
+ packageJson.devDependencies = replaceWorkspaceVersions(packageJson.devDependencies, version);
495
+ packageJson.optionalDependencies = replaceWorkspaceVersions(packageJson.optionalDependencies, version);
496
+ packageJson.peerDependencies = replaceWorkspaceVersions(packageJson.peerDependencies, version);
497
+ }
498
+ await fs.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, " ")}\n`, "utf8");
499
+ }
500
+ async function updateGeneratedFormatConfig(args) {
501
+ const { dirPath, version } = args;
502
+ const configPath = resolve(dirPath, "format.config.json");
503
+ const raw = await fs.readFile(configPath, "utf8");
504
+ const config = JSON.parse(raw);
505
+ const withVersion = {
506
+ $schema: config.$schema,
507
+ version,
508
+ ...config
509
+ };
510
+ await fs.writeFile(configPath, `${JSON.stringify(withVersion, null, " ")}\n`, "utf8");
511
+ }
512
+ function isCommandNotFoundError(error) {
513
+ return !!(error && "code" in error && error.code === "ENOENT");
514
+ }
515
+ async function installProjectDependencies(args) {
516
+ const { dirPath, preferredPackageManager, onProgress } = args;
517
+ const attempt = async (packageManager) => {
518
+ const { command, args: commandArgs } = installCommand(packageManager);
519
+ return runInstall$2({
520
+ command,
521
+ args: commandArgs,
522
+ cwd: dirPath,
523
+ onProgress
524
+ });
525
+ };
526
+ const first = await attempt(preferredPackageManager);
527
+ if (first.ok) return {
528
+ ok: true,
529
+ packageManagerUsed: preferredPackageManager,
530
+ fellBackToNpm: false
531
+ };
532
+ if (!(preferredPackageManager !== "npm" && isCommandNotFoundError(first.error))) return {
533
+ ok: false,
534
+ packageManagerUsed: preferredPackageManager,
535
+ fellBackToNpm: false,
536
+ message: first.error?.message ?? `${preferredPackageManager} exited with code ${first.code}`,
537
+ output: first.output
538
+ };
539
+ const fallback = await attempt("npm");
540
+ if (fallback.ok) return {
541
+ ok: true,
542
+ packageManagerUsed: "npm",
543
+ fellBackToNpm: true
544
+ };
545
+ return {
546
+ ok: false,
547
+ packageManagerUsed: "npm",
548
+ fellBackToNpm: true,
549
+ message: fallback.error?.message ?? `npm exited with code ${fallback.code}`,
550
+ output: fallback.output
551
+ };
552
+ }
553
+ async function templateUsesWorkspaceDeps(dirPath) {
554
+ try {
555
+ return (await fs.readFile(resolve(dirPath, "package.json"), "utf8")).includes("workspace:");
556
+ } catch {
557
+ return false;
558
+ }
559
+ }
560
+ async function scaffoldProject(args) {
561
+ const { directory, framework, cwd } = args;
562
+ const dirPath = resolve(cwd, directory);
563
+ const projectName = deriveProjectName(directory);
564
+ const buildMode = getBuildMode();
565
+ const version = jsVersion;
566
+ await fs.mkdir(dirPath, { recursive: true });
567
+ try {
568
+ const sharedPath = resolveTemplatePath("shared");
569
+ await fs.cp(sharedPath, dirPath, { recursive: true });
570
+ } catch {}
571
+ const templatePath = resolveTemplatePath(framework);
572
+ await fs.cp(templatePath, dirPath, { recursive: true });
573
+ await restoreDotfiles(dirPath);
574
+ await updateGeneratedPackageJson({
575
+ dirPath,
576
+ projectName,
577
+ buildMode,
578
+ version
579
+ });
580
+ await updateGeneratedFormatConfig({
581
+ dirPath,
582
+ version
583
+ });
584
+ return {
585
+ dirPath,
586
+ projectName
587
+ };
588
+ }
589
+
590
+ //#endregion
591
+ //#region src/cli/commands/add.ts
592
+ const RUNTIME_PACKAGES = ["@format.dev/client", "@format.dev/zip"];
593
+ function normalisePackageName$1(name) {
594
+ if (name.startsWith("@format.dev/")) return name;
595
+ return `@format.dev/${name}`;
596
+ }
597
+ function installArgs(packageManager, packages, dev) {
598
+ const saveFlag = dev ? "-D" : "";
599
+ switch (packageManager) {
600
+ case "npm": return [
601
+ "install",
602
+ ...dev ? ["--save-dev"] : [],
603
+ ...packages
604
+ ];
605
+ default: return [
606
+ "add",
607
+ ...saveFlag ? [saveFlag] : [],
608
+ ...packages
609
+ ];
610
+ }
611
+ }
612
+ async function addCommand(cwd, packages) {
613
+ if (packages.length === 0) throw new CliError("Usage: format add <package> [...] — e.g. `format add zip` or `format add \"@format.dev/client\"`.");
614
+ const state = await loadProjectState(cwd);
615
+ if (!state.packageJsonPath) throw new CliError("No package.json found next to the config — nothing to install into.");
616
+ const pinnedVersion = state.pinnedVersion;
617
+ if (!pinnedVersion) throw new CliError(`${state.configFile.filepath} is missing its required "version" field. Run \`format init\` to set the project up.`);
618
+ const names = packages.map(normalisePackageName$1);
619
+ const runtime = names.filter((name) => RUNTIME_PACKAGES.includes(name));
620
+ const dev = names.filter((name) => !RUNTIME_PACKAGES.includes(name));
621
+ const packageManager = await detectPackageManager(state.projectDir);
622
+ for (const [group, isDev] of [[runtime, false], [dev, true]]) {
623
+ if (group.length === 0) continue;
624
+ const dependencyVersion = formatDependencyVersion(pinnedVersion);
625
+ const pinned = group.map((name) => `${name}@${dependencyVersion}`);
626
+ console.log(`Installing ${pinned.join(", ")} ${isDev ? "(devDependencies)" : "(dependencies)"} with ${packageManager}...`);
627
+ await runInstall$1(packageManager, installArgs(packageManager, pinned, isDev), state.projectDir);
628
+ }
629
+ console.log("");
630
+ console.log(`Done. Added at ${formatDependencyVersion(pinnedVersion)}.`);
631
+ return 0;
632
+ }
633
+ async function runInstall$1(packageManager, args, cwd) {
634
+ await new Promise((resolvePromise, rejectPromise) => {
635
+ const child = spawn(commandForPlatform(packageManager), args, {
636
+ cwd,
637
+ stdio: "inherit",
638
+ shell: false
639
+ });
640
+ child.on("error", (error) => {
641
+ rejectPromise(new CliError(`Failed to run ${packageManager}: ${error.message}`));
642
+ });
643
+ child.on("exit", (code) => {
644
+ if (code === 0) {
645
+ resolvePromise();
646
+ return;
647
+ }
648
+ rejectPromise(new CliError(`${packageManager} exited with code ${code}.`));
649
+ });
650
+ });
651
+ }
652
+
653
+ //#endregion
654
+ //#region src/prompts.ts
655
+ var PromptCancelledError = class extends Error {
656
+ constructor() {
657
+ super("Prompt cancelled");
658
+ this.name = "PromptCancelledError";
659
+ }
660
+ };
661
+ function isPromptCancelledError(error) {
662
+ return error instanceof PromptCancelledError;
663
+ }
664
+ function requirePrompt(value) {
665
+ if (isCancel(value)) {
666
+ cancel("Cancelled.");
667
+ throw new PromptCancelledError();
668
+ }
669
+ return value;
670
+ }
671
+
672
+ //#endregion
673
+ //#region src/scaffold/finalize-dependencies.ts
674
+ async function finalizeDependencies(args) {
675
+ const { projectDir, addedDependencies, install, skipPrompts } = args;
676
+ const hasAdded = addedDependencies.length > 0;
677
+ const verb = addedDependencies.length === 1 ? "has" : "have";
678
+ const wantInstall = await decideInstall({
679
+ install,
680
+ skipPrompts,
681
+ hasAdded,
682
+ addedLine: `${joinNames(addedDependencies.map((name) => pc.cyan(name)))} ${verb} been added to your package.json.`
683
+ });
684
+ if (!wantInstall || !hasAdded) {
685
+ if (hasAdded && !wantInstall) {
686
+ const { command, args: args$1 } = installCommand(await detectPackageManager(projectDir));
687
+ log.warn(`Run ${pc.cyan(`${command} ${args$1.join(" ")}`)} when you're ready.`);
688
+ }
689
+ return {
690
+ wantInstall,
691
+ installed: false
692
+ };
693
+ }
694
+ const installSpinner = spinner();
695
+ installSpinner.start("Installing dependencies");
696
+ const result = await installManifest({
697
+ projectDir,
698
+ onProgress: (line) => installSpinner.message(truncateForSpinner(line))
699
+ });
700
+ if (result.ok) installSpinner.stop(`Dependencies installed with ${pc.cyan(result.packageManager)}`);
701
+ if (!result.ok) installSpinner.error("Could not install dependencies");
702
+ return {
703
+ wantInstall,
704
+ installed: result.ok,
705
+ result
706
+ };
707
+ }
708
+ async function decideInstall(args) {
709
+ const { install, skipPrompts, hasAdded, addedLine } = args;
710
+ if (install !== void 0) {
711
+ if (hasAdded) log.info(addedLine);
712
+ return install;
713
+ }
714
+ if (!hasAdded) return true;
715
+ if (skipPrompts) {
716
+ log.info(addedLine);
717
+ return true;
718
+ }
719
+ return requirePrompt(await confirm({
720
+ message: `${addedLine} Automatically install dependencies?`,
721
+ initialValue: true
722
+ }));
723
+ }
724
+
725
+ //#endregion
726
+ //#region src/cli/commands/new-document.ts
727
+ const STUDIO_URL = "http://localhost:1234";
728
+ async function resolveName(args) {
729
+ const { provided, existingNames, skipPrompts } = args;
730
+ if (provided) return provided;
731
+ const suggestion = generateRandomName({ existingNames });
732
+ if (skipPrompts) return suggestion;
733
+ return requirePrompt(await text({
734
+ message: "Document name",
735
+ defaultValue: suggestion,
736
+ placeholder: suggestion,
737
+ validate: (value) => {
738
+ if (!value) return;
739
+ const result = validateDocumentName({
740
+ name: value,
741
+ existingNames
742
+ });
743
+ return result.ok ? void 0 : result.reason;
744
+ }
745
+ })) || suggestion;
746
+ }
747
+ async function resolveStylingMethod(args) {
748
+ const { provided, framework, skipPrompts } = args;
749
+ if (provided) return provided;
750
+ if (skipPrompts) return DEFAULT_STYLING;
751
+ return requirePrompt(await select({
752
+ message: "Styling method",
753
+ initialValue: DEFAULT_STYLING,
754
+ options: methodsForFramework(framework).map((method) => ({
755
+ value: method,
756
+ label: STYLING_LABELS[method]
757
+ }))
758
+ }));
759
+ }
760
+ function isSchemaChoice(value) {
761
+ return value === "none" || SCHEMA_KINDS.includes(value);
762
+ }
763
+ async function resolveSchema(args) {
764
+ const { provided, skipPrompts } = args;
765
+ if (provided !== void 0) {
766
+ if (!isSchemaChoice(provided)) throw new CliError(`Invalid --schema: "${provided}". Choices: none, ${SCHEMA_KINDS.join(", ")}.`);
767
+ return provided;
768
+ }
769
+ if (skipPrompts) return "none";
770
+ return requirePrompt(await select({
771
+ message: "Schema",
772
+ initialValue: "none",
773
+ options: [{
774
+ value: "none",
775
+ label: "None"
776
+ }, ...SCHEMA_KINDS.map((kind) => ({
777
+ value: kind,
778
+ label: SCHEMA_LABELS[kind]
779
+ }))]
780
+ }));
781
+ }
782
+ function parseDimension(args) {
783
+ const { flag, value } = args;
784
+ const parsed = Number(value);
785
+ if (!isValidDimension(parsed)) throw new CliError(`Invalid ${flag}: "${value}". Enter a number of CSS pixels from 1 to 20000.`);
786
+ return parsed;
787
+ }
788
+ async function promptCustomDimensions() {
789
+ const dimensionValidator = (value) => {
790
+ if (!value) return;
791
+ return isValidDimension(Number(value)) ? void 0 : "Enter a number of CSS pixels from 1 to 20000.";
792
+ };
793
+ const width = requirePrompt(await text({
794
+ message: "Width (px)",
795
+ defaultValue: "794",
796
+ placeholder: "794",
797
+ validate: dimensionValidator
798
+ }));
799
+ const height = requirePrompt(await text({
800
+ message: "Height (px)",
801
+ defaultValue: "1123",
802
+ placeholder: "1123",
803
+ validate: dimensionValidator
804
+ }));
805
+ return {
806
+ width: Number(width || "794"),
807
+ height: Number(height || "1123")
808
+ };
809
+ }
810
+ async function resolveDimensions(args) {
811
+ const { size, width, height, skipPrompts } = args;
812
+ if (width !== void 0 && height !== void 0) return {
813
+ width: parseDimension({
814
+ flag: "--width",
815
+ value: width
816
+ }),
817
+ height: parseDimension({
818
+ flag: "--height",
819
+ value: height
820
+ })
821
+ };
822
+ if (size && size !== "custom") {
823
+ const match$1 = PAGE_SIZES.find((candidate) => candidate.id === size);
824
+ if (!match$1) throw new CliError(`Invalid --size: "${size}". Choices: ${PAGE_SIZES.map((candidate) => candidate.id).join(", ")}, custom.`);
825
+ return {
826
+ width: match$1.width,
827
+ height: match$1.height
828
+ };
829
+ }
830
+ if (size === "custom" && (width === void 0 || height === void 0)) {
831
+ if (skipPrompts) throw new CliError("--size custom requires --width and --height.");
832
+ return promptCustomDimensions();
833
+ }
834
+ if (skipPrompts || size !== void 0) return;
835
+ const choice = requirePrompt(await select({
836
+ message: "Page size",
837
+ initialValue: DEFAULT_PAGE_SIZE_ID,
838
+ options: [...PAGE_SIZES.map((candidate) => ({
839
+ value: candidate.id,
840
+ label: `${candidate.name} (${candidate.label})`
841
+ })), {
842
+ value: "custom",
843
+ label: "Custom…"
844
+ }]
845
+ }));
846
+ if (choice === "custom") return promptCustomDimensions();
847
+ const match = PAGE_SIZES.find((candidate) => candidate.id === choice);
848
+ return match ? {
849
+ width: match.width,
850
+ height: match.height
851
+ } : void 0;
852
+ }
853
+ async function resolveData(args) {
854
+ const { provided, skipPrompts } = args;
855
+ const path = provided ?? (skipPrompts ? void 0 : await promptDataPath());
856
+ if (!path) return;
857
+ const resolved = resolve(process.cwd(), path);
858
+ let raw;
859
+ try {
860
+ raw = await readFile(resolved, "utf8");
861
+ } catch {
862
+ throw new CliError(`Couldn't read the data file at "${path}".`);
863
+ }
864
+ try {
865
+ return JSON.parse(raw);
866
+ } catch {
867
+ throw new CliError(`The data file at "${path}" isn't valid JSON.`);
868
+ }
869
+ }
870
+ async function promptDataPath() {
871
+ if (!requirePrompt(await confirm({
872
+ message: "Seed the default variant from a JSON file?",
873
+ initialValue: false
874
+ }))) return;
875
+ return requirePrompt(await text({
876
+ message: "Path to the JSON file",
877
+ placeholder: "./data.json",
878
+ validate: (value) => value && value.trim() !== "" ? void 0 : "Enter a path, or press Ctrl+C to skip."
879
+ })).trim();
880
+ }
881
+ function reportPrerequisites(prerequisites) {
882
+ if (prerequisites.missingConfig.length === 0) return;
883
+ const steps = prerequisites.missingConfig.map((config) => `• ${config.description}`);
884
+ log.warn(`To finish setting up ${STYLING_LABELS[prerequisites.method]}:\n${steps.join("\n")}`);
885
+ }
886
+ function isFramework$2(value) {
887
+ return value === "react" || value === "vue" || value === "html";
888
+ }
889
+ async function loadProject(cwd) {
890
+ const configPath = findConfigPath(cwd, { baseName: process.env.FMT_CONFIG_FILE_NAME });
891
+ if (!configPath) throw new CliError("No Format project found here. Run `npm create format@latest` to start a project, then try again.");
892
+ const configFile = await readConfigFile(configPath);
893
+ const framework = configFile.config.framework;
894
+ if (!isFramework$2(framework)) throw new CliError(`Invalid "framework" in ${configPath}: expected "react", "vue", or "html".`);
895
+ return {
896
+ config: {
897
+ framework,
898
+ rootDir: typeof configFile.config.rootDir === "string" ? configFile.config.rootDir : void 0
899
+ },
900
+ projectRoot: dirname(configPath),
901
+ formatConfigPath: configPath
902
+ };
903
+ }
904
+ async function newDocumentCommand(cwd, name, options) {
905
+ const { config, projectRoot, formatConfigPath } = await loadProject(cwd);
906
+ const empty = options.empty === true;
907
+ const skipPrompts = options.yes === true;
908
+ const embedded = options.embedded === true;
909
+ const incomingDependencies = options.incomingDependencies ?? [];
910
+ const documentsDir = getDocumentsDir({
911
+ projectRoot,
912
+ config
913
+ });
914
+ const existingNames = await readExistingDocumentNames(documentsDir);
915
+ try {
916
+ if (!embedded) intro(pc.bgCyan(pc.black(" Format ")));
917
+ const resolvedName = await resolveName({
918
+ provided: name,
919
+ existingNames,
920
+ skipPrompts
921
+ });
922
+ const styling = await resolveStylingMethod({
923
+ provided: options.styling,
924
+ framework: config.framework,
925
+ skipPrompts
926
+ });
927
+ const schemaChoice = empty ? "none" : await resolveSchema({
928
+ provided: options.schema,
929
+ skipPrompts
930
+ });
931
+ const emitSchema = schemaChoice !== "none";
932
+ const schemaKind = schemaChoice === "none" ? void 0 : schemaChoice;
933
+ const data = empty ? void 0 : await resolveData({
934
+ provided: options.data,
935
+ skipPrompts
936
+ });
937
+ const dimensions = await resolveDimensions({
938
+ size: options.size,
939
+ width: options.width,
940
+ height: options.height,
941
+ skipPrompts
942
+ });
943
+ const result = await createDocument({
944
+ config,
945
+ projectRoot,
946
+ name: resolvedName,
947
+ styling,
948
+ emitSchema,
949
+ schemaKind,
950
+ data,
951
+ width: dimensions?.width,
952
+ height: dimensions?.height,
953
+ empty
954
+ });
955
+ const documentDependencies = await writeDocumentDependencies({
956
+ projectRoot,
957
+ emitSchema,
958
+ schemaKind,
959
+ missingStylingDependencies: result.prerequisites.missingDependencies.map((dependency) => dependency.name)
960
+ });
961
+ const finalize = await finalizeDependencies({
962
+ projectDir: projectRoot,
963
+ addedDependencies: [...incomingDependencies, ...documentDependencies],
964
+ install: options.install === false ? false : void 0,
965
+ skipPrompts
966
+ });
967
+ if (finalize.result && !finalize.installed) log.error(`Failed to install dependencies automatically: ${finalize.result.message ?? "unknown error"}`);
968
+ const projectSetup = await maybeSetupProject({
969
+ wanted: finalize.wantInstall,
970
+ styling,
971
+ config,
972
+ projectRoot,
973
+ formatConfigPath,
974
+ documentsGlobBase: relative(projectRoot, documentsDir)
975
+ });
976
+ if (projectSetup) log.success(`Set up ${styling}\n${projectSetup.summary.map((line) => pc.dim(line)).join("\n")}`);
977
+ log.success(`Created document ${pc.cyan(result.documentName)}\n${pc.dim(result.documentDir)}`);
978
+ if (!finalize.installed) reportPrerequisites(result.prerequisites);
979
+ const openLine = `Open it in Studio: ${pc.cyan(`${STUDIO_URL}/${result.documentName}`)}`;
980
+ if (embedded) log.info(openLine);
981
+ if (!embedded) outro(openLine);
982
+ return 0;
983
+ } catch (error) {
984
+ if (isPromptCancelledError(error)) return 0;
985
+ const message = error instanceof Error ? error.message : String(error);
986
+ log.error(message);
987
+ return 1;
988
+ }
989
+ }
990
+
991
+ //#endregion
992
+ //#region src/cli/commands/init.ts
993
+ const SCHEMA_URL = "https://format.dev/schema/format-config.json";
994
+ const GITIGNORE_ENTRIES = [
995
+ "_generated",
996
+ ".env",
997
+ "format-env.d.ts"
998
+ ];
999
+ const FORMAT_ENV_TYPES = `/// <reference types="@format.dev/cli/env" />
1000
+
1001
+ // This file is managed by Format and regenerated with your document types when
1002
+ // you compile. Do not edit.
1003
+ `;
1004
+ function isFramework$1(value) {
1005
+ return SUPPORTED_FRAMEWORKS.includes(value);
1006
+ }
1007
+ function detectFramework(packageJson) {
1008
+ const dependencyNames = Object.keys({
1009
+ ...packageJson.dependencies,
1010
+ ...packageJson.devDependencies
1011
+ });
1012
+ if (dependencyNames.includes("react")) return "react";
1013
+ if (dependencyNames.includes("vue")) return "vue";
1014
+ return "html";
1015
+ }
1016
+ async function resolveFramework$1(args) {
1017
+ const { provided, detected, skipPrompts } = args;
1018
+ if (provided !== void 0) {
1019
+ if (!isFramework$1(provided)) throw new CliError(`Invalid --framework: "${provided}". Choices: ${SUPPORTED_FRAMEWORKS.join(", ")}.`);
1020
+ return provided;
1021
+ }
1022
+ if (skipPrompts) return detected;
1023
+ return requirePrompt(await select({
1024
+ message: "How do you want to build your PDFs?",
1025
+ initialValue: detected,
1026
+ options: [
1027
+ {
1028
+ value: "html",
1029
+ label: "HTML"
1030
+ },
1031
+ {
1032
+ value: "react",
1033
+ label: "React"
1034
+ },
1035
+ {
1036
+ value: "vue",
1037
+ label: "Vue"
1038
+ }
1039
+ ]
1040
+ }));
1041
+ }
1042
+ async function shouldRun(message, skipPrompts) {
1043
+ if (skipPrompts) return true;
1044
+ return requirePrompt(await confirm({
1045
+ message,
1046
+ initialValue: true
1047
+ }));
1048
+ }
1049
+ function normalizeRootDir(rootDir) {
1050
+ if (rootDir === void 0) return;
1051
+ const trimmed = rootDir.trim();
1052
+ if (trimmed === "" || trimmed === "." || trimmed === "./") return;
1053
+ return trimmed;
1054
+ }
1055
+ async function writeFormatConfig(args) {
1056
+ const { projectDir, framework, version, rootDir } = args;
1057
+ const configPath = join(projectDir, "format.config.json");
1058
+ const config = {
1059
+ $schema: SCHEMA_URL,
1060
+ version,
1061
+ framework,
1062
+ ...rootDir ? { rootDir } : {}
1063
+ };
1064
+ await fs.writeFile(configPath, `${JSON.stringify(config, null, " ")}\n`, "utf8");
1065
+ return configPath;
1066
+ }
1067
+ async function addPackageScripts(projectDir) {
1068
+ const packageJsonPath = join(projectDir, "package.json");
1069
+ const raw = await fs.readFile(packageJsonPath, "utf8");
1070
+ const packageJson = JSON.parse(raw);
1071
+ const scripts = { ...packageJson.scripts ?? {} };
1072
+ const wanted = [["dev", "format dev"], ["compile", "format compile"]];
1073
+ const added = [];
1074
+ const renamed = [];
1075
+ const resolvedName = {};
1076
+ for (const [name, command] of wanted) {
1077
+ if (scripts[name] === command) {
1078
+ resolvedName[name] = name;
1079
+ continue;
1080
+ }
1081
+ if (scripts[`format:${name}`] === command) {
1082
+ resolvedName[name] = `format:${name}`;
1083
+ continue;
1084
+ }
1085
+ if (scripts[name] === void 0) {
1086
+ scripts[name] = command;
1087
+ added.push([name, command]);
1088
+ resolvedName[name] = name;
1089
+ continue;
1090
+ }
1091
+ scripts[`format:${name}`] = command;
1092
+ renamed.push([`format:${name}`, command]);
1093
+ resolvedName[name] = `format:${name}`;
1094
+ }
1095
+ if (added.length > 0 || renamed.length > 0) {
1096
+ packageJson.scripts = scripts;
1097
+ const indent = detectIndent(raw);
1098
+ await fs.writeFile(packageJsonPath, `${JSON.stringify(packageJson, null, indent)}\n`, "utf8");
1099
+ }
1100
+ return {
1101
+ added,
1102
+ renamed,
1103
+ devScriptName: resolvedName.dev
1104
+ };
1105
+ }
1106
+ async function updateGitignore(projectDir) {
1107
+ const gitignorePath = join(projectDir, ".gitignore");
1108
+ const existing = existsSync(gitignorePath) ? await fs.readFile(gitignorePath, "utf8") : "";
1109
+ const lines = existing.split("\n").map((line) => line.trim());
1110
+ const missing = GITIGNORE_ENTRIES.filter((entry) => !lines.includes(entry));
1111
+ if (missing.length === 0) return [];
1112
+ const separator = existing === "" || existing.endsWith("\n") ? "" : "\n";
1113
+ await fs.writeFile(gitignorePath, `${existing}${separator}${missing.join("\n")}\n`, "utf8");
1114
+ return missing;
1115
+ }
1116
+ function packagesForFramework(framework) {
1117
+ if (framework === "react") return ["@format.dev/react", "@format.dev/client"];
1118
+ if (framework === "vue") return ["@format.dev/vue", "@format.dev/client"];
1119
+ return ["@format.dev/client"];
1120
+ }
1121
+ function formatDependencySpecs(framework, version) {
1122
+ const dependencyVersion = formatDependencyVersion(version);
1123
+ const packages = packagesForFramework(framework).map((name) => ({
1124
+ name,
1125
+ version: dependencyVersion,
1126
+ dev: !RUNTIME_PACKAGES.includes(name)
1127
+ }));
1128
+ const linked = devOnlyWorkspacePackages().map((name) => ({
1129
+ name,
1130
+ version: WORKSPACE_VERSION,
1131
+ dev: true
1132
+ }));
1133
+ return [...packages, ...linked];
1134
+ }
1135
+ async function initCommand(cwd, options) {
1136
+ const skipPrompts = options.yes === true;
1137
+ const configPath = join(cwd, "format.config.json");
1138
+ const configPathJsonc = join(cwd, "format.config.jsonc");
1139
+ if (existsSync(configPath) || existsSync(configPathJsonc)) throw new CliError("This project is already set up: a format.config file exists here.");
1140
+ const packageJsonPath = join(cwd, "package.json");
1141
+ if (!existsSync(packageJsonPath)) throw new CliError("No package.json found. Run `format init` in the root of an existing project, or start fresh with `npm create format`.");
1142
+ try {
1143
+ intro(pc.bgCyan(pc.black(" Format ")));
1144
+ const packageJson = JSON.parse(await fs.readFile(packageJsonPath, "utf8"));
1145
+ const framework = await resolveFramework$1({
1146
+ provided: options.framework,
1147
+ detected: detectFramework(packageJson),
1148
+ skipPrompts
1149
+ });
1150
+ const version = jsVersion;
1151
+ const rootDir = normalizeRootDir(options.rootDir);
1152
+ await writeFormatConfig({
1153
+ projectDir: cwd,
1154
+ framework,
1155
+ version,
1156
+ rootDir
1157
+ });
1158
+ const documentsDir = getDocumentsDir({
1159
+ projectRoot: cwd,
1160
+ config: {
1161
+ framework,
1162
+ rootDir
1163
+ }
1164
+ });
1165
+ await fs.mkdir(documentsDir, { recursive: true });
1166
+ const documentsPath = relative(cwd, documentsDir);
1167
+ await fs.writeFile(join(cwd, "format-env.d.ts"), FORMAT_ENV_TYPES, "utf8");
1168
+ const configLine = `Created ${pc.cyan("format.config.json")} ${pc.dim(`(Format ${version}, ${framework})`)}`;
1169
+ log.success(`${configLine}\n${pc.dim(`Documents directory: ${documentsPath}`)}`);
1170
+ let devScriptName;
1171
+ if (options.scripts && await shouldRun("Add format npm scripts to package.json?", skipPrompts)) {
1172
+ const { added, renamed, devScriptName: resolvedDevScript } = await addPackageScripts(cwd);
1173
+ devScriptName = resolvedDevScript;
1174
+ const scriptLines = [...added, ...renamed].map(([name, command]) => `${pc.cyan(name)}: ${pc.dim(command)}`);
1175
+ if (renamed.length > 0) scriptLines.push(pc.dim("Some script names were taken, so those use a format: prefix."));
1176
+ log.success(`Added npm scripts\n${scriptLines.join("\n")}`);
1177
+ }
1178
+ if (options.gitignore && await shouldRun("Update .gitignore?", skipPrompts)) {
1179
+ const appended = await updateGitignore(cwd);
1180
+ if (appended.length > 0) log.success(`Added to .gitignore: ${pc.dim(appended.join(", "))}`);
1181
+ }
1182
+ const addedFormatPackages = await writeDependenciesToManifest({
1183
+ projectDir: cwd,
1184
+ dependencies: formatDependencySpecs(framework, version)
1185
+ });
1186
+ const createDocumentNow = options.document && await shouldRun("Create your first document?", skipPrompts);
1187
+ if (createDocumentNow) {
1188
+ const exitCode = await newDocumentCommand(cwd, void 0, {
1189
+ yes: options.yes,
1190
+ install: options.install,
1191
+ embedded: true,
1192
+ incomingDependencies: addedFormatPackages
1193
+ });
1194
+ if (exitCode !== 0) return exitCode;
1195
+ }
1196
+ if (!createDocumentNow) {
1197
+ await fs.writeFile(join(documentsDir, ".gitkeep"), "", "utf8");
1198
+ await finalizeDependencies({
1199
+ projectDir: cwd,
1200
+ addedDependencies: addedFormatPackages,
1201
+ install: options.install ? void 0 : false,
1202
+ skipPrompts
1203
+ });
1204
+ }
1205
+ const startCommand = devScriptName ? `npm run ${devScriptName}` : "format dev";
1206
+ outro(`Format is set up. Start Studio with ${pc.cyan(startCommand)}`);
1207
+ return 0;
1208
+ } catch (error) {
1209
+ if (isPromptCancelledError(error)) return 0;
1210
+ throw error;
1211
+ }
1212
+ }
1213
+
1214
+ //#endregion
1215
+ //#region src/cli/commands/remove.ts
1216
+ function normalisePackageName(name) {
1217
+ if (name.startsWith("@format.dev/")) return name;
1218
+ return `@format.dev/${name}`;
1219
+ }
1220
+ function removeArgs(packageManager, packages) {
1221
+ if (packageManager === "npm") return ["uninstall", ...packages];
1222
+ return ["remove", ...packages];
1223
+ }
1224
+ async function removeCommand(cwd, packages) {
1225
+ if (packages.length === 0) throw new CliError("Usage: format remove <package> [...] — e.g. `format remove zip`.");
1226
+ const state = await loadProjectState(cwd);
1227
+ if (!state.packageJsonPath) throw new CliError("No package.json found next to the config — nothing to remove from.");
1228
+ const names = packages.map(normalisePackageName);
1229
+ if (names.includes("@format.dev/cli")) throw new CliError("Refusing to remove @format.dev/cli through itself. Use your package manager directly: `npm uninstall \"@format.dev/cli\"`.");
1230
+ const installedNames = state.dependencies.map((dependency) => dependency.name);
1231
+ const notInstalled = names.filter((name) => !installedNames.includes(name));
1232
+ if (notInstalled.length > 0) throw new CliError(`Not installed in this project: ${notInstalled.join(", ")}.`);
1233
+ const packageManager = await detectPackageManager(state.projectDir);
1234
+ console.log(`Removing ${names.join(", ")} with ${packageManager}...`);
1235
+ await runRemove(packageManager, removeArgs(packageManager, names), state.projectDir);
1236
+ console.log("");
1237
+ console.log("Done.");
1238
+ return 0;
1239
+ }
1240
+ async function runRemove(packageManager, args, cwd) {
1241
+ await new Promise((resolvePromise, rejectPromise) => {
1242
+ const child = spawn(commandForPlatform(packageManager), args, {
1243
+ cwd,
1244
+ stdio: "inherit",
1245
+ shell: false
1246
+ });
1247
+ child.on("error", (error) => {
1248
+ rejectPromise(new CliError(`Failed to run ${packageManager}: ${error.message}`));
1249
+ });
1250
+ child.on("exit", (code) => {
1251
+ if (code === 0) {
1252
+ resolvePromise();
1253
+ return;
1254
+ }
1255
+ rejectPromise(new CliError(`${packageManager} exited with code ${code}.`));
1256
+ });
1257
+ });
1258
+ }
1259
+
1260
+ //#endregion
1261
+ //#region src/cli/commands/new-project.ts
1262
+ function isFramework(value) {
1263
+ return SUPPORTED_FRAMEWORKS.includes(value);
1264
+ }
1265
+ async function resolveDirectory(provided, skipPrompts) {
1266
+ if (provided) return provided;
1267
+ if (skipPrompts) return "./my-format-project";
1268
+ return requirePrompt(await text({
1269
+ message: "Where should we create your project?",
1270
+ placeholder: "./my-format-project",
1271
+ defaultValue: "./my-format-project"
1272
+ }));
1273
+ }
1274
+ async function resolveFramework(provided, skipPrompts) {
1275
+ if (provided !== void 0) {
1276
+ if (!isFramework(provided)) throw new CliError(`Invalid --framework: "${provided}". Choices: ${SUPPORTED_FRAMEWORKS.join(", ")}.`);
1277
+ return provided;
1278
+ }
1279
+ if (skipPrompts) return "react";
1280
+ return requirePrompt(await select({
1281
+ message: "How do you want to build your PDFs?",
1282
+ options: [
1283
+ {
1284
+ value: "html",
1285
+ label: "HTML"
1286
+ },
1287
+ {
1288
+ value: "react",
1289
+ label: "React"
1290
+ },
1291
+ {
1292
+ value: "vue",
1293
+ label: "Vue"
1294
+ }
1295
+ ],
1296
+ initialValue: "react"
1297
+ }));
1298
+ }
1299
+ async function resolveInstall(provided, skipPrompts) {
1300
+ if (provided !== void 0) return provided;
1301
+ if (skipPrompts) return true;
1302
+ return requirePrompt(await confirm({
1303
+ message: "Automatically install dependencies?",
1304
+ initialValue: true
1305
+ }));
1306
+ }
1307
+ function runDevLine(packageManager) {
1308
+ return packageManager === "npm" ? "npm run dev" : `${packageManager} dev`;
1309
+ }
1310
+ async function newProjectCommand(cwd, providedDirectory, options) {
1311
+ const skipPrompts = options.yes === true;
1312
+ intro(pc.bgCyan(pc.black(" Format ")));
1313
+ try {
1314
+ const directory = await resolveDirectory(providedDirectory, skipPrompts);
1315
+ const framework = await resolveFramework(options.framework, skipPrompts);
1316
+ const wantInstall = await resolveInstall(options.install, skipPrompts);
1317
+ const scaffold = spinner();
1318
+ scaffold.start("Creating project");
1319
+ const { dirPath } = await scaffoldProject({
1320
+ directory,
1321
+ framework,
1322
+ cwd
1323
+ });
1324
+ scaffold.stop(`Created a ${pc.cyan(framework)} project in ${pc.cyan(directory)}`);
1325
+ let packageManager = await detectPackageManager(cwd);
1326
+ let needsInstall = !wantInstall;
1327
+ if (wantInstall) {
1328
+ const install = spinner();
1329
+ install.start(`Installing dependencies with ${packageManager}`);
1330
+ const result = await installProjectDependencies({
1331
+ dirPath,
1332
+ preferredPackageManager: packageManager,
1333
+ onProgress: (line) => install.message(truncateForSpinner(line))
1334
+ });
1335
+ if (result.ok) {
1336
+ packageManager = result.packageManagerUsed;
1337
+ needsInstall = false;
1338
+ install.stop(`Dependencies installed with ${pc.cyan(packageManager)}`);
1339
+ if (result.fellBackToNpm) log.warn("Could not find your package manager. Fell back to npm.");
1340
+ }
1341
+ if (!result.ok) {
1342
+ needsInstall = true;
1343
+ install.error(`Could not install dependencies with ${packageManager}`);
1344
+ log.error(result.message ?? "unknown error");
1345
+ if (result.output) log.message(pc.dim(result.output));
1346
+ if (await templateUsesWorkspaceDeps(dirPath)) log.warn("This template uses `workspace:*` deps, which only install inside a workspace.");
1347
+ }
1348
+ }
1349
+ const steps = [`cd ${directory}`];
1350
+ if (needsInstall) {
1351
+ const { command, args } = installCommand(packageManager);
1352
+ steps.push(`${command} ${args.join(" ")}`);
1353
+ }
1354
+ steps.push(runDevLine(packageManager));
1355
+ outro(`Start Studio with ${pc.cyan(steps.join(" && "))}`);
1356
+ return 0;
1357
+ } catch (error) {
1358
+ if (isPromptCancelledError(error)) return 0;
1359
+ throw error;
1360
+ }
1361
+ }
1362
+
1363
+ //#endregion
1364
+ //#region src/cli/commands/update.ts
1365
+ async function updateCommand(cwd, target) {
1366
+ const state = await loadProjectState(cwd);
1367
+ const targetVersion = await resolveTargetVersion({
1368
+ target,
1369
+ pinnedVersion: state.pinnedVersion
1370
+ });
1371
+ if (targetVersion !== state.pinnedVersion) {
1372
+ await writeConfigVersion({
1373
+ filepath: state.configFile.filepath,
1374
+ version: targetVersion
1375
+ });
1376
+ console.log(`Pinned Format ${targetVersion} in ${state.configFile.filepath}`);
1377
+ } else console.log(`Applying pinned Format ${targetVersion}`);
1378
+ if (!state.packageJsonPath) {
1379
+ console.log("No package.json found next to the config — nothing to install.");
1380
+ return 0;
1381
+ }
1382
+ const rewritten = await rewriteFormatDependencies({
1383
+ packageJsonPath: state.packageJsonPath,
1384
+ version: targetVersion
1385
+ });
1386
+ if (rewritten.length === 0) {
1387
+ console.log("All installed Format packages already match.");
1388
+ return 0;
1389
+ }
1390
+ for (const change of rewritten) console.log(` ${change.name} ${change.from} → ${change.to}`);
1391
+ const packageManager = await detectPackageManager(state.projectDir);
1392
+ const { command, args } = installCommand(packageManager);
1393
+ console.log("");
1394
+ console.log(`Installing with ${packageManager}...`);
1395
+ await runInstall({
1396
+ command,
1397
+ args,
1398
+ cwd: state.projectDir
1399
+ });
1400
+ console.log("");
1401
+ console.log(`Done. Your project is on Format ${targetVersion}.`);
1402
+ return 0;
1403
+ }
1404
+ async function resolveTargetVersion(args) {
1405
+ const { target, pinnedVersion } = args;
1406
+ if (target === "latest") return fetchLatestVersion();
1407
+ if (target) {
1408
+ if (!isExactVersion(target)) throw new CliError(`"${target}" is not an exact version. Use a version like 0.1.2, or \`format update latest\`.`);
1409
+ return target;
1410
+ }
1411
+ if (pinnedVersion) return pinnedVersion;
1412
+ throw new CliError("format.config.json is missing its required \"version\" field. Run `format update latest` to pin the newest release, or `format init` to set the project up.");
1413
+ }
1414
+ async function runInstall(args) {
1415
+ const { command, args: commandArgs, cwd } = args;
1416
+ await new Promise((resolvePromise, rejectPromise) => {
1417
+ const child = spawn(command, commandArgs, {
1418
+ cwd,
1419
+ stdio: "inherit",
1420
+ shell: process.platform === "win32"
1421
+ });
1422
+ child.on("error", (error) => {
1423
+ rejectPromise(new CliError(`Failed to run ${command} install: ${error.message}`));
1424
+ });
1425
+ child.on("exit", (code) => {
1426
+ if (code === 0) {
1427
+ resolvePromise();
1428
+ return;
1429
+ }
1430
+ rejectPromise(new CliError(`${command} install exited with code ${code}.`));
1431
+ });
1432
+ });
1433
+ }
1434
+
1435
+ //#endregion
1436
+ //#region src/cli/commands/version.ts
1437
+ async function versionCommand(cwd) {
1438
+ const state = await loadProjectState(cwd);
1439
+ if (!state.pinnedVersion) {
1440
+ console.log(`No "version" field found in ${state.configFile.filepath}.`);
1441
+ console.log("Run `format update latest` to pin the latest Format release.");
1442
+ return 1;
1443
+ }
1444
+ console.log(`Format ${state.pinnedVersion}`);
1445
+ if (state.mismatches.length > 0) {
1446
+ console.log("");
1447
+ console.log("Some installed Format packages do not match the pinned version:");
1448
+ for (const mismatch of state.mismatches) console.log(` ${mismatch.name} ${mismatch.actual} (expected ${mismatch.expected})`);
1449
+ console.log("");
1450
+ console.log("Run `format update` to bring everything in line.");
1451
+ return 1;
1452
+ }
1453
+ const alignedCount = state.dependencies.length;
1454
+ if (alignedCount > 0) {
1455
+ const names = state.dependencies.map((dependency) => dependency.name).join(", ");
1456
+ console.log(`${alignedCount} installed Format package${alignedCount === 1 ? "" : "s"}: ${names}`);
1457
+ }
1458
+ await reportNewerRelease(state.pinnedVersion);
1459
+ return 0;
1460
+ }
1461
+
1462
+ //#endregion
1463
+ //#region src/cli/index.ts
1464
+ const STUDIO_COMMANDS = [
1465
+ {
1466
+ name: "dev",
1467
+ description: "Open Studio for this project."
1468
+ },
1469
+ {
1470
+ name: "compile",
1471
+ description: "Compile this project's documents into a renderer."
1472
+ },
1473
+ {
1474
+ name: "clear-cache",
1475
+ description: "Clear Studio's local cache."
1476
+ }
1477
+ ];
1478
+ function createCommanderOption(optionSpec) {
1479
+ const option = new Option(optionSpec.flags, optionSpec.description);
1480
+ if (optionSpec.choices && optionSpec.choices.length > 0) option.choices(optionSpec.choices);
1481
+ return option;
1482
+ }
1483
+ function registerArguments(command, argumentSpecs) {
1484
+ if (!argumentSpecs) return;
1485
+ for (const argumentSpec of argumentSpecs) {
1486
+ const token = argumentSpec.required ? `<${argumentSpec.name}>` : `[${argumentSpec.name}]`;
1487
+ command.argument(token, argumentSpec.description);
1488
+ }
1489
+ }
1490
+ function registerCommand(parent, commandSpec, path, handlers) {
1491
+ const command = parent.command(commandSpec.name).description(commandSpec.description);
1492
+ registerArguments(command, commandSpec.arguments);
1493
+ for (const optionSpec of commandSpec.options) command.addOption(createCommanderOption(optionSpec));
1494
+ const fullPath = [...path, commandSpec.name];
1495
+ if (commandSpec.commands && commandSpec.commands.length > 0) {
1496
+ for (const childSpec of commandSpec.commands) registerCommand(command, childSpec, fullPath, handlers);
1497
+ return;
1498
+ }
1499
+ const key = fullPath.join(" ");
1500
+ const handler = handlers[key];
1501
+ if (!handler) throw new Error(`Missing command handler for "${key}"`);
1502
+ command.action(async (...args) => {
1503
+ process.exitCode = await handler(...args);
1504
+ });
1505
+ }
1506
+ function createProgram() {
1507
+ const program = new Command();
1508
+ const cliSpec = cli_spec_default;
1509
+ const cwd = process.cwd();
1510
+ program.name("format").description("The Format CLI: create projects and documents, run Studio, compile renderers, and keep every Format package on one release.").enablePositionalOptions();
1511
+ const handlers = {
1512
+ "new project": (directory, options) => newProjectCommand(cwd, directory, options),
1513
+ "new document": (name, options) => newDocumentCommand(cwd, name, options),
1514
+ init: (options) => initCommand(cwd, options),
1515
+ add: (packages) => addCommand(cwd, packages),
1516
+ remove: (packages) => removeCommand(cwd, packages),
1517
+ update: (version) => updateCommand(cwd, version),
1518
+ version: () => versionCommand(cwd)
1519
+ };
1520
+ for (const commandSpec of cliSpec.commands) registerCommand(program, commandSpec, [], handlers);
1521
+ for (const { name, description } of STUDIO_COMMANDS) program.command(name).description(description).helpOption(false).allowUnknownOption().passThroughOptions().argument("[args...]").action(async (_args, _options, command) => {
1522
+ process.exitCode = await runStudioCommand(cwd, [name, ...command.args]);
1523
+ });
1524
+ return program;
1525
+ }
1526
+
1527
+ //#endregion
1528
+ //#region src/cli/bin.ts
1529
+ try {
1530
+ await createProgram().parseAsync();
1531
+ } catch (error) {
1532
+ if (error instanceof CliError) {
1533
+ console.error(error.message);
1534
+ process.exitCode = 1;
1535
+ } else throw error;
1536
+ }
1537
+
1538
+ //#endregion
1539
+ export { };