hazo_env 0.3.0 → 0.4.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.
- package/CHANGE_LOG.md +21 -0
- package/README.md +10 -6
- package/SETUP_CHECKLIST.md +6 -0
- package/dist/cli.js +18 -4
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +152 -0
- package/dist/migrate/db.d.ts +3 -0
- package/dist/migrate/db.d.ts.map +1 -1
- package/dist/migrate/db.js +6 -1
- package/dist/migrate/db.postgrest.d.ts +4 -0
- package/dist/migrate/db.postgrest.d.ts.map +1 -0
- package/dist/migrate/db.postgrest.js +93 -0
- package/dist/migrate/files.d.ts.map +1 -1
- package/dist/migrate/files.js +2 -4
- package/dist/migrate/run.d.ts.map +1 -1
- package/dist/migrate/run.js +50 -15
- package/dist/migrate/snapshot.d.ts.map +1 -1
- package/dist/migrate/snapshot.js +3 -0
- package/dist/migrate/verify.d.ts +1 -0
- package/dist/migrate/verify.d.ts.map +1 -1
- package/dist/migrate/verify.js +120 -30
- package/dist/resolve/files.d.ts +6 -0
- package/dist/resolve/files.d.ts.map +1 -1
- package/dist/resolve/files.js +23 -0
- package/dist/resolve/migrate.d.ts +3 -0
- package/dist/resolve/migrate.d.ts.map +1 -0
- package/dist/resolve/migrate.js +29 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +9 -9
package/CHANGE_LOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# hazo_env — Change Log
|
|
2
2
|
|
|
3
|
+
## 0.4.0 — 2026-06-25
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `verifyFiles()` L2–L5 — DB-backed file existence (L2), size check (L3), hash check L4 (`full|sample|none`; sample = 1% + top-10 by size + top-10 by changed_at), orphan detection L5. Previously all layers were stubs.
|
|
7
|
+
- `VerifyOptions.adapter` — pass a `HazoConnectAdapter` to enable DB-backed checks; without it verify falls back to sentinel-only (disk) and sets `report.skippedReason`.
|
|
8
|
+
- `VerifyReport` — new fields: `hashed?: number`, `skippedReason?: string`.
|
|
9
|
+
- `runMigration` — step 7 verify now passes `tgtAdapter`; emits detailed warning on `!ok` (counts + snapshotId) but continues (G2: warns-not-aborts).
|
|
10
|
+
- CLI `hazo-env verify` — new `--hash full|sample|none` flag (default `sample`); exits 1 on failure (CI-safe).
|
|
11
|
+
- `doctor()` — section 7a: `_migrations` parity check across all SQLite envs (`--probe --all`); section 7b: masking ruleset column validation against live schema.
|
|
12
|
+
- `DoctorOptions.probe` + `DoctorOptions.all` — enable live DB reachability probes + multi-env checks.
|
|
13
|
+
- `hazo_files` added as optional peer dependency (`^3.5.0`).
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- `DoctorReport.passed` replaces `DoctorReport.ok` (previously typo in docs).
|
|
17
|
+
- `DoctorCheck.status` is `'ok'|'warn'|'error'` (not `boolean`).
|
|
18
|
+
|
|
19
|
+
### Test-app
|
|
20
|
+
- Seed script creates 7 `hazo_files` fixture rows (3 clean, 1 size-mismatch, 1 hash-mismatch, 1 missing, 1 orphan) + physical files under `data/test/files/`.
|
|
21
|
+
- `/api/verify` route + `/verify` report page added.
|
|
22
|
+
- `'verify-fixtures'` autotest scenario added (5 cases).
|
|
23
|
+
|
|
3
24
|
## 0.3.0 — 2026-06-10
|
|
4
25
|
|
|
5
26
|
### Added
|
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ Canonical environment resolver for hazo apps. Typed env names, per-env DB/file/s
|
|
|
10
10
|
- **Secrets** — `getSecret()` resolves from `.env.local` only; placeholders in `hazo_env_config.ini` are substituted at runtime without storing secrets.
|
|
11
11
|
- **Migration engine** — `runMigration({ from, to })` copies a DB + files between envs. Validates → snapshots target → copies tables (paged, schema-checked) → copies files → verifies → writes hazo_audit entry. Prod target refused without an explicit confirm token.
|
|
12
12
|
- **PII masking** — masking rules declared per table/column in `hazo_env_masking.ini` (seed) and synced to `hazo_app_config` at runtime. Built-in transforms: `mask_email`, `fake_name`, `mask_phone`, `jitter_date`, `hash`, `tokenize`, `drop`, `nullify`. Custom transforms via `registerMask()`. Applied automatically when the migration target role is `test` or `staging`.
|
|
13
|
-
- **Doctor** — `doctor()` / `hazo-env doctor` validates pattern, DB reachability, required secrets (no values printed), data_root writability, and
|
|
13
|
+
- **Doctor** — `doctor()` / `hazo-env doctor` validates pattern, DB reachability, required secrets (no values printed), data_root writability, schema level, and (with `--probe --all`) migration parity across envs + masking ruleset column validation.
|
|
14
14
|
- **CLI** — `hazo-env current | doctor | snapshot | migrate | verify | restore | mask`.
|
|
15
15
|
|
|
16
16
|
## Installation
|
|
@@ -147,9 +147,11 @@ getSecret('POSTGREST_API_KEY', { required: true })
|
|
|
147
147
|
```ts
|
|
148
148
|
import { doctor } from 'hazo_env';
|
|
149
149
|
|
|
150
|
-
const report = await doctor();
|
|
151
|
-
// report.
|
|
152
|
-
// report.checks DoctorCheck[] — { label, ok,
|
|
150
|
+
const report = await doctor({ probe: true, all: true });
|
|
151
|
+
// report.passed boolean
|
|
152
|
+
// report.checks DoctorCheck[] — { label, status: 'ok'|'warn'|'error', detail? }
|
|
153
|
+
// Migration-readiness checks (--probe --all): _migrations parity across envs,
|
|
154
|
+
// masking ruleset column validation against live schema.
|
|
153
155
|
```
|
|
154
156
|
|
|
155
157
|
### Migration engine (server-only)
|
|
@@ -171,8 +173,10 @@ const result = await runMigration({
|
|
|
171
173
|
});
|
|
172
174
|
// result.ok, result.db, result.files, result.snapshotId, result.warnings, result.durationMs
|
|
173
175
|
|
|
174
|
-
// Stand-alone verification
|
|
175
|
-
await verifyFiles('staging', dataRoot, { hash: 'sample' });
|
|
176
|
+
// Stand-alone verification (with optional hazo_connect adapter for L2–L5 checks)
|
|
177
|
+
await verifyFiles('staging', dataRoot, { hash: 'sample', checkOrphans: true, adapter });
|
|
178
|
+
// report.ok, report.checked, report.hashed, report.missing[], report.sizeMismatch[],
|
|
179
|
+
// report.hashMismatch[], report.orphans[], report.skippedReason?
|
|
176
180
|
|
|
177
181
|
// Manual snapshot / restore
|
|
178
182
|
const snap = takeSnapshot(connectConfig); // returns { snapshotId }
|
package/SETUP_CHECKLIST.md
CHANGED
|
@@ -94,6 +94,12 @@ All checks should be green before going to production. The doctor validates:
|
|
|
94
94
|
- All required secrets are present in `.env.local` (values are never printed)
|
|
95
95
|
- `data_root` exists and is writable
|
|
96
96
|
|
|
97
|
+
Add `--probe --all` for migration-readiness checks:
|
|
98
|
+
```bash
|
|
99
|
+
npx hazo-env doctor --probe --all
|
|
100
|
+
```
|
|
101
|
+
This additionally checks: live DB reachability for all envs, `_migrations` table parity across envs, masking ruleset column validation against live schema.
|
|
102
|
+
|
|
97
103
|
## 8. (Next.js) Ensure `hazo_env` is in `transpilePackages`
|
|
98
104
|
|
|
99
105
|
```js
|
package/dist/cli.js
CHANGED
|
@@ -122,12 +122,22 @@ async function runMigrate() {
|
|
|
122
122
|
}
|
|
123
123
|
async function runVerify() {
|
|
124
124
|
const targetEnv = args[0] ?? getEnv();
|
|
125
|
-
|
|
125
|
+
// Parse --hash full|sample|none (default: sample) and --no-orphans
|
|
126
|
+
const hashIdx = args.indexOf('--hash');
|
|
127
|
+
const hashArg = hashIdx >= 0 ? args[hashIdx + 1] : 'sample';
|
|
128
|
+
const hash = (hashArg === 'full' || hashArg === 'none' || hashArg === 'sample') ? hashArg : 'sample';
|
|
129
|
+
const checkOrphans = !args.includes('--no-orphans');
|
|
130
|
+
console.log(`\n${pc.bold('hazo-env verify')} ${pc.dim(targetEnv)} ${pc.dim(`--hash ${hash}`)}\n`);
|
|
126
131
|
const { verifyFiles } = await import('./migrate/verify.js');
|
|
127
132
|
const { resolveFilesConfig } = await import('./resolve/files.js');
|
|
128
133
|
const dataRoot = resolveFilesConfig().local.basePath;
|
|
129
|
-
const report = await verifyFiles(targetEnv, dataRoot, { hash
|
|
130
|
-
|
|
134
|
+
const report = await verifyFiles(targetEnv, dataRoot, { hash, checkOrphans });
|
|
135
|
+
if (report.skippedReason) {
|
|
136
|
+
console.log(` ${pc.yellow('⚠')} ${report.skippedReason}`);
|
|
137
|
+
}
|
|
138
|
+
console.log(` Checked: ${report.checked} files${report.hashed != null ? `, hashed: ${report.hashed}` : ''}`);
|
|
139
|
+
if (report.orphans.length)
|
|
140
|
+
console.log(` ${pc.dim('ℹ')} Orphans (no DB row): ${report.orphans.join(', ')}`);
|
|
131
141
|
if (report.ok) {
|
|
132
142
|
console.log(` ${pc.green('✓')} All files verified\n`);
|
|
133
143
|
}
|
|
@@ -136,8 +146,10 @@ async function runVerify() {
|
|
|
136
146
|
console.log(` ${pc.red('✗')} Missing: ${report.missing.join(', ')}`);
|
|
137
147
|
if (report.sizeMismatch.length)
|
|
138
148
|
console.log(` ${pc.yellow('⚠')} Size mismatch: ${report.sizeMismatch.join(', ')}`);
|
|
149
|
+
if (report.hashMismatch.length)
|
|
150
|
+
console.log(` ${pc.yellow('⚠')} Hash mismatch: ${report.hashMismatch.join(', ')}`);
|
|
139
151
|
console.log('');
|
|
140
|
-
process.exit(1);
|
|
152
|
+
process.exit(1); // S4: standalone verify exits 1 on failure (CI-friendly)
|
|
141
153
|
}
|
|
142
154
|
}
|
|
143
155
|
async function runRestore() {
|
|
@@ -221,6 +233,8 @@ Usage:
|
|
|
221
233
|
--no-db / --no-files Skip DB or file copy
|
|
222
234
|
--allow-prod-target --confirm <token> Override prod-target safety
|
|
223
235
|
hazo-env verify <env> Verify files for an environment
|
|
236
|
+
--hash full|sample|none Hash-check all / sampled / none (default: sample)
|
|
237
|
+
--no-orphans Skip orphan detection
|
|
224
238
|
hazo-env restore <env> --snapshot <id> Restore a snapshot
|
|
225
239
|
hazo-env snapshot <env> Take a snapshot of an environment
|
|
226
240
|
hazo-env mask sync Load masking ruleset from INI into DB
|
package/dist/doctor.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,IAAI,GAAG,MAAM,GAAG,OAAO,CAAC;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,kEAAkE;IAClE,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAMD;;;GAGG;AACH,wBAAsB,MAAM,CAAC,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,YAAY,CAAC,
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,IAAI,GAAG,MAAM,GAAG,OAAO,CAAC;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,kEAAkE;IAClE,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAMD;;;GAGG;AACH,wBAAsB,MAAM,CAAC,IAAI,GAAE,aAAkB,GAAG,OAAO,CAAC,YAAY,CAAC,CA8R5E"}
|
package/dist/doctor.js
CHANGED
|
@@ -166,6 +166,158 @@ export async function doctor(opts = {}) {
|
|
|
166
166
|
});
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
|
+
// 7. Migration readiness checks
|
|
170
|
+
// 7a. _migrations parity (probe mode, --all): open each SQLite env and compare name-sets
|
|
171
|
+
if (opts.probe && opts.all) {
|
|
172
|
+
const migSets = new Map();
|
|
173
|
+
for (const env of envsToCheck) {
|
|
174
|
+
let cfg;
|
|
175
|
+
try {
|
|
176
|
+
cfg = resolveConnectConfig({ env, allowOtherEnv: true });
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (cfg.type !== 'sqlite' || !cfg.sqlite)
|
|
182
|
+
continue;
|
|
183
|
+
try {
|
|
184
|
+
const Database = require('better-sqlite3');
|
|
185
|
+
const db = Database(cfg.sqlite.database_path);
|
|
186
|
+
let names;
|
|
187
|
+
try {
|
|
188
|
+
names = db.prepare('SELECT name FROM _migrations').all().map((r) => r.name);
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
names = [];
|
|
192
|
+
}
|
|
193
|
+
db.close();
|
|
194
|
+
migSets.set(env, new Set(names));
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
// DB not openable — skip (probe check already reported error)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (migSets.size >= 2) {
|
|
201
|
+
const envList = [...migSets.keys()];
|
|
202
|
+
const referenceSet = migSets.get(envList[0]);
|
|
203
|
+
const referenceEnv = envList[0];
|
|
204
|
+
for (const env of envList.slice(1)) {
|
|
205
|
+
const s = migSets.get(env);
|
|
206
|
+
const onlyInRef = [...referenceSet].filter((n) => !s.has(n));
|
|
207
|
+
const onlyInEnv = [...s].filter((n) => !referenceSet.has(n));
|
|
208
|
+
if (onlyInRef.length === 0 && onlyInEnv.length === 0) {
|
|
209
|
+
checks.push({ label: `Migration parity (${referenceEnv} ↔ ${env})`, status: 'ok', detail: `${s.size} applied` });
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
const detail = [
|
|
213
|
+
onlyInRef.length ? `only in ${referenceEnv}: ${onlyInRef.join(', ')}` : '',
|
|
214
|
+
onlyInEnv.length ? `only in ${env}: ${onlyInEnv.join(', ')}` : '',
|
|
215
|
+
].filter(Boolean).join('; ');
|
|
216
|
+
checks.push({ label: `Migration parity (${referenceEnv} ↔ ${env})`, status: 'warn', detail });
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// 7b. Masking ruleset column validation: every (table, column) in the masking INI must
|
|
222
|
+
// exist in at least one SQLite env's schema. Skip silently for PostgREST envs.
|
|
223
|
+
const maskingConfigPath = path.resolve(process.cwd(), 'config', 'hazo_env_masking.ini');
|
|
224
|
+
if (fs.existsSync(maskingConfigPath)) {
|
|
225
|
+
try {
|
|
226
|
+
// Parse the masking INI directly — HazoConfig doesn't expose section enumeration.
|
|
227
|
+
// Format: each [section] is a table name; keys are column names.
|
|
228
|
+
const iniContent = fs.readFileSync(maskingConfigPath, 'utf-8');
|
|
229
|
+
const rules = [];
|
|
230
|
+
let currentTable = '';
|
|
231
|
+
for (const rawLine of iniContent.split('\n')) {
|
|
232
|
+
const line = rawLine.trim();
|
|
233
|
+
if (!line || line.startsWith(';') || line.startsWith('#'))
|
|
234
|
+
continue;
|
|
235
|
+
const sectionMatch = line.match(/^\[([^\]]+)\]$/);
|
|
236
|
+
if (sectionMatch) {
|
|
237
|
+
currentTable = sectionMatch[1].trim();
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
if (!currentTable)
|
|
241
|
+
continue;
|
|
242
|
+
const eqIdx = line.indexOf('=');
|
|
243
|
+
if (eqIdx < 0)
|
|
244
|
+
continue;
|
|
245
|
+
const column = line.slice(0, eqIdx).trim();
|
|
246
|
+
if (column)
|
|
247
|
+
rules.push({ table: currentTable, column });
|
|
248
|
+
}
|
|
249
|
+
if (rules.length > 0) {
|
|
250
|
+
// Collect SQLite schemas for checked envs
|
|
251
|
+
const schemas = new Map(); // "table.column" → present
|
|
252
|
+
for (const env of envsToCheck) {
|
|
253
|
+
let cfg;
|
|
254
|
+
try {
|
|
255
|
+
cfg = resolveConnectConfig({ env, allowOtherEnv: true });
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
if (cfg.type !== 'sqlite' || !cfg.sqlite)
|
|
261
|
+
continue;
|
|
262
|
+
try {
|
|
263
|
+
const Database = require('better-sqlite3');
|
|
264
|
+
const db = Database(cfg.sqlite.database_path);
|
|
265
|
+
try {
|
|
266
|
+
const tables = db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all().map((r) => r.name);
|
|
267
|
+
for (const tbl of tables) {
|
|
268
|
+
const cols = db.prepare(`PRAGMA table_info(${tbl})`).all();
|
|
269
|
+
for (const col of cols) {
|
|
270
|
+
schemas.set(`${tbl}.${col.name}`, new Set());
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
catch { /* DB empty or unreadable */ }
|
|
275
|
+
db.close();
|
|
276
|
+
}
|
|
277
|
+
catch { /* DB not openable */ }
|
|
278
|
+
}
|
|
279
|
+
if (schemas.size > 0) {
|
|
280
|
+
const badRules = [];
|
|
281
|
+
for (const { table, column } of rules) {
|
|
282
|
+
if (!schemas.has(`${table}.${column}`)) {
|
|
283
|
+
badRules.push(`${table}.${column}`);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
if (badRules.length === 0) {
|
|
287
|
+
checks.push({ label: 'Masking ruleset columns', status: 'ok', detail: `${rules.length} rule(s) validated` });
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
checks.push({
|
|
291
|
+
label: 'Masking ruleset columns',
|
|
292
|
+
status: 'warn',
|
|
293
|
+
detail: `Column(s) not found in schema: ${badRules.join(', ')} (PostgREST envs skipped)`,
|
|
294
|
+
});
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
else {
|
|
298
|
+
// No SQLite envs probed — can't validate columns; skip silently
|
|
299
|
+
const hasPostgrest = envsToCheck.some((env) => {
|
|
300
|
+
try {
|
|
301
|
+
return resolveConnectConfig({ env, allowOtherEnv: true }).type === 'postgrest';
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
if (hasPostgrest) {
|
|
308
|
+
checks.push({
|
|
309
|
+
label: 'Masking ruleset columns',
|
|
310
|
+
status: 'ok',
|
|
311
|
+
detail: 'PostgREST env — column validation skipped',
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
catch {
|
|
318
|
+
// Masking config parse failure — non-fatal, skip
|
|
319
|
+
}
|
|
320
|
+
}
|
|
169
321
|
const passed = checks.every((c) => c.status !== 'error');
|
|
170
322
|
return { env: targetEnv, checks, passed };
|
|
171
323
|
}
|
package/dist/migrate/db.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { HazoConnectAdapter } from 'hazo_connect';
|
|
2
2
|
export interface DbCopyOptions {
|
|
3
|
+
type?: 'sqlite' | 'postgrest';
|
|
3
4
|
tables?: '*' | string[];
|
|
5
|
+
preserve?: string[];
|
|
6
|
+
pkOverrides?: Record<string, string>;
|
|
4
7
|
scrubHook?: (tableName: string, row: Record<string, unknown>) => Record<string, unknown>;
|
|
5
8
|
onProgress?: (msg: string) => void;
|
|
6
9
|
}
|
package/dist/migrate/db.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/migrate/db.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,GAAG,GAAG,MAAM,EAAE,CAAC;IACxB,SAAS,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzF,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AA6BD,wBAAsB,MAAM,CAC1B,UAAU,EAAE,kBAAkB,EAC9B,UAAU,EAAE,kBAAkB,EAC9B,IAAI,GAAE,aAAkB,GACvB,OAAO,CAAC,YAAY,CAAC,
|
|
1
|
+
{"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../src/migrate/db.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,QAAQ,GAAG,WAAW,CAAC;IAC9B,MAAM,CAAC,EAAE,GAAG,GAAG,MAAM,EAAE,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,SAAS,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACzF,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AA6BD,wBAAsB,MAAM,CAC1B,UAAU,EAAE,kBAAkB,EAC9B,UAAU,EAAE,kBAAkB,EAC9B,IAAI,GAAE,aAAkB,GACvB,OAAO,CAAC,YAAY,CAAC,CAqHvB"}
|
package/dist/migrate/db.js
CHANGED
|
@@ -21,6 +21,11 @@ async function getMigrationNames(adapter) {
|
|
|
21
21
|
return rows.map((r) => r.name);
|
|
22
22
|
}
|
|
23
23
|
export async function copyDb(srcAdapter, tgtAdapter, opts = {}) {
|
|
24
|
+
// PostgREST path: dispatch to dedicated implementation
|
|
25
|
+
if (opts.type === 'postgrest') {
|
|
26
|
+
const { copyDbPostgrest } = await import('./db.postgrest.js');
|
|
27
|
+
return copyDbPostgrest(srcAdapter, tgtAdapter, opts);
|
|
28
|
+
}
|
|
24
29
|
const connect = await optional_import('hazo_connect/server');
|
|
25
30
|
if (!connect) {
|
|
26
31
|
throw new HazoError({
|
|
@@ -29,7 +34,7 @@ export async function copyDb(srcAdapter, tgtAdapter, opts = {}) {
|
|
|
29
34
|
message: 'hazo_connect is required for DB migration. Install hazo_connect to continue.',
|
|
30
35
|
});
|
|
31
36
|
}
|
|
32
|
-
// Schema parity check via _migrations
|
|
37
|
+
// Schema parity check via _migrations (SQLite only — PostgREST apps may not use _migrations)
|
|
33
38
|
const srcMigrations = await getMigrationNames(srcAdapter);
|
|
34
39
|
const tgtMigrations = await getMigrationNames(tgtAdapter);
|
|
35
40
|
if (srcMigrations === null || tgtMigrations === null) {
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { HazoConnectAdapter } from 'hazo_connect';
|
|
2
|
+
import type { DbCopyOptions, DbCopyResult } from './db.js';
|
|
3
|
+
export declare function copyDbPostgrest(srcAdapter: HazoConnectAdapter, tgtAdapter: HazoConnectAdapter, opts?: DbCopyOptions): Promise<DbCopyResult>;
|
|
4
|
+
//# sourceMappingURL=db.postgrest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db.postgrest.d.ts","sourceRoot":"","sources":["../../src/migrate/db.postgrest.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAM3D,wBAAsB,eAAe,CACnC,UAAU,EAAE,kBAAkB,EAC9B,UAAU,EAAE,kBAAkB,EAC9B,IAAI,GAAE,aAAkB,GACvB,OAAO,CAAC,YAAY,CAAC,CA6GvB"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// hazo_env/src/migrate/db.postgrest.ts — PostgREST-based DB copy (REST-only, no raw SQL)
|
|
2
|
+
import { HazoError, optional_import } from 'hazo_core';
|
|
3
|
+
import { isSecretColumn } from '../lib/secret_columns.js';
|
|
4
|
+
const PAGE_SIZE = 500;
|
|
5
|
+
export async function copyDbPostgrest(srcAdapter, tgtAdapter, opts = {}) {
|
|
6
|
+
const connect = await optional_import('hazo_connect/server');
|
|
7
|
+
if (!connect) {
|
|
8
|
+
throw new HazoError({
|
|
9
|
+
code: 'HAZO_ENV_MISSING_DEPENDENCY',
|
|
10
|
+
pkg: 'hazo_env',
|
|
11
|
+
message: 'hazo_connect is required for DB migration. Install hazo_connect to continue.',
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
// Table list is required for PostgREST (no auto-discovery — FK ordering unsafe without explicit list)
|
|
15
|
+
if (!opts.tables || opts.tables === '*') {
|
|
16
|
+
throw new HazoError({
|
|
17
|
+
code: 'HAZO_ENV_MISSING_TABLE_LIST',
|
|
18
|
+
pkg: 'hazo_env',
|
|
19
|
+
message: 'PostgREST migration requires an explicit table list. ' +
|
|
20
|
+
'Set [migrate] tables = ... in hazo_env_config.ini or pass --tables to the CLI.',
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
const orderedTables = opts.tables;
|
|
24
|
+
const preserveSet = new Set(opts.preserve ?? []);
|
|
25
|
+
const pkOverrides = opts.pkOverrides ?? {};
|
|
26
|
+
// Tables to actually process (exclude preserve list)
|
|
27
|
+
const tablesToProcess = orderedTables.filter((t) => !preserveSet.has(t));
|
|
28
|
+
// --- Clear pass: reverse dependency order ---
|
|
29
|
+
opts.onProgress?.('Clearing target tables (reverse order for FK safety)...');
|
|
30
|
+
for (const table of [...tablesToProcess].reverse()) {
|
|
31
|
+
const pk = pkOverrides[table] ?? 'id';
|
|
32
|
+
try {
|
|
33
|
+
// PostgREST match-all DELETE: DELETE /table?pk=not.is.null
|
|
34
|
+
await tgtAdapter.rawQuery(`/${table}?${pk}=not.is.null`, { method: 'DELETE' });
|
|
35
|
+
opts.onProgress?.(`Cleared: ${table}`);
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
39
|
+
opts.onProgress?.(`Warning: could not clear ${table}: ${msg}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
let totalRows = 0;
|
|
43
|
+
let totalScrubbed = 0;
|
|
44
|
+
// --- Copy pass: forward dependency order ---
|
|
45
|
+
for (const table of tablesToProcess) {
|
|
46
|
+
opts.onProgress?.(`Copying table: ${table}`);
|
|
47
|
+
const pk = pkOverrides[table] ?? 'id';
|
|
48
|
+
const srcService = connect.createCrudService(srcAdapter, table, { autoId: false });
|
|
49
|
+
const tgtService = connect.createCrudService(tgtAdapter, table, { autoId: false });
|
|
50
|
+
let offset = 0;
|
|
51
|
+
let pageRows = [];
|
|
52
|
+
do {
|
|
53
|
+
pageRows = await srcService.list((qb) =>
|
|
54
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
55
|
+
qb.order(pk, 'asc').limit(PAGE_SIZE).offset(offset));
|
|
56
|
+
if (pageRows.length === 0)
|
|
57
|
+
break;
|
|
58
|
+
const batch = [];
|
|
59
|
+
for (let row of pageRows) {
|
|
60
|
+
const cleaned = {};
|
|
61
|
+
let didScrub = false;
|
|
62
|
+
for (const [col, val] of Object.entries(row)) {
|
|
63
|
+
if (isSecretColumn(col)) {
|
|
64
|
+
didScrub = true;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
cleaned[col] = val;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
let processedRow = cleaned;
|
|
71
|
+
if (opts.scrubHook) {
|
|
72
|
+
const hooked = opts.scrubHook(table, cleaned);
|
|
73
|
+
if (hooked !== cleaned)
|
|
74
|
+
didScrub = true;
|
|
75
|
+
processedRow = hooked;
|
|
76
|
+
}
|
|
77
|
+
if (didScrub)
|
|
78
|
+
totalScrubbed++;
|
|
79
|
+
batch.push(processedRow);
|
|
80
|
+
}
|
|
81
|
+
if (batch.length > 0) {
|
|
82
|
+
await tgtService.insert(batch);
|
|
83
|
+
totalRows += batch.length;
|
|
84
|
+
opts.onProgress?.(` inserted ${batch.length} rows into ${table}`);
|
|
85
|
+
}
|
|
86
|
+
offset += PAGE_SIZE;
|
|
87
|
+
} while (pageRows.length === PAGE_SIZE);
|
|
88
|
+
}
|
|
89
|
+
// Note: PostgREST has no REST endpoint for NOTIFY. Schema reload only needed after DDL.
|
|
90
|
+
// If schema DDL changed since last reload, run: NOTIFY pgrst, 'reload schema' in Postgres.
|
|
91
|
+
opts.onProgress?.("DB copy complete. If schema DDL changed, run NOTIFY pgrst, 'reload schema' in Postgres.");
|
|
92
|
+
return { tables: tablesToProcess.length, rows: totalRows, scrubbed: totalScrubbed };
|
|
93
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../src/migrate/files.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;CACvB;
|
|
1
|
+
{"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../src/migrate/files.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;CACvB;AA0BD,wBAAsB,SAAS,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CA4ChF"}
|
package/dist/migrate/files.js
CHANGED
|
@@ -6,11 +6,9 @@
|
|
|
6
6
|
// Failures are non-fatal warnings — the file copy still completes.
|
|
7
7
|
import fs from 'node:fs';
|
|
8
8
|
import path from 'node:path';
|
|
9
|
-
import {
|
|
9
|
+
import { resolveFilesRoot } from '../resolve/files.js';
|
|
10
10
|
function resolveEnvFilesRoot(env) {
|
|
11
|
-
|
|
12
|
-
const base = resolveFilesConfig().local.basePath;
|
|
13
|
-
return path.join(base, env, 'files');
|
|
11
|
+
return resolveFilesRoot(env);
|
|
14
12
|
}
|
|
15
13
|
/**
|
|
16
14
|
* Attempt to scrub a PDF file in-place using hazo_pdf/mask_pdf.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/migrate/run.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/migrate/run.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAkC,MAAM,mBAAmB,CAAC;AA0D3G,wBAAsB,YAAY,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CAkLlF"}
|
package/dist/migrate/run.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
// See: src/migrate/files.ts scrubPdfFile() and hazo_pdf/src/server/mask.ts
|
|
7
7
|
import { HazoError, optional_import } from 'hazo_core';
|
|
8
8
|
import { resolveConnectConfig } from '../resolve/connect.js';
|
|
9
|
+
import { resolveMigrateConfig } from '../resolve/migrate.js';
|
|
9
10
|
import { resolveFilesConfig } from '../resolve/files.js';
|
|
10
11
|
import { resolveTransport } from './transport.js';
|
|
11
12
|
import { takeSnapshot } from './snapshot.js';
|
|
@@ -122,20 +123,43 @@ export async function runMigration(req) {
|
|
|
122
123
|
}
|
|
123
124
|
// Create adapters
|
|
124
125
|
function makeAdapter(dbConfig, readOnly = false) {
|
|
125
|
-
if (dbConfig.type
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
126
|
+
if (dbConfig.type === 'sqlite') {
|
|
127
|
+
if (!dbConfig.sqlite) {
|
|
128
|
+
throw new HazoError({
|
|
129
|
+
code: 'HAZO_ENV_NOT_IMPLEMENTED',
|
|
130
|
+
pkg: 'hazo_env',
|
|
131
|
+
message: 'SQLite config missing database_path.',
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
return connect.createHazoConnect({
|
|
135
|
+
type: 'sqlite',
|
|
136
|
+
sqlite: {
|
|
137
|
+
database_path: dbConfig.sqlite.database_path,
|
|
138
|
+
read_only: readOnly,
|
|
139
|
+
driver: getSqliteDriver(),
|
|
140
|
+
},
|
|
130
141
|
});
|
|
131
142
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
143
|
+
if (dbConfig.type === 'postgrest') {
|
|
144
|
+
if (!dbConfig.postgrest) {
|
|
145
|
+
throw new HazoError({
|
|
146
|
+
code: 'HAZO_ENV_NOT_IMPLEMENTED',
|
|
147
|
+
pkg: 'hazo_env',
|
|
148
|
+
message: 'PostgREST config missing base_url/api_key.',
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
return connect.createHazoConnect({
|
|
152
|
+
type: 'postgrest',
|
|
153
|
+
postgrest: {
|
|
154
|
+
base_url: dbConfig.postgrest.base_url,
|
|
155
|
+
api_key: dbConfig.postgrest.api_key,
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
throw new HazoError({
|
|
160
|
+
code: 'HAZO_ENV_NOT_IMPLEMENTED',
|
|
161
|
+
pkg: 'hazo_env',
|
|
162
|
+
message: `Migration is not implemented for DB type "${dbConfig.type}".`,
|
|
139
163
|
});
|
|
140
164
|
}
|
|
141
165
|
const srcAdapter = makeAdapter(fromDbConfig, true);
|
|
@@ -144,12 +168,16 @@ export async function runMigration(req) {
|
|
|
144
168
|
emitProgress(req, { phase: 'schema-check', message: 'Checking schema parity', percent: 15 });
|
|
145
169
|
let dbResult;
|
|
146
170
|
let filesResult;
|
|
171
|
+
const migrateConfig = resolveMigrateConfig();
|
|
147
172
|
// Step 5: DB copy
|
|
148
173
|
if (includeDb) {
|
|
149
174
|
emitProgress(req, { phase: 'db', message: 'Copying database tables', percent: 20 });
|
|
150
175
|
const scrubHook = await buildScrubHook(req.to, scrubMode, tgtAdapter);
|
|
151
176
|
const result = await copyDb(srcAdapter, tgtAdapter, {
|
|
152
|
-
|
|
177
|
+
type: fromDbConfig.type,
|
|
178
|
+
tables: req.tables ?? migrateConfig.tables,
|
|
179
|
+
preserve: migrateConfig.preserve,
|
|
180
|
+
pkOverrides: migrateConfig.pkOverrides,
|
|
153
181
|
scrubHook,
|
|
154
182
|
onProgress: (msg) => emitProgress(req, { phase: 'db', message: msg }),
|
|
155
183
|
});
|
|
@@ -169,9 +197,16 @@ export async function runMigration(req) {
|
|
|
169
197
|
// Step 7: Verify
|
|
170
198
|
emitProgress(req, { phase: 'verify', message: 'Running post-migration verification', percent: 80 });
|
|
171
199
|
const dataRoot = resolveFilesConfig().local.basePath;
|
|
172
|
-
const verifyReport = await verifyFiles(req.to, dataRoot, { hash: 'sample', checkOrphans: true });
|
|
200
|
+
const verifyReport = await verifyFiles(req.to, dataRoot, { hash: 'sample', checkOrphans: true, adapter: tgtAdapter });
|
|
173
201
|
if (!verifyReport.ok) {
|
|
174
|
-
|
|
202
|
+
const parts = [];
|
|
203
|
+
if (verifyReport.missing.length)
|
|
204
|
+
parts.push(`missing=${verifyReport.missing.length}`);
|
|
205
|
+
if (verifyReport.sizeMismatch.length)
|
|
206
|
+
parts.push(`sizeMismatch=${verifyReport.sizeMismatch.length}`);
|
|
207
|
+
if (verifyReport.hashMismatch.length)
|
|
208
|
+
parts.push(`hashMismatch=${verifyReport.hashMismatch.length}`);
|
|
209
|
+
warnings.push(`Verify found issues (${parts.join(', ')}) — migration continues; snapshotId=${snapshot.snapshotId}`);
|
|
175
210
|
}
|
|
176
211
|
// Step 8: Finalize — audit
|
|
177
212
|
emitProgress(req, { phase: 'finalize', message: 'Recording audit event', percent: 95 });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../../src/migrate/snapshot.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,WAAW,GAAG,cAAc,
|
|
1
|
+
{"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../../src/migrate/snapshot.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,WAAW,GAAG,cAAc,CAsBlE;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAuB/E"}
|
package/dist/migrate/snapshot.js
CHANGED
|
@@ -3,6 +3,9 @@ import fs from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { HazoError } from 'hazo_core';
|
|
5
5
|
export function takeSnapshot(toConfig) {
|
|
6
|
+
if (toConfig.type === 'postgrest') {
|
|
7
|
+
return { snapshotId: 'postgrest:no-snapshot', createdAt: new Date().toISOString() };
|
|
8
|
+
}
|
|
6
9
|
if (toConfig.type !== 'sqlite' || !toConfig.sqlite?.database_path) {
|
|
7
10
|
throw new HazoError({
|
|
8
11
|
code: 'HAZO_ENV_NOT_IMPLEMENTED',
|
package/dist/migrate/verify.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export interface VerifyOptions {
|
|
|
3
3
|
hash?: HashMode;
|
|
4
4
|
samplePct?: number;
|
|
5
5
|
checkOrphans?: boolean;
|
|
6
|
+
adapter?: import('hazo_connect').HazoConnectAdapter;
|
|
6
7
|
}
|
|
7
8
|
export declare function verifyFiles(env: HazoEnv, dataRoot: string, opts?: VerifyOptions): Promise<VerifyReport>;
|
|
8
9
|
//# sourceMappingURL=verify.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../src/migrate/verify.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../src/migrate/verify.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAEzE,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,OAAO,CAAC,EAAE,OAAO,cAAc,EAAE,kBAAkB,CAAC;CACrD;AAqBD,wBAAsB,WAAW,CAC/B,GAAG,EAAE,OAAO,EACZ,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,aAAkB,GACvB,OAAO,CAAC,YAAY,CAAC,CA8IvB"}
|
package/dist/migrate/verify.js
CHANGED
|
@@ -1,51 +1,141 @@
|
|
|
1
1
|
// hazo_env/src/migrate/verify.ts — post-migration file verification
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { optional_import } from 'hazo_core';
|
|
4
5
|
function resolveEnvFilesRoot(env, dataRoot) {
|
|
5
6
|
return path.join(dataRoot, env, 'files');
|
|
6
7
|
}
|
|
8
|
+
function walkDir(dir) {
|
|
9
|
+
if (!fs.existsSync(dir))
|
|
10
|
+
return [];
|
|
11
|
+
return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
|
12
|
+
const absPath = path.join(dir, entry.name);
|
|
13
|
+
if (entry.isDirectory())
|
|
14
|
+
return walkDir(absPath);
|
|
15
|
+
// Return relative to dir's parent so caller can strip filesRoot prefix
|
|
16
|
+
return [absPath];
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function walkRelative(filesRoot) {
|
|
20
|
+
const abs = walkDir(filesRoot);
|
|
21
|
+
return abs.map((p) => p.slice(filesRoot.length + 1)); // strip filesRoot + sep
|
|
22
|
+
}
|
|
7
23
|
export async function verifyFiles(env, dataRoot, opts = {}) {
|
|
8
|
-
const { checkOrphans = true } = opts;
|
|
24
|
+
const { hash = 'sample', checkOrphans = true, adapter } = opts;
|
|
9
25
|
const filesRoot = resolveEnvFilesRoot(env, dataRoot);
|
|
10
26
|
const missing = [];
|
|
11
27
|
const sizeMismatch = [];
|
|
12
28
|
const hashMismatch = [];
|
|
13
29
|
const orphans = [];
|
|
14
30
|
let checked = 0;
|
|
15
|
-
let
|
|
16
|
-
// Layer 1:
|
|
17
|
-
|
|
18
|
-
|
|
31
|
+
let hashed;
|
|
32
|
+
// Layer 1: sentinel — filesRoot must exist and be readable
|
|
33
|
+
const sentinelOk = fs.existsSync(filesRoot);
|
|
34
|
+
if (!sentinelOk) {
|
|
19
35
|
return { ok: false, checked: 0, missing: [], sizeMismatch: [], hashMismatch: [], orphans: [], sentinelOk: false };
|
|
20
36
|
}
|
|
21
|
-
//
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
37
|
+
// S3: Fallback detection — need both hazo_files module and an adapter
|
|
38
|
+
const hazoFiles = await optional_import('hazo_files');
|
|
39
|
+
if (!hazoFiles || !adapter) {
|
|
40
|
+
// Disk-only fallback
|
|
41
|
+
const diskFiles = walkRelative(filesRoot);
|
|
42
|
+
checked = diskFiles.length;
|
|
43
|
+
const skippedReason = 'no hazo_files tracking — size/hash/orphan skipped';
|
|
44
|
+
const ok = sentinelOk && missing.length === 0;
|
|
45
|
+
return { ok, checked, missing, sizeMismatch, hashMismatch, orphans, sentinelOk, skippedReason };
|
|
46
|
+
}
|
|
47
|
+
// Main path: adapter + hazo_files available
|
|
48
|
+
const connectMod = await optional_import('hazo_connect/server');
|
|
49
|
+
let rows = [];
|
|
50
|
+
let skippedReason;
|
|
51
|
+
try {
|
|
52
|
+
if (!connectMod)
|
|
53
|
+
throw new Error('hazo_connect not available');
|
|
54
|
+
const service = connectMod.createCrudService(adapter, 'hazo_files');
|
|
55
|
+
const all = await service.list();
|
|
56
|
+
rows = all.filter((r) => !r.storage_type || r.storage_type === 'local');
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// Table not found or connect unavailable — disk-only fallback
|
|
60
|
+
const diskFiles = walkRelative(filesRoot);
|
|
61
|
+
checked = diskFiles.length;
|
|
62
|
+
skippedReason = 'hazo_files table not found — disk-only fallback';
|
|
63
|
+
const ok = sentinelOk && missing.length === 0;
|
|
64
|
+
return { ok, checked, missing, sizeMismatch, hashMismatch, orphans, sentinelOk, skippedReason };
|
|
65
|
+
}
|
|
66
|
+
// Normalize DB paths: strip leading slash if present
|
|
67
|
+
const normalizeDbPath = (p) => p.startsWith('/') ? p.slice(1) : p;
|
|
68
|
+
const dbPathSet = new Set(rows.map((r) => normalizeDbPath(r.file_path)));
|
|
69
|
+
// Track present rows for hash/size checks
|
|
70
|
+
const presentRows = [];
|
|
71
|
+
// L2 Existence
|
|
72
|
+
for (const row of rows) {
|
|
73
|
+
const relPath = normalizeDbPath(row.file_path);
|
|
35
74
|
const absPath = path.join(filesRoot, relPath);
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
75
|
+
if (!fs.existsSync(absPath)) {
|
|
76
|
+
missing.push(row.file_path);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
presentRows.push({ ...row, file_path: relPath });
|
|
39
80
|
}
|
|
40
|
-
|
|
41
|
-
|
|
81
|
+
}
|
|
82
|
+
// L3 Size
|
|
83
|
+
for (const row of presentRows) {
|
|
84
|
+
const absPath = path.join(filesRoot, row.file_path);
|
|
85
|
+
const stat = fs.statSync(absPath);
|
|
86
|
+
if (row.file_size != null && stat.size !== row.file_size) {
|
|
87
|
+
sizeMismatch.push(row.file_path);
|
|
88
|
+
}
|
|
89
|
+
checked++;
|
|
90
|
+
}
|
|
91
|
+
// L4 Hash
|
|
92
|
+
if (hash !== 'none') {
|
|
93
|
+
let toHash;
|
|
94
|
+
if (hash === 'full') {
|
|
95
|
+
toHash = presentRows;
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
// sample: 1% random (min 1) + top 10 by file_size DESC + top 10 by file_changed_at DESC
|
|
99
|
+
const n = Math.max(1, Math.floor(presentRows.length * 0.01));
|
|
100
|
+
const shuffled = [...presentRows].sort(() => Math.random() - 0.5).slice(0, n);
|
|
101
|
+
const bySize = [...presentRows]
|
|
102
|
+
.filter((r) => r.file_size != null)
|
|
103
|
+
.sort((a, b) => (b.file_size ?? 0) - (a.file_size ?? 0))
|
|
104
|
+
.slice(0, 10);
|
|
105
|
+
const byChanged = [...presentRows]
|
|
106
|
+
.filter((r) => r.file_changed_at != null)
|
|
107
|
+
.sort((a, b) => (b.file_changed_at ?? '') < (a.file_changed_at ?? '') ? -1 : 1)
|
|
108
|
+
.slice(0, 10);
|
|
109
|
+
// Deduplicate by file_path
|
|
110
|
+
const seen = new Set();
|
|
111
|
+
toHash = [];
|
|
112
|
+
for (const r of [...shuffled, ...bySize, ...byChanged]) {
|
|
113
|
+
if (!seen.has(r.file_path)) {
|
|
114
|
+
seen.add(r.file_path);
|
|
115
|
+
toHash.push(r);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
hashed = 0;
|
|
120
|
+
for (const row of toHash) {
|
|
121
|
+
const absPath = path.join(filesRoot, row.file_path);
|
|
122
|
+
const buf = fs.readFileSync(absPath);
|
|
123
|
+
const computed = hazoFiles.computeFileHashSync(buf);
|
|
124
|
+
if (row.file_hash && computed !== row.file_hash) {
|
|
125
|
+
hashMismatch.push(row.file_path);
|
|
126
|
+
}
|
|
127
|
+
hashed++;
|
|
42
128
|
}
|
|
43
129
|
}
|
|
44
|
-
//
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
130
|
+
// L5 Orphans
|
|
131
|
+
if (checkOrphans) {
|
|
132
|
+
const diskFiles = walkRelative(filesRoot);
|
|
133
|
+
for (const diskRel of diskFiles) {
|
|
134
|
+
if (!dbPathSet.has(diskRel)) {
|
|
135
|
+
orphans.push(diskRel);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
48
138
|
}
|
|
49
|
-
const ok = missing.length === 0 && sizeMismatch.length === 0 && hashMismatch.length === 0;
|
|
50
|
-
return { ok, checked, missing, sizeMismatch, hashMismatch, orphans, sentinelOk };
|
|
139
|
+
const ok = sentinelOk && missing.length === 0 && sizeMismatch.length === 0 && hashMismatch.length === 0;
|
|
140
|
+
return { ok, checked, missing, sizeMismatch, hashMismatch, orphans, sentinelOk, hashed, skippedReason };
|
|
51
141
|
}
|
package/dist/resolve/files.d.ts
CHANGED
|
@@ -5,4 +5,10 @@ import type { FilesEnvConfig } from '../types/index.js';
|
|
|
5
5
|
* Falls back to app_data if not configured.
|
|
6
6
|
*/
|
|
7
7
|
export declare function resolveFilesConfig(appConfigPkg?: string): FilesEnvConfig;
|
|
8
|
+
/**
|
|
9
|
+
* Resolve the file storage root path for a specific environment.
|
|
10
|
+
* Checks [files.<env>] root in hazo_env_config.ini first;
|
|
11
|
+
* falls back to <dataRoot>/<env>/files convention if absent.
|
|
12
|
+
*/
|
|
13
|
+
export declare function resolveFilesRoot(env: string): string;
|
|
8
14
|
//# sourceMappingURL=files.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../src/resolve/files.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAgBxD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,cAAc,CAoCxE"}
|
|
1
|
+
{"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../src/resolve/files.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAgBxD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,cAAc,CAoCxE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAiBpD"}
|
package/dist/resolve/files.js
CHANGED
|
@@ -51,3 +51,26 @@ export function resolveFilesConfig(appConfigPkg) {
|
|
|
51
51
|
}
|
|
52
52
|
return { provider: 'local', local };
|
|
53
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Resolve the file storage root path for a specific environment.
|
|
56
|
+
* Checks [files.<env>] root in hazo_env_config.ini first;
|
|
57
|
+
* falls back to <dataRoot>/<env>/files convention if absent.
|
|
58
|
+
*/
|
|
59
|
+
export function resolveFilesRoot(env) {
|
|
60
|
+
const envConfig = tryLoadConfig('hazo_env');
|
|
61
|
+
// Check explicit per-env root: [files.<env>] root = /some/path
|
|
62
|
+
const filesSection = envConfig?.getSection(`files.${env}`);
|
|
63
|
+
const explicitRoot = filesSection?.['root'];
|
|
64
|
+
if (explicitRoot) {
|
|
65
|
+
return path.isAbsolute(explicitRoot)
|
|
66
|
+
? explicitRoot
|
|
67
|
+
: path.resolve(process.cwd(), explicitRoot);
|
|
68
|
+
}
|
|
69
|
+
// Fall back to <dataRoot>/<env>/files convention
|
|
70
|
+
const dataSection = envConfig?.getSection('data');
|
|
71
|
+
const dataRoot = dataSection?.['root'] ?? DEFAULT_DATA_ROOT;
|
|
72
|
+
const resolvedDataRoot = path.isAbsolute(dataRoot)
|
|
73
|
+
? dataRoot
|
|
74
|
+
: path.resolve(process.cwd(), dataRoot);
|
|
75
|
+
return path.join(resolvedDataRoot, env, 'files');
|
|
76
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../src/resolve/migrate.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,wBAAgB,oBAAoB,IAAI,aAAa,CA2BpD"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// hazo_env/src/resolve/migrate.ts — read [migrate] section from hazo_env_config.ini
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { HazoConfig } from 'hazo_config/server';
|
|
4
|
+
export function resolveMigrateConfig() {
|
|
5
|
+
try {
|
|
6
|
+
const filePath = path.resolve(process.cwd(), 'config', 'hazo_env_config.ini');
|
|
7
|
+
const config = new HazoConfig({ filePath });
|
|
8
|
+
const section = config.getSection('migrate');
|
|
9
|
+
if (!section)
|
|
10
|
+
return {};
|
|
11
|
+
const tables = section['tables']
|
|
12
|
+
? section['tables'].split(',').map((t) => t.trim()).filter(Boolean)
|
|
13
|
+
: undefined;
|
|
14
|
+
const preserve = section['preserve']
|
|
15
|
+
? section['preserve'].split(',').map((t) => t.trim()).filter(Boolean)
|
|
16
|
+
: undefined;
|
|
17
|
+
// Parse per-table pk overrides: keys like "pk.some_table = custom_col"
|
|
18
|
+
const pkOverrides = {};
|
|
19
|
+
for (const [k, v] of Object.entries(section)) {
|
|
20
|
+
if (k.startsWith('pk.') && typeof v === 'string') {
|
|
21
|
+
pkOverrides[k.slice(3)] = v.trim();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return { tables, preserve, ...(Object.keys(pkOverrides).length ? { pkOverrides } : {}) };
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return {};
|
|
28
|
+
}
|
|
29
|
+
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -93,6 +93,14 @@ export interface VerifyReport {
|
|
|
93
93
|
hashMismatch: string[];
|
|
94
94
|
orphans: string[];
|
|
95
95
|
sentinelOk: boolean;
|
|
96
|
+
hashed?: number;
|
|
97
|
+
skippedReason?: string;
|
|
98
|
+
}
|
|
99
|
+
/** Parsed [migrate] section from hazo_env_config.ini */
|
|
100
|
+
export interface MigrateConfig {
|
|
101
|
+
tables?: string[];
|
|
102
|
+
preserve?: string[];
|
|
103
|
+
pkOverrides?: Record<string, string>;
|
|
96
104
|
}
|
|
97
105
|
export type MaskTransform = (value: unknown, key: string, maskKey: string) => unknown;
|
|
98
106
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hazo_env",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Canonical environment resolver — typed env names, per-env DB/file/secret config, doctor and CLI for hazo apps",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -38,10 +38,10 @@
|
|
|
38
38
|
"picocolors": "^1.1.1"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"hazo_core": "^1.
|
|
42
|
-
"hazo_config": "^2.1
|
|
43
|
-
"hazo_connect": "^3.
|
|
44
|
-
"hazo_files": "^3.
|
|
41
|
+
"hazo_core": "^1.2.0",
|
|
42
|
+
"hazo_config": "^2.4.1",
|
|
43
|
+
"hazo_connect": "^3.9.0",
|
|
44
|
+
"hazo_files": "^3.1.1",
|
|
45
45
|
"hazo_secure": "^1.2.0",
|
|
46
46
|
"hazo_audit": "^2.1.0",
|
|
47
47
|
"hazo_pdf": "^2.0.0",
|
|
@@ -84,10 +84,10 @@
|
|
|
84
84
|
"@types/node": "^22.10.0",
|
|
85
85
|
"@types/react": "^19.0.0",
|
|
86
86
|
"@types/react-dom": "^19.0.0",
|
|
87
|
-
"hazo_core": "^1.1
|
|
88
|
-
"hazo_config": "^2.1
|
|
89
|
-
"hazo_connect": "^3.
|
|
90
|
-
"hazo_files": "^3.
|
|
87
|
+
"hazo_core": "^1.2.1",
|
|
88
|
+
"hazo_config": "^2.4.1",
|
|
89
|
+
"hazo_connect": "^3.9.0",
|
|
90
|
+
"hazo_files": "^3.1.1",
|
|
91
91
|
"next": "^16.0.10",
|
|
92
92
|
"react": "^19.0.0",
|
|
93
93
|
"react-dom": "^19.0.0",
|