@everystack/cli 0.3.27 → 0.3.28
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.
package/package.json
CHANGED
|
@@ -169,6 +169,16 @@ export function buildSyncReport(run: SyncRun): string[] {
|
|
|
169
169
|
lines.push(`· ${state.classified.notices.length} notice(s) — manual follow-ups, nothing executed for them`);
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
+
// The base fingerprint is a STATE-layer fact: both endpoints are sampled around
|
|
173
|
+
// the state apply, before compute runs. Print it here, adjacent to the state
|
|
174
|
+
// section, so an empty→full move never reads as if the compute step caused it —
|
|
175
|
+
// the derived layer (functions/views/matviews) is not fingerprinted at all.
|
|
176
|
+
lines.push(
|
|
177
|
+
state.toFingerprint === state.fromFingerprint
|
|
178
|
+
? `base fingerprint: ${short(state.toFingerprint)} (unchanged)`
|
|
179
|
+
: `base fingerprint: ${short(state.fromFingerprint)} → ${short(state.toFingerprint)} (across the state apply)`,
|
|
180
|
+
);
|
|
181
|
+
|
|
172
182
|
// Compute.
|
|
173
183
|
if (compute === null) {
|
|
174
184
|
lines.push('· compute: no db/sql directory — derived layer not declared, skipped');
|
|
@@ -181,12 +191,7 @@ export function buildSyncReport(run: SyncRun): string[] {
|
|
|
181
191
|
}
|
|
182
192
|
}
|
|
183
193
|
|
|
184
|
-
//
|
|
185
|
-
lines.push(
|
|
186
|
-
state.toFingerprint === state.fromFingerprint
|
|
187
|
-
? `fingerprint: ${short(state.toFingerprint)} (unchanged)`
|
|
188
|
-
: `fingerprint: ${short(state.fromFingerprint)} → ${short(state.toFingerprint)}`,
|
|
189
|
-
);
|
|
194
|
+
// Verdict.
|
|
190
195
|
if (run.fingerprintMatch) {
|
|
191
196
|
lines.push('MATCH — the database is the state your checkout declares');
|
|
192
197
|
} else if (run.stateConverged) {
|
|
@@ -27,6 +27,13 @@
|
|
|
27
27
|
* - FUNCTIONS are excluded — they belong to the derived layer, whose own
|
|
28
28
|
* content hashes (derived-introspect) cover them. Base fingerprint + derived
|
|
29
29
|
* manifest together are the full schema state.
|
|
30
|
+
* - Expressions (column defaults, check predicates, partial-index WHERE) hash
|
|
31
|
+
* through the SAME normalizers the diff uses (`normalizeDefault` /
|
|
32
|
+
* `normalizeCheck`), applied to both sides in `canonicalizeState`. Without
|
|
33
|
+
* this the model side hashes its source form (`'primary'`, `scope = ANY(...)`)
|
|
34
|
+
* while the live side hashes pg's deparse (`'primary'::text`,
|
|
35
|
+
* `(scope = ANY(...))`) — the identity below breaks on any schema with a text
|
|
36
|
+
* default, a check, or a partial index even when the diff is a no-op.
|
|
30
37
|
* - Deparse stability caveat, stated once: policy/check predicates hash as
|
|
31
38
|
* Postgres deparses them; a major-version deparser change can shift
|
|
32
39
|
* fingerprints. The version prefix plus the re-diff path keep that honest.
|
|
@@ -43,8 +50,12 @@ import type { SchemaSnapshot, TableSchema } from './schema-introspect.js';
|
|
|
43
50
|
import type { AuthzContract, TableContract } from './authz-contract.js';
|
|
44
51
|
import { compileTableSchema, compileEnums } from './schema-compile.js';
|
|
45
52
|
import { compileTableContract } from './authz-compile.js';
|
|
53
|
+
import { normalizeDefault, normalizeCheck } from './schema-diff.js';
|
|
46
54
|
|
|
47
|
-
|
|
55
|
+
// v2: expression fields (defaults, checks, partial-index WHERE) normalize through
|
|
56
|
+
// the diff's pg-deparse normalizers before hashing, so the model form and the live
|
|
57
|
+
// deparsed form agree — the identity `fingerprintModels === fingerprintLive` holds.
|
|
58
|
+
export const FINGERPRINT_VERSION = 2;
|
|
48
59
|
|
|
49
60
|
// ---------------------------------------------------------------------------
|
|
50
61
|
// Canonical form.
|
|
@@ -70,13 +81,17 @@ function canonicalTable(table: TableSchema): Record<string, unknown> {
|
|
|
70
81
|
return {
|
|
71
82
|
table: table.table,
|
|
72
83
|
columns: byKey(
|
|
73
|
-
|
|
84
|
+
// Defaults hash SEMANTICALLY, through the same normalizer the diff uses: the Model
|
|
85
|
+
// emits `'primary'`, pg deparses it to `'primary'::text` — the same state, one hash.
|
|
86
|
+
table.columns.map((c) => ({ name: c.name, type: c.type, notNull: c.notNull, default: normalizeDefault(c.default) })),
|
|
74
87
|
(c) => c.name,
|
|
75
88
|
),
|
|
76
89
|
primaryKey: table.primaryKey,
|
|
77
90
|
// Content, not names: a pg-default `_key` and a model-named unique are the same state.
|
|
78
91
|
uniques: byKey(table.uniques.map((u) => ({ columns: u.columns })), (u) => u.columns.join(',')),
|
|
79
|
-
|
|
92
|
+
// Predicates hash as the diff compares them — one layer of pg's wrapping parens dropped
|
|
93
|
+
// (`(scope = ANY (...))` == `scope = ANY (...)`), whitespace collapsed.
|
|
94
|
+
checks: byKey(table.checks.map((c) => ({ expr: normalizeCheck(c.expr) })), (c) => c.expr),
|
|
80
95
|
foreignKeys: byKey(
|
|
81
96
|
table.foreignKeys.map((f) => ({
|
|
82
97
|
columns: f.columns, refTable: f.refTable, refColumns: f.refColumns,
|
|
@@ -86,7 +101,8 @@ function canonicalTable(table: TableSchema): Record<string, unknown> {
|
|
|
86
101
|
(f) => stableStringify(f),
|
|
87
102
|
),
|
|
88
103
|
indexes: byKey(
|
|
89
|
-
|
|
104
|
+
// Partial-index WHERE is a predicate too — normalize it the same way as a check.
|
|
105
|
+
table.indexes.map((i) => ({ columns: i.columns, unique: i.unique, ...(i.where ? { where: normalizeCheck(i.where) } : {}) })),
|
|
90
106
|
(i) => stableStringify(i),
|
|
91
107
|
),
|
|
92
108
|
};
|