hazo_env 0.1.1
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 +22 -0
- package/README.md +132 -0
- package/SETUP_CHECKLIST.md +92 -0
- package/config/hazo_env_config.ini.sample +48 -0
- package/config/hazo_env_masking.ini.sample +19 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +101 -0
- package/dist/doctor.d.ts +23 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +158 -0
- package/dist/env.server.d.ts +19 -0
- package/dist/env.server.d.ts.map +1 -0
- package/dist/env.server.js +61 -0
- package/dist/index.client.d.ts +26 -0
- package/dist/index.client.d.ts.map +1 -0
- package/dist/index.client.js +65 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/lib/index.d.ts +2 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +1 -0
- package/dist/resolve/connect.d.ts +11 -0
- package/dist/resolve/connect.d.ts.map +1 -0
- package/dist/resolve/connect.js +85 -0
- package/dist/resolve/files.d.ts +8 -0
- package/dist/resolve/files.d.ts.map +1 -0
- package/dist/resolve/files.js +53 -0
- package/dist/resolve/secrets.d.ts +16 -0
- package/dist/resolve/secrets.d.ts.map +1 -0
- package/dist/resolve/secrets.js +48 -0
- package/dist/types/index.d.ts +78 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/package.json +105 -0
package/CHANGE_LOG.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# hazo_env — Change Log
|
|
2
|
+
|
|
3
|
+
## 0.1.1 — 2026-06-09
|
|
4
|
+
|
|
5
|
+
- fix: provision `sql-wasm.wasm` into test-app `public/` via seed script (fixes Connect page error when SQLite adapter loads via sql.js WASM)
|
|
6
|
+
- fix: test-app `tsconfig.json` jsx mode corrected to `react-jsx`
|
|
7
|
+
- chore: add test-app `.gitignore` for generated artifacts (`data/`, `public/sql-wasm.wasm`, `logs/`, `.next/`)
|
|
8
|
+
- docs: fill in README.md and SETUP_CHECKLIST.md with M1 API, quick-start, and config reference
|
|
9
|
+
|
|
10
|
+
## 0.1.0 — 2026-06-08
|
|
11
|
+
|
|
12
|
+
Initial release — M1 Core.
|
|
13
|
+
|
|
14
|
+
- Env resolution: `getEnv`, `getEnvRole`, `getPattern`, `listEnvs`, `assertEnv`, `describeEnv`, `isDev`/`isTest`/`isStaging`/`isProd`
|
|
15
|
+
- Client-safe subset re-exported from `hazo_env/client`
|
|
16
|
+
- `resolveConnectConfig()` — maps current env to hazo_connect config (SQLite / PostgREST) via `hazo_env_config.ini`
|
|
17
|
+
- `resolveFilesConfig()` — maps current env to hazo_files config (data_root)
|
|
18
|
+
- `getSecret()` / `substitutePlaceholders()` — resolves secrets from `.env.local` only
|
|
19
|
+
- `doctor()` — validates pattern, DB reachability, secrets present, data_root writable, schema level
|
|
20
|
+
- `hazo-env` CLI: `current`, `doctor`, `snapshot` (stub)
|
|
21
|
+
- Test-app with sidebar, dev pages (Resolution, Connect, Files), autotest harness, seeded SQLite
|
|
22
|
+
- Unit tests via `hazo_testing`
|
package/README.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# hazo_env
|
|
2
|
+
|
|
3
|
+
Canonical environment resolver for hazo apps. Typed env names, per-env DB/file/secret config, a `doctor` diagnostic command, and a `hazo-env` CLI.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
- **Env resolution** — typed `HazoEnv` (`dev | test | staging | prod`), role mapping, pattern declaration, and `assertEnv()` to fail fast at boot if `HAZO_ENV` is invalid.
|
|
8
|
+
- **Per-env DB config** — `resolveConnectConfig()` maps the current env to its `hazo_connect` config (SQLite or PostgREST) with zero hardcoded connection strings in app code.
|
|
9
|
+
- **Per-env file config** — `resolveFilesConfig()` maps the current env to a `hazo_files` config rooted at the declared `data_root`.
|
|
10
|
+
- **Secrets** — `getSecret()` resolves from `.env.local` only; placeholders in `hazo_env_config.ini` are substituted at runtime without storing secrets.
|
|
11
|
+
- **Doctor** — `doctor()` / `hazo-env doctor` validates pattern, DB reachability, required secrets (no values printed), data_root writability, and schema level.
|
|
12
|
+
- **CLI** — `hazo-env current | doctor | snapshot`.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install hazo_env
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Peer deps (required): `hazo_core`, `hazo_config`.
|
|
21
|
+
Peer deps (optional): `hazo_connect` (for `resolveConnectConfig`), `hazo_files` (for `resolveFilesConfig`).
|
|
22
|
+
|
|
23
|
+
## Quick start
|
|
24
|
+
|
|
25
|
+
### 1. Create `hazo_env_config.ini` (copy from `config/hazo_env_config.ini.sample`)
|
|
26
|
+
|
|
27
|
+
```ini
|
|
28
|
+
[env]
|
|
29
|
+
pattern = dev, prod
|
|
30
|
+
app = myapp
|
|
31
|
+
|
|
32
|
+
[db.dev]
|
|
33
|
+
type = sqlite
|
|
34
|
+
database_path = ${DATA_ROOT}/dev.sqlite
|
|
35
|
+
|
|
36
|
+
[db.prod]
|
|
37
|
+
type = postgrest
|
|
38
|
+
base_url = ${POSTGREST_PROD_URL}
|
|
39
|
+
api_key = ${POSTGREST_API_KEY}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 2. Create `.env.local` (gitignored, one per deployment)
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
POSTGREST_PROD_URL=https://db.myapp.com
|
|
46
|
+
POSTGREST_API_KEY=...
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 3. Boot
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import { assertEnv } from 'hazo_env';
|
|
53
|
+
import { resolveConnectConfig } from 'hazo_env';
|
|
54
|
+
import { createHazoConnect } from 'hazo_connect';
|
|
55
|
+
|
|
56
|
+
assertEnv(); // throws if HAZO_ENV not in pattern
|
|
57
|
+
const adapter = await createHazoConnect(resolveConnectConfig());
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## API
|
|
61
|
+
|
|
62
|
+
### Env resolution (client-safe — also in `hazo_env/client`)
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import { getEnv, getEnvRole, getPattern, listEnvs, assertEnv, describeEnv,
|
|
66
|
+
isDev, isTest, isStaging, isProd } from 'hazo_env';
|
|
67
|
+
|
|
68
|
+
getEnv() // 'dev' | 'test' | 'staging' | 'prod'
|
|
69
|
+
getEnvRole() // 'development' | 'testing' | 'staging' | 'production'
|
|
70
|
+
getPattern() // e.g. 'dev_prod'
|
|
71
|
+
listEnvs() // ['dev', 'prod'] — valid envs for the declared pattern
|
|
72
|
+
assertEnv() // throws HazoError(ENV_INVALID) if HAZO_ENV is outside the pattern
|
|
73
|
+
describeEnv() // { env, role }
|
|
74
|
+
isDev() / isTest() / isStaging() / isProd()
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Resolvers (server-only)
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { resolveConnectConfig } from 'hazo_env';
|
|
81
|
+
import { resolveFilesConfig } from 'hazo_env';
|
|
82
|
+
import { getSecret } from 'hazo_env';
|
|
83
|
+
|
|
84
|
+
// Returns the hazo_connect config for the current env
|
|
85
|
+
resolveConnectConfig()
|
|
86
|
+
|
|
87
|
+
// Returns the hazo_files config for the current env (base_path from data_root)
|
|
88
|
+
resolveFilesConfig()
|
|
89
|
+
|
|
90
|
+
// Read a value from .env.local — throws HazoError(SECRET_MISSING) when required + absent
|
|
91
|
+
getSecret('POSTGREST_API_KEY', { required: true })
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Doctor
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import { doctor } from 'hazo_env';
|
|
98
|
+
|
|
99
|
+
const report = await doctor();
|
|
100
|
+
// report.ok boolean
|
|
101
|
+
// report.checks DoctorCheck[] — { label, ok, message }
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## CLI
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
hazo-env current # prints env, role, pattern, app, data_root
|
|
108
|
+
hazo-env doctor [--env <e>] [--all] # red/green validation table
|
|
109
|
+
hazo-env snapshot <env> # placeholder — full impl arrives in M2
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Config reference (`hazo_env_config.ini`)
|
|
113
|
+
|
|
114
|
+
See `config/hazo_env_config.ini.sample` for the full annotated template.
|
|
115
|
+
|
|
116
|
+
Key sections:
|
|
117
|
+
- `[env]` — `pattern` (comma-separated env names), `app` (app identifier)
|
|
118
|
+
- `[data]` — `root` (data_root; default `app_data`)
|
|
119
|
+
- `[db.<env>]` — `type` (`sqlite` | `postgrest`), `database_path` / `base_url` / `api_key`
|
|
120
|
+
- `[host.<env>]` — `location` (`local` | `remote`)
|
|
121
|
+
|
|
122
|
+
Secret placeholders use `${ENV_VAR_NAME}` syntax — hazo_env substitutes them from `.env.local` at runtime.
|
|
123
|
+
|
|
124
|
+
## Tailwind v4 (`@source` required if consuming UI)
|
|
125
|
+
|
|
126
|
+
```css
|
|
127
|
+
@source "../node_modules/hazo_env/dist";
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## License
|
|
131
|
+
|
|
132
|
+
MIT
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# hazo_env — Setup Checklist
|
|
2
|
+
|
|
3
|
+
Follow these steps when adding `hazo_env` to a consuming application.
|
|
4
|
+
|
|
5
|
+
## 1. Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install hazo_env hazo_core hazo_config
|
|
9
|
+
# optional — only if you use resolveConnectConfig / resolveFilesConfig:
|
|
10
|
+
npm install hazo_connect hazo_files
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## 2. Create `hazo_env_config.ini`
|
|
14
|
+
|
|
15
|
+
Copy `node_modules/hazo_env/config/hazo_env_config.ini.sample` to your app's config directory and fill in the values for each env:
|
|
16
|
+
|
|
17
|
+
```ini
|
|
18
|
+
[env]
|
|
19
|
+
pattern = dev, prod ; or: dev, test, prod / dev, test, staging, prod
|
|
20
|
+
app = myapp
|
|
21
|
+
|
|
22
|
+
[db.dev]
|
|
23
|
+
type = sqlite
|
|
24
|
+
database_path = ${DATA_ROOT}/dev.sqlite
|
|
25
|
+
|
|
26
|
+
[db.prod]
|
|
27
|
+
type = postgrest
|
|
28
|
+
base_url = ${POSTGREST_PROD_URL}
|
|
29
|
+
api_key = ${POSTGREST_API_KEY}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Never put secret values directly in the ini file** — use `${ENV_VAR_NAME}` placeholders.
|
|
33
|
+
|
|
34
|
+
## 3. Create `.env.local` (gitignored)
|
|
35
|
+
|
|
36
|
+
One file per deployment (dev machine, staging host, prod host). Contains the secrets referenced by `${…}` placeholders in the ini:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
POSTGREST_PROD_URL=https://db.myapp.com
|
|
40
|
+
POSTGREST_API_KEY=...
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Add `.env.local` to `.gitignore`.
|
|
44
|
+
|
|
45
|
+
## 4. Set `HAZO_ENV` in each deployment
|
|
46
|
+
|
|
47
|
+
Each deployment must have `HAZO_ENV` set to one of the values in `[env].pattern`:
|
|
48
|
+
|
|
49
|
+
- Local dev: `HAZO_ENV=dev` in `.env.local` or your shell profile
|
|
50
|
+
- Staging/prod: set in the process environment or platform config
|
|
51
|
+
|
|
52
|
+
## 5. Call `assertEnv()` at boot
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import { assertEnv } from 'hazo_env';
|
|
56
|
+
|
|
57
|
+
assertEnv(); // throws if HAZO_ENV is missing or not in the declared pattern
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Add this as the first call in your app's startup (e.g. `instrumentation.ts` in Next.js).
|
|
61
|
+
|
|
62
|
+
## 6. Replace hardcoded DB/file config with resolvers
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
import { resolveConnectConfig } from 'hazo_env';
|
|
66
|
+
import { resolveFilesConfig } from 'hazo_env';
|
|
67
|
+
import { createHazoConnect } from 'hazo_connect';
|
|
68
|
+
|
|
69
|
+
const adapter = await createHazoConnect(resolveConnectConfig());
|
|
70
|
+
// resolveFilesConfig() → pass to createInitializedTrackedFileManager()
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## 7. Run `hazo-env doctor` to verify
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npx hazo-env doctor
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
All checks should be green before going to production. The doctor validates:
|
|
80
|
+
- `HAZO_ENV` is in the declared pattern
|
|
81
|
+
- DB config for the current env is readable and reachable
|
|
82
|
+
- All required secrets are present in `.env.local` (values are never printed)
|
|
83
|
+
- `data_root` exists and is writable
|
|
84
|
+
|
|
85
|
+
## 8. (Next.js) Ensure `hazo_env` is in `transpilePackages`
|
|
86
|
+
|
|
87
|
+
```js
|
|
88
|
+
// next.config.js
|
|
89
|
+
const nextConfig = {
|
|
90
|
+
transpilePackages: ['hazo_env', 'hazo_core', /* … */],
|
|
91
|
+
};
|
|
92
|
+
```
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
; hazo_env configuration sample
|
|
2
|
+
; Copy to hazo_env_config.ini and fill in values.
|
|
3
|
+
; Secret values should use ${ENV_VAR_NAME} placeholders, not literal values.
|
|
4
|
+
|
|
5
|
+
[env]
|
|
6
|
+
; Declare the valid environment names for this app (comma-separated)
|
|
7
|
+
; Standard values: dev, test, staging, prod
|
|
8
|
+
pattern = dev, test, staging, prod
|
|
9
|
+
|
|
10
|
+
; Application name (used in log output and doctor reports)
|
|
11
|
+
app = myapp
|
|
12
|
+
|
|
13
|
+
[data]
|
|
14
|
+
; Base directory for all local file storage (relative to app root or absolute)
|
|
15
|
+
; Default: app_data
|
|
16
|
+
root = app_data
|
|
17
|
+
|
|
18
|
+
[db.dev]
|
|
19
|
+
; SQLite database for local development
|
|
20
|
+
type = sqlite
|
|
21
|
+
database_path = ${DATA_ROOT}/dev.sqlite
|
|
22
|
+
|
|
23
|
+
[db.test]
|
|
24
|
+
; SQLite database for automated tests
|
|
25
|
+
type = sqlite
|
|
26
|
+
database_path = ${DATA_ROOT}/test.sqlite
|
|
27
|
+
|
|
28
|
+
[db.staging]
|
|
29
|
+
; PostgREST connection for staging environment
|
|
30
|
+
type = postgrest
|
|
31
|
+
base_url = ${POSTGREST_STAGING_URL}
|
|
32
|
+
api_key = ${POSTGREST_STAGING_API_KEY}
|
|
33
|
+
|
|
34
|
+
[db.prod]
|
|
35
|
+
; PostgREST connection for production environment
|
|
36
|
+
type = postgrest
|
|
37
|
+
base_url = ${POSTGREST_PROD_URL}
|
|
38
|
+
api_key = ${POSTGREST_API_KEY}
|
|
39
|
+
|
|
40
|
+
[host.dev]
|
|
41
|
+
; Hint about where the dev environment runs (local/remote)
|
|
42
|
+
location = local
|
|
43
|
+
|
|
44
|
+
[host.staging]
|
|
45
|
+
location = remote
|
|
46
|
+
|
|
47
|
+
[host.prod]
|
|
48
|
+
location = remote
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
; hazo_env masking ruleset sample
|
|
2
|
+
; Copy to hazo_env_masking.ini and customize.
|
|
3
|
+
; This file seeds the masking engine with default rules for PII fields.
|
|
4
|
+
; The runtime source of truth (once hazo_admin is available) is the DB-backed ruleset.
|
|
5
|
+
|
|
6
|
+
[rule.users_email]
|
|
7
|
+
table = hazo_auth_users
|
|
8
|
+
column = email
|
|
9
|
+
strategy = mask_email
|
|
10
|
+
|
|
11
|
+
[rule.users_name]
|
|
12
|
+
table = hazo_auth_users
|
|
13
|
+
column = full_name
|
|
14
|
+
strategy = fake_name
|
|
15
|
+
|
|
16
|
+
[rule.api_keys]
|
|
17
|
+
table = hazo_api_keys
|
|
18
|
+
column = key_hash
|
|
19
|
+
strategy = drop
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// hazo_env/src/cli.ts — CLI entry: hazo-env current | doctor | snapshot
|
|
3
|
+
import pc from 'picocolors';
|
|
4
|
+
import { getEnv, getEnvRole } from './index.client.js';
|
|
5
|
+
import { describeEnv } from './env.server.js';
|
|
6
|
+
import { doctor } from './doctor.js';
|
|
7
|
+
const [, , command, ...args] = process.argv;
|
|
8
|
+
function statusIcon(status) {
|
|
9
|
+
if (status === 'ok')
|
|
10
|
+
return pc.green('✓');
|
|
11
|
+
if (status === 'warn')
|
|
12
|
+
return pc.yellow('⚠');
|
|
13
|
+
return pc.red('✗');
|
|
14
|
+
}
|
|
15
|
+
async function runCurrent() {
|
|
16
|
+
let desc;
|
|
17
|
+
try {
|
|
18
|
+
desc = describeEnv();
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// Fallback if no config file
|
|
22
|
+
const env = getEnv();
|
|
23
|
+
desc = { env, role: getEnvRole(env), pattern: ['dev', 'test', 'staging', 'prod'], app: '', dataRoot: '(config not found)', hostHint: 'unknown' };
|
|
24
|
+
}
|
|
25
|
+
console.log(`\n${pc.bold('hazo-env current')}\n`);
|
|
26
|
+
console.log(` env: ${pc.bold(desc.env)}`);
|
|
27
|
+
console.log(` role: ${desc.role}`);
|
|
28
|
+
console.log(` pattern: ${desc.pattern.join(', ')}`);
|
|
29
|
+
if (desc.app)
|
|
30
|
+
console.log(` app: ${desc.app}`);
|
|
31
|
+
console.log(` data root: ${desc.dataRoot}`);
|
|
32
|
+
console.log(` host hint: ${desc.hostHint}`);
|
|
33
|
+
console.log('');
|
|
34
|
+
}
|
|
35
|
+
async function runDoctor() {
|
|
36
|
+
const all = args.includes('--all');
|
|
37
|
+
const probe = args.includes('--probe');
|
|
38
|
+
// --env <e> (space-separated; also support --env=<e> for convenience)
|
|
39
|
+
const envFlagIdx = args.findIndex((a) => a === '--env');
|
|
40
|
+
const envFlag = envFlagIdx >= 0 ? args[envFlagIdx + 1]
|
|
41
|
+
: args.find((a) => a.startsWith('--env='))?.split('=')[1];
|
|
42
|
+
console.log(`\n${pc.bold('hazo-env doctor')}\n`);
|
|
43
|
+
const report = await doctor({ env: envFlag, all, probe });
|
|
44
|
+
console.log(` Checking env: ${pc.bold(report.env)}${all ? ' (all envs)' : ''}\n`);
|
|
45
|
+
const labelWidth = Math.max(...report.checks.map((c) => c.label.length)) + 2;
|
|
46
|
+
for (const check of report.checks) {
|
|
47
|
+
const icon = statusIcon(check.status);
|
|
48
|
+
const label = check.label.padEnd(labelWidth);
|
|
49
|
+
const detail = check.detail ? ` ${check.detail}` : '';
|
|
50
|
+
console.log(` ${icon} ${label}${detail}`);
|
|
51
|
+
}
|
|
52
|
+
console.log('');
|
|
53
|
+
if (report.passed) {
|
|
54
|
+
console.log(` ${pc.green('All checks passed.')}\n`);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
console.log(` ${pc.red('Some checks failed. Review the errors above.')}\n`);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async function runSnapshot() {
|
|
62
|
+
const targetEnv = args[0] ?? getEnv();
|
|
63
|
+
console.log(`\n${pc.yellow('snapshot')} — not implemented until Phase 2 (migration engine)\n`);
|
|
64
|
+
console.log(` Requested snapshot of env: ${pc.bold(targetEnv)}`);
|
|
65
|
+
console.log(` Run "hazo-env snapshot" again after the migration engine is built.\n`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
function printHelp() {
|
|
69
|
+
console.log(`
|
|
70
|
+
${pc.bold('hazo-env')} — environment resolver CLI
|
|
71
|
+
|
|
72
|
+
Usage:
|
|
73
|
+
hazo-env current Show current env, role, pattern, and data root
|
|
74
|
+
hazo-env doctor Run health checks for the current environment
|
|
75
|
+
hazo-env doctor --all Run health checks for all declared environments
|
|
76
|
+
hazo-env doctor --env <env> Run health checks for a specific environment
|
|
77
|
+
hazo-env doctor --probe Also probe live DB reachability
|
|
78
|
+
hazo-env snapshot <env> (Phase 2 — not yet implemented)
|
|
79
|
+
`);
|
|
80
|
+
}
|
|
81
|
+
(async () => {
|
|
82
|
+
try {
|
|
83
|
+
if (command === 'current') {
|
|
84
|
+
await runCurrent();
|
|
85
|
+
}
|
|
86
|
+
else if (command === 'doctor') {
|
|
87
|
+
await runDoctor();
|
|
88
|
+
}
|
|
89
|
+
else if (command === 'snapshot') {
|
|
90
|
+
await runSnapshot();
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
printHelp();
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
98
|
+
console.error(`\n${pc.red('Error:')} ${msg}\n`);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
})();
|
package/dist/doctor.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface DoctorCheck {
|
|
2
|
+
label: string;
|
|
3
|
+
status: 'ok' | 'warn' | 'error';
|
|
4
|
+
detail?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface DoctorReport {
|
|
7
|
+
env: string;
|
|
8
|
+
checks: DoctorCheck[];
|
|
9
|
+
passed: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface DoctorOptions {
|
|
12
|
+
env?: string;
|
|
13
|
+
/** Run checks for all envs in the pattern, not just the current one */
|
|
14
|
+
all?: boolean;
|
|
15
|
+
/** Run live reachability probes (sqlite open / postgrest ping) */
|
|
16
|
+
probe?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Run environment health checks.
|
|
20
|
+
* Never prints secret values.
|
|
21
|
+
*/
|
|
22
|
+
export declare function doctor(opts?: DoctorOptions): Promise<DoctorReport>;
|
|
23
|
+
//# sourceMappingURL=doctor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../src/doctor.ts"],"names":[],"mappings":"AAWA,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,CA+I5E"}
|
package/dist/doctor.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// hazo_env/src/doctor.ts — Environment health checker
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { HazoConfig } from 'hazo_config/server';
|
|
5
|
+
import { optional_import } from 'hazo_core';
|
|
6
|
+
import { getEnv } from './index.client.js';
|
|
7
|
+
import { listEnvs } from './env.server.js';
|
|
8
|
+
import { resolveConnectConfig } from './resolve/connect.js';
|
|
9
|
+
import { resolveFilesConfig } from './resolve/files.js';
|
|
10
|
+
function findConfigFile(pkg) {
|
|
11
|
+
return path.resolve(process.cwd(), 'config', `${pkg}_config.ini`);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Run environment health checks.
|
|
15
|
+
* Never prints secret values.
|
|
16
|
+
*/
|
|
17
|
+
export async function doctor(opts = {}) {
|
|
18
|
+
const currentEnv = getEnv();
|
|
19
|
+
const targetEnv = opts.env ?? currentEnv;
|
|
20
|
+
const checks = [];
|
|
21
|
+
// 1. Check config file exists
|
|
22
|
+
const configPath = findConfigFile('hazo_env');
|
|
23
|
+
if (!fs.existsSync(configPath)) {
|
|
24
|
+
checks.push({
|
|
25
|
+
label: 'Config file (hazo_env_config.ini)',
|
|
26
|
+
status: 'error',
|
|
27
|
+
detail: `Not found at ${configPath}`,
|
|
28
|
+
});
|
|
29
|
+
return { env: targetEnv, checks, passed: false };
|
|
30
|
+
}
|
|
31
|
+
checks.push({ label: 'Config file (hazo_env_config.ini)', status: 'ok', detail: configPath });
|
|
32
|
+
// 2. Parse config file
|
|
33
|
+
let config;
|
|
34
|
+
try {
|
|
35
|
+
config = new HazoConfig({ filePath: configPath });
|
|
36
|
+
}
|
|
37
|
+
catch (e) {
|
|
38
|
+
checks.push({ label: 'Config file parse', status: 'error', detail: String(e) });
|
|
39
|
+
return { env: targetEnv, checks, passed: false };
|
|
40
|
+
}
|
|
41
|
+
// 3. Check env pattern declaration
|
|
42
|
+
const patternRaw = config.getSection('env')?.['"pattern"'] ??
|
|
43
|
+
config.getSection('env')?.['pattern'] ??
|
|
44
|
+
'';
|
|
45
|
+
const declaredPattern = patternRaw
|
|
46
|
+
? patternRaw.split(',').map((s) => s.trim()).filter(Boolean)
|
|
47
|
+
: listEnvs();
|
|
48
|
+
if (patternRaw.length === 0) {
|
|
49
|
+
checks.push({
|
|
50
|
+
label: 'Env pattern ([env] pattern)',
|
|
51
|
+
status: 'warn',
|
|
52
|
+
detail: 'Not declared — using defaults',
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
checks.push({
|
|
57
|
+
label: 'Env pattern ([env] pattern)',
|
|
58
|
+
status: 'ok',
|
|
59
|
+
detail: declaredPattern.join(', '),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
// 4. Check DB config sections for the target env(s)
|
|
63
|
+
const envsToCheck = opts.all ? declaredPattern : [targetEnv];
|
|
64
|
+
for (const env of envsToCheck) {
|
|
65
|
+
try {
|
|
66
|
+
resolveConnectConfig({ env, allowOtherEnv: true });
|
|
67
|
+
checks.push({ label: `DB config [db.${env}]`, status: 'ok' });
|
|
68
|
+
}
|
|
69
|
+
catch (e) {
|
|
70
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
71
|
+
const isMissingSecret = msg.includes('SECRET_MISSING') || msg.includes('is not set') || msg.includes('Placeholder');
|
|
72
|
+
checks.push({
|
|
73
|
+
label: `DB config [db.${env}]`,
|
|
74
|
+
status: isMissingSecret ? 'warn' : 'error',
|
|
75
|
+
detail: isMissingSecret
|
|
76
|
+
? `Secret placeholder unresolved (set in .env.local)`
|
|
77
|
+
: msg,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// 5. Check data root exists and is writable
|
|
82
|
+
try {
|
|
83
|
+
const filesConfig = resolveFilesConfig();
|
|
84
|
+
const dataRoot = filesConfig.local.basePath;
|
|
85
|
+
if (!fs.existsSync(dataRoot)) {
|
|
86
|
+
checks.push({
|
|
87
|
+
label: 'Data root',
|
|
88
|
+
status: 'warn',
|
|
89
|
+
detail: `${dataRoot} does not exist (will be created on first use)`,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
try {
|
|
94
|
+
fs.accessSync(dataRoot, fs.constants.W_OK);
|
|
95
|
+
checks.push({ label: 'Data root', status: 'ok', detail: dataRoot });
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
checks.push({ label: 'Data root', status: 'error', detail: `${dataRoot} is not writable` });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch (e) {
|
|
103
|
+
checks.push({
|
|
104
|
+
label: 'Data root',
|
|
105
|
+
status: 'warn',
|
|
106
|
+
detail: `Could not resolve: ${e instanceof Error ? e.message : String(e)}`,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
// 5b. Optional probe: actually open the DB
|
|
110
|
+
if (opts.probe) {
|
|
111
|
+
for (const env of envsToCheck) {
|
|
112
|
+
// Get role for this env to guard against touching prod unattended
|
|
113
|
+
const role = env === 'dev' ? 'development' : env === 'test' ? 'test'
|
|
114
|
+
: env === 'staging' ? 'staging' : 'production';
|
|
115
|
+
if (role === 'production' && !opts.all) {
|
|
116
|
+
checks.push({
|
|
117
|
+
label: `DB probe [db.${env}]`,
|
|
118
|
+
status: 'warn',
|
|
119
|
+
detail: 'Refusing to probe a prod target without --all. Pass all:true to override.',
|
|
120
|
+
});
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
let cfg;
|
|
124
|
+
try {
|
|
125
|
+
cfg = resolveConnectConfig({ env, allowOtherEnv: true });
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
continue; // already reported as error above
|
|
129
|
+
}
|
|
130
|
+
if (cfg.type === 'sqlite' && cfg.sqlite) {
|
|
131
|
+
const hazoConnect = await optional_import('hazo_connect');
|
|
132
|
+
if (!hazoConnect) {
|
|
133
|
+
checks.push({
|
|
134
|
+
label: `DB probe [db.${env}]`,
|
|
135
|
+
status: 'warn',
|
|
136
|
+
detail: 'install hazo_connect to enable probing',
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
try {
|
|
141
|
+
const conn = hazoConnect.createHazoConnect({ type: 'sqlite', sqlite: cfg.sqlite });
|
|
142
|
+
await conn.query('SELECT 1');
|
|
143
|
+
checks.push({ label: `DB probe [db.${env}]`, status: 'ok', detail: cfg.sqlite.database_path });
|
|
144
|
+
}
|
|
145
|
+
catch (e) {
|
|
146
|
+
checks.push({
|
|
147
|
+
label: `DB probe [db.${env}]`,
|
|
148
|
+
status: 'error',
|
|
149
|
+
detail: e instanceof Error ? e.message : String(e),
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
const passed = checks.every((c) => c.status !== 'error');
|
|
157
|
+
return { env: targetEnv, checks, passed };
|
|
158
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { EnvPattern, EnvDescription } from './types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Get the declared env pattern from [env] pattern in hazo_env_config.ini.
|
|
4
|
+
* Falls back to ['dev','test','staging','prod'] if the config is absent.
|
|
5
|
+
*/
|
|
6
|
+
export declare function getPattern(): EnvPattern;
|
|
7
|
+
/** List all valid environment names per the declared pattern */
|
|
8
|
+
export declare function listEnvs(): EnvPattern;
|
|
9
|
+
/**
|
|
10
|
+
* Assert that the current env is in the declared pattern.
|
|
11
|
+
* Throws HazoError(ENV_INVALID) if not.
|
|
12
|
+
*/
|
|
13
|
+
export declare function assertEnv(pattern?: EnvPattern): void;
|
|
14
|
+
/**
|
|
15
|
+
* Describe the current environment (server-aware full version).
|
|
16
|
+
* Reads app, dataRoot, hostHint from hazo_env_config.ini.
|
|
17
|
+
*/
|
|
18
|
+
export declare function describeEnv(): EnvDescription;
|
|
19
|
+
//# sourceMappingURL=env.server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.server.d.ts","sourceRoot":"","sources":["../src/env.server.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAY,MAAM,kBAAkB,CAAC;AAc7E;;;GAGG;AACH,wBAAgB,UAAU,IAAI,UAAU,CAKvC;AAED,gEAAgE;AAChE,wBAAgB,QAAQ,IAAI,UAAU,CAErC;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI,CAUpD;AAED;;;GAGG;AACH,wBAAgB,WAAW,IAAI,cAAc,CAW5C"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// hazo_env/src/env.server.ts — Server-aware env resolution (reads hazo_env_config.ini)
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { HazoConfig } from 'hazo_config/server';
|
|
4
|
+
import { HazoError } from 'hazo_core';
|
|
5
|
+
import { getEnv, getEnvRole } from './index.client.js';
|
|
6
|
+
function findConfigFile(pkg) {
|
|
7
|
+
return path.resolve(process.cwd(), 'config', `${pkg}_config.ini`);
|
|
8
|
+
}
|
|
9
|
+
function tryLoadConfig() {
|
|
10
|
+
try {
|
|
11
|
+
return new HazoConfig({ filePath: findConfigFile('hazo_env') });
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Get the declared env pattern from [env] pattern in hazo_env_config.ini.
|
|
19
|
+
* Falls back to ['dev','test','staging','prod'] if the config is absent.
|
|
20
|
+
*/
|
|
21
|
+
export function getPattern() {
|
|
22
|
+
const config = tryLoadConfig();
|
|
23
|
+
const patternRaw = config?.getSection('env')?.['pattern'];
|
|
24
|
+
if (!patternRaw)
|
|
25
|
+
return ['dev', 'test', 'staging', 'prod'];
|
|
26
|
+
return patternRaw.split(',').map((s) => s.trim()).filter(Boolean);
|
|
27
|
+
}
|
|
28
|
+
/** List all valid environment names per the declared pattern */
|
|
29
|
+
export function listEnvs() {
|
|
30
|
+
return getPattern();
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Assert that the current env is in the declared pattern.
|
|
34
|
+
* Throws HazoError(ENV_INVALID) if not.
|
|
35
|
+
*/
|
|
36
|
+
export function assertEnv(pattern) {
|
|
37
|
+
const env = getEnv();
|
|
38
|
+
const validEnvs = pattern ?? getPattern();
|
|
39
|
+
if (!validEnvs.includes(env)) {
|
|
40
|
+
throw new HazoError({
|
|
41
|
+
code: 'ENV_INVALID',
|
|
42
|
+
pkg: 'hazo_env',
|
|
43
|
+
message: `Current env "${env}" is not in declared pattern [${validEnvs.join(', ')}]`,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Describe the current environment (server-aware full version).
|
|
49
|
+
* Reads app, dataRoot, hostHint from hazo_env_config.ini.
|
|
50
|
+
*/
|
|
51
|
+
export function describeEnv() {
|
|
52
|
+
const env = getEnv();
|
|
53
|
+
const role = getEnvRole(env);
|
|
54
|
+
const config = tryLoadConfig();
|
|
55
|
+
const pattern = getPattern();
|
|
56
|
+
const app = config?.getSection('env')?.['app'] ?? '';
|
|
57
|
+
const dataRoot = config?.getSection('data')?.['root'] ?? 'app_data';
|
|
58
|
+
const locationRaw = config?.getSection(`host.${env}`)?.['location'] ?? 'unknown';
|
|
59
|
+
const hostHint = locationRaw === 'local' ? 'local' : locationRaw === 'remote' ? 'remote' : 'unknown';
|
|
60
|
+
return { env, role, pattern, app, dataRoot, hostHint };
|
|
61
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { HazoEnv, HazoEnvRole, EnvPattern, EnvDescription } from './types/index.js';
|
|
2
|
+
export type { HazoEnv, HazoEnvRole, EnvPattern, EnvDescription };
|
|
3
|
+
/** Get the current environment name (normalized) */
|
|
4
|
+
export declare function getEnv(): HazoEnv;
|
|
5
|
+
/** Get the broad role for the current environment */
|
|
6
|
+
export declare function getEnvRole(env?: HazoEnv): HazoEnvRole;
|
|
7
|
+
export declare function isDev(env?: HazoEnv): boolean;
|
|
8
|
+
export declare function isTest(env?: HazoEnv): boolean;
|
|
9
|
+
export declare function isStaging(env?: HazoEnv): boolean;
|
|
10
|
+
export declare function isProd(env?: HazoEnv): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Get the declared env pattern (list of valid env names).
|
|
13
|
+
* On the client this is read from a build-time injected global or returns a default.
|
|
14
|
+
* The server-side index.ts provides a richer version that reads the INI.
|
|
15
|
+
*/
|
|
16
|
+
export declare function getPattern(): EnvPattern;
|
|
17
|
+
/** List all valid environment names per the declared pattern */
|
|
18
|
+
export declare function listEnvs(): EnvPattern;
|
|
19
|
+
/**
|
|
20
|
+
* Assert that the current env is in the declared pattern.
|
|
21
|
+
* Throws HazoError(ENV_INVALID) if not.
|
|
22
|
+
*/
|
|
23
|
+
export declare function assertEnv(pattern?: EnvPattern): void;
|
|
24
|
+
/** Describe the current environment (client-safe subset) */
|
|
25
|
+
export declare function describeEnv(): Pick<EnvDescription, 'env' | 'role'>;
|
|
26
|
+
//# sourceMappingURL=index.client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.client.d.ts","sourceRoot":"","sources":["../src/index.client.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACzF,YAAY,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,CAAC;AASjE,oDAAoD;AACpD,wBAAgB,MAAM,IAAI,OAAO,CAEhC;AAED,qDAAqD;AACrD,wBAAgB,UAAU,CAAC,GAAG,CAAC,EAAE,OAAO,GAAG,WAAW,CAMrD;AAED,wBAAgB,KAAK,CAAC,GAAG,CAAC,EAAE,OAAO,GAAG,OAAO,CAA8C;AAC3F,wBAAgB,MAAM,CAAC,GAAG,CAAC,EAAE,OAAO,GAAG,OAAO,CAAuC;AACrF,wBAAgB,SAAS,CAAC,GAAG,CAAC,EAAE,OAAO,GAAG,OAAO,CAA0C;AAC3F,wBAAgB,MAAM,CAAC,GAAG,CAAC,EAAE,OAAO,GAAG,OAAO,CAA6C;AAE3F;;;;GAIG;AACH,wBAAgB,UAAU,IAAI,UAAU,CAMvC;AAED,gEAAgE;AAChE,wBAAgB,QAAQ,IAAI,UAAU,CAErC;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,OAAO,CAAC,EAAE,UAAU,GAAG,IAAI,CAUpD;AAED,4DAA4D;AAC5D,wBAAgB,WAAW,IAAI,IAAI,CAAC,cAAc,EAAE,KAAK,GAAG,MAAM,CAAC,CAGlE"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// hazo_env/src/index.client.ts — Client-safe entry point
|
|
2
|
+
import { getCurrentEnv, HazoError } from 'hazo_core/client';
|
|
3
|
+
/** Normalize raw env string to canonical form */
|
|
4
|
+
function normalize(raw) {
|
|
5
|
+
if (raw === 'development')
|
|
6
|
+
return 'dev';
|
|
7
|
+
if (raw === 'production')
|
|
8
|
+
return 'prod';
|
|
9
|
+
return raw;
|
|
10
|
+
}
|
|
11
|
+
/** Get the current environment name (normalized) */
|
|
12
|
+
export function getEnv() {
|
|
13
|
+
return normalize(getCurrentEnv());
|
|
14
|
+
}
|
|
15
|
+
/** Get the broad role for the current environment */
|
|
16
|
+
export function getEnvRole(env) {
|
|
17
|
+
const e = env ?? getEnv();
|
|
18
|
+
if (e === 'dev')
|
|
19
|
+
return 'development';
|
|
20
|
+
if (e === 'test')
|
|
21
|
+
return 'test';
|
|
22
|
+
if (e === 'staging')
|
|
23
|
+
return 'staging';
|
|
24
|
+
return 'production';
|
|
25
|
+
}
|
|
26
|
+
export function isDev(env) { return getEnvRole(env) === 'development'; }
|
|
27
|
+
export function isTest(env) { return getEnvRole(env) === 'test'; }
|
|
28
|
+
export function isStaging(env) { return getEnvRole(env) === 'staging'; }
|
|
29
|
+
export function isProd(env) { return getEnvRole(env) === 'production'; }
|
|
30
|
+
/**
|
|
31
|
+
* Get the declared env pattern (list of valid env names).
|
|
32
|
+
* On the client this is read from a build-time injected global or returns a default.
|
|
33
|
+
* The server-side index.ts provides a richer version that reads the INI.
|
|
34
|
+
*/
|
|
35
|
+
export function getPattern() {
|
|
36
|
+
// Client-side: read from window.__HAZO_ENV_PATTERN__ if injected, else default
|
|
37
|
+
if (typeof globalThis !== 'undefined' && globalThis.__HAZO_ENV_PATTERN__) {
|
|
38
|
+
return globalThis.__HAZO_ENV_PATTERN__;
|
|
39
|
+
}
|
|
40
|
+
return ['dev', 'test', 'staging', 'prod'];
|
|
41
|
+
}
|
|
42
|
+
/** List all valid environment names per the declared pattern */
|
|
43
|
+
export function listEnvs() {
|
|
44
|
+
return getPattern();
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Assert that the current env is in the declared pattern.
|
|
48
|
+
* Throws HazoError(ENV_INVALID) if not.
|
|
49
|
+
*/
|
|
50
|
+
export function assertEnv(pattern) {
|
|
51
|
+
const env = getEnv();
|
|
52
|
+
const validEnvs = pattern ?? getPattern();
|
|
53
|
+
if (!validEnvs.includes(env)) {
|
|
54
|
+
throw new HazoError({
|
|
55
|
+
code: 'ENV_INVALID',
|
|
56
|
+
pkg: 'hazo_env',
|
|
57
|
+
message: `Current env "${env}" is not in declared pattern [${validEnvs.join(', ')}]`,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/** Describe the current environment (client-safe subset) */
|
|
62
|
+
export function describeEnv() {
|
|
63
|
+
const env = getEnv();
|
|
64
|
+
return { env, role: getEnvRole(env) };
|
|
65
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { getEnv, getEnvRole, isDev, isTest, isStaging, isProd } from './index.client.js';
|
|
2
|
+
export { getPattern, listEnvs, assertEnv, describeEnv } from './env.server.js';
|
|
3
|
+
export * from './types/index.js';
|
|
4
|
+
export * from './resolve/secrets.js';
|
|
5
|
+
export * from './resolve/connect.js';
|
|
6
|
+
export * from './resolve/files.js';
|
|
7
|
+
export * from './doctor.js';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEzF,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE/E,cAAc,kBAAkB,CAAC;AAEjC,cAAc,sBAAsB,CAAC;AACrC,cAAc,sBAAsB,CAAC;AACrC,cAAc,oBAAoB,CAAC;AAEnC,cAAc,aAAa,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// hazo_env/src/index.ts — Server entry point
|
|
2
|
+
// Pure client helpers re-exported explicitly (getPattern/listEnvs/assertEnv/describeEnv are overridden below)
|
|
3
|
+
export { getEnv, getEnvRole, isDev, isTest, isStaging, isProd } from './index.client.js';
|
|
4
|
+
// Server-aware overrides
|
|
5
|
+
export { getPattern, listEnvs, assertEnv, describeEnv } from './env.server.js';
|
|
6
|
+
// Types
|
|
7
|
+
export * from './types/index.js';
|
|
8
|
+
// Resolver + secrets
|
|
9
|
+
export * from './resolve/secrets.js';
|
|
10
|
+
export * from './resolve/connect.js';
|
|
11
|
+
export * from './resolve/files.js';
|
|
12
|
+
// Doctor
|
|
13
|
+
export * from './doctor.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DbEnvConfig, ResolveConnectOptions } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Resolve the connect configuration for the given (or current) environment.
|
|
4
|
+
* Reads the [db.<env>] section from hazo_env_config.ini and substitutes ${VAR} placeholders.
|
|
5
|
+
* ${DATA_ROOT} is automatically resolved from the [data] root value.
|
|
6
|
+
*
|
|
7
|
+
* @param opts.env - Target env (defaults to current env)
|
|
8
|
+
* @param opts.allowOtherEnv - Allow resolving a non-current env (default: false)
|
|
9
|
+
*/
|
|
10
|
+
export declare function resolveConnectConfig(opts?: ResolveConnectOptions): DbEnvConfig;
|
|
11
|
+
//# sourceMappingURL=connect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../../src/resolve/connect.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAmB5E;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,GAAE,qBAA0B,GAAG,WAAW,CA4DlF"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// hazo_env/src/resolve/connect.ts — Server-only connect config resolver
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { HazoConfig } from 'hazo_config/server';
|
|
4
|
+
import { HazoConfigError } from 'hazo_core';
|
|
5
|
+
import { getEnv } from '../index.client.js';
|
|
6
|
+
import { substitutePlaceholders } from './secrets.js';
|
|
7
|
+
import { resolveFilesConfig } from './files.js';
|
|
8
|
+
function findConfigFile(pkg) {
|
|
9
|
+
return path.resolve(process.cwd(), 'config', `${pkg}_config.ini`);
|
|
10
|
+
}
|
|
11
|
+
function loadConfig() {
|
|
12
|
+
const filePath = findConfigFile('hazo_env');
|
|
13
|
+
try {
|
|
14
|
+
return new HazoConfig({ filePath });
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
throw new HazoConfigError({
|
|
18
|
+
code: 'HAZO_ENV_CONFIG_FILE_MISSING',
|
|
19
|
+
pkg: 'hazo_env',
|
|
20
|
+
message: `Config file not found: ${filePath}. Create config/hazo_env_config.ini.`,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Resolve the connect configuration for the given (or current) environment.
|
|
26
|
+
* Reads the [db.<env>] section from hazo_env_config.ini and substitutes ${VAR} placeholders.
|
|
27
|
+
* ${DATA_ROOT} is automatically resolved from the [data] root value.
|
|
28
|
+
*
|
|
29
|
+
* @param opts.env - Target env (defaults to current env)
|
|
30
|
+
* @param opts.allowOtherEnv - Allow resolving a non-current env (default: false)
|
|
31
|
+
*/
|
|
32
|
+
export function resolveConnectConfig(opts = {}) {
|
|
33
|
+
const currentEnv = getEnv();
|
|
34
|
+
const targetEnv = opts.env ?? currentEnv;
|
|
35
|
+
if (!opts.allowOtherEnv && targetEnv !== currentEnv) {
|
|
36
|
+
throw new HazoConfigError({
|
|
37
|
+
code: 'HAZO_ENV_CONFIG_ENV_MISMATCH',
|
|
38
|
+
pkg: 'hazo_env',
|
|
39
|
+
message: `resolveConnectConfig: refusing to resolve env "${targetEnv}" when running as "${currentEnv}". Pass allowOtherEnv:true to override.`,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
const config = loadConfig();
|
|
43
|
+
const section = config.getSection(`db.${targetEnv}`);
|
|
44
|
+
if (!section) {
|
|
45
|
+
throw new HazoConfigError({
|
|
46
|
+
code: 'HAZO_ENV_CONFIG_MISSING_DB_SECTION',
|
|
47
|
+
pkg: 'hazo_env',
|
|
48
|
+
message: `No [db.${targetEnv}] section found in hazo_env_config.ini.`,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
// Compute DATA_ROOT for ${DATA_ROOT} substitution in sqlite paths
|
|
52
|
+
let dataRoot = 'app_data';
|
|
53
|
+
try {
|
|
54
|
+
dataRoot = resolveFilesConfig().local.basePath;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// fallback: use relative default
|
|
58
|
+
}
|
|
59
|
+
const extraVars = { DATA_ROOT: dataRoot };
|
|
60
|
+
const type = section['type'];
|
|
61
|
+
if (type === 'sqlite') {
|
|
62
|
+
const rawPath = section['database_path'];
|
|
63
|
+
if (!rawPath) {
|
|
64
|
+
throw new HazoConfigError({
|
|
65
|
+
code: 'HAZO_ENV_CONFIG_MISSING_DB_PATH',
|
|
66
|
+
pkg: 'hazo_env',
|
|
67
|
+
message: `[db.${targetEnv}] is type=sqlite but has no database_path.`,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
const database_path = substitutePlaceholders(rawPath, extraVars);
|
|
71
|
+
return { type: 'sqlite', sqlite: { database_path } };
|
|
72
|
+
}
|
|
73
|
+
if (type === 'postgrest') {
|
|
74
|
+
const rawUrl = section['base_url'] ?? '';
|
|
75
|
+
const rawKey = section['api_key'] ?? '';
|
|
76
|
+
const base_url = substitutePlaceholders(rawUrl, extraVars);
|
|
77
|
+
const api_key = substitutePlaceholders(rawKey, extraVars);
|
|
78
|
+
return { type: 'postgrest', postgrest: { base_url, api_key } };
|
|
79
|
+
}
|
|
80
|
+
throw new HazoConfigError({
|
|
81
|
+
code: 'HAZO_ENV_CONFIG_UNKNOWN_DB_TYPE',
|
|
82
|
+
pkg: 'hazo_env',
|
|
83
|
+
message: `[db.${targetEnv}] has unknown type "${type}". Expected "sqlite" or "postgrest".`,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FilesEnvConfig } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Resolve the local file storage configuration.
|
|
4
|
+
* Reads data_root from [data] section in hazo_env_config.ini.
|
|
5
|
+
* Falls back to app_data if not configured.
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveFilesConfig(appConfigPkg?: string): FilesEnvConfig;
|
|
8
|
+
//# sourceMappingURL=files.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// hazo_env/src/resolve/files.ts — Server-only files config resolver
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { HazoConfig } from 'hazo_config/server';
|
|
4
|
+
const DEFAULT_DATA_ROOT = 'app_data';
|
|
5
|
+
function findConfigFile(pkg) {
|
|
6
|
+
return path.resolve(process.cwd(), 'config', `${pkg}_config.ini`);
|
|
7
|
+
}
|
|
8
|
+
function tryLoadConfig(pkg) {
|
|
9
|
+
try {
|
|
10
|
+
return new HazoConfig({ filePath: findConfigFile(pkg) });
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Resolve the local file storage configuration.
|
|
18
|
+
* Reads data_root from [data] section in hazo_env_config.ini.
|
|
19
|
+
* Falls back to app_data if not configured.
|
|
20
|
+
*/
|
|
21
|
+
export function resolveFilesConfig(appConfigPkg) {
|
|
22
|
+
const envConfig = tryLoadConfig('hazo_env');
|
|
23
|
+
const dataSection = envConfig?.getSection('data');
|
|
24
|
+
let dataRoot = dataSection?.['root'] ?? DEFAULT_DATA_ROOT;
|
|
25
|
+
// Also check the consuming app's config if a pkg name was provided
|
|
26
|
+
if (appConfigPkg) {
|
|
27
|
+
const appConfig = tryLoadConfig(appConfigPkg);
|
|
28
|
+
const appDataSection = appConfig?.getSection('data');
|
|
29
|
+
if (appDataSection?.['root']) {
|
|
30
|
+
dataRoot = appDataSection['root'];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// Resolve to absolute path if relative
|
|
34
|
+
if (!path.isAbsolute(dataRoot)) {
|
|
35
|
+
dataRoot = path.resolve(process.cwd(), dataRoot);
|
|
36
|
+
}
|
|
37
|
+
// Read optional limits from hazo_env_config.ini [data] section
|
|
38
|
+
const maxFileSizeStr = dataSection?.['max_file_size_mb'];
|
|
39
|
+
const allowedExtensionsStr = dataSection?.['allowed_extensions'];
|
|
40
|
+
const local = { basePath: dataRoot };
|
|
41
|
+
if (maxFileSizeStr) {
|
|
42
|
+
const mb = parseInt(maxFileSizeStr, 10);
|
|
43
|
+
if (!isNaN(mb))
|
|
44
|
+
local.maxFileSize = mb * 1024 * 1024;
|
|
45
|
+
}
|
|
46
|
+
if (allowedExtensionsStr) {
|
|
47
|
+
local.allowedExtensions = allowedExtensionsStr
|
|
48
|
+
.split(',')
|
|
49
|
+
.map((e) => e.trim())
|
|
50
|
+
.filter(Boolean);
|
|
51
|
+
}
|
|
52
|
+
return { provider: 'local', local };
|
|
53
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface GetSecretOptions {
|
|
2
|
+
required?: boolean;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Get a secret value from environment variables.
|
|
6
|
+
* Loads .env.local on first call.
|
|
7
|
+
* Throws HazoConfigError with code HAZO_ENV_CONFIG_SECRET_MISSING if required and absent.
|
|
8
|
+
*/
|
|
9
|
+
export declare function getSecret(name: string, options?: GetSecretOptions): string | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Replace ${VAR_NAME} placeholders in a string with values from extraVars or the environment.
|
|
12
|
+
* extraVars take precedence over process.env (used for e.g. DATA_ROOT injection).
|
|
13
|
+
* Never logs the resolved values.
|
|
14
|
+
*/
|
|
15
|
+
export declare function substitutePlaceholders(value: string, extraVars?: Record<string, string>): string;
|
|
16
|
+
//# sourceMappingURL=secrets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secrets.d.ts","sourceRoot":"","sources":["../../src/resolve/secrets.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,MAAM,GAAG,SAAS,CAW1F;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAahG"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// hazo_env/src/resolve/secrets.ts — Server-only secret layer
|
|
2
|
+
import dotenv from 'dotenv';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { registerSingleton, getSingleton, HazoConfigError } from 'hazo_core';
|
|
5
|
+
const DOTENV_SINGLETON_KEY = 'hazo_env:dotenv_loaded';
|
|
6
|
+
/** Load .env.local once per process (idempotent via singleton registry) */
|
|
7
|
+
function ensureSecretsLoaded() {
|
|
8
|
+
if (getSingleton(DOTENV_SINGLETON_KEY))
|
|
9
|
+
return;
|
|
10
|
+
dotenv.config({ path: path.resolve(process.cwd(), '.env.local'), override: false });
|
|
11
|
+
registerSingleton(DOTENV_SINGLETON_KEY, true);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Get a secret value from environment variables.
|
|
15
|
+
* Loads .env.local on first call.
|
|
16
|
+
* Throws HazoConfigError with code HAZO_ENV_CONFIG_SECRET_MISSING if required and absent.
|
|
17
|
+
*/
|
|
18
|
+
export function getSecret(name, options = {}) {
|
|
19
|
+
ensureSecretsLoaded();
|
|
20
|
+
const value = process.env[name];
|
|
21
|
+
if ((value === undefined || value === '') && options.required) {
|
|
22
|
+
throw new HazoConfigError({
|
|
23
|
+
code: 'HAZO_ENV_CONFIG_SECRET_MISSING',
|
|
24
|
+
pkg: 'hazo_env',
|
|
25
|
+
message: `Required secret "${name}" is not set. Add it to .env.local or the environment.`,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
return value || undefined;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Replace ${VAR_NAME} placeholders in a string with values from extraVars or the environment.
|
|
32
|
+
* extraVars take precedence over process.env (used for e.g. DATA_ROOT injection).
|
|
33
|
+
* Never logs the resolved values.
|
|
34
|
+
*/
|
|
35
|
+
export function substitutePlaceholders(value, extraVars) {
|
|
36
|
+
ensureSecretsLoaded();
|
|
37
|
+
return value.replace(/\$\{([^}]+)\}/g, (_, varName) => {
|
|
38
|
+
const resolved = extraVars?.[varName] ?? process.env[varName];
|
|
39
|
+
if (resolved === undefined) {
|
|
40
|
+
throw new HazoConfigError({
|
|
41
|
+
code: 'HAZO_ENV_CONFIG_SECRET_MISSING',
|
|
42
|
+
pkg: 'hazo_env',
|
|
43
|
+
message: `Placeholder \${${varName}} references "${varName}" which is not set in extraVars or environment.`,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return resolved;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/** Canonical environment names after normalization */
|
|
2
|
+
export type HazoEnv = 'dev' | 'test' | 'staging' | 'prod' | string;
|
|
3
|
+
/** Broad role of an environment */
|
|
4
|
+
export type HazoEnvRole = 'development' | 'test' | 'staging' | 'production';
|
|
5
|
+
/** The set of valid environment names for this app, from [env] pattern in INI */
|
|
6
|
+
export type EnvPattern = string[];
|
|
7
|
+
/** Hint about where an environment runs */
|
|
8
|
+
export type HostHint = 'local' | 'remote' | 'unknown';
|
|
9
|
+
/** DB configuration for a single environment (from [db.<env>] in INI) */
|
|
10
|
+
export interface DbEnvConfig {
|
|
11
|
+
type: 'sqlite' | 'postgrest';
|
|
12
|
+
sqlite?: {
|
|
13
|
+
database_path: string;
|
|
14
|
+
};
|
|
15
|
+
postgrest?: {
|
|
16
|
+
base_url: string;
|
|
17
|
+
api_key: string;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/** Files configuration returned by resolveFilesConfig */
|
|
21
|
+
export interface FilesEnvConfig {
|
|
22
|
+
provider: 'local';
|
|
23
|
+
local: {
|
|
24
|
+
basePath: string;
|
|
25
|
+
maxFileSize?: number;
|
|
26
|
+
allowedExtensions?: string[];
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/** Result of describeEnv() */
|
|
30
|
+
export interface EnvDescription {
|
|
31
|
+
env: HazoEnv;
|
|
32
|
+
role: HazoEnvRole;
|
|
33
|
+
pattern: EnvPattern;
|
|
34
|
+
app: string;
|
|
35
|
+
dataRoot: string;
|
|
36
|
+
hostHint: HostHint;
|
|
37
|
+
}
|
|
38
|
+
/** Options for resolveConnectConfig */
|
|
39
|
+
export interface ResolveConnectOptions {
|
|
40
|
+
env?: HazoEnv;
|
|
41
|
+
allowOtherEnv?: boolean;
|
|
42
|
+
}
|
|
43
|
+
/** Describes a requested migration between two environments */
|
|
44
|
+
export interface MigrationRequest {
|
|
45
|
+
fromEnv: HazoEnv;
|
|
46
|
+
toEnv: HazoEnv;
|
|
47
|
+
includeFiles?: boolean;
|
|
48
|
+
dryRun?: boolean;
|
|
49
|
+
}
|
|
50
|
+
/** Result of a completed migration */
|
|
51
|
+
export interface MigrationResult {
|
|
52
|
+
success: boolean;
|
|
53
|
+
rowsMigrated?: number;
|
|
54
|
+
filesMigrated?: number;
|
|
55
|
+
error?: string;
|
|
56
|
+
}
|
|
57
|
+
/** Progress event emitted during a migration run */
|
|
58
|
+
export interface MigrationProgress {
|
|
59
|
+
phase: 'db' | 'files' | 'verify' | 'done';
|
|
60
|
+
message: string;
|
|
61
|
+
percent?: number;
|
|
62
|
+
}
|
|
63
|
+
/** Result of a post-migration file verification */
|
|
64
|
+
export interface VerifyReport {
|
|
65
|
+
passed: boolean;
|
|
66
|
+
missingFiles: string[];
|
|
67
|
+
extraFiles: string[];
|
|
68
|
+
checksumErrors: string[];
|
|
69
|
+
}
|
|
70
|
+
/** A transform rule for scrubbing/masking sensitive fields */
|
|
71
|
+
export interface ScrubTransform {
|
|
72
|
+
table: string;
|
|
73
|
+
column: string;
|
|
74
|
+
/** Transform strategy: replace with fixed value, hash, or nullify */
|
|
75
|
+
strategy: 'replace' | 'hash' | 'nullify';
|
|
76
|
+
replacement?: string;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
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,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;CACpB;AAED,uCAAuC;AACvC,MAAM,WAAW,qBAAqB;IACpC,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAID,+DAA+D;AAC/D,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,sCAAsC;AACtC,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,oDAAoD;AACpD,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,IAAI,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,mDAAmD;AACnD,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,OAAO,CAAC;IAChB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,cAAc,EAAE,MAAM,EAAE,CAAC;CAC1B;AAID,8DAA8D;AAC9D,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,QAAQ,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS,CAAC;IACzC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB"}
|
package/package.json
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "hazo_env",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Canonical environment resolver — typed env names, per-env DB/file/secret config, doctor and CLI for hazo apps",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./client": {
|
|
14
|
+
"types": "./dist/index.client.d.ts",
|
|
15
|
+
"import": "./dist/index.client.js"
|
|
16
|
+
},
|
|
17
|
+
"./package.json": "./package.json"
|
|
18
|
+
},
|
|
19
|
+
"bin": {
|
|
20
|
+
"hazo-env": "./dist/cli.js"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"config",
|
|
25
|
+
"README.md",
|
|
26
|
+
"SETUP_CHECKLIST.md",
|
|
27
|
+
"CHANGE_LOG.md"
|
|
28
|
+
],
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsc -p tsconfig.build.json",
|
|
31
|
+
"lint": "tsc --noEmit",
|
|
32
|
+
"test": "NODE_OPTIONS=--experimental-vm-modules jest",
|
|
33
|
+
"dev:test-app": "npm run build && cd test-app && npm run dev",
|
|
34
|
+
"build:test-app": "npm run build && cd test-app && npm run build"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"dotenv": "^16.4.5",
|
|
38
|
+
"picocolors": "^1.1.1"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"hazo_core": "^1.1.0",
|
|
42
|
+
"hazo_config": "^2.1.10",
|
|
43
|
+
"hazo_connect": "^3.4.0",
|
|
44
|
+
"hazo_files": "^3.0.0",
|
|
45
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
46
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
47
|
+
"next": "^14.0.0 || ^16.0.0"
|
|
48
|
+
},
|
|
49
|
+
"peerDependenciesMeta": {
|
|
50
|
+
"hazo_connect": {
|
|
51
|
+
"optional": true
|
|
52
|
+
},
|
|
53
|
+
"hazo_files": {
|
|
54
|
+
"optional": true
|
|
55
|
+
},
|
|
56
|
+
"react": {
|
|
57
|
+
"optional": true
|
|
58
|
+
},
|
|
59
|
+
"react-dom": {
|
|
60
|
+
"optional": true
|
|
61
|
+
},
|
|
62
|
+
"next": {
|
|
63
|
+
"optional": true
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
"devDependencies": {
|
|
67
|
+
"typescript": "^5.7.2",
|
|
68
|
+
"jest": "^30.2.0",
|
|
69
|
+
"ts-jest": "^29.4.5",
|
|
70
|
+
"@types/jest": "^30.0.0",
|
|
71
|
+
"jest-environment-node": "^30.2.0",
|
|
72
|
+
"@types/node": "^22.10.0",
|
|
73
|
+
"@types/react": "^19.0.0",
|
|
74
|
+
"@types/react-dom": "^19.0.0",
|
|
75
|
+
"hazo_core": "^1.1.0",
|
|
76
|
+
"hazo_config": "^2.1.10",
|
|
77
|
+
"hazo_connect": "^3.4.0",
|
|
78
|
+
"hazo_files": "^3.0.0",
|
|
79
|
+
"next": "^16.0.10",
|
|
80
|
+
"react": "^19.0.0",
|
|
81
|
+
"react-dom": "^19.0.0",
|
|
82
|
+
"lucide-react": "^0.553.0",
|
|
83
|
+
"tailwindcss": "^4.2.4",
|
|
84
|
+
"@tailwindcss/postcss": "^4.2.4",
|
|
85
|
+
"postcss": "^8.4.49"
|
|
86
|
+
},
|
|
87
|
+
"keywords": [
|
|
88
|
+
"hazo",
|
|
89
|
+
"env",
|
|
90
|
+
"environment",
|
|
91
|
+
"config",
|
|
92
|
+
"resolver",
|
|
93
|
+
"cli"
|
|
94
|
+
],
|
|
95
|
+
"author": "Pubs Abayasiri",
|
|
96
|
+
"license": "MIT",
|
|
97
|
+
"repository": {
|
|
98
|
+
"type": "git",
|
|
99
|
+
"url": "git+https://github.com/pub12/hazo_env.git"
|
|
100
|
+
},
|
|
101
|
+
"bugs": {
|
|
102
|
+
"url": "https://github.com/pub12/hazo_env/issues"
|
|
103
|
+
},
|
|
104
|
+
"homepage": "https://github.com/pub12/hazo_env#readme"
|
|
105
|
+
}
|