@hasna/hooks 0.6.6 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bin/index.js +1024 -461
- package/bin/serve.js +626 -158
- package/dist/config.d.ts +7 -5
- package/dist/db/pg-migrations.d.ts +1 -1
- package/dist/index.js +898 -340
- package/dist/lib/hook-env.d.ts +60 -4
- package/dist/lib/manifest.d.ts +11 -0
- package/dist/lib/semver.d.ts +27 -0
- package/dist/openapi.d.ts +216 -0
- package/dist/storage.js +34 -34
- package/hooks/codewith-native-common.test.ts +25 -25
- package/hooks/codewith-native-common.ts +1 -1
- package/hooks/hook-autoformat/package.json +1 -1
- package/hooks/hook-desktopnotify/package.json +1 -1
- package/hooks/hook-envsetup/package.json +1 -1
- package/hooks/hook-knowledge-context/README.md +1 -1
- package/hooks/hook-knowledge-context/src/hook.test.ts +1 -1
- package/hooks/hook-permissionguard/package.json +1 -1
- package/hooks/hook-protectfiles/package.json +1 -1
- package/hooks/hook-spiral-detector/LICENSE +191 -0
- package/hooks/hook-spiral-detector/README.md +23 -0
- package/hooks/hook-spiral-detector/package.json +12 -0
- package/hooks/hook-spiral-detector/src/hook.test.ts +72 -0
- package/hooks/hook-spiral-detector/src/hook.ts +93 -0
- package/hooks/hook-workspace-repos-guard/README.md +4 -3
- package/hooks/hook-workspace-repos-guard/src/hook.test.ts +7 -7
- package/hooks/hook-workspace-repos-guard/src/hook.ts +5 -3
- package/hooks/mention-context/src/hasna-mention-context.py +2 -2
- package/hooks/mention-context/src/hasna-mention-warm.py +1 -1
- package/hooks/pre-bash/README.md +1 -1
- package/hooks/prompt-guard/README.md +1 -1
- package/hooks/session-start/README.md +1 -1
- package/hooks/stop-sync/README.md +1 -1
- package/hooks/worktree-guard/README.md +1 -1
- package/package.json +10 -6
package/dist/index.js
CHANGED
|
@@ -49,7 +49,7 @@ function up(db) {
|
|
|
49
49
|
db.exec(idx);
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
-
var
|
|
52
|
+
var init__001_initial = __esm(() => {
|
|
53
53
|
init_schema();
|
|
54
54
|
});
|
|
55
55
|
|
|
@@ -78,7 +78,7 @@ function up2(db) {
|
|
|
78
78
|
throw err;
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
|
-
var
|
|
81
|
+
var init__002_session_events = __esm(() => {
|
|
82
82
|
init_schema();
|
|
83
83
|
});
|
|
84
84
|
|
|
@@ -107,7 +107,7 @@ function up3(db) {
|
|
|
107
107
|
throw err;
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
|
-
var
|
|
110
|
+
var init__003_user_prompt_submit_event = __esm(() => {
|
|
111
111
|
init_schema();
|
|
112
112
|
});
|
|
113
113
|
|
|
@@ -131,7 +131,7 @@ var CREATE_HOOKS_TABLE = `
|
|
|
131
131
|
last_verified_at TEXT
|
|
132
132
|
)
|
|
133
133
|
`, CREATE_HOOKS_INDEXES;
|
|
134
|
-
var
|
|
134
|
+
var init__004_hooks_table = __esm(() => {
|
|
135
135
|
CREATE_HOOKS_INDEXES = [
|
|
136
136
|
`CREATE INDEX IF NOT EXISTS idx_hooks_name ON hooks (name)`
|
|
137
137
|
];
|
|
@@ -162,7 +162,7 @@ function up5(db) {
|
|
|
162
162
|
throw err;
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
|
-
var
|
|
165
|
+
var init__005_subagent_start_event = __esm(() => {
|
|
166
166
|
init_schema();
|
|
167
167
|
});
|
|
168
168
|
|
|
@@ -194,11 +194,11 @@ function runMigrations(db) {
|
|
|
194
194
|
}
|
|
195
195
|
var MIGRATIONS;
|
|
196
196
|
var init_migrations = __esm(() => {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
197
|
+
init__001_initial();
|
|
198
|
+
init__002_session_events();
|
|
199
|
+
init__003_user_prompt_submit_event();
|
|
200
|
+
init__004_hooks_table();
|
|
201
|
+
init__005_subagent_start_event();
|
|
202
202
|
MIGRATIONS = [
|
|
203
203
|
{ version: "001_initial", up },
|
|
204
204
|
{ version: "002_session_events", up: up2 },
|
|
@@ -453,7 +453,7 @@ var init_redact = __esm(() => {
|
|
|
453
453
|
];
|
|
454
454
|
});
|
|
455
455
|
|
|
456
|
-
// node_modules/zod/v3/helpers/util.js
|
|
456
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/util.js
|
|
457
457
|
var util, objectUtil, ZodParsedType, getParsedType = (data) => {
|
|
458
458
|
const t = typeof data;
|
|
459
459
|
switch (t) {
|
|
@@ -584,7 +584,7 @@ var init_util = __esm(() => {
|
|
|
584
584
|
]);
|
|
585
585
|
});
|
|
586
586
|
|
|
587
|
-
// node_modules/zod/v3/ZodError.js
|
|
587
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/ZodError.js
|
|
588
588
|
var ZodIssueCode, quotelessJson = (obj) => {
|
|
589
589
|
const json = JSON.stringify(obj, null, 2);
|
|
590
590
|
return json.replace(/"([^"]+)":/g, "$1:");
|
|
@@ -705,7 +705,7 @@ var init_ZodError = __esm(() => {
|
|
|
705
705
|
};
|
|
706
706
|
});
|
|
707
707
|
|
|
708
|
-
// node_modules/zod/v3/locales/en.js
|
|
708
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/locales/en.js
|
|
709
709
|
var errorMap = (issue, _ctx) => {
|
|
710
710
|
let message;
|
|
711
711
|
switch (issue.code) {
|
|
@@ -812,7 +812,7 @@ var init_en = __esm(() => {
|
|
|
812
812
|
en_default = errorMap;
|
|
813
813
|
});
|
|
814
814
|
|
|
815
|
-
// node_modules/zod/v3/errors.js
|
|
815
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/errors.js
|
|
816
816
|
function setErrorMap(map) {
|
|
817
817
|
overrideErrorMap = map;
|
|
818
818
|
}
|
|
@@ -825,7 +825,7 @@ var init_errors = __esm(() => {
|
|
|
825
825
|
overrideErrorMap = en_default;
|
|
826
826
|
});
|
|
827
827
|
|
|
828
|
-
// node_modules/zod/v3/helpers/parseUtil.js
|
|
828
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js
|
|
829
829
|
function addIssueToContext(ctx, issueData) {
|
|
830
830
|
const overrideMap = getErrorMap();
|
|
831
831
|
const issue = makeIssue({
|
|
@@ -930,10 +930,10 @@ var init_parseUtil = __esm(() => {
|
|
|
930
930
|
});
|
|
931
931
|
});
|
|
932
932
|
|
|
933
|
-
// node_modules/zod/v3/helpers/typeAliases.js
|
|
933
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/typeAliases.js
|
|
934
934
|
var init_typeAliases = () => {};
|
|
935
935
|
|
|
936
|
-
// node_modules/zod/v3/helpers/errorUtil.js
|
|
936
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/helpers/errorUtil.js
|
|
937
937
|
var errorUtil;
|
|
938
938
|
var init_errorUtil = __esm(() => {
|
|
939
939
|
(function(errorUtil2) {
|
|
@@ -942,7 +942,7 @@ var init_errorUtil = __esm(() => {
|
|
|
942
942
|
})(errorUtil || (errorUtil = {}));
|
|
943
943
|
});
|
|
944
944
|
|
|
945
|
-
// node_modules/zod/v3/types.js
|
|
945
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/types.js
|
|
946
946
|
class ParseInputLazyPath {
|
|
947
947
|
constructor(parent, value, path, key) {
|
|
948
948
|
this._cachedPath = [];
|
|
@@ -4293,116 +4293,116 @@ var init_types = __esm(() => {
|
|
|
4293
4293
|
NEVER = INVALID;
|
|
4294
4294
|
});
|
|
4295
4295
|
|
|
4296
|
-
// node_modules/zod/v3/external.js
|
|
4296
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/v3/external.js
|
|
4297
4297
|
var exports_external = {};
|
|
4298
4298
|
__export(exports_external, {
|
|
4299
|
-
|
|
4300
|
-
util: () => util,
|
|
4301
|
-
unknown: () => unknownType,
|
|
4302
|
-
union: () => unionType,
|
|
4303
|
-
undefined: () => undefinedType,
|
|
4304
|
-
tuple: () => tupleType,
|
|
4305
|
-
transformer: () => effectsType,
|
|
4306
|
-
symbol: () => symbolType,
|
|
4307
|
-
string: () => stringType,
|
|
4308
|
-
strictObject: () => strictObjectType,
|
|
4309
|
-
setErrorMap: () => setErrorMap,
|
|
4310
|
-
set: () => setType,
|
|
4311
|
-
record: () => recordType,
|
|
4312
|
-
quotelessJson: () => quotelessJson,
|
|
4313
|
-
promise: () => promiseType,
|
|
4314
|
-
preprocess: () => preprocessType,
|
|
4315
|
-
pipeline: () => pipelineType,
|
|
4316
|
-
ostring: () => ostring,
|
|
4317
|
-
optional: () => optionalType,
|
|
4318
|
-
onumber: () => onumber,
|
|
4319
|
-
oboolean: () => oboolean,
|
|
4320
|
-
objectUtil: () => objectUtil,
|
|
4321
|
-
object: () => objectType,
|
|
4322
|
-
number: () => numberType,
|
|
4323
|
-
nullable: () => nullableType,
|
|
4324
|
-
null: () => nullType,
|
|
4325
|
-
never: () => neverType,
|
|
4326
|
-
nativeEnum: () => nativeEnumType,
|
|
4327
|
-
nan: () => nanType,
|
|
4328
|
-
map: () => mapType,
|
|
4329
|
-
makeIssue: () => makeIssue,
|
|
4330
|
-
literal: () => literalType,
|
|
4331
|
-
lazy: () => lazyType,
|
|
4332
|
-
late: () => late,
|
|
4333
|
-
isValid: () => isValid,
|
|
4334
|
-
isDirty: () => isDirty,
|
|
4335
|
-
isAsync: () => isAsync,
|
|
4336
|
-
isAborted: () => isAborted,
|
|
4337
|
-
intersection: () => intersectionType,
|
|
4338
|
-
instanceof: () => instanceOfType,
|
|
4339
|
-
getParsedType: () => getParsedType,
|
|
4340
|
-
getErrorMap: () => getErrorMap,
|
|
4341
|
-
function: () => functionType,
|
|
4342
|
-
enum: () => enumType,
|
|
4343
|
-
effect: () => effectsType,
|
|
4344
|
-
discriminatedUnion: () => discriminatedUnionType,
|
|
4345
|
-
defaultErrorMap: () => en_default,
|
|
4346
|
-
datetimeRegex: () => datetimeRegex,
|
|
4347
|
-
date: () => dateType,
|
|
4348
|
-
custom: () => custom,
|
|
4349
|
-
coerce: () => coerce,
|
|
4350
|
-
boolean: () => booleanType,
|
|
4351
|
-
bigint: () => bigIntType,
|
|
4352
|
-
array: () => arrayType,
|
|
4353
|
-
any: () => anyType,
|
|
4354
|
-
addIssueToContext: () => addIssueToContext,
|
|
4355
|
-
ZodVoid: () => ZodVoid,
|
|
4356
|
-
ZodUnknown: () => ZodUnknown,
|
|
4357
|
-
ZodUnion: () => ZodUnion,
|
|
4358
|
-
ZodUndefined: () => ZodUndefined,
|
|
4359
|
-
ZodType: () => ZodType,
|
|
4360
|
-
ZodTuple: () => ZodTuple,
|
|
4361
|
-
ZodTransformer: () => ZodEffects,
|
|
4362
|
-
ZodSymbol: () => ZodSymbol,
|
|
4363
|
-
ZodString: () => ZodString,
|
|
4364
|
-
ZodSet: () => ZodSet,
|
|
4365
|
-
ZodSchema: () => ZodType,
|
|
4366
|
-
ZodRecord: () => ZodRecord,
|
|
4367
|
-
ZodReadonly: () => ZodReadonly,
|
|
4368
|
-
ZodPromise: () => ZodPromise,
|
|
4369
|
-
ZodPipeline: () => ZodPipeline,
|
|
4370
|
-
ZodParsedType: () => ZodParsedType,
|
|
4371
|
-
ZodOptional: () => ZodOptional,
|
|
4372
|
-
ZodObject: () => ZodObject,
|
|
4373
|
-
ZodNumber: () => ZodNumber,
|
|
4374
|
-
ZodNullable: () => ZodNullable,
|
|
4375
|
-
ZodNull: () => ZodNull,
|
|
4376
|
-
ZodNever: () => ZodNever,
|
|
4377
|
-
ZodNativeEnum: () => ZodNativeEnum,
|
|
4378
|
-
ZodNaN: () => ZodNaN,
|
|
4379
|
-
ZodMap: () => ZodMap,
|
|
4380
|
-
ZodLiteral: () => ZodLiteral,
|
|
4381
|
-
ZodLazy: () => ZodLazy,
|
|
4382
|
-
ZodIssueCode: () => ZodIssueCode,
|
|
4383
|
-
ZodIntersection: () => ZodIntersection,
|
|
4384
|
-
ZodFunction: () => ZodFunction,
|
|
4385
|
-
ZodFirstPartyTypeKind: () => ZodFirstPartyTypeKind,
|
|
4386
|
-
ZodError: () => ZodError,
|
|
4387
|
-
ZodEnum: () => ZodEnum,
|
|
4388
|
-
ZodEffects: () => ZodEffects,
|
|
4389
|
-
ZodDiscriminatedUnion: () => ZodDiscriminatedUnion,
|
|
4390
|
-
ZodDefault: () => ZodDefault,
|
|
4391
|
-
ZodDate: () => ZodDate,
|
|
4392
|
-
ZodCatch: () => ZodCatch,
|
|
4393
|
-
ZodBranded: () => ZodBranded,
|
|
4394
|
-
ZodBoolean: () => ZodBoolean,
|
|
4395
|
-
ZodBigInt: () => ZodBigInt,
|
|
4396
|
-
ZodArray: () => ZodArray,
|
|
4397
|
-
ZodAny: () => ZodAny,
|
|
4398
|
-
Schema: () => ZodType,
|
|
4399
|
-
ParseStatus: () => ParseStatus,
|
|
4400
|
-
OK: () => OK,
|
|
4401
|
-
NEVER: () => NEVER,
|
|
4402
|
-
INVALID: () => INVALID,
|
|
4403
|
-
EMPTY_PATH: () => EMPTY_PATH,
|
|
4299
|
+
BRAND: () => BRAND,
|
|
4404
4300
|
DIRTY: () => DIRTY,
|
|
4405
|
-
|
|
4301
|
+
EMPTY_PATH: () => EMPTY_PATH,
|
|
4302
|
+
INVALID: () => INVALID,
|
|
4303
|
+
NEVER: () => NEVER,
|
|
4304
|
+
OK: () => OK,
|
|
4305
|
+
ParseStatus: () => ParseStatus,
|
|
4306
|
+
Schema: () => ZodType,
|
|
4307
|
+
ZodAny: () => ZodAny,
|
|
4308
|
+
ZodArray: () => ZodArray,
|
|
4309
|
+
ZodBigInt: () => ZodBigInt,
|
|
4310
|
+
ZodBoolean: () => ZodBoolean,
|
|
4311
|
+
ZodBranded: () => ZodBranded,
|
|
4312
|
+
ZodCatch: () => ZodCatch,
|
|
4313
|
+
ZodDate: () => ZodDate,
|
|
4314
|
+
ZodDefault: () => ZodDefault,
|
|
4315
|
+
ZodDiscriminatedUnion: () => ZodDiscriminatedUnion,
|
|
4316
|
+
ZodEffects: () => ZodEffects,
|
|
4317
|
+
ZodEnum: () => ZodEnum,
|
|
4318
|
+
ZodError: () => ZodError,
|
|
4319
|
+
ZodFirstPartyTypeKind: () => ZodFirstPartyTypeKind,
|
|
4320
|
+
ZodFunction: () => ZodFunction,
|
|
4321
|
+
ZodIntersection: () => ZodIntersection,
|
|
4322
|
+
ZodIssueCode: () => ZodIssueCode,
|
|
4323
|
+
ZodLazy: () => ZodLazy,
|
|
4324
|
+
ZodLiteral: () => ZodLiteral,
|
|
4325
|
+
ZodMap: () => ZodMap,
|
|
4326
|
+
ZodNaN: () => ZodNaN,
|
|
4327
|
+
ZodNativeEnum: () => ZodNativeEnum,
|
|
4328
|
+
ZodNever: () => ZodNever,
|
|
4329
|
+
ZodNull: () => ZodNull,
|
|
4330
|
+
ZodNullable: () => ZodNullable,
|
|
4331
|
+
ZodNumber: () => ZodNumber,
|
|
4332
|
+
ZodObject: () => ZodObject,
|
|
4333
|
+
ZodOptional: () => ZodOptional,
|
|
4334
|
+
ZodParsedType: () => ZodParsedType,
|
|
4335
|
+
ZodPipeline: () => ZodPipeline,
|
|
4336
|
+
ZodPromise: () => ZodPromise,
|
|
4337
|
+
ZodReadonly: () => ZodReadonly,
|
|
4338
|
+
ZodRecord: () => ZodRecord,
|
|
4339
|
+
ZodSchema: () => ZodType,
|
|
4340
|
+
ZodSet: () => ZodSet,
|
|
4341
|
+
ZodString: () => ZodString,
|
|
4342
|
+
ZodSymbol: () => ZodSymbol,
|
|
4343
|
+
ZodTransformer: () => ZodEffects,
|
|
4344
|
+
ZodTuple: () => ZodTuple,
|
|
4345
|
+
ZodType: () => ZodType,
|
|
4346
|
+
ZodUndefined: () => ZodUndefined,
|
|
4347
|
+
ZodUnion: () => ZodUnion,
|
|
4348
|
+
ZodUnknown: () => ZodUnknown,
|
|
4349
|
+
ZodVoid: () => ZodVoid,
|
|
4350
|
+
addIssueToContext: () => addIssueToContext,
|
|
4351
|
+
any: () => anyType,
|
|
4352
|
+
array: () => arrayType,
|
|
4353
|
+
bigint: () => bigIntType,
|
|
4354
|
+
boolean: () => booleanType,
|
|
4355
|
+
coerce: () => coerce,
|
|
4356
|
+
custom: () => custom,
|
|
4357
|
+
date: () => dateType,
|
|
4358
|
+
datetimeRegex: () => datetimeRegex,
|
|
4359
|
+
defaultErrorMap: () => en_default,
|
|
4360
|
+
discriminatedUnion: () => discriminatedUnionType,
|
|
4361
|
+
effect: () => effectsType,
|
|
4362
|
+
enum: () => enumType,
|
|
4363
|
+
function: () => functionType,
|
|
4364
|
+
getErrorMap: () => getErrorMap,
|
|
4365
|
+
getParsedType: () => getParsedType,
|
|
4366
|
+
instanceof: () => instanceOfType,
|
|
4367
|
+
intersection: () => intersectionType,
|
|
4368
|
+
isAborted: () => isAborted,
|
|
4369
|
+
isAsync: () => isAsync,
|
|
4370
|
+
isDirty: () => isDirty,
|
|
4371
|
+
isValid: () => isValid,
|
|
4372
|
+
late: () => late,
|
|
4373
|
+
lazy: () => lazyType,
|
|
4374
|
+
literal: () => literalType,
|
|
4375
|
+
makeIssue: () => makeIssue,
|
|
4376
|
+
map: () => mapType,
|
|
4377
|
+
nan: () => nanType,
|
|
4378
|
+
nativeEnum: () => nativeEnumType,
|
|
4379
|
+
never: () => neverType,
|
|
4380
|
+
null: () => nullType,
|
|
4381
|
+
nullable: () => nullableType,
|
|
4382
|
+
number: () => numberType,
|
|
4383
|
+
object: () => objectType,
|
|
4384
|
+
objectUtil: () => objectUtil,
|
|
4385
|
+
oboolean: () => oboolean,
|
|
4386
|
+
onumber: () => onumber,
|
|
4387
|
+
optional: () => optionalType,
|
|
4388
|
+
ostring: () => ostring,
|
|
4389
|
+
pipeline: () => pipelineType,
|
|
4390
|
+
preprocess: () => preprocessType,
|
|
4391
|
+
promise: () => promiseType,
|
|
4392
|
+
quotelessJson: () => quotelessJson,
|
|
4393
|
+
record: () => recordType,
|
|
4394
|
+
set: () => setType,
|
|
4395
|
+
setErrorMap: () => setErrorMap,
|
|
4396
|
+
strictObject: () => strictObjectType,
|
|
4397
|
+
string: () => stringType,
|
|
4398
|
+
symbol: () => symbolType,
|
|
4399
|
+
transformer: () => effectsType,
|
|
4400
|
+
tuple: () => tupleType,
|
|
4401
|
+
undefined: () => undefinedType,
|
|
4402
|
+
union: () => unionType,
|
|
4403
|
+
unknown: () => unknownType,
|
|
4404
|
+
util: () => util,
|
|
4405
|
+
void: () => voidType
|
|
4406
4406
|
});
|
|
4407
4407
|
var init_external = __esm(() => {
|
|
4408
4408
|
init_errors();
|
|
@@ -4413,50 +4413,324 @@ var init_external = __esm(() => {
|
|
|
4413
4413
|
init_ZodError();
|
|
4414
4414
|
});
|
|
4415
4415
|
|
|
4416
|
-
// node_modules/zod/index.js
|
|
4416
|
+
// ../../node_modules/.bun/zod@3.25.76/node_modules/zod/index.js
|
|
4417
4417
|
var init_zod = __esm(() => {
|
|
4418
4418
|
init_external();
|
|
4419
4419
|
init_external();
|
|
4420
4420
|
});
|
|
4421
4421
|
|
|
4422
|
-
//
|
|
4423
|
-
import {
|
|
4422
|
+
// ../contracts/dist/client/transport.js
|
|
4423
|
+
import { readFileSync as readFileSync2, statSync } from "fs";
|
|
4424
4424
|
import { join as join3 } from "path";
|
|
4425
|
+
function envToken(name) {
|
|
4426
|
+
return name.toUpperCase().replace(/-/g, "_");
|
|
4427
|
+
}
|
|
4428
|
+
function clientTransportEnvKeys(name) {
|
|
4429
|
+
const envSegment = envToken(name);
|
|
4430
|
+
return {
|
|
4431
|
+
apiUrlKeys: [`HASNA_${envSegment}_API_URL`, `${envSegment}_API_URL`],
|
|
4432
|
+
apiKeyKeys: [`HASNA_${envSegment}_API_KEY`, `${envSegment}_API_KEY`]
|
|
4433
|
+
};
|
|
4434
|
+
}
|
|
4435
|
+
function credentialOverrideEnvKey(name) {
|
|
4436
|
+
return `HASNA_${envToken(name)}_API_KEY_OVERRIDE`;
|
|
4437
|
+
}
|
|
4438
|
+
function homeDir(env) {
|
|
4439
|
+
const home = env.HOME?.trim();
|
|
4440
|
+
return home ? home : null;
|
|
4441
|
+
}
|
|
4442
|
+
function credentialDiskSources(name, env) {
|
|
4443
|
+
return profileDiskSources(name, env, null);
|
|
4444
|
+
}
|
|
4445
|
+
function profileDiskSources(name, env, profile) {
|
|
4446
|
+
const home = homeDir(env);
|
|
4447
|
+
if (!home || !SAFE_APP_SLUG.test(name))
|
|
4448
|
+
return [];
|
|
4449
|
+
const stem = profile ? `${name}.${profile}` : name;
|
|
4450
|
+
const configStem = profile ? `${name}-${profile}` : name;
|
|
4451
|
+
return [
|
|
4452
|
+
join3(home, HASNA_STATE_DIR, FLEET_CREDENTIAL_DIR, `${stem}.env`),
|
|
4453
|
+
join3(home, CONFIG_DIR, CONFIG_NAMESPACE, `${configStem}-cloud.env`)
|
|
4454
|
+
];
|
|
4455
|
+
}
|
|
4456
|
+
function parseEnvFile(text) {
|
|
4457
|
+
const values = new Map;
|
|
4458
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
4459
|
+
const line = rawLine.trim();
|
|
4460
|
+
if (line.length === 0 || line.startsWith("#"))
|
|
4461
|
+
continue;
|
|
4462
|
+
const withoutExport = line.startsWith("export ") ? line.slice("export ".length).trim() : line;
|
|
4463
|
+
const equals = withoutExport.indexOf("=");
|
|
4464
|
+
if (equals <= 0)
|
|
4465
|
+
continue;
|
|
4466
|
+
const key = withoutExport.slice(0, equals).trim();
|
|
4467
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
|
|
4468
|
+
continue;
|
|
4469
|
+
let value = withoutExport.slice(equals + 1).trim();
|
|
4470
|
+
const quote = value[0];
|
|
4471
|
+
if (quote === '"' || quote === "'") {
|
|
4472
|
+
if (value.length < 2 || !value.endsWith(quote))
|
|
4473
|
+
continue;
|
|
4474
|
+
value = value.slice(1, -1);
|
|
4475
|
+
}
|
|
4476
|
+
if (value.length === 0)
|
|
4477
|
+
continue;
|
|
4478
|
+
values.set(key, value);
|
|
4479
|
+
}
|
|
4480
|
+
return values;
|
|
4481
|
+
}
|
|
4482
|
+
function readAppConfigFile(path) {
|
|
4483
|
+
let text;
|
|
4484
|
+
try {
|
|
4485
|
+
const stats = statSync(path);
|
|
4486
|
+
if (!stats.isFile() || stats.size > MAX_CREDENTIAL_FILE_BYTES)
|
|
4487
|
+
return null;
|
|
4488
|
+
text = readFileSync2(path, "utf8");
|
|
4489
|
+
} catch {
|
|
4490
|
+
return null;
|
|
4491
|
+
}
|
|
4492
|
+
return parseEnvFile(text);
|
|
4493
|
+
}
|
|
4494
|
+
function readCredentialFile(path, apiKeyKeys) {
|
|
4495
|
+
const values = readAppConfigFile(path);
|
|
4496
|
+
if (!values)
|
|
4497
|
+
return null;
|
|
4498
|
+
for (const key of apiKeyKeys) {
|
|
4499
|
+
const value = values.get(key)?.trim();
|
|
4500
|
+
if (value)
|
|
4501
|
+
return value;
|
|
4502
|
+
}
|
|
4503
|
+
return null;
|
|
4504
|
+
}
|
|
4505
|
+
function assertUsableCredential(appName, source, value) {
|
|
4506
|
+
if (!ILLEGAL_IN_HEADER_VALUE.test(value))
|
|
4507
|
+
return;
|
|
4508
|
+
throw new CredentialResolutionError(appName, `The credential from ${source} contains characters that cannot be sent in an HTTP header ` + `(a control character or non-ASCII byte). A file written with CR-only line endings is the usual ` + `cause. Rewrite that credential file with one LF-terminated KEY=value line. ` + `The value is not shown here, and is deliberately never logged.`, [source]);
|
|
4509
|
+
}
|
|
4510
|
+
function sealCredential(fields) {
|
|
4511
|
+
const { apiKey } = fields;
|
|
4512
|
+
const visible = {
|
|
4513
|
+
tier: fields.tier,
|
|
4514
|
+
source: fields.source,
|
|
4515
|
+
deliberate: fields.deliberate,
|
|
4516
|
+
deprecated: fields.deprecated,
|
|
4517
|
+
diskCandidates: Object.freeze([...fields.diskCandidates]),
|
|
4518
|
+
warning: fields.warning
|
|
4519
|
+
};
|
|
4520
|
+
const sealed = { ...visible };
|
|
4521
|
+
Object.defineProperty(sealed, "apiKey", {
|
|
4522
|
+
value: apiKey,
|
|
4523
|
+
enumerable: false,
|
|
4524
|
+
writable: false,
|
|
4525
|
+
configurable: false
|
|
4526
|
+
});
|
|
4527
|
+
Object.defineProperty(sealed, INSPECT_CUSTOM, {
|
|
4528
|
+
value: () => ({ ...visible, apiKey: "[redacted]" }),
|
|
4529
|
+
enumerable: false,
|
|
4530
|
+
writable: false,
|
|
4531
|
+
configurable: false
|
|
4532
|
+
});
|
|
4533
|
+
Object.defineProperty(sealed, CREDENTIAL_SEAL, {
|
|
4534
|
+
value: true,
|
|
4535
|
+
enumerable: false,
|
|
4536
|
+
writable: false,
|
|
4537
|
+
configurable: false
|
|
4538
|
+
});
|
|
4539
|
+
return Object.freeze(sealed);
|
|
4540
|
+
}
|
|
4541
|
+
function firstEnvValue(env, keys) {
|
|
4542
|
+
for (const key of keys) {
|
|
4543
|
+
const value = env[key]?.trim();
|
|
4544
|
+
if (value)
|
|
4545
|
+
return { key, value };
|
|
4546
|
+
}
|
|
4547
|
+
return null;
|
|
4548
|
+
}
|
|
4549
|
+
function deprecationNotified() {
|
|
4550
|
+
const host = globalThis;
|
|
4551
|
+
const existing = host[DEPRECATION_REGISTRY];
|
|
4552
|
+
if (existing instanceof Set)
|
|
4553
|
+
return existing;
|
|
4554
|
+
const created = new Set;
|
|
4555
|
+
host[DEPRECATION_REGISTRY] = created;
|
|
4556
|
+
return created;
|
|
4557
|
+
}
|
|
4558
|
+
function defaultDeprecationSink(message) {
|
|
4559
|
+
if (typeof process !== "undefined" && process.stderr) {
|
|
4560
|
+
process.stderr.write(`${message}
|
|
4561
|
+
`);
|
|
4562
|
+
}
|
|
4563
|
+
}
|
|
4564
|
+
function resolveCredential(name, env, options = {}) {
|
|
4565
|
+
const { apiKeyKeys } = clientTransportEnvKeys(name);
|
|
4566
|
+
const diskPaths = credentialDiskSources(name, env);
|
|
4567
|
+
const explicitKey = options.apiKey?.trim();
|
|
4568
|
+
if (explicitKey) {
|
|
4569
|
+
assertUsableCredential(name, "the explicit apiKey argument", explicitKey);
|
|
4570
|
+
return sealCredential({
|
|
4571
|
+
apiKey: explicitKey,
|
|
4572
|
+
tier: "argument",
|
|
4573
|
+
source: "explicit apiKey argument",
|
|
4574
|
+
deliberate: true,
|
|
4575
|
+
deprecated: false,
|
|
4576
|
+
diskCandidates: diskPaths,
|
|
4577
|
+
warning: null
|
|
4578
|
+
});
|
|
4579
|
+
}
|
|
4580
|
+
const overrideKeyName = credentialOverrideEnvKey(name);
|
|
4581
|
+
const overrideRaw = env[overrideKeyName];
|
|
4582
|
+
if (overrideRaw !== undefined) {
|
|
4583
|
+
const override = overrideRaw.trim();
|
|
4584
|
+
if (!override) {
|
|
4585
|
+
throw new CredentialResolutionError(name, `${overrideKeyName} is set but empty. It is a deliberate override, so it is not resolved around: ` + `either give it a real key or unset it to fall back to the credential on disk.`, [overrideKeyName]);
|
|
4586
|
+
}
|
|
4587
|
+
assertUsableCredential(name, overrideKeyName, override);
|
|
4588
|
+
return sealCredential({
|
|
4589
|
+
apiKey: override,
|
|
4590
|
+
tier: "override",
|
|
4591
|
+
source: overrideKeyName,
|
|
4592
|
+
deliberate: true,
|
|
4593
|
+
deprecated: false,
|
|
4594
|
+
diskCandidates: diskPaths,
|
|
4595
|
+
warning: null
|
|
4596
|
+
});
|
|
4597
|
+
}
|
|
4598
|
+
const profile = options.profile?.trim() || env[CREDENTIAL_PROFILE_ENV_KEY]?.trim();
|
|
4599
|
+
if (profile) {
|
|
4600
|
+
const profileSource = options.profile?.trim() ? "explicit profile argument" : CREDENTIAL_PROFILE_ENV_KEY;
|
|
4601
|
+
if (!SAFE_PROFILE.test(profile)) {
|
|
4602
|
+
throw new CredentialResolutionError(name, `Profile name from ${profileSource} is not usable in a path. ` + `Use letters, digits, dot, dash, or underscore.`, [profileSource]);
|
|
4603
|
+
}
|
|
4604
|
+
const paths = profileDiskSources(name, env, profile);
|
|
4605
|
+
for (const path of paths) {
|
|
4606
|
+
const value = readCredentialFile(path, apiKeyKeys);
|
|
4607
|
+
if (value) {
|
|
4608
|
+
assertUsableCredential(name, path, value);
|
|
4609
|
+
return sealCredential({
|
|
4610
|
+
apiKey: value,
|
|
4611
|
+
tier: "profile",
|
|
4612
|
+
source: path,
|
|
4613
|
+
deliberate: true,
|
|
4614
|
+
deprecated: false,
|
|
4615
|
+
diskCandidates: paths,
|
|
4616
|
+
warning: null
|
|
4617
|
+
});
|
|
4618
|
+
}
|
|
4619
|
+
}
|
|
4620
|
+
throw new CredentialResolutionError(name, `Profile '${profile}' (from ${profileSource}) has no ${apiKeyKeys[0]} for '${name}'. ` + `Looked in: ${paths.join(", ") || "<no HOME in this environment>"}. ` + `A profile names WHICH identity to use, so it is never resolved around \u2014 ` + `create the profile's credential file or unset ${CREDENTIAL_PROFILE_ENV_KEY}.`, paths);
|
|
4621
|
+
}
|
|
4622
|
+
const diskHits = diskPaths.map((path) => ({ path, value: readCredentialFile(path, apiKeyKeys) })).filter((hit) => hit.value !== null);
|
|
4623
|
+
if (diskHits.length > 0) {
|
|
4624
|
+
const winner = diskHits[0];
|
|
4625
|
+
assertUsableCredential(name, winner.path, winner.value);
|
|
4626
|
+
const divergentSources = [
|
|
4627
|
+
...diskHits.slice(1).filter((hit) => hit.value !== winner.value).map((hit) => hit.path),
|
|
4628
|
+
...(() => {
|
|
4629
|
+
const legacyHit = firstEnvValue(env, apiKeyKeys);
|
|
4630
|
+
return legacyHit && legacyHit.value !== winner.value ? [legacyHit.key] : [];
|
|
4631
|
+
})()
|
|
4632
|
+
];
|
|
4633
|
+
const warning = divergentSources.length > 0 ? `Credential sources disagree for '${name}': ${winner.path} and ` + `${divergentSources.join(", ")} hold different keys. ${winner.path} wins, because a file on ` + `disk is re-read on every call while an environment variable is a snapshot. Reconcile them \u2014 ` + `a rotation that updated only one leaves the other to fail 401 wherever it is loaded first.` : null;
|
|
4634
|
+
return sealCredential({
|
|
4635
|
+
apiKey: winner.value,
|
|
4636
|
+
tier: "disk",
|
|
4637
|
+
source: winner.path,
|
|
4638
|
+
deliberate: false,
|
|
4639
|
+
deprecated: false,
|
|
4640
|
+
diskCandidates: diskPaths,
|
|
4641
|
+
warning
|
|
4642
|
+
});
|
|
4643
|
+
}
|
|
4644
|
+
const legacy = firstEnvValue(env, apiKeyKeys);
|
|
4645
|
+
if (legacy) {
|
|
4646
|
+
assertUsableCredential(name, legacy.key, legacy.value);
|
|
4647
|
+
const where = diskPaths.length > 0 ? `Put the current key in ${diskPaths[0]} \u2014 it is re-read on every call, so rotations take effect immediately.` : `This environment has no HOME, so no credential file could be consulted at all; the disk tier is ` + `unavailable here and this process will keep using the environment snapshot.`;
|
|
4648
|
+
const message = `[${name}] DEPRECATED: the API key came from ${legacy.key} in this process's environment. ` + `Environment variables are a snapshot taken when this process started, so a shell that started ` + `before a key rotation keeps using the old key until it exits. ${where}`;
|
|
4649
|
+
const sink = options.onDeprecation ?? defaultDeprecationSink;
|
|
4650
|
+
const notified = deprecationNotified();
|
|
4651
|
+
if (!notified.has(name)) {
|
|
4652
|
+
notified.add(name);
|
|
4653
|
+
sink(message);
|
|
4654
|
+
}
|
|
4655
|
+
return sealCredential({
|
|
4656
|
+
apiKey: legacy.value,
|
|
4657
|
+
tier: "legacy-env",
|
|
4658
|
+
source: legacy.key,
|
|
4659
|
+
deliberate: false,
|
|
4660
|
+
deprecated: true,
|
|
4661
|
+
diskCandidates: diskPaths,
|
|
4662
|
+
warning: message
|
|
4663
|
+
});
|
|
4664
|
+
}
|
|
4665
|
+
return null;
|
|
4666
|
+
}
|
|
4667
|
+
var CREDENTIAL_PROFILE_ENV_KEY = "HASNA_PROFILE", CredentialResolutionError, HASNA_STATE_DIR = ".hasna", FLEET_CREDENTIAL_DIR = "cloud", CONFIG_DIR = ".config", CONFIG_NAMESPACE = "hasna", MAX_CREDENTIAL_FILE_BYTES, SAFE_APP_SLUG, SAFE_PROFILE, ILLEGAL_IN_HEADER_VALUE, INSPECT_CUSTOM, CREDENTIAL_SEAL, DEPRECATION_REGISTRY, IDEMPOTENT_METHODS, AUTHORITY_OVERRIDE_HEADERS;
|
|
4668
|
+
var init_transport = __esm(() => {
|
|
4669
|
+
CredentialResolutionError = class CredentialResolutionError extends Error {
|
|
4670
|
+
appName;
|
|
4671
|
+
attempted;
|
|
4672
|
+
constructor(appName, message, attempted) {
|
|
4673
|
+
super(message);
|
|
4674
|
+
this.name = "CredentialResolutionError";
|
|
4675
|
+
this.appName = appName;
|
|
4676
|
+
this.attempted = attempted;
|
|
4677
|
+
}
|
|
4678
|
+
};
|
|
4679
|
+
MAX_CREDENTIAL_FILE_BYTES = 64 * 1024;
|
|
4680
|
+
SAFE_APP_SLUG = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
4681
|
+
SAFE_PROFILE = /^[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$/;
|
|
4682
|
+
ILLEGAL_IN_HEADER_VALUE = /[^\t\x20-\x7e]/;
|
|
4683
|
+
INSPECT_CUSTOM = Symbol.for("nodejs.util.inspect.custom");
|
|
4684
|
+
CREDENTIAL_SEAL = Symbol.for("hasna:contracts:sealedCredential");
|
|
4685
|
+
DEPRECATION_REGISTRY = Symbol.for("hasna:contracts:credentialDeprecationNotices");
|
|
4686
|
+
IDEMPOTENT_METHODS = new Set(["GET", "HEAD", "PUT", "DELETE", "OPTIONS"]);
|
|
4687
|
+
AUTHORITY_OVERRIDE_HEADERS = new Set([
|
|
4688
|
+
"host",
|
|
4689
|
+
":authority",
|
|
4690
|
+
"forwarded",
|
|
4691
|
+
"x-forwarded-host",
|
|
4692
|
+
"x-original-host"
|
|
4693
|
+
]);
|
|
4694
|
+
});
|
|
4695
|
+
|
|
4696
|
+
// src/config.ts
|
|
4697
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync } from "fs";
|
|
4698
|
+
import { join as join4 } from "path";
|
|
4425
4699
|
import { homedir as homedir3 } from "os";
|
|
4426
4700
|
function getHooksDataDir() {
|
|
4427
4701
|
const explicit = process.env.HASNA_HOOKS_DATA_DIR ?? process.env.HOOKS_DATA_DIR;
|
|
4428
4702
|
if (explicit)
|
|
4429
4703
|
return explicit;
|
|
4430
|
-
return
|
|
4704
|
+
return join4(homedir3(), ".hasna", "hooks");
|
|
4431
4705
|
}
|
|
4432
4706
|
function getCustomHooksDir() {
|
|
4433
|
-
return
|
|
4707
|
+
return join4(getHooksDataDir(), "hooks");
|
|
4434
4708
|
}
|
|
4435
4709
|
function getLockPath() {
|
|
4436
4710
|
const explicit = process.env.HASNA_HOOKS_LOCK_PATH ?? process.env.HOOKS_LOCK_PATH;
|
|
4437
4711
|
if (explicit)
|
|
4438
4712
|
return explicit;
|
|
4439
|
-
return
|
|
4713
|
+
return join4(getHooksDataDir(), "hooks.lock");
|
|
4440
4714
|
}
|
|
4441
4715
|
function getConfigPath() {
|
|
4442
4716
|
const explicit = process.env.HASNA_HOOKS_CONFIG_PATH ?? process.env.HOOKS_CONFIG_PATH;
|
|
4443
4717
|
if (explicit)
|
|
4444
4718
|
return explicit;
|
|
4445
|
-
return
|
|
4719
|
+
return join4(getHooksDataDir(), "config.json");
|
|
4446
4720
|
}
|
|
4447
4721
|
function readConfig() {
|
|
4448
4722
|
try {
|
|
4449
4723
|
const path = getConfigPath();
|
|
4450
4724
|
if (!existsSync3(path))
|
|
4451
4725
|
return {};
|
|
4452
|
-
return JSON.parse(
|
|
4726
|
+
return JSON.parse(readFileSync3(path, "utf-8"));
|
|
4453
4727
|
} catch {
|
|
4454
4728
|
return {};
|
|
4455
4729
|
}
|
|
4456
4730
|
}
|
|
4457
4731
|
function writeConfig(config) {
|
|
4458
4732
|
const path = getConfigPath();
|
|
4459
|
-
mkdirSync2(
|
|
4733
|
+
mkdirSync2(join4(path, ".."), { recursive: true });
|
|
4460
4734
|
writeFileSync(path, JSON.stringify(config, null, 2) + `
|
|
4461
4735
|
`, "utf-8");
|
|
4462
4736
|
return path;
|
|
@@ -4469,36 +4743,38 @@ function resolveApiUrl() {
|
|
|
4469
4743
|
return cfg.api_url ? cfg.api_url.replace(/\/+$/, "") : undefined;
|
|
4470
4744
|
}
|
|
4471
4745
|
function resolveApiKey() {
|
|
4472
|
-
return
|
|
4746
|
+
return resolveCredential("hooks", process.env)?.apiKey;
|
|
4473
4747
|
}
|
|
4474
|
-
var init_config = () => {
|
|
4748
|
+
var init_config = __esm(() => {
|
|
4749
|
+
init_transport();
|
|
4750
|
+
});
|
|
4475
4751
|
|
|
4476
4752
|
// src/lib/semver.ts
|
|
4477
|
-
var SEMVER_PATTERN;
|
|
4753
|
+
var NUMERIC_ID = "(0|[1-9]\\d{0,15})", ALNUM_ID = "\\d*[A-Za-z-][0-9A-Za-z-]*", SEMVER_PATTERN;
|
|
4478
4754
|
var init_semver = __esm(() => {
|
|
4479
|
-
SEMVER_PATTERN =
|
|
4755
|
+
SEMVER_PATTERN = new RegExp(`^${NUMERIC_ID}\\.${NUMERIC_ID}\\.${NUMERIC_ID}` + `(?:-(?:${NUMERIC_ID}|${ALNUM_ID})(?:\\.(?:${NUMERIC_ID}|${ALNUM_ID}))*)?` + `(?:\\+[0-9A-Za-z.-]+)?$`);
|
|
4480
4756
|
});
|
|
4481
4757
|
|
|
4482
4758
|
// src/lib/manifest.ts
|
|
4483
4759
|
var exports_manifest = {};
|
|
4484
4760
|
__export(exports_manifest, {
|
|
4485
|
-
|
|
4486
|
-
shortManifestName: () => shortManifestName,
|
|
4487
|
-
scriptRelFor: () => scriptRelFor,
|
|
4488
|
-
resolveScript: () => resolveScript,
|
|
4489
|
-
readCustomManifest: () => readCustomManifest,
|
|
4490
|
-
parseManifest: () => parseManifest,
|
|
4491
|
-
normalizeManifestName: () => normalizeManifestName,
|
|
4492
|
-
manifestSchema: () => manifestSchema,
|
|
4493
|
-
listCustomHooks: () => listCustomHooks,
|
|
4494
|
-
isInlineScript: () => isInlineScript,
|
|
4495
|
-
customHookDir: () => customHookDir,
|
|
4496
|
-
assertContained: () => assertContained,
|
|
4761
|
+
HOOK_NAME_RE: () => HOOK_NAME_RE,
|
|
4497
4762
|
ScriptContainmentError: () => ScriptContainmentError,
|
|
4498
|
-
|
|
4763
|
+
assertContained: () => assertContained,
|
|
4764
|
+
customHookDir: () => customHookDir,
|
|
4765
|
+
isInlineScript: () => isInlineScript,
|
|
4766
|
+
listCustomHooks: () => listCustomHooks,
|
|
4767
|
+
manifestSchema: () => manifestSchema,
|
|
4768
|
+
normalizeManifestName: () => normalizeManifestName,
|
|
4769
|
+
parseManifest: () => parseManifest,
|
|
4770
|
+
readCustomManifest: () => readCustomManifest,
|
|
4771
|
+
resolveScript: () => resolveScript,
|
|
4772
|
+
scriptRelFor: () => scriptRelFor,
|
|
4773
|
+
shortManifestName: () => shortManifestName,
|
|
4774
|
+
writeCustomHook: () => writeCustomHook
|
|
4499
4775
|
});
|
|
4500
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as
|
|
4501
|
-
import { basename, dirname, isAbsolute, join as
|
|
4776
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, readdirSync as readdirSync2, realpathSync, writeFileSync as writeFileSync2 } from "fs";
|
|
4777
|
+
import { basename, dirname, isAbsolute, join as join5, normalize, resolve, sep } from "path";
|
|
4502
4778
|
function parseManifest(json) {
|
|
4503
4779
|
const parsed = JSON.parse(json);
|
|
4504
4780
|
return manifestSchema.parse(parsed);
|
|
@@ -4530,7 +4806,7 @@ function realpathNearest(path) {
|
|
|
4530
4806
|
tail.unshift(basename(cur));
|
|
4531
4807
|
cur = parent;
|
|
4532
4808
|
}
|
|
4533
|
-
return
|
|
4809
|
+
return join5(realpathSync(cur), ...tail);
|
|
4534
4810
|
}
|
|
4535
4811
|
function assertContainedReal(root, target, raw) {
|
|
4536
4812
|
const rootReal = realpathSync(root);
|
|
@@ -4563,25 +4839,25 @@ function resolveScript(manifest, manifestDir) {
|
|
|
4563
4839
|
if (isInlineScript(manifest))
|
|
4564
4840
|
return { path: scriptRelFor(manifest), content: manifest.script };
|
|
4565
4841
|
const rel = scriptRelFor(manifest);
|
|
4566
|
-
const resolved = assertContained(
|
|
4842
|
+
const resolved = assertContained(join5(manifestDir, rel), manifestDir, manifest.script);
|
|
4567
4843
|
if (!existsSync4(resolved)) {
|
|
4568
4844
|
throw new Error(`manifest script file not found: ${resolved}`);
|
|
4569
4845
|
}
|
|
4570
|
-
return { path: rel, content:
|
|
4846
|
+
return { path: rel, content: readFileSync4(resolved, "utf-8") };
|
|
4571
4847
|
}
|
|
4572
4848
|
function customHookDir(name) {
|
|
4573
|
-
return
|
|
4849
|
+
return join5(getCustomHooksDir(), shortManifestName(name));
|
|
4574
4850
|
}
|
|
4575
4851
|
function readCustomManifest(name) {
|
|
4576
4852
|
const dir = customHookDir(name);
|
|
4577
|
-
const manifestPath =
|
|
4853
|
+
const manifestPath = join5(dir, "manifest.json");
|
|
4578
4854
|
if (!existsSync4(manifestPath))
|
|
4579
4855
|
return;
|
|
4580
4856
|
try {
|
|
4581
|
-
const manifest = parseManifest(
|
|
4857
|
+
const manifest = parseManifest(readFileSync4(manifestPath, "utf-8"));
|
|
4582
4858
|
const script = resolveScript(manifest, dir);
|
|
4583
4859
|
const scriptIsInline = isInlineScript(manifest);
|
|
4584
|
-
return { manifest, scriptPath:
|
|
4860
|
+
return { manifest, scriptPath: join5(dir, script.path), scriptContent: script.content, scriptIsInline };
|
|
4585
4861
|
} catch (err) {
|
|
4586
4862
|
if (err instanceof ScriptContainmentError)
|
|
4587
4863
|
throw err;
|
|
@@ -4605,7 +4881,7 @@ function writeCustomHook(name, manifest, scriptContent, scriptRelPath) {
|
|
|
4605
4881
|
if (isAbsolute(scriptRelPath)) {
|
|
4606
4882
|
throw new ScriptContainmentError(scriptRelPath, dir);
|
|
4607
4883
|
}
|
|
4608
|
-
const scriptPath = scriptRelPath.includes("/") ?
|
|
4884
|
+
const scriptPath = scriptRelPath.includes("/") ? join5(dir, scriptRelPath) : join5(dir, scriptRelPath);
|
|
4609
4885
|
assertContainedLexical(scriptPath, dir, scriptRelPath);
|
|
4610
4886
|
mkdirSync3(dir, { recursive: true });
|
|
4611
4887
|
assertContainedReal(dir, scriptPath, scriptRelPath);
|
|
@@ -4613,7 +4889,7 @@ function writeCustomHook(name, manifest, scriptContent, scriptRelPath) {
|
|
|
4613
4889
|
if (scriptDir !== dir)
|
|
4614
4890
|
mkdirSync3(scriptDir, { recursive: true });
|
|
4615
4891
|
writeFileSync2(scriptPath, scriptContent, "utf-8");
|
|
4616
|
-
writeFileSync2(
|
|
4892
|
+
writeFileSync2(join5(dir, "manifest.json"), JSON.stringify(manifest, null, 2) + `
|
|
4617
4893
|
`, "utf-8");
|
|
4618
4894
|
return { dir, scriptPath };
|
|
4619
4895
|
}
|
|
@@ -4638,7 +4914,8 @@ var init_manifest = __esm(() => {
|
|
|
4638
4914
|
script: exports_external.string().min(1, "script must name a relative path or inline content"),
|
|
4639
4915
|
script_kind: exports_external.enum(["inline", "file"]).optional(),
|
|
4640
4916
|
args: exports_external.array(exports_external.string()).optional(),
|
|
4641
|
-
timeout_ms: exports_external.number().int().positive().optional()
|
|
4917
|
+
timeout_ms: exports_external.number().int().positive().optional(),
|
|
4918
|
+
env: exports_external.record(exports_external.string(), exports_external.string()).optional()
|
|
4642
4919
|
});
|
|
4643
4920
|
ScriptContainmentError = class ScriptContainmentError extends Error {
|
|
4644
4921
|
constructor(path, hookDir, raw) {
|
|
@@ -4651,26 +4928,26 @@ var init_manifest = __esm(() => {
|
|
|
4651
4928
|
// src/lib/store.ts
|
|
4652
4929
|
var exports_store = {};
|
|
4653
4930
|
__export(exports_store, {
|
|
4654
|
-
|
|
4655
|
-
verifyScriptHash: () => verifyScriptHash,
|
|
4656
|
-
upsertHookRecord: () => upsertHookRecord,
|
|
4657
|
-
sha256Of: () => sha256Of,
|
|
4658
|
-
sha256File: () => sha256File,
|
|
4659
|
-
setPinnedHook: () => setPinnedHook,
|
|
4660
|
-
retrustHook: () => retrustHook,
|
|
4661
|
-
removePinnedHook: () => removePinnedHook,
|
|
4662
|
-
removeHookRecord: () => removeHookRecord,
|
|
4663
|
-
removeHookFromStore: () => removeHookFromStore,
|
|
4664
|
-
readLock: () => readLock,
|
|
4665
|
-
pinInstalledHook: () => pinInstalledHook,
|
|
4666
|
-
listHookRecords: () => listHookRecords,
|
|
4667
|
-
getPinnedHook: () => getPinnedHook,
|
|
4668
|
-
getHookRecord: () => getHookRecord,
|
|
4931
|
+
LockFileError: () => LockFileError,
|
|
4669
4932
|
checkScriptHash: () => checkScriptHash,
|
|
4670
|
-
|
|
4933
|
+
getHookRecord: () => getHookRecord,
|
|
4934
|
+
getPinnedHook: () => getPinnedHook,
|
|
4935
|
+
listHookRecords: () => listHookRecords,
|
|
4936
|
+
pinInstalledHook: () => pinInstalledHook,
|
|
4937
|
+
readLock: () => readLock,
|
|
4938
|
+
removeHookFromStore: () => removeHookFromStore,
|
|
4939
|
+
removeHookRecord: () => removeHookRecord,
|
|
4940
|
+
removePinnedHook: () => removePinnedHook,
|
|
4941
|
+
retrustHook: () => retrustHook,
|
|
4942
|
+
setPinnedHook: () => setPinnedHook,
|
|
4943
|
+
sha256File: () => sha256File,
|
|
4944
|
+
sha256Of: () => sha256Of,
|
|
4945
|
+
upsertHookRecord: () => upsertHookRecord,
|
|
4946
|
+
verifyScriptHash: () => verifyScriptHash,
|
|
4947
|
+
writeLock: () => writeLock
|
|
4671
4948
|
});
|
|
4672
4949
|
import { createHash } from "crypto";
|
|
4673
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as
|
|
4950
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync3, renameSync } from "fs";
|
|
4674
4951
|
import { readFile } from "fs/promises";
|
|
4675
4952
|
import { randomBytes } from "crypto";
|
|
4676
4953
|
function sha256Of(input) {
|
|
@@ -4736,7 +5013,7 @@ function readLock() {
|
|
|
4736
5013
|
return { hooks: {} };
|
|
4737
5014
|
let raw;
|
|
4738
5015
|
try {
|
|
4739
|
-
raw =
|
|
5016
|
+
raw = readFileSync5(path, "utf-8");
|
|
4740
5017
|
} catch {
|
|
4741
5018
|
throw new LockFileError(`hooks.lock (${path}) could not be read. Repair: fix permissions or move the file aside and run 'hooks sync'.`);
|
|
4742
5019
|
}
|
|
@@ -4833,7 +5110,7 @@ async function verifyScriptHash(name, scriptPath) {
|
|
|
4833
5110
|
return checkScriptHash(name, await sha256File(scriptPath));
|
|
4834
5111
|
}
|
|
4835
5112
|
function retrustHook(name, scriptPath, version, source) {
|
|
4836
|
-
const actual = sha256Of(
|
|
5113
|
+
const actual = sha256Of(readFileSync5(scriptPath));
|
|
4837
5114
|
const db = getDb();
|
|
4838
5115
|
upsertHookRecord(db, {
|
|
4839
5116
|
name,
|
|
@@ -4860,24 +5137,24 @@ var init_store = __esm(() => {
|
|
|
4860
5137
|
// src/lib/profiles.ts
|
|
4861
5138
|
var exports_profiles = {};
|
|
4862
5139
|
__export(exports_profiles, {
|
|
4863
|
-
|
|
4864
|
-
touchProfile: () => touchProfile,
|
|
4865
|
-
listProfiles: () => listProfiles,
|
|
4866
|
-
importProfiles: () => importProfiles,
|
|
4867
|
-
getProfilesDir: () => getProfilesDir,
|
|
4868
|
-
getProfile: () => getProfile,
|
|
4869
|
-
exportProfiles: () => exportProfiles,
|
|
5140
|
+
createProfile: () => createProfile,
|
|
4870
5141
|
deleteProfile: () => deleteProfile,
|
|
4871
|
-
|
|
5142
|
+
exportProfiles: () => exportProfiles,
|
|
5143
|
+
getProfile: () => getProfile,
|
|
5144
|
+
getProfilesDir: () => getProfilesDir,
|
|
5145
|
+
importProfiles: () => importProfiles,
|
|
5146
|
+
listProfiles: () => listProfiles,
|
|
5147
|
+
touchProfile: () => touchProfile,
|
|
5148
|
+
updateProfile: () => updateProfile
|
|
4872
5149
|
});
|
|
4873
|
-
import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as
|
|
4874
|
-
import { join as
|
|
5150
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync5, readdirSync as readdirSync3, rmSync as rmSync2, cpSync as cpSync2 } from "fs";
|
|
5151
|
+
import { join as join8 } from "path";
|
|
4875
5152
|
import { homedir as homedir5 } from "os";
|
|
4876
5153
|
function resolveProfilesDir() {
|
|
4877
|
-
const newDir =
|
|
4878
|
-
const oldDir =
|
|
5154
|
+
const newDir = join8(homedir5(), ".hasna", "hooks", "profiles");
|
|
5155
|
+
const oldDir = join8(homedir5(), ".hooks", "profiles");
|
|
4879
5156
|
if (!existsSync8(newDir) && existsSync8(oldDir)) {
|
|
4880
|
-
mkdirSync6(
|
|
5157
|
+
mkdirSync6(join8(homedir5(), ".hasna", "hooks"), { recursive: true });
|
|
4881
5158
|
cpSync2(oldDir, newDir, { recursive: true });
|
|
4882
5159
|
}
|
|
4883
5160
|
return newDir;
|
|
@@ -4888,7 +5165,7 @@ function ensureProfilesDir() {
|
|
|
4888
5165
|
}
|
|
4889
5166
|
}
|
|
4890
5167
|
function profilePath(id) {
|
|
4891
|
-
return
|
|
5168
|
+
return join8(PROFILES_DIR, `${id}.json`);
|
|
4892
5169
|
}
|
|
4893
5170
|
function shortUuid() {
|
|
4894
5171
|
return crypto.randomUUID().slice(0, 8);
|
|
@@ -4919,7 +5196,7 @@ function getProfile(id) {
|
|
|
4919
5196
|
try {
|
|
4920
5197
|
if (!existsSync8(path))
|
|
4921
5198
|
return null;
|
|
4922
|
-
return JSON.parse(
|
|
5199
|
+
return JSON.parse(readFileSync7(path, "utf-8"));
|
|
4923
5200
|
} catch {
|
|
4924
5201
|
return null;
|
|
4925
5202
|
}
|
|
@@ -4932,7 +5209,7 @@ function listProfiles() {
|
|
|
4932
5209
|
const profiles = [];
|
|
4933
5210
|
for (const file of files) {
|
|
4934
5211
|
try {
|
|
4935
|
-
const content =
|
|
5212
|
+
const content = readFileSync7(join8(PROFILES_DIR, file), "utf-8");
|
|
4936
5213
|
profiles.push(JSON.parse(content));
|
|
4937
5214
|
} catch {}
|
|
4938
5215
|
}
|
|
@@ -5001,6 +5278,8 @@ var init_profiles = __esm(() => {
|
|
|
5001
5278
|
});
|
|
5002
5279
|
|
|
5003
5280
|
// src/lib/hook-env.ts
|
|
5281
|
+
import { dirname as dirname5, isAbsolute as isAbsolute2 } from "path";
|
|
5282
|
+
import { existsSync as existsSync10, statSync as statSync2 } from "fs";
|
|
5004
5283
|
function isDeniedEnvName(name) {
|
|
5005
5284
|
const upper = name.toUpperCase();
|
|
5006
5285
|
if (DENY_PREFIXES.some((prefix) => upper.startsWith(prefix)))
|
|
@@ -5009,13 +5288,74 @@ function isDeniedEnvName(name) {
|
|
|
5009
5288
|
return true;
|
|
5010
5289
|
return DENY_CONTAINS.some((token) => upper.includes(token));
|
|
5011
5290
|
}
|
|
5291
|
+
function isInterpreterInjectionEnvName(name) {
|
|
5292
|
+
if (INTERPRETER_INJECTION.has(name))
|
|
5293
|
+
return true;
|
|
5294
|
+
return INTERPRETER_INJECTION_PREFIXES.some((prefix) => name.startsWith(prefix));
|
|
5295
|
+
}
|
|
5296
|
+
function isBashFunctionEnvName(name) {
|
|
5297
|
+
return name.startsWith("BASH_FUNC_");
|
|
5298
|
+
}
|
|
5299
|
+
function isUnsafePathEntry(entry, home) {
|
|
5300
|
+
if (entry === "" || !isAbsolute2(entry))
|
|
5301
|
+
return true;
|
|
5302
|
+
if (home && (entry === home || entry.startsWith(home + "/")))
|
|
5303
|
+
return true;
|
|
5304
|
+
if (entry === "/tmp" || entry.startsWith("/tmp/"))
|
|
5305
|
+
return true;
|
|
5306
|
+
if (entry === "/var/tmp" || entry.startsWith("/var/tmp/"))
|
|
5307
|
+
return true;
|
|
5308
|
+
try {
|
|
5309
|
+
if ((statSync2(entry).mode & 2) !== 0)
|
|
5310
|
+
return true;
|
|
5311
|
+
} catch {}
|
|
5312
|
+
return false;
|
|
5313
|
+
}
|
|
5314
|
+
function sanitizeHookPath(pathValue, home) {
|
|
5315
|
+
const baseline = PATH_BASELINE.slice();
|
|
5316
|
+
if (process.platform === "darwin" && existsSync10("/opt/homebrew/bin"))
|
|
5317
|
+
baseline.push("/opt/homebrew/bin");
|
|
5318
|
+
const bunDir = dirname5(process.execPath);
|
|
5319
|
+
const seen = new Set;
|
|
5320
|
+
const out = [];
|
|
5321
|
+
for (const dir of baseline) {
|
|
5322
|
+
if (seen.has(dir))
|
|
5323
|
+
continue;
|
|
5324
|
+
seen.add(dir);
|
|
5325
|
+
out.push(dir);
|
|
5326
|
+
}
|
|
5327
|
+
if (!seen.has(bunDir)) {
|
|
5328
|
+
seen.add(bunDir);
|
|
5329
|
+
out.push(bunDir);
|
|
5330
|
+
}
|
|
5331
|
+
for (const entry of pathValue.split(":")) {
|
|
5332
|
+
if (seen.has(entry))
|
|
5333
|
+
continue;
|
|
5334
|
+
if (isUnsafePathEntry(entry, home))
|
|
5335
|
+
continue;
|
|
5336
|
+
seen.add(entry);
|
|
5337
|
+
out.push(entry);
|
|
5338
|
+
}
|
|
5339
|
+
return out.join(":");
|
|
5340
|
+
}
|
|
5012
5341
|
function buildHookEnv(source, extra) {
|
|
5013
5342
|
const out = {};
|
|
5014
5343
|
for (const name of ALLOWLIST) {
|
|
5344
|
+
if (name === "PATH")
|
|
5345
|
+
continue;
|
|
5015
5346
|
const value = source[name] ?? extra?.[name];
|
|
5016
5347
|
if (value !== undefined && value !== null)
|
|
5017
5348
|
out[name] = value;
|
|
5018
5349
|
}
|
|
5350
|
+
const pathOverride = extra?.PATH;
|
|
5351
|
+
if (pathOverride !== undefined && pathOverride !== null) {
|
|
5352
|
+
out.PATH = pathOverride;
|
|
5353
|
+
} else {
|
|
5354
|
+
const parentPath = source.PATH;
|
|
5355
|
+
if (parentPath !== undefined && parentPath !== null) {
|
|
5356
|
+
out.PATH = sanitizeHookPath(parentPath, source.HOME ?? extra?.HOME);
|
|
5357
|
+
}
|
|
5358
|
+
}
|
|
5019
5359
|
for (const [from, to] of CONFIG_PROJECTIONS) {
|
|
5020
5360
|
const value = source[from] ?? extra?.[from];
|
|
5021
5361
|
if (value !== undefined && value !== null)
|
|
@@ -5027,14 +5367,44 @@ function buildHookEnv(source, extra) {
|
|
|
5027
5367
|
continue;
|
|
5028
5368
|
if (isDeniedEnvName(name))
|
|
5029
5369
|
continue;
|
|
5370
|
+
if (isInterpreterInjectionEnvName(name))
|
|
5371
|
+
continue;
|
|
5372
|
+
if (isBashFunctionEnvName(name))
|
|
5373
|
+
continue;
|
|
5030
5374
|
if (value !== undefined && value !== null)
|
|
5031
5375
|
out[name] = value;
|
|
5032
5376
|
}
|
|
5033
5377
|
return out;
|
|
5034
5378
|
}
|
|
5035
|
-
var ALLOWLIST, DENY_SUFFIXES, DENY_PREFIXES, DENY_CONTAINS, CONFIG_PROJECTIONS;
|
|
5379
|
+
var ALLOWLIST, INTERPRETER_INJECTION, INTERPRETER_INJECTION_PREFIXES, DENY_SUFFIXES, DENY_PREFIXES, DENY_CONTAINS, PATH_BASELINE, CONFIG_PROJECTIONS;
|
|
5036
5380
|
var init_hook_env = __esm(() => {
|
|
5037
5381
|
ALLOWLIST = new Set(["PATH", "HOME", "LANG", "TZ", "SHELL", "TERM", "USER", "PWD"]);
|
|
5382
|
+
INTERPRETER_INJECTION = new Set([
|
|
5383
|
+
"BASH_ENV",
|
|
5384
|
+
"ENV",
|
|
5385
|
+
"BASHOPTS",
|
|
5386
|
+
"SHELLOPTS",
|
|
5387
|
+
"NODE_OPTIONS",
|
|
5388
|
+
"NODE_PATH",
|
|
5389
|
+
"PYTHONSTARTUP",
|
|
5390
|
+
"PYTHONINSPECT",
|
|
5391
|
+
"PYTHONPATH",
|
|
5392
|
+
"PYTHONHOME",
|
|
5393
|
+
"LD_PRELOAD",
|
|
5394
|
+
"LD_LIBRARY_PATH",
|
|
5395
|
+
"GCONV_PATH",
|
|
5396
|
+
"LOCPATH",
|
|
5397
|
+
"PERL5OPT",
|
|
5398
|
+
"RUBYOPT",
|
|
5399
|
+
"NODE_EXTRA_CA_CERTS",
|
|
5400
|
+
"NODE_TLS_REJECT_UNAUTHORIZED",
|
|
5401
|
+
"SSL_CERT_FILE",
|
|
5402
|
+
"SSL_CERT_DIR",
|
|
5403
|
+
"CURL_CA_BUNDLE",
|
|
5404
|
+
"GIT_SSL_CAINFO",
|
|
5405
|
+
"AWS_CA_BUNDLE"
|
|
5406
|
+
]);
|
|
5407
|
+
INTERPRETER_INJECTION_PREFIXES = ["GIT_CONFIG_"];
|
|
5038
5408
|
DENY_SUFFIXES = ["KEY", "TOKEN", "SECRET", "PASSWORD", "PASSWD", "CREDENTIAL", "CREDENTIALS", "URL", "URI"];
|
|
5039
5409
|
DENY_PREFIXES = [
|
|
5040
5410
|
"HASNA_",
|
|
@@ -5052,6 +5422,7 @@ var init_hook_env = __esm(() => {
|
|
|
5052
5422
|
"MEMENTOS_"
|
|
5053
5423
|
];
|
|
5054
5424
|
DENY_CONTAINS = ["DATABASE_URL"];
|
|
5425
|
+
PATH_BASELINE = ["/usr/bin", "/bin", "/usr/local/bin", "/sbin", "/usr/sbin"];
|
|
5055
5426
|
CONFIG_PROJECTIONS = [
|
|
5056
5427
|
["HASNA_HOOKS_DATA_DIR", "HOOKS_DATA_DIR"],
|
|
5057
5428
|
["HASNA_HOOKS_DB_PATH", "HOOKS_DB_PATH"],
|
|
@@ -5063,12 +5434,12 @@ var init_hook_env = __esm(() => {
|
|
|
5063
5434
|
// src/lib/run.ts
|
|
5064
5435
|
var exports_run = {};
|
|
5065
5436
|
__export(exports_run, {
|
|
5066
|
-
|
|
5067
|
-
|
|
5437
|
+
HookTimeoutError: () => HookTimeoutError,
|
|
5438
|
+
executeVerifiedScript: () => executeVerifiedScript
|
|
5068
5439
|
});
|
|
5069
5440
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
5070
5441
|
import { rmSync as rmSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
5071
|
-
import { basename as basename2, dirname as
|
|
5442
|
+
import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute3, join as join11 } from "path";
|
|
5072
5443
|
function extensionOf(scriptPath) {
|
|
5073
5444
|
const base = basename2(scriptPath);
|
|
5074
5445
|
const dot = base.lastIndexOf(".");
|
|
@@ -5095,13 +5466,13 @@ function interpreterFor(name, scriptPath, content) {
|
|
|
5095
5466
|
if (interp === "bash" || interp === "sh")
|
|
5096
5467
|
return { command: ["/bin/bash"], tempExt: "sh" };
|
|
5097
5468
|
if (interp === "node" || interp === "bun")
|
|
5098
|
-
return { command: [
|
|
5469
|
+
return { command: [process.execPath, "run"], tempExt: BUN_EXTENSIONS.has(ext) ? ext : "ts" };
|
|
5099
5470
|
throw new Error(`Refusing to run hook '${name}': shebang '${firstLine}' is not a recognized interpreter (supported: bash/sh, node/bun)`);
|
|
5100
5471
|
}
|
|
5101
5472
|
if (BASH_EXTENSIONS.has(ext))
|
|
5102
5473
|
return { command: ["/bin/bash"], tempExt: "sh" };
|
|
5103
5474
|
if (BUN_EXTENSIONS.has(ext))
|
|
5104
|
-
return { command: [
|
|
5475
|
+
return { command: [process.execPath, "run"], tempExt: ext };
|
|
5105
5476
|
throw new Error(`Refusing to run hook '${name}': unsupported script extension '.${ext}' (supported: .sh .bash .ts .tsx .js .jsx .mjs .cjs .mts .cts, or a recognized shebang)`);
|
|
5106
5477
|
}
|
|
5107
5478
|
async function readPipeWithDeadline(stream, deadlineMs) {
|
|
@@ -5137,9 +5508,9 @@ async function readPipeWithDeadline(stream, deadlineMs) {
|
|
|
5137
5508
|
return chunks.join("");
|
|
5138
5509
|
}
|
|
5139
5510
|
async function executeVerifiedScript(options) {
|
|
5140
|
-
const scriptDir =
|
|
5511
|
+
const scriptDir = dirname6(options.scriptPath);
|
|
5141
5512
|
const interpreter = interpreterFor(options.name, options.scriptPath, options.content);
|
|
5142
|
-
const tempPath =
|
|
5513
|
+
const tempPath = join11(scriptDir, `.hooks-verified-${randomBytes2(12).toString("hex")}.${interpreter.tempExt}`);
|
|
5143
5514
|
let proc = null;
|
|
5144
5515
|
let timedOut = false;
|
|
5145
5516
|
const timeoutTimer = options.timeout ? setTimeout(() => {
|
|
@@ -5148,7 +5519,7 @@ async function executeVerifiedScript(options) {
|
|
|
5148
5519
|
}, options.timeout) : null;
|
|
5149
5520
|
try {
|
|
5150
5521
|
writeFileSync6(tempPath, options.content, { flag: "wx", mode: 384 });
|
|
5151
|
-
proc = Bun.spawn([...interpreter.command,
|
|
5522
|
+
proc = Bun.spawn([...interpreter.command, isAbsolute3(tempPath) ? tempPath : `./${tempPath}`, ...options.args ?? []], {
|
|
5152
5523
|
stdin: new Response(options.stdin),
|
|
5153
5524
|
stdout: "pipe",
|
|
5154
5525
|
stderr: "pipe",
|
|
@@ -5226,10 +5597,10 @@ var init_run = __esm(() => {
|
|
|
5226
5597
|
// src/lib/db-writer.ts
|
|
5227
5598
|
var exports_db_writer = {};
|
|
5228
5599
|
__export(exports_db_writer, {
|
|
5229
|
-
|
|
5230
|
-
resolveEventType: () => resolveEventType,
|
|
5600
|
+
normalizeEventType: () => normalizeEventType,
|
|
5231
5601
|
recordHookRun: () => recordHookRun,
|
|
5232
|
-
|
|
5602
|
+
resolveEventType: () => resolveEventType,
|
|
5603
|
+
writeHookEvent: () => writeHookEvent
|
|
5233
5604
|
});
|
|
5234
5605
|
function nanoid2() {
|
|
5235
5606
|
return crypto.randomUUID().replace(/-/g, "").slice(0, 21);
|
|
@@ -6128,6 +6499,16 @@ var HOOKS = [
|
|
|
6128
6499
|
matcher: "",
|
|
6129
6500
|
tags: ["errors", "failures", "logging", "debugging"]
|
|
6130
6501
|
},
|
|
6502
|
+
{
|
|
6503
|
+
name: "spiral-detector",
|
|
6504
|
+
displayName: "Spiral Detector",
|
|
6505
|
+
description: "Interrupts sessions after five consecutive identical command failures",
|
|
6506
|
+
version: "0.1.0",
|
|
6507
|
+
category: "Observability",
|
|
6508
|
+
event: "PostToolUse",
|
|
6509
|
+
matcher: "",
|
|
6510
|
+
tags: ["spiral", "failures", "retries", "safety", "interrupt"]
|
|
6511
|
+
},
|
|
6131
6512
|
{
|
|
6132
6513
|
name: "stylescheck",
|
|
6133
6514
|
displayName: "Styles Check",
|
|
@@ -6293,21 +6674,21 @@ function getHook(name) {
|
|
|
6293
6674
|
return HOOKS.find((h) => h.name === name);
|
|
6294
6675
|
}
|
|
6295
6676
|
// src/lib/installer.ts
|
|
6296
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as
|
|
6297
|
-
import { join as
|
|
6677
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync6, rmSync, writeFileSync as writeFileSync4 } from "fs";
|
|
6678
|
+
import { join as join7, dirname as dirname3 } from "path";
|
|
6298
6679
|
import { homedir as homedir4 } from "os";
|
|
6299
6680
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6300
6681
|
|
|
6301
6682
|
// src/lib/resolve.ts
|
|
6302
|
-
import { dirname as dirname2, join as
|
|
6683
|
+
import { dirname as dirname2, join as join6 } from "path";
|
|
6303
6684
|
import { existsSync as existsSync5 } from "fs";
|
|
6304
6685
|
import { fileURLToPath } from "url";
|
|
6305
6686
|
init_manifest();
|
|
6306
6687
|
function resolveBundledHooksDir(moduleDir = dirname2(fileURLToPath(import.meta.url))) {
|
|
6307
|
-
const sourceLayout =
|
|
6308
|
-
if (existsSync5(
|
|
6688
|
+
const sourceLayout = join6(moduleDir, "..", "..", "hooks");
|
|
6689
|
+
if (existsSync5(join6(sourceLayout, "hook-gitguard")))
|
|
6309
6690
|
return sourceLayout;
|
|
6310
|
-
return
|
|
6691
|
+
return join6(moduleDir, "..", "hooks");
|
|
6311
6692
|
}
|
|
6312
6693
|
function resolveHookDir(name) {
|
|
6313
6694
|
const custom2 = readCustomManifest(name);
|
|
@@ -6318,10 +6699,10 @@ function resolveHookDir(name) {
|
|
|
6318
6699
|
return;
|
|
6319
6700
|
const short = shortManifestName(name);
|
|
6320
6701
|
const bundledBase = resolveBundledHooksDir();
|
|
6321
|
-
const direct =
|
|
6702
|
+
const direct = join6(bundledBase, short);
|
|
6322
6703
|
if (existsSync5(direct))
|
|
6323
6704
|
return direct;
|
|
6324
|
-
const prefixed =
|
|
6705
|
+
const prefixed = join6(bundledBase, `hook-${short}`);
|
|
6325
6706
|
return existsSync5(prefixed) ? prefixed : undefined;
|
|
6326
6707
|
}
|
|
6327
6708
|
function resolveScriptPath(name) {
|
|
@@ -6331,7 +6712,7 @@ function resolveScriptPath(name) {
|
|
|
6331
6712
|
const dir = resolveHookDir(name);
|
|
6332
6713
|
if (!dir)
|
|
6333
6714
|
return;
|
|
6334
|
-
const script =
|
|
6715
|
+
const script = join6(dir, "src", "hook.ts");
|
|
6335
6716
|
return existsSync5(script) ? script : undefined;
|
|
6336
6717
|
}
|
|
6337
6718
|
function resolveHookMeta(name) {
|
|
@@ -6387,7 +6768,7 @@ function resolveHook(name) {
|
|
|
6387
6768
|
init_manifest();
|
|
6388
6769
|
init_store();
|
|
6389
6770
|
var __dirname2 = dirname3(fileURLToPath2(import.meta.url));
|
|
6390
|
-
var HOOKS_DIR = existsSync7(
|
|
6771
|
+
var HOOKS_DIR = existsSync7(join7(__dirname2, "..", "..", "hooks", "hook-gitguard")) ? join7(__dirname2, "..", "..", "hooks") : join7(__dirname2, "..", "hooks");
|
|
6391
6772
|
function normalizeHookName(name) {
|
|
6392
6773
|
return name.startsWith("hook-") ? name : `hook-${name}`;
|
|
6393
6774
|
}
|
|
@@ -6457,16 +6838,16 @@ function getSettingsPath(scope = "global", target = "claude", codewithConfigPath
|
|
|
6457
6838
|
return globalOverride;
|
|
6458
6839
|
const dir = getTargetSettingsDir(target);
|
|
6459
6840
|
if (scope === "project") {
|
|
6460
|
-
return target === "codewith" ?
|
|
6841
|
+
return target === "codewith" ? join7(process.cwd(), dir, "config.toml") : join7(process.cwd(), dir, "settings.json");
|
|
6461
6842
|
}
|
|
6462
|
-
return target === "codewith" ?
|
|
6843
|
+
return target === "codewith" ? join7(homedir4(), dir, "config.toml") : join7(homedir4(), dir, "settings.json");
|
|
6463
6844
|
}
|
|
6464
6845
|
function getHookPath(name) {
|
|
6465
6846
|
const shortName = shortHookName(name);
|
|
6466
6847
|
const resolved = resolveHookDir(shortName);
|
|
6467
6848
|
if (resolved)
|
|
6468
6849
|
return resolved;
|
|
6469
|
-
return
|
|
6850
|
+
return join7(HOOKS_DIR, normalizeHookName(shortName));
|
|
6470
6851
|
}
|
|
6471
6852
|
function hookExists(name) {
|
|
6472
6853
|
return existsSync7(getHookPath(name));
|
|
@@ -6475,7 +6856,7 @@ function readSettings(scope = "global", target = "claude") {
|
|
|
6475
6856
|
const path = getSettingsPath(scope, target);
|
|
6476
6857
|
try {
|
|
6477
6858
|
if (existsSync7(path)) {
|
|
6478
|
-
return JSON.parse(
|
|
6859
|
+
return JSON.parse(readFileSync6(path, "utf-8"));
|
|
6479
6860
|
}
|
|
6480
6861
|
} catch (error) {
|
|
6481
6862
|
console.warn(`[hooks] Failed to read settings at ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -6574,7 +6955,7 @@ function buildCodewithTomlFragment(name, profile) {
|
|
|
6574
6955
|
function readCodewithConfig(scope, configPath) {
|
|
6575
6956
|
const path = getSettingsPath(scope, "codewith", configPath);
|
|
6576
6957
|
try {
|
|
6577
|
-
return existsSync7(path) ?
|
|
6958
|
+
return existsSync7(path) ? readFileSync6(path, "utf-8") : "";
|
|
6578
6959
|
} catch {
|
|
6579
6960
|
return "";
|
|
6580
6961
|
}
|
|
@@ -6584,7 +6965,7 @@ function appendCodewithFragment(fragment, scope, configPath) {
|
|
|
6584
6965
|
const dir = dirname3(path);
|
|
6585
6966
|
if (!existsSync7(dir))
|
|
6586
6967
|
mkdirSync5(dir, { recursive: true });
|
|
6587
|
-
const existing = existsSync7(path) ?
|
|
6968
|
+
const existing = existsSync7(path) ? readFileSync6(path, "utf-8") : "";
|
|
6588
6969
|
const sep2 = existing.trim() ? `
|
|
6589
6970
|
|
|
6590
6971
|
` : "";
|
|
@@ -6629,7 +7010,7 @@ function installForTarget(name, scope, overwrite, target, profile, codewithMode
|
|
|
6629
7010
|
target,
|
|
6630
7011
|
fragment,
|
|
6631
7012
|
applied: false,
|
|
6632
|
-
note: "Codewith install is fragment-only by default;
|
|
7013
|
+
note: "Codewith install is fragment-only by default; configs should own applying this TOML."
|
|
6633
7014
|
};
|
|
6634
7015
|
}
|
|
6635
7016
|
if (!codewithConfigPath) {
|
|
@@ -6656,7 +7037,7 @@ function installForTarget(name, scope, overwrite, target, profile, codewithMode
|
|
|
6656
7037
|
fragment,
|
|
6657
7038
|
applied: true,
|
|
6658
7039
|
configPath: path,
|
|
6659
|
-
note: "Direct Codewith config write was explicitly requested; prefer
|
|
7040
|
+
note: "Direct Codewith config write was explicitly requested; prefer configs for managed machines."
|
|
6660
7041
|
};
|
|
6661
7042
|
} catch (error) {
|
|
6662
7043
|
return {
|
|
@@ -6888,7 +7269,7 @@ function codewithHasHookEntry(name, scope = "global") {
|
|
|
6888
7269
|
if (!existsSync7(path))
|
|
6889
7270
|
return false;
|
|
6890
7271
|
try {
|
|
6891
|
-
return codewithHasHookEntryInText(
|
|
7272
|
+
return codewithHasHookEntryInText(readFileSync6(path, "utf-8"), name);
|
|
6892
7273
|
} catch {
|
|
6893
7274
|
return false;
|
|
6894
7275
|
}
|
|
@@ -6920,7 +7301,7 @@ function uninstallHook(name, scope = "global", target = "claude") {
|
|
|
6920
7301
|
if (target === "codewith" || target === "all") {
|
|
6921
7302
|
const codewithPath = getSettingsPath(scope, "codewith");
|
|
6922
7303
|
if (existsSync7(codewithPath)) {
|
|
6923
|
-
const before =
|
|
7304
|
+
const before = readFileSync6(codewithPath, "utf-8");
|
|
6924
7305
|
const after = removeCodewithHookEntry(before, shortName);
|
|
6925
7306
|
if (after.removed) {
|
|
6926
7307
|
writeFileSync4(codewithPath, after.text, "utf-8");
|
|
@@ -6964,20 +7345,20 @@ init_profiles();
|
|
|
6964
7345
|
init_config();
|
|
6965
7346
|
init_store();
|
|
6966
7347
|
init_manifest();
|
|
6967
|
-
import { existsSync as
|
|
7348
|
+
import { existsSync as existsSync11, readFileSync as readFileSync11 } from "fs";
|
|
6968
7349
|
|
|
6969
7350
|
// src/lib/custom-install.ts
|
|
6970
7351
|
init_config();
|
|
6971
7352
|
init_manifest();
|
|
6972
|
-
import { existsSync as existsSync9, mkdirSync as mkdirSync7, mkdtempSync, readFileSync as
|
|
6973
|
-
import { join as
|
|
7353
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync7, mkdtempSync, readFileSync as readFileSync8, rmSync as rmSync3 } from "fs";
|
|
7354
|
+
import { join as join9 } from "path";
|
|
6974
7355
|
import { tmpdir } from "os";
|
|
6975
7356
|
function manifestAt(dir) {
|
|
6976
|
-
const path =
|
|
7357
|
+
const path = join9(dir, "manifest.json");
|
|
6977
7358
|
if (!existsSync9(path))
|
|
6978
7359
|
return;
|
|
6979
7360
|
try {
|
|
6980
|
-
return parseManifest(
|
|
7361
|
+
return parseManifest(readFileSync8(path, "utf-8"));
|
|
6981
7362
|
} catch {
|
|
6982
7363
|
return;
|
|
6983
7364
|
}
|
|
@@ -6996,8 +7377,8 @@ function writeFromManifest(manifest, srcDir, source, kind) {
|
|
|
6996
7377
|
return { name, version: manifest.version, kind, source, dir, scriptPath };
|
|
6997
7378
|
}
|
|
6998
7379
|
async function cloneToTemp(gitUrl) {
|
|
6999
|
-
const tmp = mkdtempSync(
|
|
7000
|
-
const proc = Bun.spawn(["git", "clone", "--depth", "1", "--quiet", gitUrl,
|
|
7380
|
+
const tmp = mkdtempSync(join9(tmpdir(), "hooks-install-"));
|
|
7381
|
+
const proc = Bun.spawn(["git", "clone", "--depth", "1", "--quiet", gitUrl, join9(tmp, "repo")], {
|
|
7001
7382
|
stdout: "pipe",
|
|
7002
7383
|
stderr: "pipe"
|
|
7003
7384
|
});
|
|
@@ -7010,7 +7391,7 @@ async function cloneToTemp(gitUrl) {
|
|
|
7010
7391
|
rmSync3(tmp, { recursive: true, force: true });
|
|
7011
7392
|
throw new Error(`git clone failed (${exitCode}): ${stderr.trim()}`);
|
|
7012
7393
|
}
|
|
7013
|
-
return
|
|
7394
|
+
return join9(tmp, "repo");
|
|
7014
7395
|
}
|
|
7015
7396
|
function isGitUrl(value) {
|
|
7016
7397
|
return value.startsWith("git@") || value.startsWith("ssh://") || value.startsWith("file://") || value.endsWith(".git");
|
|
@@ -7038,7 +7419,7 @@ async function installCustomSource(source) {
|
|
|
7038
7419
|
throw new Error(`No such local path: ${source}`);
|
|
7039
7420
|
}
|
|
7040
7421
|
if (source.endsWith("manifest.json")) {
|
|
7041
|
-
const manifest = parseManifest(
|
|
7422
|
+
const manifest = parseManifest(readFileSync8(source, "utf-8"));
|
|
7042
7423
|
return writeFromManifest(manifest, source.substring(0, source.lastIndexOf("/")), source, "local");
|
|
7043
7424
|
}
|
|
7044
7425
|
return copyManifestDir(source, source, "local");
|
|
@@ -7069,7 +7450,7 @@ async function installManifestUrl(url) {
|
|
|
7069
7450
|
}
|
|
7070
7451
|
// src/lib/sync.ts
|
|
7071
7452
|
import { createHash as createHash2 } from "crypto";
|
|
7072
|
-
import { readFileSync as
|
|
7453
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
7073
7454
|
init_config();
|
|
7074
7455
|
init_db();
|
|
7075
7456
|
init_store();
|
|
@@ -7078,7 +7459,7 @@ function sha256OfText(text) {
|
|
|
7078
7459
|
return createHash2("sha256").update(text).digest("hex");
|
|
7079
7460
|
}
|
|
7080
7461
|
function sha256OfPath(path) {
|
|
7081
|
-
return createHash2("sha256").update(
|
|
7462
|
+
return createHash2("sha256").update(readFileSync9(path)).digest("hex");
|
|
7082
7463
|
}
|
|
7083
7464
|
function collectBundledCatalog() {
|
|
7084
7465
|
const out = [];
|
|
@@ -7333,6 +7714,9 @@ async function fetchPinnedHook(name, version, apiUrl) {
|
|
|
7333
7714
|
init_manifest();
|
|
7334
7715
|
init_store();
|
|
7335
7716
|
init_config();
|
|
7717
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
7718
|
+
import { dirname as dirname4, join as join10 } from "path";
|
|
7719
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
7336
7720
|
|
|
7337
7721
|
// src/lib/secure-compare.ts
|
|
7338
7722
|
function secureEqual(a, b) {
|
|
@@ -7347,11 +7731,170 @@ function secureEqual(a, b) {
|
|
|
7347
7731
|
return diff === 0;
|
|
7348
7732
|
}
|
|
7349
7733
|
|
|
7734
|
+
// src/openapi.ts
|
|
7735
|
+
var openApiDocument = {
|
|
7736
|
+
openapi: "3.1.0",
|
|
7737
|
+
info: {
|
|
7738
|
+
title: "Hooks Registry API",
|
|
7739
|
+
version: "0.1.0",
|
|
7740
|
+
description: "Local registry HTTP API for @hasna/hooks: catalog, lock, and artifact reads are open; publishing (PUT /api/v1/hooks) requires the API key (authorization: Bearer or x-api-key)."
|
|
7741
|
+
},
|
|
7742
|
+
servers: [{ url: "/" }],
|
|
7743
|
+
paths: {
|
|
7744
|
+
"/health": {
|
|
7745
|
+
get: {
|
|
7746
|
+
summary: "Liveness probe",
|
|
7747
|
+
operationId: "getHealth",
|
|
7748
|
+
responses: {
|
|
7749
|
+
"200": {
|
|
7750
|
+
description: "Service is alive",
|
|
7751
|
+
content: {
|
|
7752
|
+
"application/json": {
|
|
7753
|
+
schema: { type: "object", additionalProperties: true }
|
|
7754
|
+
}
|
|
7755
|
+
}
|
|
7756
|
+
}
|
|
7757
|
+
}
|
|
7758
|
+
}
|
|
7759
|
+
},
|
|
7760
|
+
"/ready": {
|
|
7761
|
+
get: {
|
|
7762
|
+
summary: "Readiness probe \u2014 the hooks.lock store is readable",
|
|
7763
|
+
operationId: "getReady",
|
|
7764
|
+
responses: {
|
|
7765
|
+
"200": { description: "Lock store is readable" },
|
|
7766
|
+
"503": { description: "Lock store is not readable" }
|
|
7767
|
+
}
|
|
7768
|
+
}
|
|
7769
|
+
},
|
|
7770
|
+
"/version": {
|
|
7771
|
+
get: {
|
|
7772
|
+
summary: "Package version",
|
|
7773
|
+
operationId: "getVersion",
|
|
7774
|
+
responses: {
|
|
7775
|
+
"200": {
|
|
7776
|
+
description: "The installed @hasna/hooks version",
|
|
7777
|
+
content: {
|
|
7778
|
+
"application/json": {
|
|
7779
|
+
schema: { type: "object", properties: { version: { type: "string" } } }
|
|
7780
|
+
}
|
|
7781
|
+
}
|
|
7782
|
+
}
|
|
7783
|
+
}
|
|
7784
|
+
}
|
|
7785
|
+
},
|
|
7786
|
+
"/openapi.json": {
|
|
7787
|
+
get: {
|
|
7788
|
+
summary: "This OpenAPI document",
|
|
7789
|
+
operationId: "getOpenApi",
|
|
7790
|
+
responses: { "200": { description: "OpenAPI 3.1 document" } }
|
|
7791
|
+
}
|
|
7792
|
+
},
|
|
7793
|
+
"/api/v1/catalog": {
|
|
7794
|
+
get: {
|
|
7795
|
+
summary: "List the full hook catalog with versions and sha256",
|
|
7796
|
+
operationId: "getCatalog",
|
|
7797
|
+
responses: {
|
|
7798
|
+
"200": {
|
|
7799
|
+
description: "Catalog entries",
|
|
7800
|
+
content: {
|
|
7801
|
+
"application/json": {
|
|
7802
|
+
schema: {
|
|
7803
|
+
type: "object",
|
|
7804
|
+
properties: {
|
|
7805
|
+
hooks: {
|
|
7806
|
+
type: "array",
|
|
7807
|
+
items: {
|
|
7808
|
+
type: "object",
|
|
7809
|
+
properties: {
|
|
7810
|
+
name: { type: "string" },
|
|
7811
|
+
version: { type: "string" },
|
|
7812
|
+
sha256: { type: "string" },
|
|
7813
|
+
events: { type: "array", items: { type: "string" } }
|
|
7814
|
+
}
|
|
7815
|
+
}
|
|
7816
|
+
}
|
|
7817
|
+
}
|
|
7818
|
+
}
|
|
7819
|
+
}
|
|
7820
|
+
}
|
|
7821
|
+
}
|
|
7822
|
+
}
|
|
7823
|
+
}
|
|
7824
|
+
},
|
|
7825
|
+
"/api/v1/lock": {
|
|
7826
|
+
get: {
|
|
7827
|
+
summary: "Read the published hooks.lock with per-hook versions",
|
|
7828
|
+
operationId: "getLock",
|
|
7829
|
+
responses: { "200": { description: "The lock file" } }
|
|
7830
|
+
}
|
|
7831
|
+
},
|
|
7832
|
+
"/api/v1/hooks/{name}/{version}": {
|
|
7833
|
+
get: {
|
|
7834
|
+
summary: "Fetch one hook's manifest and script",
|
|
7835
|
+
operationId: "getHookArtifact",
|
|
7836
|
+
parameters: [
|
|
7837
|
+
{ name: "name", in: "path", required: true, schema: { type: "string" } },
|
|
7838
|
+
{ name: "version", in: "path", required: true, schema: { type: "string" } }
|
|
7839
|
+
],
|
|
7840
|
+
responses: {
|
|
7841
|
+
"200": {
|
|
7842
|
+
description: "Hook artifact; x-hook-sha256 header carries the script hash"
|
|
7843
|
+
},
|
|
7844
|
+
"400": { description: "Invalid URL encoding or semver version" },
|
|
7845
|
+
"404": { description: "Hook not found locally" }
|
|
7846
|
+
}
|
|
7847
|
+
}
|
|
7848
|
+
},
|
|
7849
|
+
"/api/v1/hooks": {
|
|
7850
|
+
put: {
|
|
7851
|
+
summary: "Publish/retrust a hook (requires the API key)",
|
|
7852
|
+
operationId: "putHook",
|
|
7853
|
+
requestBody: {
|
|
7854
|
+
required: true,
|
|
7855
|
+
content: {
|
|
7856
|
+
"application/json": {
|
|
7857
|
+
schema: {
|
|
7858
|
+
type: "object",
|
|
7859
|
+
properties: { name: { type: "string" }, version: { type: "string" } }
|
|
7860
|
+
}
|
|
7861
|
+
}
|
|
7862
|
+
}
|
|
7863
|
+
},
|
|
7864
|
+
responses: {
|
|
7865
|
+
"200": { description: "Hook retrusted and pin updated" },
|
|
7866
|
+
"401": { description: "Missing or invalid API key" },
|
|
7867
|
+
"404": { description: "Hook not found in local store" },
|
|
7868
|
+
"409": { description: "Version mismatch with the local store" }
|
|
7869
|
+
},
|
|
7870
|
+
security: [{ apiKey: [] }]
|
|
7871
|
+
}
|
|
7872
|
+
}
|
|
7873
|
+
},
|
|
7874
|
+
components: {
|
|
7875
|
+
securitySchemes: {
|
|
7876
|
+
apiKey: {
|
|
7877
|
+
type: "http",
|
|
7878
|
+
scheme: "bearer",
|
|
7879
|
+
description: "The registry API key (HASNA_HOOKS_API_KEY / HOOKS_API_KEY)."
|
|
7880
|
+
}
|
|
7881
|
+
}
|
|
7882
|
+
}
|
|
7883
|
+
};
|
|
7884
|
+
|
|
7350
7885
|
// src/serve.ts
|
|
7351
7886
|
init_semver();
|
|
7352
7887
|
var DEFAULT_SERVE_PORT = 39428;
|
|
7353
7888
|
var SERVE_HOST = "127.0.0.1";
|
|
7354
7889
|
var SERVE_SERVICE_NAME = "hooks-registry";
|
|
7890
|
+
function packageVersion() {
|
|
7891
|
+
try {
|
|
7892
|
+
const pkgPath = join10(dirname4(fileURLToPath3(import.meta.url)), "..", "package.json");
|
|
7893
|
+
return JSON.parse(readFileSync10(pkgPath, "utf-8")).version || "0.0.0";
|
|
7894
|
+
} catch {
|
|
7895
|
+
return "0.0.0";
|
|
7896
|
+
}
|
|
7897
|
+
}
|
|
7355
7898
|
async function buildCatalog() {
|
|
7356
7899
|
const byName = new Map;
|
|
7357
7900
|
for (const meta of HOOKS) {
|
|
@@ -7424,6 +7967,20 @@ function handleServeRequest(req, apiKey) {
|
|
|
7424
7967
|
if (url.pathname === "/health" && req.method === "GET") {
|
|
7425
7968
|
return Promise.resolve(json({ status: "ok", name: SERVE_SERVICE_NAME }));
|
|
7426
7969
|
}
|
|
7970
|
+
if (url.pathname === "/ready" && req.method === "GET") {
|
|
7971
|
+
try {
|
|
7972
|
+
readLock();
|
|
7973
|
+
return Promise.resolve(json({ status: "ready", name: SERVE_SERVICE_NAME }));
|
|
7974
|
+
} catch {
|
|
7975
|
+
return Promise.resolve(json({ status: "not-ready", name: SERVE_SERVICE_NAME }, 503));
|
|
7976
|
+
}
|
|
7977
|
+
}
|
|
7978
|
+
if (url.pathname === "/version" && req.method === "GET") {
|
|
7979
|
+
return Promise.resolve(json({ version: packageVersion() }));
|
|
7980
|
+
}
|
|
7981
|
+
if (url.pathname === "/openapi.json" && req.method === "GET") {
|
|
7982
|
+
return Promise.resolve(json(openApiDocument));
|
|
7983
|
+
}
|
|
7427
7984
|
if (url.pathname === "/api/v1/catalog" && req.method === "GET") {
|
|
7428
7985
|
return buildCatalog().then((catalog) => json({ hooks: catalog }));
|
|
7429
7986
|
}
|
|
@@ -7594,11 +8151,11 @@ async function runHook(name, input, options = {}) {
|
|
|
7594
8151
|
throw new Error(`Hook '${name}' not found`);
|
|
7595
8152
|
const hookScript = getHookPath(name) + "/src/hook.ts";
|
|
7596
8153
|
const resolvedScript = custom2?.scriptPath;
|
|
7597
|
-
const script = resolvedScript ?? (
|
|
8154
|
+
const script = resolvedScript ?? (existsSync11(hookScript) ? hookScript : undefined);
|
|
7598
8155
|
if (!script)
|
|
7599
8156
|
throw new Error(`Hook script not found: ${name}`);
|
|
7600
8157
|
const { sha256Of: sha256Of2, checkScriptHash: checkScriptHash2 } = await Promise.resolve().then(() => (init_store(), exports_store));
|
|
7601
|
-
const content =
|
|
8158
|
+
const content = readFileSync11(script);
|
|
7602
8159
|
const check = checkScriptHash2(name, sha256Of2(content));
|
|
7603
8160
|
if (!check.ok) {
|
|
7604
8161
|
throw new Error(`Hook '${name}' script changed since it was trusted (sha256 ${check.expected} != ${check.actual}). Run 'hooks trust ${name}' to trust the new content.`);
|
|
@@ -7629,6 +8186,7 @@ async function runHook(name, input, options = {}) {
|
|
|
7629
8186
|
scriptPath: script,
|
|
7630
8187
|
content,
|
|
7631
8188
|
args: custom2?.manifest.args ?? [],
|
|
8189
|
+
env: custom2?.manifest.env,
|
|
7632
8190
|
stdin: JSON.stringify(hookInput),
|
|
7633
8191
|
timeout: effectiveTimeout ?? undefined
|
|
7634
8192
|
}));
|
|
@@ -7686,102 +8244,102 @@ async function runHook(name, input, options = {}) {
|
|
|
7686
8244
|
return { output, stderr: stderrText, exitCode };
|
|
7687
8245
|
}
|
|
7688
8246
|
export {
|
|
7689
|
-
|
|
7690
|
-
writeCustomHook,
|
|
7691
|
-
writeConfig,
|
|
7692
|
-
verifyScriptHash,
|
|
7693
|
-
upsertHookRecord,
|
|
7694
|
-
updateProfile,
|
|
7695
|
-
uninstallHook,
|
|
7696
|
-
touchProfile,
|
|
7697
|
-
syncHooks,
|
|
7698
|
-
storageSync,
|
|
7699
|
-
storagePush,
|
|
7700
|
-
storagePull,
|
|
7701
|
-
startServeServer,
|
|
7702
|
-
sha256Of,
|
|
7703
|
-
sha256File,
|
|
7704
|
-
setPinnedHook,
|
|
7705
|
-
searchHooks,
|
|
7706
|
-
runStorageMigrations,
|
|
7707
|
-
runHook,
|
|
7708
|
-
retrustHook,
|
|
7709
|
-
resolveTables,
|
|
7710
|
-
resolveScriptPath,
|
|
7711
|
-
resolveScript,
|
|
7712
|
-
resolveHookMeta,
|
|
7713
|
-
resolveHookDir,
|
|
7714
|
-
resolveHook,
|
|
7715
|
-
resolveApiUrl,
|
|
7716
|
-
resolveApiKey,
|
|
7717
|
-
removeProjectHook,
|
|
7718
|
-
removePinnedHook,
|
|
7719
|
-
removeHookRecord,
|
|
7720
|
-
removeHookFromStore,
|
|
7721
|
-
removeHook,
|
|
7722
|
-
readLock,
|
|
7723
|
-
readCustomManifest,
|
|
7724
|
-
readConfig,
|
|
7725
|
-
provisionCloudflareResources,
|
|
7726
|
-
planSync,
|
|
7727
|
-
pinInstalledHook,
|
|
7728
|
-
parseStorageTables,
|
|
7729
|
-
parseManifest,
|
|
7730
|
-
listProjectHooks,
|
|
7731
|
-
listProfiles,
|
|
7732
|
-
listHookRecords,
|
|
7733
|
-
listCustomHooks,
|
|
7734
|
-
isEventSupported,
|
|
7735
|
-
isCustomSource,
|
|
7736
|
-
installHooksForProject,
|
|
7737
|
-
installHooks,
|
|
7738
|
-
installHookForProject,
|
|
7739
|
-
installHook,
|
|
7740
|
-
installCustomSource,
|
|
7741
|
-
importProfiles,
|
|
7742
|
-
hookExists,
|
|
7743
|
-
handleServeRequest,
|
|
7744
|
-
getSyncMetaAll,
|
|
7745
|
-
getStorageStatus,
|
|
7746
|
-
getStoragePg,
|
|
7747
|
-
getStorageDatabaseUrl,
|
|
7748
|
-
getStorageDatabaseEnvName,
|
|
7749
|
-
getStorageDatabaseEnv,
|
|
7750
|
-
getStorageBackend,
|
|
7751
|
-
getSettingsPath,
|
|
7752
|
-
getRegisteredHooksForTarget,
|
|
7753
|
-
getRegisteredHooks,
|
|
7754
|
-
getProfilesDir,
|
|
7755
|
-
getProfile,
|
|
7756
|
-
getPinnedHook,
|
|
7757
|
-
getLockPath,
|
|
7758
|
-
getInstalledHooks,
|
|
7759
|
-
getHooksDataDir,
|
|
7760
|
-
getHooksByCategory,
|
|
7761
|
-
getHookRecord,
|
|
7762
|
-
getHookPath,
|
|
7763
|
-
getHookEvents,
|
|
7764
|
-
getHook,
|
|
7765
|
-
getCustomHooksDir,
|
|
7766
|
-
getConfigPath,
|
|
7767
|
-
fetchPinnedHook,
|
|
7768
|
-
exportProfiles,
|
|
7769
|
-
deleteProfile,
|
|
7770
|
-
createProfile,
|
|
7771
|
-
checkScriptHash,
|
|
7772
|
-
buildCodewithTomlFragment,
|
|
7773
|
-
STORAGE_TABLES,
|
|
7774
|
-
STORAGE_DATABASE_ENV,
|
|
7775
|
-
STORAGE_BACKEND_ENV,
|
|
7776
|
-
STORAGE_BACKENDS,
|
|
7777
|
-
RETIRED_STORAGE_MODE_ENV,
|
|
7778
|
-
HOOK_EVENTS,
|
|
7779
|
-
HOOKS_STORAGE_TABLES,
|
|
7780
|
-
HOOKS_STORAGE_FALLBACK_ENV,
|
|
7781
|
-
HOOKS_STORAGE_ENV,
|
|
7782
|
-
HOOKS_STORAGE_BACKEND_FALLBACK_ENV,
|
|
7783
|
-
HOOKS_STORAGE_BACKEND_ENV,
|
|
7784
|
-
HOOKS,
|
|
8247
|
+
CATEGORIES,
|
|
7785
8248
|
DEFAULT_SERVE_PORT,
|
|
7786
|
-
|
|
8249
|
+
HOOKS,
|
|
8250
|
+
HOOKS_STORAGE_BACKEND_ENV,
|
|
8251
|
+
HOOKS_STORAGE_BACKEND_FALLBACK_ENV,
|
|
8252
|
+
HOOKS_STORAGE_ENV,
|
|
8253
|
+
HOOKS_STORAGE_FALLBACK_ENV,
|
|
8254
|
+
HOOKS_STORAGE_TABLES,
|
|
8255
|
+
HOOK_EVENTS,
|
|
8256
|
+
RETIRED_STORAGE_MODE_ENV,
|
|
8257
|
+
STORAGE_BACKENDS,
|
|
8258
|
+
STORAGE_BACKEND_ENV,
|
|
8259
|
+
STORAGE_DATABASE_ENV,
|
|
8260
|
+
STORAGE_TABLES,
|
|
8261
|
+
buildCodewithTomlFragment,
|
|
8262
|
+
checkScriptHash,
|
|
8263
|
+
createProfile,
|
|
8264
|
+
deleteProfile,
|
|
8265
|
+
exportProfiles,
|
|
8266
|
+
fetchPinnedHook,
|
|
8267
|
+
getConfigPath,
|
|
8268
|
+
getCustomHooksDir,
|
|
8269
|
+
getHook,
|
|
8270
|
+
getHookEvents,
|
|
8271
|
+
getHookPath,
|
|
8272
|
+
getHookRecord,
|
|
8273
|
+
getHooksByCategory,
|
|
8274
|
+
getHooksDataDir,
|
|
8275
|
+
getInstalledHooks,
|
|
8276
|
+
getLockPath,
|
|
8277
|
+
getPinnedHook,
|
|
8278
|
+
getProfile,
|
|
8279
|
+
getProfilesDir,
|
|
8280
|
+
getRegisteredHooks,
|
|
8281
|
+
getRegisteredHooksForTarget,
|
|
8282
|
+
getSettingsPath,
|
|
8283
|
+
getStorageBackend,
|
|
8284
|
+
getStorageDatabaseEnv,
|
|
8285
|
+
getStorageDatabaseEnvName,
|
|
8286
|
+
getStorageDatabaseUrl,
|
|
8287
|
+
getStoragePg,
|
|
8288
|
+
getStorageStatus,
|
|
8289
|
+
getSyncMetaAll,
|
|
8290
|
+
handleServeRequest,
|
|
8291
|
+
hookExists,
|
|
8292
|
+
importProfiles,
|
|
8293
|
+
installCustomSource,
|
|
8294
|
+
installHook,
|
|
8295
|
+
installHookForProject,
|
|
8296
|
+
installHooks,
|
|
8297
|
+
installHooksForProject,
|
|
8298
|
+
isCustomSource,
|
|
8299
|
+
isEventSupported,
|
|
8300
|
+
listCustomHooks,
|
|
8301
|
+
listHookRecords,
|
|
8302
|
+
listProfiles,
|
|
8303
|
+
listProjectHooks,
|
|
8304
|
+
parseManifest,
|
|
8305
|
+
parseStorageTables,
|
|
8306
|
+
pinInstalledHook,
|
|
8307
|
+
planSync,
|
|
8308
|
+
provisionCloudflareResources,
|
|
8309
|
+
readConfig,
|
|
8310
|
+
readCustomManifest,
|
|
8311
|
+
readLock,
|
|
8312
|
+
removeHook,
|
|
8313
|
+
removeHookFromStore,
|
|
8314
|
+
removeHookRecord,
|
|
8315
|
+
removePinnedHook,
|
|
8316
|
+
removeProjectHook,
|
|
8317
|
+
resolveApiKey,
|
|
8318
|
+
resolveApiUrl,
|
|
8319
|
+
resolveHook,
|
|
8320
|
+
resolveHookDir,
|
|
8321
|
+
resolveHookMeta,
|
|
8322
|
+
resolveScript,
|
|
8323
|
+
resolveScriptPath,
|
|
8324
|
+
resolveTables,
|
|
8325
|
+
retrustHook,
|
|
8326
|
+
runHook,
|
|
8327
|
+
runStorageMigrations,
|
|
8328
|
+
searchHooks,
|
|
8329
|
+
setPinnedHook,
|
|
8330
|
+
sha256File,
|
|
8331
|
+
sha256Of,
|
|
8332
|
+
startServeServer,
|
|
8333
|
+
storagePull,
|
|
8334
|
+
storagePush,
|
|
8335
|
+
storageSync,
|
|
8336
|
+
syncHooks,
|
|
8337
|
+
touchProfile,
|
|
8338
|
+
uninstallHook,
|
|
8339
|
+
updateProfile,
|
|
8340
|
+
upsertHookRecord,
|
|
8341
|
+
verifyScriptHash,
|
|
8342
|
+
writeConfig,
|
|
8343
|
+
writeCustomHook,
|
|
8344
|
+
writeLock
|
|
7787
8345
|
};
|