@hasna/domains 0.0.46 → 0.1.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.
Files changed (57) hide show
  1. package/README.md +58 -34
  2. package/dist/cli/commands/dns.d.ts.map +1 -1
  3. package/dist/cli/commands/doctor.d.ts.map +1 -1
  4. package/dist/cli/commands/serve.d.ts +1 -0
  5. package/dist/cli/commands/serve.d.ts.map +1 -1
  6. package/dist/cli/index.js +4946 -2042
  7. package/dist/db/database.d.ts +6 -21
  8. package/dist/db/database.d.ts.map +1 -1
  9. package/dist/db/domains.d.ts +3 -14
  10. package/dist/db/domains.d.ts.map +1 -1
  11. package/dist/db/store.d.ts +36 -32
  12. package/dist/db/store.d.ts.map +1 -1
  13. package/dist/generated/storage-kit/backend.d.ts +4 -4
  14. package/dist/generated/storage-kit/backend.d.ts.map +1 -1
  15. package/dist/generated/storage-kit/index.d.ts +1 -1
  16. package/dist/generated/storage-kit/index.d.ts.map +1 -1
  17. package/dist/generated/storage-kit/migrations.d.ts.map +1 -1
  18. package/dist/generated/storage-kit/pool.d.ts +2 -5
  19. package/dist/generated/storage-kit/pool.d.ts.map +1 -1
  20. package/dist/index.d.ts +1 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +1268 -1639
  23. package/dist/lib/app-home.d.ts +48 -0
  24. package/dist/lib/app-home.d.ts.map +1 -0
  25. package/dist/lib/client-storage-policy.d.ts +10 -0
  26. package/dist/lib/client-storage-policy.d.ts.map +1 -0
  27. package/dist/lib/client-types.d.ts +184 -0
  28. package/dist/lib/client-types.d.ts.map +1 -0
  29. package/dist/lib/cloudflare.d.ts.map +1 -1
  30. package/dist/lib/cloudflare.js +23 -0
  31. package/dist/lib/config.d.ts +8 -17
  32. package/dist/lib/config.d.ts.map +1 -1
  33. package/dist/lib/dns-plan.d.ts +1 -1
  34. package/dist/lib/dns-plan.d.ts.map +1 -1
  35. package/dist/lib/domains-resolver.d.ts +96 -0
  36. package/dist/lib/domains-resolver.d.ts.map +1 -0
  37. package/dist/lib/registrar.d.ts +8 -0
  38. package/dist/lib/registrar.d.ts.map +1 -1
  39. package/dist/lib/registrar.js +23 -0
  40. package/dist/mcp/harness.d.ts +46 -0
  41. package/dist/mcp/harness.d.ts.map +1 -0
  42. package/dist/mcp/http.d.ts +3 -2
  43. package/dist/mcp/http.d.ts.map +1 -1
  44. package/dist/mcp/index.d.ts.map +1 -1
  45. package/dist/mcp/index.js +23611 -24591
  46. package/dist/mcp/tool-filter.d.ts +6 -0
  47. package/dist/mcp/tool-filter.d.ts.map +1 -1
  48. package/dist/sdk/index.d.ts +21 -10
  49. package/dist/sdk/index.d.ts.map +1 -1
  50. package/dist/sdk/index.js +861 -245
  51. package/dist/server/index.d.ts +6 -0
  52. package/dist/server/index.d.ts.map +1 -1
  53. package/dist/server/index.js +84 -29
  54. package/dist/server/migrations.d.ts +35 -0
  55. package/dist/server/migrations.d.ts.map +1 -1
  56. package/package.json +8 -8
  57. package/postinstall.js +41 -0
