Conversation
metalwarrior665
left a comment
There was a problem hiding this comment.
This looks simpler than I thought :) I have just minor remarks, feel free to argue against them. Let's wait for Juan but this seems quite uncontroversial.
| for (const [index, configEntry] of configs.entries()) { | ||
| const { folder, actorFullName } = configEntry as ActorGlobConfigEntry; | ||
|
|
||
| // TODO: Allow for combined filtering? |
There was a problem hiding this comment.
I would allow this, don't think it is that confusing or dangerous. You simply filter once per folder and then per name. There might be legit use-cases for this
| import { selectActors } from './actor-filtering.js'; | ||
| import { isPathWithinScope } from './path-utils.js'; | ||
| import type { ActorConfig, ActorConfigFile } from './types.js'; | ||
| import type { ActorConfig, ActorConfigFile, ActorConfigFileEntry, ActorGlobConfigEntry } from './types.js'; |
There was a problem hiding this comment.
These types don't match very well, one says "File", other doesn't.
btw we already have ActorConfig type which is basically the same thing, we should either unify them or derive one from the other. Are there cases where these will differ? If we are simply merging them then they should not differ. No need to solve that in this PR but sooner rather than later.
|
|
||
| const validateGlobConfigEntries = (configs: unknown[]): ActorGlobConfigEntry[] => { | ||
| for (const [index, configEntry] of configs.entries()) { | ||
| const { folder, actorFullName } = configEntry as ActorGlobConfigEntry; |
There was a problem hiding this comment.
Could be zod parse I guess but I don't know if you can get such a nice errors from it, don't have experience
There was a problem hiding this comment.
I also don't have much experience with zod but when I looked into it I remember that there was a lot of overhead to make it work. I don't think it's necessary, as long as the inputs here are somewhat limited in number
|
|
||
| let overlay: Partial<ActorConfigFileEntry> = {}; | ||
| for (const configEntry of [...matchingFolderConfigs, ...matchingActorFullNameConfigs]) { | ||
| const { folder: matchedFolder, actorFullName: matchedActorFullName, ...rest } = configEntry; |
There was a problem hiding this comment.
Let's think a bit if we shouldn't separate the matching fields folder, actorFullName vs the configs, it is a bit weird they are on the same level
| ); | ||
|
|
||
| let overlay: Partial<ActorConfigFileEntry> = {}; | ||
| for (const configEntry of [...matchingFolderConfigs, ...matchingActorFullNameConfigs]) { |
There was a problem hiding this comment.
If we want to support having both folder and name with AND logic, this would need to change
| const matchingActorFullNameConfigs = configs.filter( | ||
| (configEntry) => | ||
| configEntry.actorFullName !== undefined && | ||
| typeof actorEntry.actorFullName === 'string' && |
There was a problem hiding this comment.
We already validate this eariler and the type should be string | undefined now, no?
|
In this PR I postponed the problem that would pose adding the notifier object. As of now, an object would be completely rewritten and it does not have a partial merging logic (e.g. every notifier would get the same token but each actor needs to get its own slack channel for test report). I gave it some thought and even though it's not necessary as of this PR would be merged, we want to add it soon so it makes sense to do it here, so that the diff is tidy. @metalwarrior665 I'll do your requested changes first and then I'll try to add the deep object merging logic. |
| 1. entries matching on `folder` alone | ||
| 2. entries matching on `actorFullName` alone | ||
| 3. entries matching on both `folder` and `actorFullName` together |
There was a problem hiding this comment.
This might be more complicated than just order based
| const validateGlobConfigEntries = (configs: unknown[]): ActorGlobConfigEntry[] => { | ||
| for (const [index, configEntry] of configs.entries()) { | ||
| const { folder, actorFullName } = configEntry as ActorGlobConfigEntry; | ||
| const { match, set } = configEntry as ActorGlobConfigEntry; |
There was a problem hiding this comment.
I would give this 2nd thought; maybe it is unnecessarily nested now just for the 2 matcher fields.
| 3. entries matching on both `folder` and `actorFullName` together | ||
| 4. the actor's own literal entry in `actors[]` | ||
|
|
||
| Within a single tier, if more than one entry matches, only the _last_ one (array order) applies — earlier same-tier matches are dropped entirely, not merged in. Across tiers, results are deep-merged from lowest to highest precedence: a higher tier wins on any field it sets, but a field it doesn't set is inherited from a lower tier rather than being lost. Object-valued fields merge key by key; array-valued fields (e.g. `overrideActorContext`) keep the higher tier's array intact and append only the lower tier's entries that aren't already present, preserving the higher tier's order. |
There was a problem hiding this comment.
This is too complex. I would drop the notion of tiers completely. 1-3 can just be taken in order (any use-case for the current way?). And the point 4 can stay undocumented (for backward compat) and we just append it at the end of the glob array so it will override any conflicting fields before.
only the last one (array order) applies — earlier same-tier matches are dropped entirely, not merged in
I don't get this. I thought the whole goal is to merge here. E.g. one glob sets token on Actor X, another glob sets slack on Actor X etc., these are merged in the config object. Theoretically, we could only merge the top level config fields but I'm not sure about this
array-valued fields (e.g.
overrideActorContext) keep the higher tier's array intact
I think we should not merge arrays. They shouldn't include more nested objects so there won't be need for any deep merging. Users can just retype the whole array in the later glob.
Closes #128
configs?: ActorGlobConfigEntry[]on the config file. Each entry is{ match, set }:matchrequires at least one offolder/actorFullName(glob patterns viaminimatch), and both can be set together as an AND match (more specific than either alone).setis required — amatchwith nothing to apply is a misconfiguration andreadConfigFilethrows.setoverlays whatever properties it carries with no special knowledge oftokenEnvVaror any other field name. An unrecognized/misspelled property merges in and simply has no effect, same as today.folder-only entries → matchingactorFullName-only entries → matching combined (folderANDactorFullName) entries → the actor's own literal entry (never overridden). Within a single tier, only the last matching entry (array position, not pattern specificity) applies — earlier same-tier matches are dropped outright, not merged in.notifier: { slack: {...}, email: {...} }can be filled in piecemeal across tiers instead of one config entry clobbering the whole object), and array fields (e.g.overrideActorContext) keep the higher tier's array intact and append only the lower tier's genuinely novel entries, preserving the higher tier's order. This is backed by a new hand-rolled, unit-testeddeepMergehelper.minimatchglob rules, see https://github.com/isaacs/minimatch for the full pattern syntax. Notably, "match every actor" needs**, not*, since*doesn't cross path segments.tokenEnvVaris optional on an actor entry now, but still required overall: once an actor's full config is resolved (literal + any matchingconfigs), atokenEnvVarmust be present orreadConfigFilethrows — this is the only place the code cares about that specific field name. This is a design choice and should be work as example. Merging the globs doesn't assume anything about the structure of the ending result of the config file, check on the correct shape of the actor configuration must be placed after the globs merge.configsis entirely optional; a config file without it behaves exactly as before.ActorIdentity(folder/actorFullName) andActorSettings(tokenEnvVar/overrideActorContext) are now shared building blocks — anactors[]entry isActorDeclaration = ActorIdentity & ActorSettings, and the resolved outputActorConfig extends ActorIdentity, so the two can't silently drift apart. The top-level config file shape is renamedConfigFileSchema(previouslyActorConfigFile, which read too similarly to the unrelatedActorConfig).