hazo_env 0.2.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 +47 -0
- package/README.md +83 -17
- package/SETUP_CHECKLIST.md +32 -0
- package/config/hazo_env_config.ini.sample +16 -0
- package/dist/cli.js +21 -7
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +168 -3
- package/dist/env.server.d.ts +7 -1
- package/dist/env.server.d.ts.map +1 -1
- package/dist/env.server.js +25 -2
- package/dist/index.client.d.ts +6 -2
- package/dist/index.client.d.ts.map +1 -1
- package/dist/index.client.js +15 -9
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -2
- 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/progress.d.ts +5 -0
- package/dist/migrate/progress.d.ts.map +1 -0
- package/dist/migrate/progress.js +24 -0
- package/dist/migrate/run.d.ts.map +1 -1
- package/dist/migrate/run.js +60 -25
- 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 +13 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +9 -9
package/dist/index.client.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// hazo_env/src/index.client.ts — Client-safe entry point
|
|
2
2
|
import { getCurrentEnv, HazoError } from 'hazo_core/client';
|
|
3
3
|
/** Normalize raw env string to canonical form */
|
|
4
|
-
function normalize(raw) {
|
|
4
|
+
export function normalize(raw) {
|
|
5
5
|
if (raw === 'development')
|
|
6
6
|
return 'dev';
|
|
7
7
|
if (raw === 'production')
|
|
@@ -12,16 +12,22 @@ function normalize(raw) {
|
|
|
12
12
|
export function getEnv() {
|
|
13
13
|
return normalize(getCurrentEnv());
|
|
14
14
|
}
|
|
15
|
+
const DEFAULT_ROLE_MAP = {
|
|
16
|
+
dev: 'development',
|
|
17
|
+
test: 'test',
|
|
18
|
+
staging: 'staging',
|
|
19
|
+
prod: 'production',
|
|
20
|
+
};
|
|
21
|
+
/** Get the role map, merging any build-time injected overrides from __HAZO_ENV_ROLES__ */
|
|
22
|
+
export function getRoleMap() {
|
|
23
|
+
const g = globalThis;
|
|
24
|
+
const injected = g['__HAZO_ENV_ROLES__'];
|
|
25
|
+
return { ...DEFAULT_ROLE_MAP, ...(injected ?? {}) };
|
|
26
|
+
}
|
|
15
27
|
/** Get the broad role for the current environment */
|
|
16
28
|
export function getEnvRole(env) {
|
|
17
|
-
const e = env ?? getEnv();
|
|
18
|
-
|
|
19
|
-
return 'development';
|
|
20
|
-
if (e === 'test')
|
|
21
|
-
return 'test';
|
|
22
|
-
if (e === 'staging')
|
|
23
|
-
return 'staging';
|
|
24
|
-
return 'production';
|
|
29
|
+
const e = normalize(env ?? getEnv());
|
|
30
|
+
return getRoleMap()[e] ?? 'development';
|
|
25
31
|
}
|
|
26
32
|
export function isDev(env) { return getEnvRole(env) === 'development'; }
|
|
27
33
|
export function isTest(env) { return getEnvRole(env) === 'test'; }
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { getEnv,
|
|
2
|
-
export { getPattern, listEnvs, assertEnv, describeEnv } from './env.server.js';
|
|
1
|
+
export { getEnv, isDev, isTest, isStaging, isProd } from './index.client.js';
|
|
2
|
+
export { getPattern, listEnvs, assertEnv, describeEnv, getEnvRole, getRoleMap } from './env.server.js';
|
|
3
3
|
export * from './types/index.js';
|
|
4
4
|
export * from './resolve/secrets.js';
|
|
5
5
|
export * from './resolve/connect.js';
|
|
@@ -8,7 +8,10 @@ export * from './doctor.js';
|
|
|
8
8
|
export { runMigration } from './migrate/run.js';
|
|
9
9
|
export { verifyFiles } from './migrate/verify.js';
|
|
10
10
|
export { takeSnapshot, restoreSnapshot } from './migrate/snapshot.js';
|
|
11
|
+
export { writeMigrationProgress, readMigrationProgress, clearMigrationProgress } from './migrate/progress.js';
|
|
11
12
|
export { registerMask } from './mask/registry.js';
|
|
13
|
+
export { loadRuleset, syncRulesetFromIni, parseIniRules } from './mask/ruleset.js';
|
|
14
|
+
export type { MaskRule } from './mask/ruleset.js';
|
|
12
15
|
export type { MaskTransform } from './types/index.js';
|
|
13
16
|
export * from './lib/index.js';
|
|
14
17
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,
|
|
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"}
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// hazo_env/src/index.ts — Server entry point
|
|
2
2
|
// Pure client helpers re-exported explicitly (getPattern/listEnvs/assertEnv/describeEnv are overridden below)
|
|
3
|
-
export { getEnv,
|
|
3
|
+
export { getEnv, isDev, isTest, isStaging, isProd } from './index.client.js';
|
|
4
4
|
// Server-aware overrides
|
|
5
|
-
export { getPattern, listEnvs, assertEnv, describeEnv } from './env.server.js';
|
|
5
|
+
export { getPattern, listEnvs, assertEnv, describeEnv, getEnvRole, getRoleMap } from './env.server.js';
|
|
6
6
|
// Types
|
|
7
7
|
export * from './types/index.js';
|
|
8
8
|
// Resolver + secrets
|
|
@@ -15,7 +15,10 @@ export * from './doctor.js';
|
|
|
15
15
|
export { runMigration } from './migrate/run.js';
|
|
16
16
|
export { verifyFiles } from './migrate/verify.js';
|
|
17
17
|
export { takeSnapshot, restoreSnapshot } from './migrate/snapshot.js';
|
|
18
|
+
export { writeMigrationProgress, readMigrationProgress, clearMigrationProgress } from './migrate/progress.js';
|
|
18
19
|
// Masking registry
|
|
19
20
|
export { registerMask } from './mask/registry.js';
|
|
21
|
+
// Masking ruleset
|
|
22
|
+
export { loadRuleset, syncRulesetFromIni, parseIniRules } from './mask/ruleset.js';
|
|
20
23
|
// Lib
|
|
21
24
|
export * from './lib/index.js';
|
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.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { MigrationProgress } from '../types/index.js';
|
|
2
|
+
export declare function writeMigrationProgress(progressDir: string, jobId: string, p: MigrationProgress): void;
|
|
3
|
+
export declare function readMigrationProgress(progressDir: string, jobId: string): MigrationProgress | null;
|
|
4
|
+
export declare function clearMigrationProgress(progressDir: string, jobId: string): void;
|
|
5
|
+
//# sourceMappingURL=progress.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"progress.d.ts","sourceRoot":"","sources":["../../src/migrate/progress.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,iBAAiB,GAAG,IAAI,CAGrG;AAED,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI,CAQlG;AAED,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAG/E"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// hazo_env/src/migrate/progress.ts — Filesystem-backed migration progress store
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
export function writeMigrationProgress(progressDir, jobId, p) {
|
|
5
|
+
fs.mkdirSync(progressDir, { recursive: true });
|
|
6
|
+
fs.writeFileSync(path.join(progressDir, `${jobId}.json`), JSON.stringify(p), 'utf-8');
|
|
7
|
+
}
|
|
8
|
+
export function readMigrationProgress(progressDir, jobId) {
|
|
9
|
+
const filePath = path.join(progressDir, `${jobId}.json`);
|
|
10
|
+
try {
|
|
11
|
+
const raw = fs.readFileSync(filePath, 'utf-8');
|
|
12
|
+
return JSON.parse(raw);
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function clearMigrationProgress(progressDir, jobId) {
|
|
19
|
+
const filePath = path.join(progressDir, `${jobId}.json`);
|
|
20
|
+
try {
|
|
21
|
+
fs.unlinkSync(filePath);
|
|
22
|
+
}
|
|
23
|
+
catch { /* ignore */ }
|
|
24
|
+
}
|
|
@@ -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';
|
|
@@ -13,13 +14,13 @@ import { copyDb } from './db.js';
|
|
|
13
14
|
import { copyFiles } from './files.js';
|
|
14
15
|
import { verifyFiles } from './verify.js';
|
|
15
16
|
import { auditMigration } from './audit.js';
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
function isProdEnv(env) {
|
|
19
|
-
return PROD_ROLES.has(env.toLowerCase());
|
|
20
|
-
}
|
|
17
|
+
import { writeMigrationProgress } from './progress.js';
|
|
18
|
+
import { getEnvRole } from '../env.server.js';
|
|
21
19
|
function emitProgress(req, p) {
|
|
22
20
|
req.onProgress?.(p);
|
|
21
|
+
if (req.jobId && req.progressDir) {
|
|
22
|
+
writeMigrationProgress(req.progressDir, req.jobId, p);
|
|
23
|
+
}
|
|
23
24
|
}
|
|
24
25
|
// Resolve the SQLite driver — prefer better-sqlite3 in tests when env var is set
|
|
25
26
|
function getSqliteDriver() {
|
|
@@ -29,9 +30,8 @@ function getSqliteDriver() {
|
|
|
29
30
|
}
|
|
30
31
|
async function buildScrubHook(toEnv, scrubMode, tgtAdapter) {
|
|
31
32
|
const maskKey = process.env['HAZO_ENV_MASK_KEY'];
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
(toEnv === 'test' || toEnv === 'staging');
|
|
33
|
+
const toRole = getEnvRole(toEnv);
|
|
34
|
+
const shouldScrub = scrubMode !== 'none' && !!maskKey && (toRole === 'test' || toRole === 'staging');
|
|
35
35
|
if (!shouldScrub)
|
|
36
36
|
return undefined;
|
|
37
37
|
const { loadRuleset } = await import('../mask/ruleset.js');
|
|
@@ -85,14 +85,15 @@ export async function runMigration(req) {
|
|
|
85
85
|
if (req.from === req.to) {
|
|
86
86
|
throw new HazoError({ code: 'HAZO_ENV_INVALID_REQUEST', pkg: 'hazo_env', message: `Source and target environments must differ (got "${req.from}" for both).` });
|
|
87
87
|
}
|
|
88
|
-
|
|
88
|
+
const toRole = getEnvRole(req.to);
|
|
89
|
+
if (toRole === 'production' && !req.allowProdTarget) {
|
|
89
90
|
throw new HazoError({
|
|
90
91
|
code: 'HAZO_ENV_PROD_TARGET_REFUSED',
|
|
91
92
|
pkg: 'hazo_env',
|
|
92
93
|
message: `Refusing to migrate to production environment "${req.to}". Pass allowProdTarget:true and a confirmToken to override.`,
|
|
93
94
|
});
|
|
94
95
|
}
|
|
95
|
-
if (
|
|
96
|
+
if (toRole === 'production' && req.allowProdTarget && !req.confirmToken) {
|
|
96
97
|
throw new HazoError({
|
|
97
98
|
code: 'HAZO_ENV_PROD_TARGET_REFUSED',
|
|
98
99
|
pkg: 'hazo_env',
|
|
@@ -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
|
+
},
|
|
141
|
+
});
|
|
142
|
+
}
|
|
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
|
+
},
|
|
130
157
|
});
|
|
131
158
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
read_only: readOnly,
|
|
137
|
-
driver: getSqliteDriver(),
|
|
138
|
-
},
|
|
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"}
|