@@ -15,4 +15,10 @@ export declare const SIGNING_KEY_ENVS: readonly ["HASNA_DOMAINS_API_SIGNING_KEY"
15
15
  /** Normalize the module-injected env names to the kit/auth conventions. */
16
16
  export declare function normalizeEnv(env?: NodeJS.ProcessEnv): void;
17
17
  export declare function resolveSigningSecret(env?: NodeJS.ProcessEnv): string;
18
+ export declare const USAGE = "domains-serve \u2014 standalone HTTP API server for @hasna/domains (PostgreSQL backend)\n\nUsage: domains-serve [--port <n>] [--host <addr>]\n\nOptions:\n --port <n> Port to listen on (default: $PORT or 8080)\n --host <addr> Address to bind (default: $HOST or 0.0.0.0)\n -h, --help Print this help and exit\n -V, --version Print the package version and exit\n\nEnvironment:\n HASNA_DOMAINS_DATABASE_URL PostgreSQL DSN (falls back to DATABASE_URL)\n HASNA_DOMAINS_API_SIGNING_KEY HMAC signing secret for API keys\n (falls back to API_KEY_SIGNING_SECRET)";
19
+ /** Shared authenticated server for both domains serve and domains-serve. */
20
+ export declare function startDomainsServer(options: {
21
+ port: number;
22
+ host: string;
23
+ }): Promise<void>;
18
24
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;GAWG;AASH,eAAO,MAAM,gBAAgB,+FAInB,CAAC;AAEX,2EAA2E;AAC3E,wBAAgB,YAAY,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,IAAI,CAIvE;AAED,wBAAgB,oBAAoB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAQjF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;GAWG;AASH,eAAO,MAAM,gBAAgB,+FAInB,CAAC;AAEX,2EAA2E;AAC3E,wBAAgB,YAAY,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,IAAI,CAIvE;AAED,wBAAgB,oBAAoB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,CAQjF;AAUD,eAAO,MAAM,KAAK,0mBAasD,CAAC;AAwBzE,4EAA4E;AAC5E,wBAAsB,kBAAkB,CAAC,OAAO,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAgD/F"}
@@ -79,7 +79,7 @@ function viewWindow(view) {
79
79
  }
80
80
  function toBuffer(secret) {
81
81
  if (typeof secret === "string")
82
- return Buffer.from(secret, "utf8");
82
+ return Buffer.from(secret.trim(), "utf8");
83
83
  if (ArrayBuffer.isView(secret)) {
84
84
  const [store, byteOffset, byteLength] = viewWindow(secret);
85
85
  return Buffer.from(store, byteOffset, byteLength);
@@ -386,11 +386,17 @@ class ApiKeyStore {
386
386
  return status !== "active";
387
387
  };
388
388
  }
389
- async revoke(kid, reason, atMs = Date.now()) {
389
+ async revoke(kid, reason, atMs = Date.now(), options = {}) {
390
+ const params = [kid, new Date(atMs).toISOString(), reason ?? null];
391
+ let scope = "";
392
+ if (options.app !== undefined) {
393
+ params.push(options.app);
394
+ scope = ` AND app = $${params.length}`;
395
+ }
390
396
  const row = await this.client.get(`UPDATE ${this.table}
391
397
  SET revoked_at = COALESCE(revoked_at, $2), revoked_reason = COALESCE(revoked_reason, $3)
392
- WHERE kid = $1
393
- RETURNING kid`, [kid, new Date(atMs).toISOString(), reason ?? null]);
398
+ WHERE kid = $1${scope}
399
+ RETURNING kid`, params);
394
400
  return row !== null;
395
401
  }
396
402
  async touchLastUsed(kid, atMs = Date.now()) {
@@ -629,24 +635,43 @@ function serverDataBackendEnvKeys(name) {
629
635
  databaseUrlKeys: [`HASNA_${token}_DATABASE_URL`, `${token}_DATABASE_URL`]
630
636
  };
631
637
  }
632
- function firstEnv(env, keys) {
633
- for (const key of keys) {
634
- const value = ownString(env, key)?.trim();
635
- if (value)
636
- return { key, value };
638
+ function definedDatabaseUrlEntries(env, keys) {
639
+ return keys.map((key) => ({ key, value: ownString(env, key) })).filter((entry) => entry.value !== undefined);
640
+ }
641
+ function assertPostgresqlDatabaseUrl(name, entries) {
642
+ const canonicalKey = serverDataBackendEnvKeys(name).databaseUrlKeys[0];
643
+ if (entries.length === 0) {
644
+ throw new Error(`${canonicalKey} is required; servers use PostgreSQL and never default to SQLite.`);
637
645
  }
638
- return null;
646
+ const blank = entries.filter((entry) => entry.value.trim().length === 0);
647
+ if (blank.length > 0) {
648
+ throw new Error(`${blank.map((entry) => entry.key).join(" and ")} is set but blank; a PostgreSQL database URL is required.`);
649
+ }
650
+ const controlled = entries.find((entry) => /[\u0000-\u001f\u007f]/.test(entry.value));
651
+ if (controlled)
652
+ throw new Error(`${controlled.key} must not contain ASCII control characters.`);
653
+ const normalized = entries.map((entry) => ({ key: entry.key, value: entry.value.trim() }));
654
+ if (normalized.length > 1 && new Set(normalized.map((entry) => entry.value)).size > 1) {
655
+ throw new Error(`${normalized.map((entry) => entry.key).join(" and ")} disagree; database URL aliases must be identical or only one may be set.`);
656
+ }
657
+ const selected = normalized[0];
658
+ let parsed;
659
+ try {
660
+ parsed = new URL(selected.value);
661
+ } catch {
662
+ throw new Error(`${selected.key} must be an absolute PostgreSQL connection URL.`);
663
+ }
664
+ if (parsed.protocol !== "postgres:" && parsed.protocol !== "postgresql:") {
665
+ throw new Error(`${selected.key} must use the postgres or postgresql scheme.`);
666
+ }
667
+ if (!parsed.hostname || parsed.pathname.length <= 1) {
668
+ throw new Error(`${selected.key} must name a PostgreSQL host and database.`);
669
+ }
670
+ return selected;
639
671
  }
640
672
  function resolveServerDataBackend(name, env = process.env) {
641
- const databaseUrl = firstEnv(env, serverDataBackendEnvKeys(name).databaseUrlKeys);
642
- if (!databaseUrl) {
643
- return {
644
- backend: "sqlite",
645
- source: "default",
646
- databaseUrlPresent: false,
647
- databaseUrlSource: null
648
- };
649
- }
673
+ const keys = serverDataBackendEnvKeys(name).databaseUrlKeys;
674
+ const databaseUrl = assertPostgresqlDatabaseUrl(name, definedDatabaseUrlEntries(env, keys));
650
675
  return {
651
676
  backend: "postgresql",
652
677
  source: databaseUrl.key,
@@ -655,8 +680,8 @@ function resolveServerDataBackend(name, env = process.env) {
655
680
  };
656
681
  }
657
682
  function resolveDatabaseUrl(name, env = process.env) {
658
- const hit = firstEnv(env, serverDataBackendEnvKeys(name).databaseUrlKeys);
659
- return hit?.value ?? null;
683
+ const keys = serverDataBackendEnvKeys(name).databaseUrlKeys;
684
+ return assertPostgresqlDatabaseUrl(name, definedDatabaseUrlEntries(env, keys)).value;
660
685
  }
661
686
  // src/generated/storage-kit/tls.ts
662
687
  import { readFileSync } from "fs";
@@ -903,9 +928,6 @@ function createServerPoolFromEnv(appName, options = {}) {
903
928
  const env = own.env ?? process.env;
904
929
  const resolution = resolveServerDataBackend(appName, env);
905
930
  const connectionString = resolveDatabaseUrl(appName, env);
906
- if (!connectionString) {
907
- throw new Error(`postgresql storage for ${appName} needs a database URL. Set ` + `HASNA_${appName.toUpperCase().replace(/-/g, "_")}_DATABASE_URL.`);
908
- }
909
931
  const pool = createPgPool({
910
932
  ...own,
911
933
  connectionString,
@@ -913,7 +935,7 @@ function createServerPoolFromEnv(appName, options = {}) {
913
935
  });
914
936
  return {
915
937
  client: createQueryClient(pool),
916
- connectionSource: resolution.databaseUrlSource ?? "unknown"
938
+ connectionSource: resolution.databaseUrlSource
917
939
  };
918
940
  }
919
941
  // src/generated/storage-kit/migrations.ts
@@ -2680,11 +2702,44 @@ function parseArg(name, fallback) {
2680
2702
  return process.argv[idx + 1];
2681
2703
  return fallback;
2682
2704
  }
2705
+ var USAGE = `domains-serve \u2014 standalone HTTP API server for @hasna/domains (PostgreSQL backend)
2706
+
2707
+ Usage: domains-serve [--port <n>] [--host <addr>]
2708
+
2709
+ Options:
2710
+ --port <n> Port to listen on (default: $PORT or ${DEFAULT_PORT})
2711
+ --host <addr> Address to bind (default: $HOST or 0.0.0.0)
2712
+ -h, --help Print this help and exit
2713
+ -V, --version Print the package version and exit
2714
+
2715
+ Environment:
2716
+ HASNA_DOMAINS_DATABASE_URL PostgreSQL DSN (falls back to DATABASE_URL)
2717
+ HASNA_DOMAINS_API_SIGNING_KEY HMAC signing secret for API keys
2718
+ (falls back to API_KEY_SIGNING_SECRET)`;
2719
+ function earlyArgAnswer(argv) {
2720
+ if (argv.includes("--help") || argv.includes("-h"))
2721
+ return USAGE;
2722
+ if (argv.includes("--version") || argv.includes("-V"))
2723
+ return getPackageVersion();
2724
+ return;
2725
+ }
2683
2726
  async function main() {
2684
- if (process.argv.includes("--version") || process.argv.includes("-V")) {
2685
- console.log(getPackageVersion());
2727
+ const early = earlyArgAnswer(process.argv);
2728
+ if (early !== undefined) {
2729
+ console.log(early);
2686
2730
  return;
2687
2731
  }
2732
+ await startDomainsServer({
2733
+ port: Number(parseArg("--port", process.env["PORT"]) ?? DEFAULT_PORT),
2734
+ host: parseArg("--host", process.env["HOST"]) ?? "0.0.0.0"
2735
+ });
2736
+ }
2737
+ async function startDomainsServer(options) {
2738
+ const { port, host } = options;
2739
+ if (!Number.isInteger(port) || port < 1 || port > 65535)
2740
+ throw new Error("Port must be an integer from 1 to 65535.");
2741
+ if (!host.trim())
2742
+ throw new Error("Host must not be blank.");
2688
2743
  normalizeEnv();
2689
2744
  const version = getPackageVersion();
2690
2745
  const signingSecret = resolveSigningSecret();
@@ -2704,8 +2759,6 @@ async function main() {
2704
2759
  }
2705
2760
  }
2706
2761
  });
2707
- const port = Number(parseArg("--port", process.env["PORT"]) ?? DEFAULT_PORT) || DEFAULT_PORT;
2708
- const host = parseArg("--host", process.env["HOST"]) ?? "0.0.0.0";
2709
2762
  Bun.serve({
2710
2763
  port,
2711
2764
  hostname: host,
@@ -2728,7 +2781,9 @@ if (import.meta.main) {
2728
2781
  });
2729
2782
  }
2730
2783
  export {
2784
+ startDomainsServer,
2731
2785
  resolveSigningSecret,
2732
2786
  normalizeEnv,
2787
+ USAGE,
2733
2788
  SIGNING_KEY_ENVS
2734
2789
  };
@@ -12,6 +12,41 @@ import { type Migration, type MigrationResult } from "../generated/storage-kit/i
12
12
  /** Env var holding the owner-role DSN (DDL privileges). Falls back to the app DSN. */
13
13
  export declare const OWNER_DSN_ENV = "HASNA_DOMAINS_DATABASE_URL_OWNER";
14
14
  export declare const APP_DSN_ENV = "HASNA_DOMAINS_DATABASE_URL";
15
+ /**
16
+ * Applied-ledger rows recorded before the ledger's id scheme stabilized, which
17
+ * no build in this repo's history generates (verified across every published
18
+ * tarball 0.0.30-0.0.46 and all git history). They are acknowledged via the
19
+ * storage kit's `acknowledgedLegacyIds`: they pass the downgrade guard, are
20
+ * never checksum-compared (their SQL is not reproducible from any source), and
21
+ * are never re-applied or re-inserted.
22
+ *
23
+ * `domains_apikeys_tenancy_0001`, `domains_apikeys_tenancy_0002`,
24
+ * `domains_tenancy_0001`..`domains_tenancy_0010` were recorded against the
25
+ * prod ledger out-of-band during the 2026-07 self-hosted cutover. Deploy
26
+ * evidence 2026-08-25: the 02:00Z pass failed on `_0001` (O15-00671 filed);
27
+ * the 16:44Z pass, carrying the kit with `_0001` acknowledged
28
+ * (hasna/apps#1176), advanced to `_0002`; the next pass then failed on the
29
+ * third row `domains_tenancy_0001` (O15-00758 filed), the 2026-08-25 PASS-18
30
+ * pass then failed on the fourth row `domains_tenancy_0002` at
31
+ * `domains-prod-migrate:42` (O15-00762 filed), and the 2026-08-25 pass then
32
+ * failed on the fifth row `domains_tenancy_0003` at `domains-prod-migrate:42`
33
+ * (O15-00766 filed). Deploy evidence 2026-08-26: passes 48-96 then failed on
34
+ * the sixth row `domains_tenancy_0004` at `domains-prod-migrate:44` through
35
+ * `:49` (todos 2b474505). Deploy evidence 2026-08-26/27: the 2026-08-27
36
+ * 06:47Z pass failed on the seventh row `domains_tenancy_0005` at
37
+ * `domains-prod-migrate:50` (O15-01822 filed). The ledger census (inspection
38
+ * task oss-fleet-prod/4b7c37626965439e96d5386c8e8a73d4) shows the complete
39
+ * out-of-band set is the 12 rows below — `domains_apikeys_tenancy_0001-0002`
40
+ * plus `domains_tenancy_0001..0010` — so this list covers the full measured
41
+ * prod ledger rather than one row per deploy pass. The apikeys rows'
42
+ * substance — the api-keys tenancy (`tid`) column — is carried today by
43
+ * `hasna_auth_0003_api_keys_tenant`; the substance of the `domains_tenancy_*`
44
+ * rows is not reproducible from any source in this repo, and no build
45
+ * generates the ids, so they are acknowledged as history rather than
46
+ * re-applied. Acknowledging all twelve unblocks `domains db migrate` (the ECS
47
+ * migrate task) and therefore the domains deploy lane.
48
+ */
49
+ export declare const ACKNOWLEDGED_LEGACY_MIGRATION_IDS: readonly string[];
15
50
  /** The ordered migration set: app schema first, then the shared api-keys table. */
16
51
  export declare function buildMigrations(): Migration[];
17
52
  /** Run all pending migrations against the owner DSN. */
@@ -1 +1 @@
1
- {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../src/server/migrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,eAAe,EACrB,MAAM,mCAAmC,CAAC;AAK3C,sFAAsF;AACtF,eAAO,MAAM,aAAa,qCAAqC,CAAC;AAChE,eAAO,MAAM,WAAW,+BAA+B,CAAC;AAGxD,mFAAmF;AACnF,wBAAgB,eAAe,IAAI,SAAS,EAAE,CAU7C;AAYD,wDAAwD;AACxD,wBAAsB,aAAa,CACjC,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;CAAO,GACvD,OAAO,CAAC,eAAe,CAAC,CAiB1B"}
1
+ {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../src/server/migrations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,EAGL,KAAK,SAAS,EACd,KAAK,eAAe,EACrB,MAAM,mCAAmC,CAAC;AAK3C,sFAAsF;AACtF,eAAO,MAAM,aAAa,qCAAqC,CAAC;AAChE,eAAO,MAAM,WAAW,+BAA+B,CAAC;AAGxD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,eAAO,MAAM,iCAAiC,EAAE,SAAS,MAAM,EAa9D,CAAC;AAEF,mFAAmF;AACnF,wBAAgB,eAAe,IAAI,SAAS,EAAE,CAU7C;AAYD,wDAAwD;AACxD,wBAAsB,aAAa,CACjC,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAA;CAAO,GACvD,OAAO,CAAC,eAAe,CAAC,CAa1B"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hasna/domains",
3
- "version": "0.0.46",
4
- "description": "Domain portfolio, registrar, marketplace, and DNS management for AI agents — CLI + MCP + HTTP API + SDK, local SQLite or cloud Postgres",
3
+ "version": "0.1.0",
4
+ "description": "Domain portfolio, registrar, marketplace, and DNS management for AI agents — CLI + MCP + HTTP API + SDK, shared account API and PostgreSQL",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -47,7 +47,8 @@
47
47
  "files": [
48
48
  "dist",
49
49
  "LICENSE",
50
- "README.md"
50
+ "README.md",
51
+ "postinstall.js"
51
52
  ],
52
53
  "scripts": {
53
54
  "clean": "rm -rf dist",
@@ -60,9 +61,9 @@
60
61
  "dev:mcp": "bun run src/mcp/index.ts",
61
62
  "dev:serve": "bun run src/server/index.ts",
62
63
  "prepublishOnly": "bun run build",
63
- "postinstall": "mkdir -p $HOME/.hasna/domains 2>/dev/null || true",
64
+ "postinstall": "node postinstall.js",
64
65
  "prepack": "bun run build && bun run artifact-scan",
65
- "artifact-scan": "bun pm pack --ignore-scripts --quiet --filename \"$PWD/domains-artifact-scan.tgz\" && bunx @hasna/contracts@0.14.1 artifact-scan domains-artifact-scan.tgz; rc=$?; rm -f \"$PWD/domains-artifact-scan.tgz\"; exit $rc"
66
+ "artifact-scan": "bun pm pack --ignore-scripts --quiet --filename \"$PWD/domains-artifact-scan.tgz\" && bunx @hasna/contracts@1.0.2 artifact-scan domains-artifact-scan.tgz; rc=$?; rm -f \"$PWD/domains-artifact-scan.tgz\"; exit $rc"
66
67
  },
67
68
  "keywords": [
68
69
  "domains",
@@ -97,7 +98,6 @@
97
98
  "@aws-sdk/client-route-53": "^3.1067.0",
98
99
  "@aws-sdk/client-route-53-domains": "^3.1067.0",
99
100
  "@aws-sdk/credential-provider-ini": "3.972.53",
100
- "@hasna/contracts": "0.14.1",
101
101
  "@modelcontextprotocol/sdk": "^1.29.0",
102
102
  "chalk": "^5.4.1",
103
103
  "commander": "^13.1.0",
@@ -119,8 +119,8 @@
119
119
  }
120
120
  },
121
121
  "devDependencies": {
122
- "@hasna/mcp-harness": "0.1.0",
123
- "@types/bun": "^1.3.14",
122
+ "@hasna/contracts": "1.0.2",
123
+ "@types/bun": "1.3.14",
124
124
  "@types/pg": "^8.20.0",
125
125
  "@types/react": "^18.3.18",
126
126
  "bun-types": "1.3.14",
package/postinstall.js ADDED
@@ -0,0 +1,41 @@
1
+ // Best-effort install-time creation of the domains LOCAL DATA home directory,
2
+ // resolving the SAME effective home the runtime uses (src/lib/app-home.ts):
3
+ // an exact-app override (HASNA_DOMAINS_HOME / HASNA_DOMAINS_DIR, then the
4
+ // legacy unprefixed DOMAINS_HOME / DOMAINS_DIR aliases) wins; otherwise
5
+ // $HASNA_HOME/domains (the shared root override); otherwise the legacy
6
+ // ~/.hasna/domains default. Faithfully mirrors the runtime module — no
7
+ // @hasna/paths, no XDG, no HASNA_*_HOME kind overrides (stripped, hasna/apps#1720
8
+ // class B). Failures are non-fatal: the runtime creates the same directory on
9
+ // first use.
10
+ import { chmodSync, existsSync, mkdirSync } from "node:fs";
11
+ import { homedir } from "node:os";
12
+ import { join } from "node:path";
13
+
14
+ try {
15
+ const env = process.env;
16
+ const override = (
17
+ env["HASNA_DOMAINS_HOME"] ||
18
+ env["HASNA_DOMAINS_DIR"] ||
19
+ env["DOMAINS_HOME"] ||
20
+ env["DOMAINS_DIR"] ||
21
+ ""
22
+ ).trim();
23
+ const home = env["HOME"] || env["USERPROFILE"] || homedir();
24
+
25
+ let dir;
26
+ if (override) {
27
+ dir = override;
28
+ } else {
29
+ const hasnaHome = (env["HASNA_HOME"] || "").trim();
30
+ dir = hasnaHome ? join(hasnaHome, "domains") : join(home, ".hasna", "domains");
31
+ }
32
+
33
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true, mode: 0o700 });
34
+ try {
35
+ chmodSync(dir, 0o700);
36
+ } catch {
37
+ // best-effort on platforms without POSIX perms
38
+ }
39
+ } catch {
40
+ // never fail an install over pre-created directories
41
+ }