hazo_env 0.4.0 → 0.5.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 CHANGED
@@ -1,5 +1,20 @@
1
1
  # hazo_env — Change Log
2
2
 
3
+ ## 0.5.0 — 2026-06-25
4
+
5
+ ### Added
6
+ - `clearEnv()` — delete all non-preserved tables via PostgREST (reverse FK order) + optional file-dir wipe. Exported from `src/index.ts`. CLI: `hazo-env clear <env>` (prod-target guard: `--allow-prod-target --confirm=<token>`).
7
+ - `ClearRequest` / `ClearResult` types in `src/types/index.ts`; `'clear'` added to `MigrationProgress.phase` union.
8
+ - rsync-over-SSH file transport — `copyFiles()` auto-dispatches to rsync when `[transport.ssh.<fromEnv>]` is configured. `copyFilesRsync()` in `src/migrate/files.ts` spawns `rsync -az --partial --info=progress2`, parses `to-chk=N/TOTAL` for live percent. `onProgress` signature extended with optional `percent` arg.
9
+ - `resolveSshConfig(env)` in `src/resolve/ssh.ts` — reads `[transport.ssh.<env>]` section (host/user/key/path) with `${VAR}` expansion.
10
+ - `resolveTransport()` return type widened to `'local' | 'api' | 'rsync'`; `'ssh'` input now returns `'rsync'` instead of throwing.
11
+ - Per-table DB progress — `DbCopyOptions.onTableProgress` callback fires once per table during PostgREST copy pass; `run.ts` maps it to the 20→70% band with a `"table N/M: <name>"` message.
12
+ - `[transport.ssh.<env>]` section added to `config/hazo_env_config.ini.sample`.
13
+
14
+ ### Test-app
15
+ - `POST /api/clear` route added (mirrors `/api/migrate`).
16
+ - `'clear-env'` autotest scenario added (dry-run guard: verifies prod-target refuses without confirm).
17
+
3
18
  ## 0.4.0 — 2026-06-25
4
19
 
5
20
  ### Added
@@ -62,3 +62,21 @@ location = remote
62
62
 
63
63
  [host.prod]
64
64
  location = remote
65
+
66
+ ; ─── SSH transport for cross-host file rsync ─────────────────────────────────
67
+ ; When [transport.ssh.<env>] is present, copyFiles will use rsync-over-SSH
68
+ ; to pull files from that environment's file server instead of local copy.
69
+ ; This enables prod→dev migration when both envs run on separate hosts.
70
+
71
+ [transport.ssh.prod]
72
+ ; SSH connection to the prod file server (used when pulling prod→dev).
73
+ ; The worker runs rsync FROM prod TO dev — prod stays read-only.
74
+ host = ${SSH_HOST_PROD}
75
+ user = ${SSH_USER_PROD}
76
+ key = ${SSH_KEY_PATH_PROD}
77
+ path = /home/pubs/kinstripe/var/uploads
78
+ ; Optional: pin the server's host key (output of `ssh-keyscan <host>` minus the
79
+ ; leading hostname, e.g. "ssh-ed25519 AAAA..."). When set, host-key checking is
80
+ ; strict and pinned — no trust-on-first-use. When omitted, the first connection
81
+ ; is accepted (accept-new) and persisted to config/hazo_env_known_hosts.
82
+ ; host_key = ${SSH_HOST_KEY_PROD}
package/dist/cli.js CHANGED
@@ -167,6 +167,39 @@ async function runRestore() {
167
167
  restoreSnapshot(toConfig, snapshotId);
168
168
  console.log(` ${pc.green('✓')} Restored\n`);
169
169
  }
