@hasna/events 0.1.17 → 0.1.18
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/README.md +15 -1
- package/dist/cli/index.js +67 -70
- package/dist/commander.js +50 -46
- package/dist/durable.js +46 -53
- package/dist/index.js +38 -45
- package/dist/storage.js +38 -45
- package/package.json +2 -3
- package/types/app-home.d.ts +9 -0
- package/types/cli-webhook-policy.d.ts +8 -0
package/README.md
CHANGED
|
@@ -469,7 +469,10 @@ const events = new EventsClient({
|
|
|
469
469
|
|
|
470
470
|
## CLI
|
|
471
471
|
|
|
472
|
-
The package exposes `events` and `hasna-events`.
|
|
472
|
+
The package exposes `events` and `hasna-events`. `hasna-events` is a
|
|
473
|
+
**deprecated alias** of `events` (same entrypoint, kept for npm parity with
|
|
474
|
+
0.1.15); new usage should call `events` directly, and the alias is scheduled
|
|
475
|
+
for removal in a future major release (hasna/apps#1602).
|
|
473
476
|
|
|
474
477
|
Global options must come before the command group:
|
|
475
478
|
|
|
@@ -491,6 +494,17 @@ events channels match ops
|
|
|
491
494
|
events channels remove ops
|
|
492
495
|
```
|
|
493
496
|
|
|
497
|
+
`channels test` exits with code 1 when delivery fails and code 0 when delivery
|
|
498
|
+
succeeds or is skipped by `--honor-filters`. Its JSON delivery result and human
|
|
499
|
+
status output are preserved in either case.
|
|
500
|
+
|
|
501
|
+
The standalone CLI and default embedded Commander commands deny private webhook
|
|
502
|
+
targets unless the administrator sets `HASNA_EVENTS_ALLOW_PRIVATE_WEBHOOK_TARGETS`
|
|
503
|
+
to a comma-separated list of exact hostnames or IP addresses. For an intentional
|
|
504
|
+
local receiver, set `HASNA_EVENTS_ALLOW_PRIVATE_WEBHOOK_TARGETS=127.0.0.1`.
|
|
505
|
+
This setting does not change SDK defaults or override an embedded host's custom
|
|
506
|
+
`createClient` policy. Redirects and DNS results still undergo target validation.
|
|
507
|
+
|
|
494
508
|
Field filters can match nested `data` or `metadata` values. Plain
|
|
495
509
|
`--data`/`--metadata` values are strings, which keeps ids and slugs such as
|
|
496
510
|
`001` intact. Use `--data-json` or `--metadata-json` for typed JSON predicates.
|
package/dist/cli/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
// src/cli/index.ts
|
|
5
5
|
import { readFileSync as readFileSync2 } from "fs";
|
|
6
|
-
import { dirname, join as
|
|
6
|
+
import { dirname, join as join6 } from "path";
|
|
7
7
|
import { fileURLToPath } from "url";
|
|
8
8
|
|
|
9
9
|
// src/index.ts
|
|
@@ -110,87 +110,80 @@ function channelMatchesEvent(channel, event) {
|
|
|
110
110
|
import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
111
111
|
import { Buffer as Buffer2 } from "buffer";
|
|
112
112
|
import { existsSync as existsSync2 } from "fs";
|
|
113
|
-
import { join as
|
|
113
|
+
import { join as join2 } from "path";
|
|
114
114
|
|
|
115
115
|
// src/app-home.ts
|
|
116
116
|
import { existsSync } from "fs";
|
|
117
|
-
import { homedir as homedir2 } from "os";
|
|
118
|
-
import { join as join2, resolve } from "path";
|
|
119
|
-
|
|
120
|
-
// ../../node_modules/.bun/@hasna+paths@0.1.0/node_modules/@hasna/paths/dist/index.js
|
|
121
117
|
import { homedir } from "os";
|
|
122
|
-
import { join } from "path";
|
|
123
|
-
|
|
118
|
+
import { join, resolve } from "path";
|
|
119
|
+
import { homedir as pathsResolverHomedir } from "os";
|
|
120
|
+
import { join as pathsResolverJoin } from "path";
|
|
121
|
+
var PATHS_RESOLVER_KIND_ENV = {
|
|
124
122
|
config: "HASNA_CONFIG_HOME",
|
|
125
123
|
data: "HASNA_DATA_HOME",
|
|
126
124
|
state: "HASNA_STATE_HOME",
|
|
127
125
|
cache: "HASNA_CACHE_HOME"
|
|
128
126
|
};
|
|
129
|
-
var
|
|
130
|
-
function
|
|
127
|
+
var PATHS_RESOLVER_APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
128
|
+
function pathsResolverAssertApp(app) {
|
|
131
129
|
if (typeof app !== "string" || app.length === 0) {
|
|
132
130
|
throw new TypeError("paths: app must be a non-empty string");
|
|
133
131
|
}
|
|
134
|
-
if (!
|
|
132
|
+
if (!PATHS_RESOLVER_APP_SLUG_RE.test(app)) {
|
|
135
133
|
throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
|
|
136
134
|
}
|
|
137
135
|
}
|
|
138
|
-
function
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const value = envOf(options)[KIND_ENV[kind]];
|
|
143
|
-
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
144
|
-
}
|
|
145
|
-
function isMacOS(platform) {
|
|
146
|
-
return platform === "darwin";
|
|
136
|
+
function pathsResolverAssertKind(kind) {
|
|
137
|
+
if (!Object.keys(PATHS_RESOLVER_KIND_ENV).includes(kind)) {
|
|
138
|
+
throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(PATHS_RESOLVER_KIND_ENV).join(", ")}`);
|
|
139
|
+
}
|
|
147
140
|
}
|
|
148
|
-
function
|
|
149
|
-
|
|
150
|
-
|
|
141
|
+
function pathsResolverBaseDir(kind, options) {
|
|
142
|
+
pathsResolverAssertKind(kind);
|
|
143
|
+
const env = options.env ?? process.env;
|
|
144
|
+
const override = env[PATHS_RESOLVER_KIND_ENV[kind]];
|
|
145
|
+
if (typeof override === "string" && override.length > 0)
|
|
151
146
|
return override;
|
|
152
|
-
const home = options.home ??
|
|
147
|
+
const home = options.home ?? pathsResolverHomedir();
|
|
153
148
|
const platform = options.platform ?? process.platform;
|
|
154
|
-
if (
|
|
149
|
+
if (platform === "darwin") {
|
|
155
150
|
switch (kind) {
|
|
156
151
|
case "config":
|
|
157
152
|
case "data":
|
|
158
|
-
return
|
|
153
|
+
return pathsResolverJoin(home, "Library", "Application Support", "Hasna");
|
|
159
154
|
case "cache":
|
|
160
|
-
return
|
|
155
|
+
return pathsResolverJoin(home, "Library", "Caches", "Hasna");
|
|
161
156
|
case "state":
|
|
162
|
-
return
|
|
157
|
+
return pathsResolverJoin(home, "Library", "Logs", "Hasna");
|
|
163
158
|
}
|
|
164
159
|
}
|
|
165
160
|
switch (kind) {
|
|
166
161
|
case "config":
|
|
167
|
-
return
|
|
162
|
+
return pathsResolverJoin(home, ".config", "hasna");
|
|
168
163
|
case "data":
|
|
169
|
-
return
|
|
164
|
+
return pathsResolverJoin(home, ".local", "share", "hasna");
|
|
170
165
|
case "state":
|
|
171
|
-
return
|
|
166
|
+
return pathsResolverJoin(home, ".local", "state", "hasna");
|
|
172
167
|
case "cache":
|
|
173
|
-
return
|
|
168
|
+
return pathsResolverJoin(home, ".cache", "hasna");
|
|
174
169
|
}
|
|
175
170
|
}
|
|
176
|
-
function
|
|
177
|
-
|
|
178
|
-
const appSegment = options.internal === true ?
|
|
179
|
-
return
|
|
171
|
+
function pathsResolverResolve(kind, options) {
|
|
172
|
+
pathsResolverAssertApp(options.app);
|
|
173
|
+
const appSegment = options.internal === true ? pathsResolverJoin("internal", options.app) : options.app;
|
|
174
|
+
return pathsResolverJoin(pathsResolverBaseDir(kind, options), appSegment);
|
|
180
175
|
}
|
|
181
176
|
function dataDir(options) {
|
|
182
|
-
return
|
|
177
|
+
return pathsResolverResolve("data", options);
|
|
183
178
|
}
|
|
184
|
-
|
|
185
|
-
// src/app-home.ts
|
|
186
179
|
var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
|
|
187
180
|
var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
|
|
188
181
|
var EVENTS_STORE_SENTINEL_FILE = "events.json";
|
|
189
182
|
function effectiveHome() {
|
|
190
|
-
return process.env["HOME"] || process.env["USERPROFILE"] ||
|
|
183
|
+
return process.env["HOME"] || process.env["USERPROFILE"] || homedir();
|
|
191
184
|
}
|
|
192
185
|
function legacyHomeDir() {
|
|
193
|
-
return
|
|
186
|
+
return join(effectiveHome(), ".hasna", "events");
|
|
194
187
|
}
|
|
195
188
|
function resolverHome() {
|
|
196
189
|
return dataDir({ app: "events", home: effectiveHome() || undefined });
|
|
@@ -199,7 +192,7 @@ function adoptResolverHome(resolved, env = process.env) {
|
|
|
199
192
|
const dataOverride = env.HASNA_DATA_HOME;
|
|
200
193
|
if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
|
|
201
194
|
return true;
|
|
202
|
-
return existsSync(
|
|
195
|
+
return existsSync(join(resolved, EVENTS_STORE_SENTINEL_FILE));
|
|
203
196
|
}
|
|
204
197
|
function exactEventsHome() {
|
|
205
198
|
const dir = process.env[HASNA_EVENTS_DIR_ENV];
|
|
@@ -242,9 +235,9 @@ class JsonEventsStore {
|
|
|
242
235
|
constructor(dataDir2 = getEventsDataDir()) {
|
|
243
236
|
this.dataDir = dataDir2;
|
|
244
237
|
this.runtime = localJsonRuntime(dataDir2);
|
|
245
|
-
this.channelsPath =
|
|
246
|
-
this.eventsPath =
|
|
247
|
-
this.deliveriesPath =
|
|
238
|
+
this.channelsPath = join2(dataDir2, "channels.json");
|
|
239
|
+
this.eventsPath = join2(dataDir2, "events.json");
|
|
240
|
+
this.deliveriesPath = join2(dataDir2, "deliveries.json");
|
|
248
241
|
}
|
|
249
242
|
async init() {
|
|
250
243
|
await mkdir(this.dataDir, { recursive: true, mode: 448 });
|
|
@@ -512,7 +505,7 @@ async function getEventsStatus(dataDir2) {
|
|
|
512
505
|
};
|
|
513
506
|
}
|
|
514
507
|
function statusFile(dataDir2, fileName, records) {
|
|
515
|
-
const path =
|
|
508
|
+
const path = join2(dataDir2, fileName);
|
|
516
509
|
return { path, exists: existsSync2(path), records };
|
|
517
510
|
}
|
|
518
511
|
|
|
@@ -1414,7 +1407,7 @@ import {
|
|
|
1414
1407
|
unlinkSync,
|
|
1415
1408
|
writeFileSync
|
|
1416
1409
|
} from "fs";
|
|
1417
|
-
import { basename, join as
|
|
1410
|
+
import { basename, join as join3 } from "path";
|
|
1418
1411
|
var DURABLE_SCHEMA_VERSION = 1;
|
|
1419
1412
|
var MAX_RETRY_ATTEMPTS = 1000;
|
|
1420
1413
|
var MAX_RETRY_DELAY_MS = 365 * 24 * 60 * 60 * 1000;
|
|
@@ -1557,7 +1550,7 @@ class DurableEventsBroker {
|
|
|
1557
1550
|
if (!options.dataDir)
|
|
1558
1551
|
throw new Error("DurableEventsBroker requires dataDir");
|
|
1559
1552
|
this.dataDir = options.dataDir;
|
|
1560
|
-
this.databasePath =
|
|
1553
|
+
this.databasePath = join3(options.dataDir, options.databaseName ?? "events.sqlite");
|
|
1561
1554
|
this.now = options.now ?? (() => new Date);
|
|
1562
1555
|
this.transportOptions = {
|
|
1563
1556
|
fetchImpl: options.fetchImpl,
|
|
@@ -1701,14 +1694,14 @@ class DurableEventsBroker {
|
|
|
1701
1694
|
return summary;
|
|
1702
1695
|
}
|
|
1703
1696
|
importSpool(options = {}) {
|
|
1704
|
-
const inboxDir =
|
|
1697
|
+
const inboxDir = join3(this.dataDir, "spool", "inbox");
|
|
1705
1698
|
if (!existsSync3(inboxDir))
|
|
1706
1699
|
return { scanned: 0, imported: 0, deduped: 0, queued: 0, quarantined: 0 };
|
|
1707
1700
|
const limit = normalizePositiveInteger(options.limit, 100, "limit");
|
|
1708
1701
|
const names = readdirSync(inboxDir).filter((name) => /^[a-f0-9]{64}\.json$/.test(name)).sort().slice(0, limit);
|
|
1709
1702
|
const result = { scanned: names.length, imported: 0, deduped: 0, queued: 0, quarantined: 0 };
|
|
1710
1703
|
for (const name of names) {
|
|
1711
|
-
const path =
|
|
1704
|
+
const path = join3(inboxDir, name);
|
|
1712
1705
|
let event;
|
|
1713
1706
|
try {
|
|
1714
1707
|
event = parseSpoolEnvelope(readFileSync(path, "utf8"));
|
|
@@ -2180,22 +2173,22 @@ function syncDirectory(path) {
|
|
|
2180
2173
|
}
|
|
2181
2174
|
}
|
|
2182
2175
|
function quarantineSpoolRecord(dataDir2, path, reason) {
|
|
2183
|
-
const spoolDir =
|
|
2184
|
-
const quarantineDir =
|
|
2176
|
+
const spoolDir = join3(dataDir2, "spool");
|
|
2177
|
+
const quarantineDir = join3(spoolDir, "quarantine");
|
|
2185
2178
|
mkdirSync(quarantineDir, { recursive: true, mode: 448 });
|
|
2186
2179
|
chmodSync(spoolDir, 448);
|
|
2187
2180
|
chmodSync(quarantineDir, 448);
|
|
2188
2181
|
const name = basename(path);
|
|
2189
2182
|
const base = name.replace(/\.json$/, "");
|
|
2190
2183
|
const suffix = `${Date.now()}-${randomUUID3().slice(0, 8)}`;
|
|
2191
|
-
const destination =
|
|
2184
|
+
const destination = join3(quarantineDir, `${base}.${suffix}.json`);
|
|
2192
2185
|
renameSync(path, destination);
|
|
2193
2186
|
const metadata = {
|
|
2194
2187
|
quarantinedAt: new Date().toISOString(),
|
|
2195
2188
|
originalName: name,
|
|
2196
2189
|
reason
|
|
2197
2190
|
};
|
|
2198
|
-
writeFileSync(
|
|
2191
|
+
writeFileSync(join3(quarantineDir, `${base}.${suffix}.meta.json`), `${JSON.stringify(metadata, null, 2)}
|
|
2199
2192
|
`, { mode: 384 });
|
|
2200
2193
|
syncDirectory(quarantineDir);
|
|
2201
2194
|
}
|
|
@@ -2210,7 +2203,7 @@ function spoolFileName(event) {
|
|
|
2210
2203
|
// src/durable-worker.ts
|
|
2211
2204
|
import { chmodSync as chmodSync2, mkdirSync as mkdirSync2, watch } from "fs";
|
|
2212
2205
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
2213
|
-
import { join as
|
|
2206
|
+
import { join as join5 } from "path";
|
|
2214
2207
|
|
|
2215
2208
|
// src/durable-spool.ts
|
|
2216
2209
|
import { createHash as createHash2, randomUUID as randomUUID4 } from "crypto";
|
|
@@ -2224,7 +2217,7 @@ import {
|
|
|
2224
2217
|
stat,
|
|
2225
2218
|
unlink
|
|
2226
2219
|
} from "fs/promises";
|
|
2227
|
-
import { join as
|
|
2220
|
+
import { join as join4 } from "path";
|
|
2228
2221
|
class DurableEventSpool {
|
|
2229
2222
|
dataDir;
|
|
2230
2223
|
inboxDir;
|
|
@@ -2232,13 +2225,13 @@ class DurableEventSpool {
|
|
|
2232
2225
|
if (!options.dataDir)
|
|
2233
2226
|
throw new Error("DurableEventSpool requires dataDir");
|
|
2234
2227
|
this.dataDir = options.dataDir;
|
|
2235
|
-
this.inboxDir =
|
|
2228
|
+
this.inboxDir = join4(options.dataDir, "spool", "inbox");
|
|
2236
2229
|
}
|
|
2237
2230
|
async enqueue(input) {
|
|
2238
2231
|
const event = redactSensitiveKeys(createSpoolEvent(input));
|
|
2239
2232
|
await this.ensureInbox();
|
|
2240
2233
|
const finalPath = this.pathFor(event);
|
|
2241
|
-
const tempPath =
|
|
2234
|
+
const tempPath = join4(this.inboxDir, `.tmp-${process.pid}-${randomUUID4()}`);
|
|
2242
2235
|
const handle = await open(tempPath, "wx", 384);
|
|
2243
2236
|
try {
|
|
2244
2237
|
await handle.writeFile(`${JSON.stringify(event)}
|
|
@@ -2271,7 +2264,7 @@ class DurableEventSpool {
|
|
|
2271
2264
|
const result = { recovered: 0, deduped: 0, cleaned: 0 };
|
|
2272
2265
|
const names = (await readdir(this.inboxDir)).filter((name) => name.startsWith(".tmp-")).sort();
|
|
2273
2266
|
for (const name of names) {
|
|
2274
|
-
const tempPath =
|
|
2267
|
+
const tempPath = join4(this.inboxDir, name);
|
|
2275
2268
|
const details = await stat(tempPath).catch(() => {
|
|
2276
2269
|
return;
|
|
2277
2270
|
});
|
|
@@ -2309,7 +2302,7 @@ class DurableEventSpool {
|
|
|
2309
2302
|
pathFor(event) {
|
|
2310
2303
|
const identity = event.dedupeKey ?? event.id;
|
|
2311
2304
|
const digest = createHash2("sha256").update(identity, "utf8").digest("hex");
|
|
2312
|
-
return
|
|
2305
|
+
return join4(this.inboxDir, `${digest}.json`);
|
|
2313
2306
|
}
|
|
2314
2307
|
async assertSameIdentity(path, event) {
|
|
2315
2308
|
const existing = parseEnvelope(await readFile2(path, "utf8"));
|
|
@@ -2318,7 +2311,7 @@ class DurableEventSpool {
|
|
|
2318
2311
|
throw new Error("Durable spool identity collision");
|
|
2319
2312
|
}
|
|
2320
2313
|
async ensureInbox() {
|
|
2321
|
-
const spoolDir =
|
|
2314
|
+
const spoolDir = join4(this.dataDir, "spool");
|
|
2322
2315
|
await mkdir2(this.inboxDir, { recursive: true, mode: 448 });
|
|
2323
2316
|
await chmod2(this.dataDir, 448);
|
|
2324
2317
|
await chmod2(spoolDir, 448);
|
|
@@ -2386,7 +2379,7 @@ async function runDurableWorker(options) {
|
|
|
2386
2379
|
const spool = new DurableEventSpool({ dataDir: options.broker.dataDir });
|
|
2387
2380
|
const inboxDir = spool.inboxDir;
|
|
2388
2381
|
mkdirSync2(inboxDir, { recursive: true, mode: 448 });
|
|
2389
|
-
chmodSync2(
|
|
2382
|
+
chmodSync2(join5(options.broker.dataDir, "spool"), 448);
|
|
2390
2383
|
chmodSync2(inboxDir, 448);
|
|
2391
2384
|
const totals = {
|
|
2392
2385
|
workerId,
|
|
@@ -2612,10 +2605,19 @@ function parseMatcherExpression(value, label) {
|
|
|
2612
2605
|
};
|
|
2613
2606
|
}
|
|
2614
2607
|
|
|
2608
|
+
// src/cli-webhook-policy.ts
|
|
2609
|
+
function webhookTargetPolicyFromEnv() {
|
|
2610
|
+
const value = process.env.HASNA_EVENTS_ALLOW_PRIVATE_WEBHOOK_TARGETS;
|
|
2611
|
+
if (!value)
|
|
2612
|
+
return;
|
|
2613
|
+
const hosts = value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
2614
|
+
return hosts.length > 0 ? { allowPrivateHosts: hosts } : undefined;
|
|
2615
|
+
}
|
|
2616
|
+
|
|
2615
2617
|
// src/cli/index.ts
|
|
2616
2618
|
function version() {
|
|
2617
2619
|
try {
|
|
2618
|
-
const packagePath =
|
|
2620
|
+
const packagePath = join6(dirname(fileURLToPath(import.meta.url)), "..", "..", "package.json");
|
|
2619
2621
|
return JSON.parse(readFileSync2(packagePath, "utf-8")).version ?? "0.0.0";
|
|
2620
2622
|
} catch {
|
|
2621
2623
|
return "0.0.0";
|
|
@@ -3158,6 +3160,8 @@ async function handleChannels(client, command, tail, parsed, options) {
|
|
|
3158
3160
|
metadata: parseJsonOption(takeOption(args, "--metadata"), {})
|
|
3159
3161
|
}, { honorFilters });
|
|
3160
3162
|
output(parsed, result, () => console.log(`${result.status}: ${result.channelId}`));
|
|
3163
|
+
if (result.status === "failed")
|
|
3164
|
+
process.exitCode = 1;
|
|
3161
3165
|
return;
|
|
3162
3166
|
}
|
|
3163
3167
|
if (command === "match") {
|
|
@@ -3268,13 +3272,6 @@ function replaySummary(events, deliveries, nextCursor) {
|
|
|
3268
3272
|
const suffix = nextCursor ? `, next cursor: ${nextCursor}` : "";
|
|
3269
3273
|
return `Replayed ${events} event(s), ${deliveries} delivery result(s)${suffix}`;
|
|
3270
3274
|
}
|
|
3271
|
-
function webhookTargetPolicyFromEnv() {
|
|
3272
|
-
const value = process.env.HASNA_EVENTS_ALLOW_PRIVATE_WEBHOOK_TARGETS;
|
|
3273
|
-
if (!value)
|
|
3274
|
-
return;
|
|
3275
|
-
const hosts = value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
3276
|
-
return hosts.length > 0 ? { allowPrivateHosts: hosts } : undefined;
|
|
3277
|
-
}
|
|
3278
3275
|
if (import.meta.main) {
|
|
3279
3276
|
runEventsCli().catch((error) => {
|
|
3280
3277
|
const parsed = parseGlobalArgs(process.argv.slice(2));
|
package/dist/commander.js
CHANGED
|
@@ -100,87 +100,80 @@ function channelMatchesEvent(channel, event) {
|
|
|
100
100
|
import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
101
101
|
import { Buffer as Buffer2 } from "buffer";
|
|
102
102
|
import { existsSync as existsSync2 } from "fs";
|
|
103
|
-
import { join as
|
|
103
|
+
import { join as join2 } from "path";
|
|
104
104
|
|
|
105
105
|
// src/app-home.ts
|
|
106
106
|
import { existsSync } from "fs";
|
|
107
|
-
import { homedir as homedir2 } from "os";
|
|
108
|
-
import { join as join2, resolve } from "path";
|
|
109
|
-
|
|
110
|
-
// ../../node_modules/.bun/@hasna+paths@0.1.0/node_modules/@hasna/paths/dist/index.js
|
|
111
107
|
import { homedir } from "os";
|
|
112
|
-
import { join } from "path";
|
|
113
|
-
|
|
108
|
+
import { join, resolve } from "path";
|
|
109
|
+
import { homedir as pathsResolverHomedir } from "os";
|
|
110
|
+
import { join as pathsResolverJoin } from "path";
|
|
111
|
+
var PATHS_RESOLVER_KIND_ENV = {
|
|
114
112
|
config: "HASNA_CONFIG_HOME",
|
|
115
113
|
data: "HASNA_DATA_HOME",
|
|
116
114
|
state: "HASNA_STATE_HOME",
|
|
117
115
|
cache: "HASNA_CACHE_HOME"
|
|
118
116
|
};
|
|
119
|
-
var
|
|
120
|
-
function
|
|
117
|
+
var PATHS_RESOLVER_APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
118
|
+
function pathsResolverAssertApp(app) {
|
|
121
119
|
if (typeof app !== "string" || app.length === 0) {
|
|
122
120
|
throw new TypeError("paths: app must be a non-empty string");
|
|
123
121
|
}
|
|
124
|
-
if (!
|
|
122
|
+
if (!PATHS_RESOLVER_APP_SLUG_RE.test(app)) {
|
|
125
123
|
throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
|
|
126
124
|
}
|
|
127
125
|
}
|
|
128
|
-
function
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const value = envOf(options)[KIND_ENV[kind]];
|
|
133
|
-
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
134
|
-
}
|
|
135
|
-
function isMacOS(platform) {
|
|
136
|
-
return platform === "darwin";
|
|
126
|
+
function pathsResolverAssertKind(kind) {
|
|
127
|
+
if (!Object.keys(PATHS_RESOLVER_KIND_ENV).includes(kind)) {
|
|
128
|
+
throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(PATHS_RESOLVER_KIND_ENV).join(", ")}`);
|
|
129
|
+
}
|
|
137
130
|
}
|
|
138
|
-
function
|
|
139
|
-
|
|
140
|
-
|
|
131
|
+
function pathsResolverBaseDir(kind, options) {
|
|
132
|
+
pathsResolverAssertKind(kind);
|
|
133
|
+
const env = options.env ?? process.env;
|
|
134
|
+
const override = env[PATHS_RESOLVER_KIND_ENV[kind]];
|
|
135
|
+
if (typeof override === "string" && override.length > 0)
|
|
141
136
|
return override;
|
|
142
|
-
const home = options.home ??
|
|
137
|
+
const home = options.home ?? pathsResolverHomedir();
|
|
143
138
|
const platform = options.platform ?? process.platform;
|
|
144
|
-
if (
|
|
139
|
+
if (platform === "darwin") {
|
|
145
140
|
switch (kind) {
|
|
146
141
|
case "config":
|
|
147
142
|
case "data":
|
|
148
|
-
return
|
|
143
|
+
return pathsResolverJoin(home, "Library", "Application Support", "Hasna");
|
|
149
144
|
case "cache":
|
|
150
|
-
return
|
|
145
|
+
return pathsResolverJoin(home, "Library", "Caches", "Hasna");
|
|
151
146
|
case "state":
|
|
152
|
-
return
|
|
147
|
+
return pathsResolverJoin(home, "Library", "Logs", "Hasna");
|
|
153
148
|
}
|
|
154
149
|
}
|
|
155
150
|
switch (kind) {
|
|
156
151
|
case "config":
|
|
157
|
-
return
|
|
152
|
+
return pathsResolverJoin(home, ".config", "hasna");
|
|
158
153
|
case "data":
|
|
159
|
-
return
|
|
154
|
+
return pathsResolverJoin(home, ".local", "share", "hasna");
|
|
160
155
|
case "state":
|
|
161
|
-
return
|
|
156
|
+
return pathsResolverJoin(home, ".local", "state", "hasna");
|
|
162
157
|
case "cache":
|
|
163
|
-
return
|
|
158
|
+
return pathsResolverJoin(home, ".cache", "hasna");
|
|
164
159
|
}
|
|
165
160
|
}
|
|
166
|
-
function
|
|
167
|
-
|
|
168
|
-
const appSegment = options.internal === true ?
|
|
169
|
-
return
|
|
161
|
+
function pathsResolverResolve(kind, options) {
|
|
162
|
+
pathsResolverAssertApp(options.app);
|
|
163
|
+
const appSegment = options.internal === true ? pathsResolverJoin("internal", options.app) : options.app;
|
|
164
|
+
return pathsResolverJoin(pathsResolverBaseDir(kind, options), appSegment);
|
|
170
165
|
}
|
|
171
166
|
function dataDir(options) {
|
|
172
|
-
return
|
|
167
|
+
return pathsResolverResolve("data", options);
|
|
173
168
|
}
|
|
174
|
-
|
|
175
|
-
// src/app-home.ts
|
|
176
169
|
var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
|
|
177
170
|
var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
|
|
178
171
|
var EVENTS_STORE_SENTINEL_FILE = "events.json";
|
|
179
172
|
function effectiveHome() {
|
|
180
|
-
return process.env["HOME"] || process.env["USERPROFILE"] ||
|
|
173
|
+
return process.env["HOME"] || process.env["USERPROFILE"] || homedir();
|
|
181
174
|
}
|
|
182
175
|
function legacyHomeDir() {
|
|
183
|
-
return
|
|
176
|
+
return join(effectiveHome(), ".hasna", "events");
|
|
184
177
|
}
|
|
185
178
|
function resolverHome() {
|
|
186
179
|
return dataDir({ app: "events", home: effectiveHome() || undefined });
|
|
@@ -189,7 +182,7 @@ function adoptResolverHome(resolved, env = process.env) {
|
|
|
189
182
|
const dataOverride = env.HASNA_DATA_HOME;
|
|
190
183
|
if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
|
|
191
184
|
return true;
|
|
192
|
-
return existsSync(
|
|
185
|
+
return existsSync(join(resolved, EVENTS_STORE_SENTINEL_FILE));
|
|
193
186
|
}
|
|
194
187
|
function exactEventsHome() {
|
|
195
188
|
const dir = process.env[HASNA_EVENTS_DIR_ENV];
|
|
@@ -232,9 +225,9 @@ class JsonEventsStore {
|
|
|
232
225
|
constructor(dataDir2 = getEventsDataDir()) {
|
|
233
226
|
this.dataDir = dataDir2;
|
|
234
227
|
this.runtime = localJsonRuntime(dataDir2);
|
|
235
|
-
this.channelsPath =
|
|
236
|
-
this.eventsPath =
|
|
237
|
-
this.deliveriesPath =
|
|
228
|
+
this.channelsPath = join2(dataDir2, "channels.json");
|
|
229
|
+
this.eventsPath = join2(dataDir2, "events.json");
|
|
230
|
+
this.deliveriesPath = join2(dataDir2, "deliveries.json");
|
|
238
231
|
}
|
|
239
232
|
async init() {
|
|
240
233
|
await mkdir(this.dataDir, { recursive: true, mode: 448 });
|
|
@@ -502,7 +495,7 @@ async function getEventsStatus(dataDir2) {
|
|
|
502
495
|
};
|
|
503
496
|
}
|
|
504
497
|
function statusFile(dataDir2, fileName, records) {
|
|
505
|
-
const path =
|
|
498
|
+
const path = join2(dataDir2, fileName);
|
|
506
499
|
return { path, exists: existsSync2(path), records };
|
|
507
500
|
}
|
|
508
501
|
|
|
@@ -1980,6 +1973,15 @@ function parseMatcherExpression(value, label) {
|
|
|
1980
1973
|
};
|
|
1981
1974
|
}
|
|
1982
1975
|
|
|
1976
|
+
// src/cli-webhook-policy.ts
|
|
1977
|
+
function webhookTargetPolicyFromEnv() {
|
|
1978
|
+
const value = process.env.HASNA_EVENTS_ALLOW_PRIVATE_WEBHOOK_TARGETS;
|
|
1979
|
+
if (!value)
|
|
1980
|
+
return;
|
|
1981
|
+
const hosts = value.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0);
|
|
1982
|
+
return hosts.length > 0 ? { allowPrivateHosts: hosts } : undefined;
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1983
1985
|
// src/commander.ts
|
|
1984
1986
|
var DEFAULT_EVENT_LIST_LIMIT = 100;
|
|
1985
1987
|
function parseJsonObject(value, fallback) {
|
|
@@ -2006,7 +2008,7 @@ function parseHeaders(values) {
|
|
|
2006
2008
|
function createClient(options) {
|
|
2007
2009
|
if (options.createClient)
|
|
2008
2010
|
return options.createClient();
|
|
2009
|
-
return new EventsClient({ store: new JsonEventsStore(options.dataDir) });
|
|
2011
|
+
return new EventsClient({ store: new JsonEventsStore(options.dataDir), webhookTargetPolicy: webhookTargetPolicyFromEnv() });
|
|
2010
2012
|
}
|
|
2011
2013
|
function print(value, json, text) {
|
|
2012
2014
|
if (json)
|
|
@@ -2087,6 +2089,8 @@ function registerChannelCommands(program, options) {
|
|
|
2087
2089
|
metadata: parseJsonObject(actionOptions.metadata, {})
|
|
2088
2090
|
}, { honorFilters: actionOptions.honorFilters });
|
|
2089
2091
|
print(result, json, `${result.status}: ${result.channelId}`);
|
|
2092
|
+
if (result.status === "failed")
|
|
2093
|
+
process.exitCode = 1;
|
|
2090
2094
|
} catch (error) {
|
|
2091
2095
|
fail(error, json);
|
|
2092
2096
|
}
|
package/dist/durable.js
CHANGED
|
@@ -100,87 +100,80 @@ function channelMatchesEvent(channel, event) {
|
|
|
100
100
|
import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
101
101
|
import { Buffer as Buffer2 } from "buffer";
|
|
102
102
|
import { existsSync as existsSync2 } from "fs";
|
|
103
|
-
import { join as
|
|
103
|
+
import { join as join2 } from "path";
|
|
104
104
|
|
|
105
105
|
// src/app-home.ts
|
|
106
106
|
import { existsSync } from "fs";
|
|
107
|
-
import { homedir as homedir2 } from "os";
|
|
108
|
-
import { join as join2, resolve } from "path";
|
|
109
|
-
|
|
110
|
-
// ../../node_modules/.bun/@hasna+paths@0.1.0/node_modules/@hasna/paths/dist/index.js
|
|
111
107
|
import { homedir } from "os";
|
|
112
|
-
import { join } from "path";
|
|
113
|
-
|
|
108
|
+
import { join, resolve } from "path";
|
|
109
|
+
import { homedir as pathsResolverHomedir } from "os";
|
|
110
|
+
import { join as pathsResolverJoin } from "path";
|
|
111
|
+
var PATHS_RESOLVER_KIND_ENV = {
|
|
114
112
|
config: "HASNA_CONFIG_HOME",
|
|
115
113
|
data: "HASNA_DATA_HOME",
|
|
116
114
|
state: "HASNA_STATE_HOME",
|
|
117
115
|
cache: "HASNA_CACHE_HOME"
|
|
118
116
|
};
|
|
119
|
-
var
|
|
120
|
-
function
|
|
117
|
+
var PATHS_RESOLVER_APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
118
|
+
function pathsResolverAssertApp(app) {
|
|
121
119
|
if (typeof app !== "string" || app.length === 0) {
|
|
122
120
|
throw new TypeError("paths: app must be a non-empty string");
|
|
123
121
|
}
|
|
124
|
-
if (!
|
|
122
|
+
if (!PATHS_RESOLVER_APP_SLUG_RE.test(app)) {
|
|
125
123
|
throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
|
|
126
124
|
}
|
|
127
125
|
}
|
|
128
|
-
function
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const value = envOf(options)[KIND_ENV[kind]];
|
|
133
|
-
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
134
|
-
}
|
|
135
|
-
function isMacOS(platform) {
|
|
136
|
-
return platform === "darwin";
|
|
126
|
+
function pathsResolverAssertKind(kind) {
|
|
127
|
+
if (!Object.keys(PATHS_RESOLVER_KIND_ENV).includes(kind)) {
|
|
128
|
+
throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(PATHS_RESOLVER_KIND_ENV).join(", ")}`);
|
|
129
|
+
}
|
|
137
130
|
}
|
|
138
|
-
function
|
|
139
|
-
|
|
140
|
-
|
|
131
|
+
function pathsResolverBaseDir(kind, options) {
|
|
132
|
+
pathsResolverAssertKind(kind);
|
|
133
|
+
const env = options.env ?? process.env;
|
|
134
|
+
const override = env[PATHS_RESOLVER_KIND_ENV[kind]];
|
|
135
|
+
if (typeof override === "string" && override.length > 0)
|
|
141
136
|
return override;
|
|
142
|
-
const home = options.home ??
|
|
137
|
+
const home = options.home ?? pathsResolverHomedir();
|
|
143
138
|
const platform = options.platform ?? process.platform;
|
|
144
|
-
if (
|
|
139
|
+
if (platform === "darwin") {
|
|
145
140
|
switch (kind) {
|
|
146
141
|
case "config":
|
|
147
142
|
case "data":
|
|
148
|
-
return
|
|
143
|
+
return pathsResolverJoin(home, "Library", "Application Support", "Hasna");
|
|
149
144
|
case "cache":
|
|
150
|
-
return
|
|
145
|
+
return pathsResolverJoin(home, "Library", "Caches", "Hasna");
|
|
151
146
|
case "state":
|
|
152
|
-
return
|
|
147
|
+
return pathsResolverJoin(home, "Library", "Logs", "Hasna");
|
|
153
148
|
}
|
|
154
149
|
}
|
|
155
150
|
switch (kind) {
|
|
156
151
|
case "config":
|
|
157
|
-
return
|
|
152
|
+
return pathsResolverJoin(home, ".config", "hasna");
|
|
158
153
|
case "data":
|
|
159
|
-
return
|
|
154
|
+
return pathsResolverJoin(home, ".local", "share", "hasna");
|
|
160
155
|
case "state":
|
|
161
|
-
return
|
|
156
|
+
return pathsResolverJoin(home, ".local", "state", "hasna");
|
|
162
157
|
case "cache":
|
|
163
|
-
return
|
|
158
|
+
return pathsResolverJoin(home, ".cache", "hasna");
|
|
164
159
|
}
|
|
165
160
|
}
|
|
166
|
-
function
|
|
167
|
-
|
|
168
|
-
const appSegment = options.internal === true ?
|
|
169
|
-
return
|
|
161
|
+
function pathsResolverResolve(kind, options) {
|
|
162
|
+
pathsResolverAssertApp(options.app);
|
|
163
|
+
const appSegment = options.internal === true ? pathsResolverJoin("internal", options.app) : options.app;
|
|
164
|
+
return pathsResolverJoin(pathsResolverBaseDir(kind, options), appSegment);
|
|
170
165
|
}
|
|
171
166
|
function dataDir(options) {
|
|
172
|
-
return
|
|
167
|
+
return pathsResolverResolve("data", options);
|
|
173
168
|
}
|
|
174
|
-
|
|
175
|
-
// src/app-home.ts
|
|
176
169
|
var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
|
|
177
170
|
var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
|
|
178
171
|
var EVENTS_STORE_SENTINEL_FILE = "events.json";
|
|
179
172
|
function effectiveHome() {
|
|
180
|
-
return process.env["HOME"] || process.env["USERPROFILE"] ||
|
|
173
|
+
return process.env["HOME"] || process.env["USERPROFILE"] || homedir();
|
|
181
174
|
}
|
|
182
175
|
function legacyHomeDir() {
|
|
183
|
-
return
|
|
176
|
+
return join(effectiveHome(), ".hasna", "events");
|
|
184
177
|
}
|
|
185
178
|
function resolverHome() {
|
|
186
179
|
return dataDir({ app: "events", home: effectiveHome() || undefined });
|
|
@@ -189,7 +182,7 @@ function adoptResolverHome(resolved, env = process.env) {
|
|
|
189
182
|
const dataOverride = env.HASNA_DATA_HOME;
|
|
190
183
|
if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
|
|
191
184
|
return true;
|
|
192
|
-
return existsSync(
|
|
185
|
+
return existsSync(join(resolved, EVENTS_STORE_SENTINEL_FILE));
|
|
193
186
|
}
|
|
194
187
|
function exactEventsHome() {
|
|
195
188
|
const dir = process.env[HASNA_EVENTS_DIR_ENV];
|
|
@@ -232,9 +225,9 @@ class JsonEventsStore {
|
|
|
232
225
|
constructor(dataDir2 = getEventsDataDir()) {
|
|
233
226
|
this.dataDir = dataDir2;
|
|
234
227
|
this.runtime = localJsonRuntime(dataDir2);
|
|
235
|
-
this.channelsPath =
|
|
236
|
-
this.eventsPath =
|
|
237
|
-
this.deliveriesPath =
|
|
228
|
+
this.channelsPath = join2(dataDir2, "channels.json");
|
|
229
|
+
this.eventsPath = join2(dataDir2, "events.json");
|
|
230
|
+
this.deliveriesPath = join2(dataDir2, "deliveries.json");
|
|
238
231
|
}
|
|
239
232
|
async init() {
|
|
240
233
|
await mkdir(this.dataDir, { recursive: true, mode: 448 });
|
|
@@ -502,7 +495,7 @@ async function getEventsStatus(dataDir2) {
|
|
|
502
495
|
};
|
|
503
496
|
}
|
|
504
497
|
function statusFile(dataDir2, fileName, records) {
|
|
505
|
-
const path =
|
|
498
|
+
const path = join2(dataDir2, fileName);
|
|
506
499
|
return { path, exists: existsSync2(path), records };
|
|
507
500
|
}
|
|
508
501
|
|
|
@@ -1924,7 +1917,7 @@ import {
|
|
|
1924
1917
|
unlinkSync,
|
|
1925
1918
|
writeFileSync
|
|
1926
1919
|
} from "fs";
|
|
1927
|
-
import { basename, join as
|
|
1920
|
+
import { basename, join as join3 } from "path";
|
|
1928
1921
|
var DURABLE_SCHEMA_VERSION = 1;
|
|
1929
1922
|
var MAX_RETRY_ATTEMPTS = 1000;
|
|
1930
1923
|
var MAX_RETRY_DELAY_MS = 365 * 24 * 60 * 60 * 1000;
|
|
@@ -2067,7 +2060,7 @@ class DurableEventsBroker {
|
|
|
2067
2060
|
if (!options.dataDir)
|
|
2068
2061
|
throw new Error("DurableEventsBroker requires dataDir");
|
|
2069
2062
|
this.dataDir = options.dataDir;
|
|
2070
|
-
this.databasePath =
|
|
2063
|
+
this.databasePath = join3(options.dataDir, options.databaseName ?? "events.sqlite");
|
|
2071
2064
|
this.now = options.now ?? (() => new Date);
|
|
2072
2065
|
this.transportOptions = {
|
|
2073
2066
|
fetchImpl: options.fetchImpl,
|
|
@@ -2211,14 +2204,14 @@ class DurableEventsBroker {
|
|
|
2211
2204
|
return summary;
|
|
2212
2205
|
}
|
|
2213
2206
|
importSpool(options = {}) {
|
|
2214
|
-
const inboxDir =
|
|
2207
|
+
const inboxDir = join3(this.dataDir, "spool", "inbox");
|
|
2215
2208
|
if (!existsSync3(inboxDir))
|
|
2216
2209
|
return { scanned: 0, imported: 0, deduped: 0, queued: 0, quarantined: 0 };
|
|
2217
2210
|
const limit = normalizePositiveInteger(options.limit, 100, "limit");
|
|
2218
2211
|
const names = readdirSync(inboxDir).filter((name) => /^[a-f0-9]{64}\.json$/.test(name)).sort().slice(0, limit);
|
|
2219
2212
|
const result = { scanned: names.length, imported: 0, deduped: 0, queued: 0, quarantined: 0 };
|
|
2220
2213
|
for (const name of names) {
|
|
2221
|
-
const path =
|
|
2214
|
+
const path = join3(inboxDir, name);
|
|
2222
2215
|
let event;
|
|
2223
2216
|
try {
|
|
2224
2217
|
event = parseSpoolEnvelope(readFileSync(path, "utf8"));
|
|
@@ -2690,22 +2683,22 @@ function syncDirectory(path) {
|
|
|
2690
2683
|
}
|
|
2691
2684
|
}
|
|
2692
2685
|
function quarantineSpoolRecord(dataDir2, path, reason) {
|
|
2693
|
-
const spoolDir =
|
|
2694
|
-
const quarantineDir =
|
|
2686
|
+
const spoolDir = join3(dataDir2, "spool");
|
|
2687
|
+
const quarantineDir = join3(spoolDir, "quarantine");
|
|
2695
2688
|
mkdirSync(quarantineDir, { recursive: true, mode: 448 });
|
|
2696
2689
|
chmodSync(spoolDir, 448);
|
|
2697
2690
|
chmodSync(quarantineDir, 448);
|
|
2698
2691
|
const name = basename(path);
|
|
2699
2692
|
const base = name.replace(/\.json$/, "");
|
|
2700
2693
|
const suffix = `${Date.now()}-${randomUUID3().slice(0, 8)}`;
|
|
2701
|
-
const destination =
|
|
2694
|
+
const destination = join3(quarantineDir, `${base}.${suffix}.json`);
|
|
2702
2695
|
renameSync(path, destination);
|
|
2703
2696
|
const metadata = {
|
|
2704
2697
|
quarantinedAt: new Date().toISOString(),
|
|
2705
2698
|
originalName: name,
|
|
2706
2699
|
reason
|
|
2707
2700
|
};
|
|
2708
|
-
writeFileSync(
|
|
2701
|
+
writeFileSync(join3(quarantineDir, `${base}.${suffix}.meta.json`), `${JSON.stringify(metadata, null, 2)}
|
|
2709
2702
|
`, { mode: 384 });
|
|
2710
2703
|
syncDirectory(quarantineDir);
|
|
2711
2704
|
}
|
package/dist/index.js
CHANGED
|
@@ -100,87 +100,80 @@ function channelMatchesEvent(channel, event) {
|
|
|
100
100
|
import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
101
101
|
import { Buffer as Buffer2 } from "buffer";
|
|
102
102
|
import { existsSync as existsSync2 } from "fs";
|
|
103
|
-
import { join as
|
|
103
|
+
import { join as join2 } from "path";
|
|
104
104
|
|
|
105
105
|
// src/app-home.ts
|
|
106
106
|
import { existsSync } from "fs";
|
|
107
|
-
import { homedir as homedir2 } from "os";
|
|
108
|
-
import { join as join2, resolve } from "path";
|
|
109
|
-
|
|
110
|
-
// ../../node_modules/.bun/@hasna+paths@0.1.0/node_modules/@hasna/paths/dist/index.js
|
|
111
107
|
import { homedir } from "os";
|
|
112
|
-
import { join } from "path";
|
|
113
|
-
|
|
108
|
+
import { join, resolve } from "path";
|
|
109
|
+
import { homedir as pathsResolverHomedir } from "os";
|
|
110
|
+
import { join as pathsResolverJoin } from "path";
|
|
111
|
+
var PATHS_RESOLVER_KIND_ENV = {
|
|
114
112
|
config: "HASNA_CONFIG_HOME",
|
|
115
113
|
data: "HASNA_DATA_HOME",
|
|
116
114
|
state: "HASNA_STATE_HOME",
|
|
117
115
|
cache: "HASNA_CACHE_HOME"
|
|
118
116
|
};
|
|
119
|
-
var
|
|
120
|
-
function
|
|
117
|
+
var PATHS_RESOLVER_APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
118
|
+
function pathsResolverAssertApp(app) {
|
|
121
119
|
if (typeof app !== "string" || app.length === 0) {
|
|
122
120
|
throw new TypeError("paths: app must be a non-empty string");
|
|
123
121
|
}
|
|
124
|
-
if (!
|
|
122
|
+
if (!PATHS_RESOLVER_APP_SLUG_RE.test(app)) {
|
|
125
123
|
throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
|
|
126
124
|
}
|
|
127
125
|
}
|
|
128
|
-
function
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const value = envOf(options)[KIND_ENV[kind]];
|
|
133
|
-
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
134
|
-
}
|
|
135
|
-
function isMacOS(platform) {
|
|
136
|
-
return platform === "darwin";
|
|
126
|
+
function pathsResolverAssertKind(kind) {
|
|
127
|
+
if (!Object.keys(PATHS_RESOLVER_KIND_ENV).includes(kind)) {
|
|
128
|
+
throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(PATHS_RESOLVER_KIND_ENV).join(", ")}`);
|
|
129
|
+
}
|
|
137
130
|
}
|
|
138
|
-
function
|
|
139
|
-
|
|
140
|
-
|
|
131
|
+
function pathsResolverBaseDir(kind, options) {
|
|
132
|
+
pathsResolverAssertKind(kind);
|
|
133
|
+
const env = options.env ?? process.env;
|
|
134
|
+
const override = env[PATHS_RESOLVER_KIND_ENV[kind]];
|
|
135
|
+
if (typeof override === "string" && override.length > 0)
|
|
141
136
|
return override;
|
|
142
|
-
const home = options.home ??
|
|
137
|
+
const home = options.home ?? pathsResolverHomedir();
|
|
143
138
|
const platform = options.platform ?? process.platform;
|
|
144
|
-
if (
|
|
139
|
+
if (platform === "darwin") {
|
|
145
140
|
switch (kind) {
|
|
146
141
|
case "config":
|
|
147
142
|
case "data":
|
|
148
|
-
return
|
|
143
|
+
return pathsResolverJoin(home, "Library", "Application Support", "Hasna");
|
|
149
144
|
case "cache":
|
|
150
|
-
return
|
|
145
|
+
return pathsResolverJoin(home, "Library", "Caches", "Hasna");
|
|
151
146
|
case "state":
|
|
152
|
-
return
|
|
147
|
+
return pathsResolverJoin(home, "Library", "Logs", "Hasna");
|
|
153
148
|
}
|
|
154
149
|
}
|
|
155
150
|
switch (kind) {
|
|
156
151
|
case "config":
|
|
157
|
-
return
|
|
152
|
+
return pathsResolverJoin(home, ".config", "hasna");
|
|
158
153
|
case "data":
|
|
159
|
-
return
|
|
154
|
+
return pathsResolverJoin(home, ".local", "share", "hasna");
|
|
160
155
|
case "state":
|
|
161
|
-
return
|
|
156
|
+
return pathsResolverJoin(home, ".local", "state", "hasna");
|
|
162
157
|
case "cache":
|
|
163
|
-
return
|
|
158
|
+
return pathsResolverJoin(home, ".cache", "hasna");
|
|
164
159
|
}
|
|
165
160
|
}
|
|
166
|
-
function
|
|
167
|
-
|
|
168
|
-
const appSegment = options.internal === true ?
|
|
169
|
-
return
|
|
161
|
+
function pathsResolverResolve(kind, options) {
|
|
162
|
+
pathsResolverAssertApp(options.app);
|
|
163
|
+
const appSegment = options.internal === true ? pathsResolverJoin("internal", options.app) : options.app;
|
|
164
|
+
return pathsResolverJoin(pathsResolverBaseDir(kind, options), appSegment);
|
|
170
165
|
}
|
|
171
166
|
function dataDir(options) {
|
|
172
|
-
return
|
|
167
|
+
return pathsResolverResolve("data", options);
|
|
173
168
|
}
|
|
174
|
-
|
|
175
|
-
// src/app-home.ts
|
|
176
169
|
var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
|
|
177
170
|
var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
|
|
178
171
|
var EVENTS_STORE_SENTINEL_FILE = "events.json";
|
|
179
172
|
function effectiveHome() {
|
|
180
|
-
return process.env["HOME"] || process.env["USERPROFILE"] ||
|
|
173
|
+
return process.env["HOME"] || process.env["USERPROFILE"] || homedir();
|
|
181
174
|
}
|
|
182
175
|
function legacyHomeDir() {
|
|
183
|
-
return
|
|
176
|
+
return join(effectiveHome(), ".hasna", "events");
|
|
184
177
|
}
|
|
185
178
|
function resolverHome() {
|
|
186
179
|
return dataDir({ app: "events", home: effectiveHome() || undefined });
|
|
@@ -189,7 +182,7 @@ function adoptResolverHome(resolved, env = process.env) {
|
|
|
189
182
|
const dataOverride = env.HASNA_DATA_HOME;
|
|
190
183
|
if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
|
|
191
184
|
return true;
|
|
192
|
-
return existsSync(
|
|
185
|
+
return existsSync(join(resolved, EVENTS_STORE_SENTINEL_FILE));
|
|
193
186
|
}
|
|
194
187
|
function exactEventsHome() {
|
|
195
188
|
const dir = process.env[HASNA_EVENTS_DIR_ENV];
|
|
@@ -232,9 +225,9 @@ class JsonEventsStore {
|
|
|
232
225
|
constructor(dataDir2 = getEventsDataDir()) {
|
|
233
226
|
this.dataDir = dataDir2;
|
|
234
227
|
this.runtime = localJsonRuntime(dataDir2);
|
|
235
|
-
this.channelsPath =
|
|
236
|
-
this.eventsPath =
|
|
237
|
-
this.deliveriesPath =
|
|
228
|
+
this.channelsPath = join2(dataDir2, "channels.json");
|
|
229
|
+
this.eventsPath = join2(dataDir2, "events.json");
|
|
230
|
+
this.deliveriesPath = join2(dataDir2, "deliveries.json");
|
|
238
231
|
}
|
|
239
232
|
async init() {
|
|
240
233
|
await mkdir(this.dataDir, { recursive: true, mode: 448 });
|
|
@@ -502,7 +495,7 @@ async function getEventsStatus(dataDir2) {
|
|
|
502
495
|
};
|
|
503
496
|
}
|
|
504
497
|
function statusFile(dataDir2, fileName, records) {
|
|
505
|
-
const path =
|
|
498
|
+
const path = join2(dataDir2, fileName);
|
|
506
499
|
return { path, exists: existsSync2(path), records };
|
|
507
500
|
}
|
|
508
501
|
|
package/dist/storage.js
CHANGED
|
@@ -3,87 +3,80 @@
|
|
|
3
3
|
import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
4
4
|
import { Buffer } from "buffer";
|
|
5
5
|
import { existsSync as existsSync2 } from "fs";
|
|
6
|
-
import { join as
|
|
6
|
+
import { join as join2 } from "path";
|
|
7
7
|
|
|
8
8
|
// src/app-home.ts
|
|
9
9
|
import { existsSync } from "fs";
|
|
10
|
-
import { homedir as homedir2 } from "os";
|
|
11
|
-
import { join as join2, resolve } from "path";
|
|
12
|
-
|
|
13
|
-
// ../../node_modules/.bun/@hasna+paths@0.1.0/node_modules/@hasna/paths/dist/index.js
|
|
14
10
|
import { homedir } from "os";
|
|
15
|
-
import { join } from "path";
|
|
16
|
-
|
|
11
|
+
import { join, resolve } from "path";
|
|
12
|
+
import { homedir as pathsResolverHomedir } from "os";
|
|
13
|
+
import { join as pathsResolverJoin } from "path";
|
|
14
|
+
var PATHS_RESOLVER_KIND_ENV = {
|
|
17
15
|
config: "HASNA_CONFIG_HOME",
|
|
18
16
|
data: "HASNA_DATA_HOME",
|
|
19
17
|
state: "HASNA_STATE_HOME",
|
|
20
18
|
cache: "HASNA_CACHE_HOME"
|
|
21
19
|
};
|
|
22
|
-
var
|
|
23
|
-
function
|
|
20
|
+
var PATHS_RESOLVER_APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
21
|
+
function pathsResolverAssertApp(app) {
|
|
24
22
|
if (typeof app !== "string" || app.length === 0) {
|
|
25
23
|
throw new TypeError("paths: app must be a non-empty string");
|
|
26
24
|
}
|
|
27
|
-
if (!
|
|
25
|
+
if (!PATHS_RESOLVER_APP_SLUG_RE.test(app)) {
|
|
28
26
|
throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
|
|
29
27
|
}
|
|
30
28
|
}
|
|
31
|
-
function
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
const value = envOf(options)[KIND_ENV[kind]];
|
|
36
|
-
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
37
|
-
}
|
|
38
|
-
function isMacOS(platform) {
|
|
39
|
-
return platform === "darwin";
|
|
29
|
+
function pathsResolverAssertKind(kind) {
|
|
30
|
+
if (!Object.keys(PATHS_RESOLVER_KIND_ENV).includes(kind)) {
|
|
31
|
+
throw new TypeError(`paths: invalid path kind "${kind}" \u2014 expected one of ${Object.keys(PATHS_RESOLVER_KIND_ENV).join(", ")}`);
|
|
32
|
+
}
|
|
40
33
|
}
|
|
41
|
-
function
|
|
42
|
-
|
|
43
|
-
|
|
34
|
+
function pathsResolverBaseDir(kind, options) {
|
|
35
|
+
pathsResolverAssertKind(kind);
|
|
36
|
+
const env = options.env ?? process.env;
|
|
37
|
+
const override = env[PATHS_RESOLVER_KIND_ENV[kind]];
|
|
38
|
+
if (typeof override === "string" && override.length > 0)
|
|
44
39
|
return override;
|
|
45
|
-
const home = options.home ??
|
|
40
|
+
const home = options.home ?? pathsResolverHomedir();
|
|
46
41
|
const platform = options.platform ?? process.platform;
|
|
47
|
-
if (
|
|
42
|
+
if (platform === "darwin") {
|
|
48
43
|
switch (kind) {
|
|
49
44
|
case "config":
|
|
50
45
|
case "data":
|
|
51
|
-
return
|
|
46
|
+
return pathsResolverJoin(home, "Library", "Application Support", "Hasna");
|
|
52
47
|
case "cache":
|
|
53
|
-
return
|
|
48
|
+
return pathsResolverJoin(home, "Library", "Caches", "Hasna");
|
|
54
49
|
case "state":
|
|
55
|
-
return
|
|
50
|
+
return pathsResolverJoin(home, "Library", "Logs", "Hasna");
|
|
56
51
|
}
|
|
57
52
|
}
|
|
58
53
|
switch (kind) {
|
|
59
54
|
case "config":
|
|
60
|
-
return
|
|
55
|
+
return pathsResolverJoin(home, ".config", "hasna");
|
|
61
56
|
case "data":
|
|
62
|
-
return
|
|
57
|
+
return pathsResolverJoin(home, ".local", "share", "hasna");
|
|
63
58
|
case "state":
|
|
64
|
-
return
|
|
59
|
+
return pathsResolverJoin(home, ".local", "state", "hasna");
|
|
65
60
|
case "cache":
|
|
66
|
-
return
|
|
61
|
+
return pathsResolverJoin(home, ".cache", "hasna");
|
|
67
62
|
}
|
|
68
63
|
}
|
|
69
|
-
function
|
|
70
|
-
|
|
71
|
-
const appSegment = options.internal === true ?
|
|
72
|
-
return
|
|
64
|
+
function pathsResolverResolve(kind, options) {
|
|
65
|
+
pathsResolverAssertApp(options.app);
|
|
66
|
+
const appSegment = options.internal === true ? pathsResolverJoin("internal", options.app) : options.app;
|
|
67
|
+
return pathsResolverJoin(pathsResolverBaseDir(kind, options), appSegment);
|
|
73
68
|
}
|
|
74
69
|
function dataDir(options) {
|
|
75
|
-
return
|
|
70
|
+
return pathsResolverResolve("data", options);
|
|
76
71
|
}
|
|
77
|
-
|
|
78
|
-
// src/app-home.ts
|
|
79
72
|
var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
|
|
80
73
|
var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
|
|
81
74
|
var EVENTS_STORE_SENTINEL_FILE = "events.json";
|
|
82
75
|
function effectiveHome() {
|
|
83
|
-
return process.env["HOME"] || process.env["USERPROFILE"] ||
|
|
76
|
+
return process.env["HOME"] || process.env["USERPROFILE"] || homedir();
|
|
84
77
|
}
|
|
85
78
|
function legacyHomeDir() {
|
|
86
|
-
return
|
|
79
|
+
return join(effectiveHome(), ".hasna", "events");
|
|
87
80
|
}
|
|
88
81
|
function resolverHome() {
|
|
89
82
|
return dataDir({ app: "events", home: effectiveHome() || undefined });
|
|
@@ -92,7 +85,7 @@ function adoptResolverHome(resolved, env = process.env) {
|
|
|
92
85
|
const dataOverride = env.HASNA_DATA_HOME;
|
|
93
86
|
if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
|
|
94
87
|
return true;
|
|
95
|
-
return existsSync(
|
|
88
|
+
return existsSync(join(resolved, EVENTS_STORE_SENTINEL_FILE));
|
|
96
89
|
}
|
|
97
90
|
function exactEventsHome() {
|
|
98
91
|
const dir = process.env[HASNA_EVENTS_DIR_ENV];
|
|
@@ -135,9 +128,9 @@ class JsonEventsStore {
|
|
|
135
128
|
constructor(dataDir2 = getEventsDataDir()) {
|
|
136
129
|
this.dataDir = dataDir2;
|
|
137
130
|
this.runtime = localJsonRuntime(dataDir2);
|
|
138
|
-
this.channelsPath =
|
|
139
|
-
this.eventsPath =
|
|
140
|
-
this.deliveriesPath =
|
|
131
|
+
this.channelsPath = join2(dataDir2, "channels.json");
|
|
132
|
+
this.eventsPath = join2(dataDir2, "events.json");
|
|
133
|
+
this.deliveriesPath = join2(dataDir2, "deliveries.json");
|
|
141
134
|
}
|
|
142
135
|
async init() {
|
|
143
136
|
await mkdir(this.dataDir, { recursive: true, mode: 448 });
|
|
@@ -405,7 +398,7 @@ async function getEventsStatus(dataDir2) {
|
|
|
405
398
|
};
|
|
406
399
|
}
|
|
407
400
|
function statusFile(dataDir2, fileName, records) {
|
|
408
|
-
const path =
|
|
401
|
+
const path = join2(dataDir2, fileName);
|
|
409
402
|
return { path, exists: existsSync2(path), records };
|
|
410
403
|
}
|
|
411
404
|
export {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/events",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.18",
|
|
4
4
|
"description": "Shared event envelopes, local channels, replay, and delivery transports for Hasna open-source apps",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -113,12 +113,11 @@
|
|
|
113
113
|
"author": "Hasna",
|
|
114
114
|
"license": "Apache-2.0",
|
|
115
115
|
"dependencies": {
|
|
116
|
-
"@hasna/paths": "0.1.0",
|
|
117
116
|
"commander": "^13.1.0"
|
|
118
117
|
},
|
|
119
118
|
"devDependencies": {
|
|
120
119
|
"@hasna/contracts": "0.8.5",
|
|
121
|
-
"@types/bun": "
|
|
120
|
+
"@types/bun": "1.3.14",
|
|
122
121
|
"typescript": "^5.7.3"
|
|
123
122
|
}
|
|
124
123
|
}
|
package/types/app-home.d.ts
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
export type PathKind = "config" | "data" | "state" | "cache";
|
|
2
|
+
export interface PathsResolverOptions {
|
|
3
|
+
app: string;
|
|
4
|
+
internal?: boolean;
|
|
5
|
+
platform?: string;
|
|
6
|
+
home?: string;
|
|
7
|
+
env?: Record<string, string | undefined>;
|
|
8
|
+
}
|
|
9
|
+
export declare function dataDir(options: PathsResolverOptions): string;
|
|
1
10
|
/** Env var names for the exact-app data-home overrides (preserved, highest precedence). */
|
|
2
11
|
export declare const HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
|
|
3
12
|
export declare const HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared CLI-only administrator opt-in for intentional private webhook ingress.
|
|
3
|
+
* The transport still requires an exact hostname/IP allowlist match and pins
|
|
4
|
+
* the validated address. SDK clients do not read this setting automatically.
|
|
5
|
+
*/
|
|
6
|
+
export declare function webhookTargetPolicyFromEnv(): {
|
|
7
|
+
allowPrivateHosts: string[];
|
|
8
|
+
} | undefined;
|