@format.dev/cli 0.1.1 → 0.1.2

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 (2) hide show
  1. package/dist/index.mjs +157 -7
  2. package/package.json +3 -3
package/dist/index.mjs CHANGED
@@ -250,7 +250,7 @@ var cli_spec_default = { commands: [
250
250
 
251
251
  //#endregion
252
252
  //#region src/versioning/js-version.ts
253
- const jsVersion = "0.1.1";
253
+ const jsVersion = "0.1.2";
254
254
 
255
255
  //#endregion
256
256
  //#region src/versioning/registry.ts
@@ -272,11 +272,43 @@ async function fetchLatestVersion() {
272
272
  return data.version;
273
273
  }
274
274
 
275
+ //#endregion
276
+ //#region src/versioning/changelog.ts
277
+ const CHANGELOG_URL = "https://format.dev/changelog";
278
+ const CHANGELOG_INDEX_URL = process.env.FMT_CHANGELOG_INDEX_URL ?? "https://format.dev/changelog.json";
279
+ const FETCH_TIMEOUT_MS = 3e3;
280
+ function changelogEntriesBetween(args) {
281
+ const { entries, fromVersion, toVersion } = args;
282
+ return entries.filter((entry) => isNewer(entry.version, fromVersion) && !isNewer(entry.version, toVersion)).sort((a, b) => isNewer(a.version, b.version) ? 1 : -1);
283
+ }
284
+ async function fetchChangelogIndex() {
285
+ const controller = new AbortController();
286
+ const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
287
+ try {
288
+ const response = await fetch(CHANGELOG_INDEX_URL, { signal: controller.signal });
289
+ if (!response.ok) throw new Error(`Changelog index returned ${response.status}`);
290
+ return (await response.json()).entries;
291
+ } finally {
292
+ clearTimeout(timeout);
293
+ }
294
+ }
295
+ async function changelogLines(args) {
296
+ const { fromVersion, toVersion, fetchEntries = fetchChangelogIndex } = args;
297
+ const entries = await fetchEntries().catch(() => null);
298
+ if (!entries) return [`See what's changed: ${CHANGELOG_URL}`];
299
+ const inRange = changelogEntriesBetween({
300
+ entries,
301
+ fromVersion,
302
+ toVersion
303
+ });
304
+ if (inRange.length === 0) return [];
305
+ return [`What's changed since ${fromVersion}:`, ...inRange.map((entry) => ` ${entry.version} ${entry.url}`)];
306
+ }
307
+
275
308
  //#endregion
276
309
  //#region src/versioning/update-check.ts
277
310
  const CACHE_FILE_NAME = "update-check.json";
278
311
  const CACHE_MAX_AGE_MS = 1440 * 60 * 1e3;
279
- const CHANGELOG_URL = "https://format.dev/changelog";
280
312
  function cacheDir() {
281
313
  const home = os.homedir();
282
314
  if (process.env.XDG_CACHE_HOME) return join(process.env.XDG_CACHE_HOME, "format");
@@ -599,11 +631,19 @@ function installArgs(packageManager, packages, dev) {
599
631
  switch (packageManager) {
600
632
  case "npm": return [
601
633
  "install",
634
+ "--save-exact",
602
635
  ...dev ? ["--save-dev"] : [],
603
636
  ...packages
604
637
  ];
638
+ case "pnpm": return [
639
+ "add",
640
+ "--save-exact",
641
+ ...saveFlag ? [saveFlag] : [],
642
+ ...packages
643
+ ];
605
644
  default: return [
606
645
  "add",
646
+ "--exact",
607
647
  ...saveFlag ? [saveFlag] : [],
608
648
  ...packages
609
649
  ];
@@ -1360,6 +1400,94 @@ async function newProjectCommand(cwd, providedDirectory, options) {
1360
1400
  }
1361
1401
  }
1362
1402
 
1403
+ //#endregion
1404
+ //#region src/versioning/cli-package.ts
1405
+ const CLI_PACKAGE_NAME$1 = "@format.dev/cli";
1406
+ async function readCliPackageVersion() {
1407
+ let dir = dirname(fileURLToPath(import.meta.url));
1408
+ for (;;) {
1409
+ const candidate = join(dir, "package.json");
1410
+ if (existsSync(candidate)) {
1411
+ const manifest = JSON.parse(await fs.readFile(candidate, "utf8"));
1412
+ if (manifest.name === CLI_PACKAGE_NAME$1) return manifest.version;
1413
+ }
1414
+ const parent = dirname(dir);
1415
+ if (parent === dir) throw new Error(`Could not find the ${CLI_PACKAGE_NAME$1} package.json above ${fileURLToPath(import.meta.url)}`);
1416
+ dir = parent;
1417
+ }
1418
+ }
1419
+
1420
+ //#endregion
1421
+ //#region src/versioning/handoff.ts
1422
+ const HANDOFF_ENV = "FMT_UPDATE_HANDOFF";
1423
+ const CLI_PACKAGE_NAME = "@format.dev/cli";
1424
+ function readHandoffFromVersion() {
1425
+ return process.env[HANDOFF_ENV] ?? null;
1426
+ }
1427
+ function environmentWithoutHandoff() {
1428
+ const { [HANDOFF_ENV]: _handoff, ...environment } = process.env;
1429
+ return environment;
1430
+ }
1431
+ async function handOffUpdate(args) {
1432
+ const { targetVersion, fromVersion, cwd } = args;
1433
+ const commandArgs = [
1434
+ "exec",
1435
+ "--yes",
1436
+ `--package=${CLI_PACKAGE_NAME}@${targetVersion}`,
1437
+ "--",
1438
+ "format",
1439
+ "update",
1440
+ targetVersion
1441
+ ];
1442
+ return new Promise((resolvePromise, rejectPromise) => {
1443
+ const child = spawn("npm", commandArgs, {
1444
+ cwd,
1445
+ stdio: "inherit",
1446
+ shell: process.platform === "win32",
1447
+ env: {
1448
+ ...process.env,
1449
+ [HANDOFF_ENV]: fromVersion ?? "none"
1450
+ }
1451
+ });
1452
+ child.on("error", (error) => {
1453
+ rejectPromise(new CliError(`Failed to run the Format ${targetVersion} CLI: ${error.message}`));
1454
+ });
1455
+ child.on("exit", (code) => {
1456
+ resolvePromise(code ?? 1);
1457
+ });
1458
+ });
1459
+ }
1460
+
1461
+ //#endregion
1462
+ //#region src/versioning/fresh-install.ts
1463
+ const FORMAT_SCOPE = "@format.dev/";
1464
+ const DEPENDENCY_SECTIONS = [
1465
+ "dependencies",
1466
+ "devDependencies",
1467
+ "optionalDependencies"
1468
+ ];
1469
+ function withoutFormatDependencies(manifest) {
1470
+ const copy = { ...manifest };
1471
+ for (const section of DEPENDENCY_SECTIONS) {
1472
+ const entries = manifest[section];
1473
+ if (!entries) continue;
1474
+ copy[section] = Object.fromEntries(Object.entries(entries).filter(([name, version]) => !name.startsWith(FORMAT_SCOPE) || version.startsWith("workspace:")));
1475
+ }
1476
+ return copy;
1477
+ }
1478
+ async function installWithFreshFormatTree(args) {
1479
+ const { packageJsonPath, install } = args;
1480
+ const targetSource = await fs.readFile(packageJsonPath, "utf8");
1481
+ const withoutFormat = withoutFormatDependencies(JSON.parse(targetSource));
1482
+ try {
1483
+ await fs.writeFile(packageJsonPath, `${JSON.stringify(withoutFormat, null, " ")}\n`, "utf8");
1484
+ await install();
1485
+ } finally {
1486
+ await fs.writeFile(packageJsonPath, targetSource, "utf8");
1487
+ }
1488
+ await install();
1489
+ }
1490
+
1363
1491
  //#endregion
1364
1492
  //#region src/cli/commands/update.ts
1365
1493
  async function updateCommand(cwd, target) {
@@ -1368,6 +1496,13 @@ async function updateCommand(cwd, target) {
1368
1496
  target,
1369
1497
  pinnedVersion: state.pinnedVersion
1370
1498
  });
1499
+ const cliVersion = await readCliPackageVersion();
1500
+ const handoffFromVersion = readHandoffFromVersion();
1501
+ if (handoffFromVersion === null && cliVersion !== targetVersion) return handOffUpdate({
1502
+ targetVersion,
1503
+ fromVersion: state.pinnedVersion,
1504
+ cwd: state.projectDir
1505
+ });
1371
1506
  if (targetVersion !== state.pinnedVersion) {
1372
1507
  await writeConfigVersion({
1373
1508
  filepath: state.configFile.filepath,
@@ -1392,13 +1527,27 @@ async function updateCommand(cwd, target) {
1392
1527
  const { command, args } = installCommand(packageManager);
1393
1528
  console.log("");
1394
1529
  console.log(`Installing with ${packageManager}...`);
1395
- await runInstall({
1396
- command,
1397
- args,
1398
- cwd: state.projectDir
1530
+ await installWithFreshFormatTree({
1531
+ packageJsonPath: state.packageJsonPath,
1532
+ install: () => runInstall({
1533
+ command,
1534
+ args,
1535
+ cwd: state.projectDir
1536
+ })
1399
1537
  });
1400
1538
  console.log("");
1401
1539
  console.log(`Done. Your project is on Format ${targetVersion}.`);
1540
+ const fromVersion = handoffFromVersion ?? state.pinnedVersion;
1541
+ if (fromVersion !== null && fromVersion !== "none" && fromVersion !== targetVersion) {
1542
+ const lines = await changelogLines({
1543
+ fromVersion,
1544
+ toVersion: targetVersion
1545
+ });
1546
+ if (lines.length > 0) {
1547
+ console.log("");
1548
+ lines.forEach((line) => console.log(line));
1549
+ }
1550
+ }
1402
1551
  return 0;
1403
1552
  }
1404
1553
  async function resolveTargetVersion(args) {
@@ -1417,7 +1566,8 @@ async function runInstall(args) {
1417
1566
  const child = spawn(command, commandArgs, {
1418
1567
  cwd,
1419
1568
  stdio: "inherit",
1420
- shell: process.platform === "win32"
1569
+ shell: process.platform === "win32",
1570
+ env: environmentWithoutHandoff()
1421
1571
  });
1422
1572
  child.on("error", (error) => {
1423
1573
  rejectPromise(new CliError(`Failed to run ${command} install: ${error.message}`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@format.dev/cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "license": "MIT",
5
5
  "description": "The Format CLI: run Studio, compile documents, and keep Format packages in lockstep",
6
6
  "author": "the format.dev developers",
@@ -72,8 +72,8 @@
72
72
  "verdaccio": "^6.8.0",
73
73
  "vitest": "^4.0.18",
74
74
  "vue": "^3.5.25",
75
- "@format.dev/vue": "0.1.1",
76
- "@format.dev/react": "0.1.1"
75
+ "@format.dev/vue": "0.1.2",
76
+ "@format.dev/react": "0.1.2"
77
77
  },
78
78
  "engines": {
79
79
  "node": ">=22.12.0"