170
+ async function runClear() {
171
+ const targetEnv = args[0] ?? getEnv();
172
+ const allowProd = args.includes('--allow-prod-target');
173
+ const confirm = args.find((a) => a.startsWith('--confirm='))?.split('=')[1];
174
+ console.log(`\n${pc.bold('hazo-env clear')} ${pc.dim(targetEnv)}\n`);
175
+ const { clearEnv } = await import('./migrate/clear.js');
176
+ try {
177
+ let lastMsg = '';
178
+ const result = await clearEnv({
179
+ env: targetEnv,
180
+ allowProdTarget: allowProd,
181
+ confirmToken: confirm,
182
+ onProgress: (p) => {
183
+ if (p.message !== lastMsg) {
184
+ const pctStr = p.percent != null ? ` [${p.percent}%]` : '';
185
+ console.log(` ${pc.dim('·')} ${p.message}${pctStr}`);
186
+ lastMsg = p.message;
187
+ }
188
+ },
189
+ });
190
+ if (result.ok) {
191
+ console.log(` ${pc.green('✓')} Cleared ${result.tablesCleared} tables${result.filesCleared ? ' + files' : ''} in ${result.durationMs}ms\n`);
192
+ if (result.warnings.length)
193
+ result.warnings.forEach((w) => console.log(` ${pc.yellow('⚠')} ${w}`));
194
+ }
195
+ }
196
+ catch (e) {
197
+ const msg = e instanceof Error ? e.message : String(e);
198
+ console.error(`\n ${pc.red('✗')} ${msg}\n`);
199
+ process.exit(1);
200
+ }
201
+ console.log('');
202
+ }
170
203
  async function runMask() {
171
204
  const subCmd = args[0];
172
205
  if (subCmd === 'sync') {
@@ -239,6 +272,8 @@ Usage:
239
272
  hazo-env snapshot <env> Take a snapshot of an environment
240
273
  hazo-env mask sync Load masking ruleset from INI into DB
241
274
  hazo-env mask list List registered mask transforms
275
+ hazo-env clear <env> Wipe DB tables + files for an environment
276
+ --allow-prod-target --confirm=<token> Override prod-target safety
242
277
  `);
243
278
  }
244
279
  (async () => {
@@ -264,6 +299,9 @@ Usage:
264
299
  else if (command === 'mask') {
265
300
  await runMask();
266
301
  }
302
+ else if (command === 'clear') {
303
+ await runClear();
304
+ }
267
305
  else {
268
306
  printHelp();
269
307
  }
package/dist/index.d.ts CHANGED
@@ -6,6 +6,7 @@ export * from './resolve/connect.js';
6
6
  export * from './resolve/files.js';
7
7
  export * from './doctor.js';
8
8
  export { runMigration } from './migrate/run.js';
9
+ export { clearEnv } from './migrate/clear.js';
9
10
  export { verifyFiles } from './migrate/verify.js';
10
11
  export { takeSnapshot, restoreSnapshot } from './migrate/snapshot.js';
11
12
  export { writeMigrationProgress, readMigrationProgress, clearMigrationProgress } from './migrate/progress.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAE7E,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAEvG,cAAc,kBAAkB,CAAC;AAEjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AAEnC,cAAc,aAAa,CAAC;AAE5B,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAE9G,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACnF,YAAY,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,cAAc,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAE7E,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAEvG,cAAc,kBAAkB,CAAC;AAEjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AAEnC,cAAc,aAAa,CAAC;AAE5B,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACtE,OAAO,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAE9G,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACnF,YAAY,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAClD,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEtD,cAAc,gBAAgB,CAAC"}
package/dist/index.js CHANGED
@@ -13,6 +13,7 @@ export * from './resolve/files.js';
13
13
  export * from './doctor.js';
14
14
  // Migration engine (Phase 2)
15
15
  export { runMigration } from './migrate/run.js';
16
+ export { clearEnv } from './migrate/clear.js';
16
17
  export { verifyFiles } from './migrate/verify.js';
17
18
  export { takeSnapshot, restoreSnapshot } from './migrate/snapshot.js';
18
19
  export { writeMigrationProgress, readMigrationProgress, clearMigrationProgress } from './migrate/progress.js';
@@ -0,0 +1,3 @@
1
+ import type { ClearRequest, ClearResult } from '../types/index.js';
2
+ export declare function clearEnv(req: ClearRequest): Promise<ClearResult>;
3
+ //# sourceMappingURL=clear.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clear.d.ts","sourceRoot":"","sources":["../../src/migrate/clear.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAqB,MAAM,mBAAmB,CAAC;AAYtF,wBAAsB,QAAQ,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAoHtE"}
@@ -0,0 +1,117 @@
1
+ // hazo_env/src/migrate/clear.ts — clearEnv: wipe DB tables + files for a target environment
2
+ import { HazoError, optional_import } from 'hazo_core';
3
+ import { getEnvRole } from '../env.server.js';
4
+ import { resolveMigrateConfig } from '../resolve/migrate.js';
5
+ import { resolveConnectConfig } from '../resolve/connect.js';
6
+ import { resolveFilesRoot } from '../resolve/files.js';
7
+ import { writeMigrationProgress } from './progress.js';
8
+ import fs from 'node:fs';
9
+ function emitProgress(req, p) {
10
+ req.onProgress?.(p);
11
+ if (req.jobId && req.progressDir) {
12
+ writeMigrationProgress(req.progressDir, req.jobId, p);
13
+ }
14
+ }
15
+ export async function clearEnv(req) {
16
+ const startMs = Date.now();
17
+ const warnings = [];
18
+ // Guard: refuse production target unless explicitly unlocked with a confirm token
19
+ const role = getEnvRole(req.env);
20
+ if (role === 'production' && !req.allowProdTarget) {
21
+ throw new HazoError({
22
+ code: 'HAZO_ENV_PROD_TARGET_REFUSED',
23
+ pkg: 'hazo_env',
24
+ message: `Refusing to clear production environment "${req.env}". Pass allowProdTarget:true and a confirmToken to override.`,
25
+ });
26
+ }
27
+ if (role === 'production' && req.allowProdTarget && !req.confirmToken) {
28
+ throw new HazoError({
29
+ code: 'HAZO_ENV_PROD_TARGET_REFUSED',
30
+ pkg: 'hazo_env',
31
+ message: 'Production target requires a confirmToken for safety.',
32
+ });
33
+ }
34
+ emitProgress(req, { phase: 'validate', message: 'Validating clear request', percent: 0 });
35
+ const migrateConfig = resolveMigrateConfig();
36
+ const { tables, preserve = [], pkOverrides = {} } = migrateConfig;
37
+ if (!tables || tables.length === 0) {
38
+ throw new HazoError({
39
+ code: 'HAZO_ENV_MISSING_TABLE_LIST',
40
+ pkg: 'hazo_env',
41
+ message: 'clearEnv requires an explicit table list. ' +
42
+ 'Set [migrate] tables = ... in hazo_env_config.ini.',
43
+ });
44
+ }
45
+ const tablesToClear = [...tables.filter((t) => !preserve.includes(t))].reverse();
46
+ const dbConfig = resolveConnectConfig({ env: req.env, allowOtherEnv: true });
47
+ if (dbConfig.type !== 'postgrest') {
48
+ throw new HazoError({
49
+ code: 'HAZO_ENV_NOT_IMPLEMENTED',
50
+ pkg: 'hazo_env',
51
+ message: `clearEnv only supports PostgREST connections (got "${dbConfig.type}").`,
52
+ });
53
+ }
54
+ if (!dbConfig.postgrest) {
55
+ throw new HazoError({
56
+ code: 'HAZO_ENV_NOT_IMPLEMENTED',
57
+ pkg: 'hazo_env',
58
+ message: 'PostgREST config missing base_url/api_key.',
59
+ });
60
+ }
61
+ const connect = await optional_import('hazo_connect/server');
62
+ if (!connect) {
63
+ throw new HazoError({
64
+ code: 'HAZO_ENV_MISSING_DEPENDENCY',
65
+ pkg: 'hazo_env',
66
+ message: 'hazo_connect is required for clearEnv. Install hazo_connect to continue.',
67
+ });
68
+ }
69
+ const tgtAdapter = connect.createHazoConnect({
70
+ type: 'postgrest',
71
+ postgrest: {
72
+ base_url: dbConfig.postgrest.base_url,
73
+ api_key: dbConfig.postgrest.api_key,
74
+ },
75
+ });
76
+ // DB clear (only if not explicitly disabled)
77
+ let tablesCleared = 0;
78
+ if (req.include?.db !== false) {
79
+ for (let idx = 0; idx < tablesToClear.length; idx++) {
80
+ const table = tablesToClear[idx];
81
+ const percent = Math.round(((idx + 1) / tablesToClear.length) * 90);
82
+ emitProgress(req, {
83
+ phase: 'clear',
84
+ message: `Clearing table ${idx + 1}/${tablesToClear.length}: ${table}`,
85
+ percent,
86
+ });
87
+ const pk = pkOverrides[table] ?? 'id';
88
+ try {
89
+ await tgtAdapter.rawQuery(`/${table}?${pk}=not.is.null`, { method: 'DELETE' });
90
+ tablesCleared++;
91
+ }
92
+ catch (err) {
93
+ const msg = err instanceof Error ? err.message : String(err);
94
+ warnings.push(`Warning: could not clear ${table}: ${msg}`);
95
+ }
96
+ }
97
+ }
98
+ // File clear
99
+ let filesCleared = false;
100
+ if (req.include?.files !== false) {
101
+ emitProgress(req, { phase: 'clear', message: 'Clearing files...', percent: 92 });
102
+ const filesRoot = resolveFilesRoot(req.env);
103
+ if (fs.existsSync(filesRoot)) {
104
+ fs.rmSync(filesRoot, { recursive: true, force: true });
105
+ }
106
+ fs.mkdirSync(filesRoot, { recursive: true });
107
+ filesCleared = true;
108
+ }
109
+ emitProgress(req, { phase: 'done', message: 'Clear complete', percent: 100 });
110
+ return {
111
+ ok: true,
112
+ tablesCleared,
113
+ filesCleared,
114
+ durationMs: Date.now() - startMs,
115
+ warnings,
116
+ };
117
+ }
@@ -6,6 +6,7 @@ export interface DbCopyOptions {
6
6
  pkOverrides?: Record<string, string>;
7
7
  scrubHook?: (tableName: string, row: Record<string, unknown>) => Record<string, unknown>;
8
8
  onProgress?: (msg: string) => void;
9
+ onTableProgress?: (tableIdx: number, totalTables: number, tableName: string) => void;
9
10
  }
10
11
  export interface DbCopyResult {
11
12
  tables: number;
@@ -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,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"}
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;IACnC,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CACtF;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"}
@@ -1 +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"}
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,CA+GvB"}
@@ -43,7 +43,9 @@ export async function copyDbPostgrest(srcAdapter, tgtAdapter, opts = {}) {
43
43
  let totalScrubbed = 0;
44
44
  // --- Copy pass: forward dependency order ---
45
45
  for (const table of tablesToProcess) {
46
- opts.onProgress?.(`Copying table: ${table}`);
46
+ const tableIdx = tablesToProcess.indexOf(table);
47
+ opts.onProgress?.(`Copying table ${tableIdx + 1}/${tablesToProcess.length}: ${table}`);
48
+ opts.onTableProgress?.(tableIdx + 1, tablesToProcess.length, table);
47
49
  const pk = pkOverrides[table] ?? 'id';
48
50
  const srcService = connect.createCrudService(srcAdapter, table, { autoId: false });
49
51
  const tgtService = connect.createCrudService(tgtAdapter, table, { autoId: false });
@@ -2,8 +2,9 @@ import type { HazoEnv } from '../types/index.js';
2
2
  export interface FilesCopyOptions {
3
3
  fromEnv: HazoEnv;
4
4
  toEnv: HazoEnv;
5
+ transport?: 'local' | 'rsync';
5
6
  fileScrubHook?: (filePath: string) => Promise<void>;
6
- onProgress?: (msg: string) => void;
7
+ onProgress?: (msg: string, percent?: number) => void;
7
8
  }
8
9
  export interface FilesCopyResult {
9
10
  copied: number;
@@ -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;AA0BD,wBAAsB,SAAS,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CA4ChF"}
1
+ {"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../src/migrate/files.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAEjD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IAC9B,aAAa,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CACtD;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,EAAE,MAAM,CAAC;CACvB;AA6GD,wBAAsB,SAAS,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CAkDhF"}
@@ -6,7 +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 { spawn } from 'node:child_process';
9
10
  import { resolveFilesRoot } from '../resolve/files.js';
11
+ import { resolveSshConfig } from '../resolve/ssh.js';
10
12
  function resolveEnvFilesRoot(env) {
11
13
  return resolveFilesRoot(env);
12
14
  }
@@ -29,7 +31,88 @@ async function scrubPdfFile(filePath) {
29
31
  // Non-fatal — PDF scrubbing failure is a warning, not an abort
30
32
  }
31
33
  }
34
+ /**
35
+ * Reject ssh config values that could smuggle extra ssh options through rsync's
36
+ * `-e` command string. rsync re-splits `-e` on whitespace, so a value starting
37
+ * with `-` (e.g. `-oProxyCommand=...`) or containing whitespace would be parsed
38
+ * as additional ssh flags rather than data. Fail loud instead of running.
39
+ */
40
+ function assertSafeSshField(name, value) {
41
+ if (/^-/.test(value) || /\s/.test(value)) {
42
+ throw new Error(`Unsafe transport.ssh.${name} value: must not start with '-' or contain whitespace`);
43
+ }
44
+ }
45
+ async function copyFilesRsync(opts, ssh) {
46
+ const tgtRoot = resolveEnvFilesRoot(opts.toEnv);
47
+ fs.mkdirSync(tgtRoot, { recursive: true });
48
+ assertSafeSshField('user', ssh.user);
49
+ assertSafeSshField('host', ssh.host);
50
+ assertSafeSshField('key', ssh.key);
51
+ assertSafeSshField('path', ssh.path);
52
+ // Persistent known_hosts so host-key trust survives across runs (no TOFU reset).
53
+ const knownHosts = path.resolve(process.cwd(), 'config', 'hazo_env_known_hosts');
54
+ fs.mkdirSync(path.dirname(knownHosts), { recursive: true });
55
+ if (ssh.hostKey) {
56
+ // Pin the operator-provided host key; strict checking, no first-use prompt.
57
+ const line = `${ssh.host} ${ssh.hostKey}\n`;
58
+ const existing = fs.existsSync(knownHosts) ? fs.readFileSync(knownHosts, 'utf8') : '';
59
+ if (!existing.includes(line.trim()))
60
+ fs.appendFileSync(knownHosts, line);
61
+ }
62
+ // Pinned key → strict verify; otherwise accept-new (trust on first use, then
63
+ // persist) — never disable host-key checking outright.
64
+ const strict = ssh.hostKey ? 'yes' : 'accept-new';
65
+ const remote = `${ssh.user}@${ssh.host}:${ssh.path}/`;
66
+ const sshCmd = `ssh -i ${ssh.key} -o StrictHostKeyChecking=${strict} -o UserKnownHostsFile=${knownHosts}`;
67
+ const args = ['-az', '--partial', '--info=progress2', '-e', sshCmd, remote, tgtRoot + '/'];
68
+ return new Promise((resolve, reject) => {
69
+ const proc = spawn('rsync', args);
70
+ let totalFiles = 0;
71
+ let doneFiles = 0;
72
+ let totalBytes = 0;
73
+ proc.stdout.on('data', (chunk) => {
74
+ const text = chunk.toString();
75
+ // Parse --info=progress2: lines like " 117,504,614 100% 108.64MB/s 0:00:01 (xfr#123, to-chk=0/456)"
76
+ for (const line of text.split('\n')) {
77
+ // Extract to-chk=N/TOTAL
78
+ const chkMatch = line.match(/to-chk=(\d+)\/(\d+)/);
79
+ if (chkMatch) {
80
+ const remaining = parseInt(chkMatch[1], 10);
81
+ const total = parseInt(chkMatch[2], 10);
82
+ if (totalFiles === 0)
83
+ totalFiles = total;
84
+ doneFiles = total - remaining;
85
+ const pct = total > 0 ? Math.round((doneFiles / total) * 100) : 0;
86
+ opts.onProgress?.(`rsync: ${doneFiles}/${total} files (${pct}%)`, pct);
87
+ }
88
+ // Extract bytes: first number on lines with a %
89
+ const bytesMatch = line.match(/^\s+([\d,]+)\s+\d+%/);
90
+ if (bytesMatch) {
91
+ totalBytes = parseInt(bytesMatch[1].replace(/,/g, ''), 10);
92
+ }
93
+ }
94
+ });
95
+ proc.stderr.on('data', (chunk) => {
96
+ opts.onProgress?.(`rsync: ${chunk.toString().trim()}`, undefined);
97
+ });
98
+ proc.on('error', (err) => reject(err));
99
+ proc.on('close', (code) => {
100
+ if (code !== 0) {
101
+ reject(new Error(`rsync exited with code ${code}`));
102
+ }
103
+ else {
104
+ resolve({ copied: doneFiles || totalFiles, bytes: totalBytes, placeholdered: 0 });
105
+ }
106
+ });
107
+ });
108
+ }
32
109
  export async function copyFiles(opts) {
110
+ const ssh = resolveSshConfig(opts.fromEnv);
111
+ if (opts.transport === 'rsync' || (opts.transport !== 'local' && ssh !== null)) {
112
+ if (!ssh)
113
+ throw new Error(`rsync requested but no [transport.ssh.${opts.fromEnv}] config found`);
114
+ return copyFilesRsync(opts, ssh);
115
+ }
33
116
  const srcRoot = resolveEnvFilesRoot(opts.fromEnv);
34
117
  const tgtRoot = resolveEnvFilesRoot(opts.toEnv);
35
118
  if (!fs.existsSync(srcRoot)) {
@@ -1 +1 @@
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"}
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,CAwLlF"}
@@ -180,6 +180,11 @@ export async function runMigration(req) {
180
180
  pkOverrides: migrateConfig.pkOverrides,
181
181
  scrubHook,
182
182
  onProgress: (msg) => emitProgress(req, { phase: 'db', message: msg }),
183
+ onTableProgress: (idx, total, name) => emitProgress(req, {
184
+ phase: 'db',
185
+ message: `table ${idx}/${total}: ${name}`,
186
+ percent: 20 + Math.round((idx / total) * 50),
187
+ }),
183
188
  });
184
189
  dbResult = result;
185
190
  emitProgress(req, { phase: 'db', message: `DB copy complete: ${result.tables} tables, ${result.rows} rows`, percent: 70 });
@@ -1,3 +1,3 @@
1
1
  import type { TransportMode, DbEnvConfig } from '../types/index.js';
2
- export declare function resolveTransport(fromConfig: DbEnvConfig, toConfig: DbEnvConfig, requested?: TransportMode): 'local' | 'api';
2
+ export declare function resolveTransport(fromConfig: DbEnvConfig, toConfig: DbEnvConfig, requested?: TransportMode): 'local' | 'api' | 'rsync';
3
3
  //# sourceMappingURL=transport.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/migrate/transport.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEpE,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,WAAW,EACvB,QAAQ,EAAE,WAAW,EACrB,SAAS,GAAE,aAAsB,GAChC,OAAO,GAAG,KAAK,CAYjB"}
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../../src/migrate/transport.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEpE,wBAAgB,gBAAgB,CAC9B,UAAU,EAAE,WAAW,EACvB,QAAQ,EAAE,WAAW,EACrB,SAAS,GAAE,aAAsB,GAChC,OAAO,GAAG,KAAK,GAAG,OAAO,CAM3B"}
@@ -1,13 +1,6 @@
1
- // hazo_env/src/migrate/transport.ts — resolve transport mode for a migration
2
- import { HazoError } from 'hazo_core';
3
1
  export function resolveTransport(fromConfig, toConfig, requested = 'auto') {
4
- if (requested === 'ssh') {
5
- throw new HazoError({
6
- code: 'HAZO_ENV_NOT_IMPLEMENTED',
7
- pkg: 'hazo_env',
8
- message: 'SSH transport is not yet implemented. Use local or api transport.',
9
- });
10
- }
2
+ if (requested === 'ssh')
3
+ return 'rsync';
11
4
  if (requested === 'api')
12
5
  return 'api';
13
6
  // auto: both sqlite → local
@@ -0,0 +1,15 @@
1
+ export interface SshConfig {
2
+ host: string;
3
+ user: string;
4
+ key: string;
5
+ path: string;
6
+ /**
7
+ * Optional pinned host public key (the right-hand side of a known_hosts line,
8
+ * e.g. "ssh-ed25519 AAAA...."). When set, host-key checking is strict and the
9
+ * key is pinned — no TOFU. When absent, the transport falls back to
10
+ * accept-new against a persistent known_hosts file.
11
+ */
12
+ hostKey?: string;
13
+ }
14
+ export declare function resolveSshConfig(env: string): SshConfig | null;
15
+ //# sourceMappingURL=ssh.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssh.d.ts","sourceRoot":"","sources":["../../src/resolve/ssh.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAcD,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAsB9D"}
@@ -0,0 +1,36 @@
1
+ // hazo_env/src/resolve/ssh.ts — SSH transport config resolver
2
+ import path from 'node:path';
3
+ import { HazoConfig } from 'hazo_config/server';
4
+ function tryLoadEnvConfig() {
5
+ try {
6
+ return new HazoConfig({ filePath: path.resolve(process.cwd(), 'config', 'hazo_env_config.ini') });
7
+ }
8
+ catch {
9
+ return null;
10
+ }
11
+ }
12
+ function expandEnvVars(value) {
13
+ return value.replace(/\$\{([^}]+)\}/g, (_, name) => process.env[name] ?? '');
14
+ }
15
+ export function resolveSshConfig(env) {
16
+ const config = tryLoadEnvConfig();
17
+ if (!config)
18
+ return null;
19
+ const section = config.getSection(`transport.ssh.${env}`);
20
+ if (!section)
21
+ return null;
22
+ const host = section['host'];
23
+ const user = section['user'];
24
+ const key = section['key'];
25
+ const remotePath = section['path'];
26
+ const hostKey = section['host_key'];
27
+ if (!host || !user || !key || !remotePath)
28
+ return null;
29
+ return {
30
+ host: expandEnvVars(host),
31
+ user: expandEnvVars(user),
32
+ key: expandEnvVars(key),
33
+ path: expandEnvVars(remotePath),
34
+ hostKey: hostKey ? expandEnvVars(hostKey) : undefined,
35
+ };
36
+ }
@@ -47,7 +47,7 @@ export type TransportMode = 'auto' | 'local' | 'ssh' | 'api';
47
47
  export type ScrubMode = 'auto' | 'none';
48
48
  export type HashMode = 'none' | 'sample' | 'full';
49
49
  export interface MigrationProgress {
50
- phase: 'validate' | 'snapshot' | 'plan' | 'schema-check' | 'db' | 'files' | 'verify' | 'finalize' | 'done';
50
+ phase: 'validate' | 'snapshot' | 'plan' | 'schema-check' | 'db' | 'files' | 'verify' | 'finalize' | 'done' | 'clear';
51
51
  message: string;
52
52
  percent?: number;
53
53
  }
@@ -102,5 +102,24 @@ export interface MigrateConfig {
102
102
  preserve?: string[];
103
103
  pkOverrides?: Record<string, string>;
104
104
  }
105
+ export interface ClearRequest {
106
+ env: HazoEnv;
107
+ include?: {
108
+ db?: boolean;
109
+ files?: boolean;
110
+ };
111
+ allowProdTarget?: boolean;
112
+ confirmToken?: string;
113
+ jobId?: string;
114
+ progressDir?: string;
115
+ onProgress?: (p: MigrationProgress) => void;
116
+ }
117
+ export interface ClearResult {
118
+ ok: boolean;
119
+ tablesCleared: number;
120
+ filesCleared: boolean;
121
+ durationMs: number;
122
+ warnings: string[];
123
+ }
105
124
  export type MaskTransform = (value: unknown, key: string, maskKey: string) => unknown;
106
125
  //# 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":"AAEA,sDAAsD;AACtD,MAAM,MAAM,OAAO,GAAG,KAAK,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;AAEnE,mCAAmC;AACnC,MAAM,MAAM,WAAW,GAAG,aAAa,GAAG,MAAM,GAAG,SAAS,GAAG,YAAY,CAAC;AAE5E,iFAAiF;AACjF,MAAM,MAAM,UAAU,GAAG,MAAM,EAAE,CAAC;AAElC,0EAA0E;AAC1E,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;AAErD,2CAA2C;AAC3C,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAC;AAEtD,yEAAyE;AACzE,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,QAAQ,GAAG,WAAW,CAAC;IAC7B,MAAM,CAAC,EAAE;QAAE,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC;IACnC,SAAS,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CACnD;AAED,yDAAyD;AACzD,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE;QACL,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;KAC9B,CAAC;CACH;AAED,8BAA8B;AAC9B,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,OAAO,CAAC;IACb,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,UAAU,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,CAAC,EAAE,UAAU,CAAC;CACpB;AAED,uCAAuC;AACvC,MAAM,WAAW,qBAAqB;IACpC,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAID,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,GAAG,KAAK,CAAC;AAC7D,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,CAAC;AACxC,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,CAAC;AAElD,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,UAAU,GAAG,UAAU,GAAG,MAAM,GAAG,cAAc,GAAG,IAAI,GAAG,OAAO,GAAG,QAAQ,GAAG,UAAU,GAAG,MAAM,GAAG,OAAO,CAAC;IACrH,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,OAAO,CAAC;IACd,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAC5C,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,MAAM,CAAC,EAAE,GAAG,GAAG,MAAM,EAAE,CAAC;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,iBAAiB,KAAK,IAAI,CAAC;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,OAAO,CAAC;IACZ,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,EAAE,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IACxD,KAAK,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC;IACjE,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,OAAO,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,wDAAwD;AACxD,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,OAAO,CAAC;IACb,OAAO,CAAC,EAAE;QAAE,EAAE,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;IAC5C,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,CAAC,CAAC,EAAE,iBAAiB,KAAK,IAAI,CAAC;CAC7C;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,OAAO,CAAC;IACZ,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,OAAO,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAGD,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hazo_env",
3
- "version": "0.4.0",
3
+ "version": "0.5.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",