Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/snapshot-unchanged.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@cloudhal/cli": patch
---

`cloudhal snapshot --publish` makes no new snapshot when nothing changed since the live one; it says "nothing changed since <snapshot>" instead (#51).
4 changes: 4 additions & 0 deletions packages/cli/src/cli/commands-local.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ const snapshotCommand: CliCommand = async (_rest, values, { projectDir }) => {
...(values.label ? { label: values.label } : {}),
...(values.publish ? { publish: true } : {}),
});
if (snap.unchanged) {
console.log(`nothing changed since ${snap.id} (live); no new snapshot`);
return 0;
}
console.log(
`${snap.id} nodes=${snap.nodes.length} +${snap.summary.added} ~${snap.summary.changed} -${snap.summary.removed}${values.publish ? " (published)" : ""}`,
);
Expand Down
29 changes: 26 additions & 3 deletions packages/cli/src/commands/snapshot.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import path from "node:path";
import type { Snapshot } from "@cloudhal/core-contracts";
import { openLocalStore } from "@cloudhal/core-ts/local";
import { type LocalStore, openLocalStore } from "@cloudhal/core-ts/local";
import { readProjectConfig } from "../config.ts";

export interface SnapshotOptions {
Expand All @@ -9,11 +9,22 @@ export interface SnapshotOptions {
readonly publish?: boolean;
}

/** Creates a snapshot of the live site (and optionally publishes it). */
export async function snapshot(options: SnapshotOptions): Promise<Snapshot> {
/** A snapshot, or (`unchanged`) the live one when publishing would change nothing. */
export type SnapshotResult = Snapshot & { readonly unchanged?: true };

/**
* Creates a snapshot of the live site (and optionally publishes it). Publishing a site whose
* graph matches its live snapshot makes no new one (#51): re-running a setup script does not pad
* the history with empty snapshots.
*/
export async function snapshot(options: SnapshotOptions): Promise<SnapshotResult> {
const cfg = await readProjectConfig(options.projectDir);
const local = await openLocalStore(path.join(options.projectDir, cfg.dataDir));
try {
if (options.publish) {
const live = await unchangedLive(local.store, cfg.siteId);
if (live) return { ...live, unchanged: true };
}
const snap = await local.store.createSnapshot(cfg.siteId, {
actor: { kind: "human", id: "user_localcli0001", name: "cloudhal CLI" },
reason: options.publish ? "publish" : "manual",
Expand All @@ -30,6 +41,18 @@ export async function snapshot(options: SnapshotOptions): Promise<Snapshot> {
}
}

/** The live snapshot when the site has one and nothing is pending against it. */
async function unchangedLive(
store: LocalStore["store"],
siteId: string,
): Promise<Snapshot | undefined> {
const site = await store.getSite(siteId);
if (!site?.publishedSnapshotId) return undefined;
const d = await store.pendingChanges(siteId);
if (d.added.length || d.removed.length || d.changed.length) return undefined;
return (await store.getSnapshot(site.publishedSnapshotId)) ?? undefined;
}

/** The snapshot `cloudhal build` renders: the published one, else the latest, else a fresh one. */
export async function resolveBuildSnapshot(projectDir: string): Promise<Snapshot> {
const cfg = await readProjectConfig(projectDir);
Expand Down
3 changes: 3 additions & 0 deletions packages/cli/test/e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,9 @@ describe("cloudhal init → dev → snapshot → build", () => {
}
const snap = await snapshot({ projectDir, label: "v1", publish: true });
expect(snap.summary.added).toBeGreaterThan(0);
// #51: publishing again with nothing changed makes no new snapshot
const again = await snapshot({ projectDir, publish: true });
expect(again).toMatchObject({ id: snap.id, unchanged: true });

const cfg = await readProjectConfig(projectDir);
const local = await openLocalStore(path.join(projectDir, cfg.dataDir));
Expand Down
Loading