140 lines
4.9 KiB
TypeScript
140 lines
4.9 KiB
TypeScript
|
|
import type { Host, Snippet } from './models';
|
||
|
|
import { hostMatchesGroupPath } from './selectHostSelection.ts';
|
||
|
|
import { normalizeGroupTargetPath } from './hostGroupPathMutations.ts';
|
||
|
|
import { isScriptSnippet } from './snippetScript.ts';
|
||
|
|
|
||
|
|
/** Whether the snippet has a runnable scope (explicit targets or all-hosts flag). */
|
||
|
|
export function snippetHasRunTargets(
|
||
|
|
snippet: Pick<Snippet, 'targets' | 'targetGroups' | 'targetsAllHosts'>,
|
||
|
|
): boolean {
|
||
|
|
if (snippet.targetsAllHosts) return true;
|
||
|
|
return Boolean(
|
||
|
|
(snippet.targets && snippet.targets.length > 0)
|
||
|
|
|| (snippet.targetGroups && snippet.targetGroups.length > 0),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
export type SnippetTargetHost = Pick<Host, 'id' | 'group'>;
|
||
|
|
|
||
|
|
export function snippetTargetsHostExplicitly(
|
||
|
|
snippet: Pick<Snippet, 'targets'>,
|
||
|
|
hostId: string,
|
||
|
|
): boolean {
|
||
|
|
return Boolean(snippet.targets?.includes(hostId));
|
||
|
|
}
|
||
|
|
|
||
|
|
export function snippetTargetsHostGroup(
|
||
|
|
snippet: Pick<Snippet, 'targetGroups'>,
|
||
|
|
host: Pick<Host, 'group'>,
|
||
|
|
): boolean {
|
||
|
|
return Boolean(snippet.targetGroups?.some(
|
||
|
|
(groupPath) => hostMatchesGroupPath(host, normalizeGroupTargetPath(groupPath)),
|
||
|
|
));
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Keep legacy unscoped onOutput semantics distinct from a deleted/cleared group scope. */
|
||
|
|
export function resolveSnippetTargetGroupsForSave(
|
||
|
|
snippet: Pick<Snippet, 'targetGroups' | 'targetsAllHosts'>,
|
||
|
|
selectedGroupPaths: string[],
|
||
|
|
): string[] | undefined {
|
||
|
|
if (snippet.targetsAllHosts) return undefined;
|
||
|
|
if (snippet.targetGroups === undefined && selectedGroupPaths.length === 0) {
|
||
|
|
return undefined;
|
||
|
|
}
|
||
|
|
return selectedGroupPaths;
|
||
|
|
}
|
||
|
|
|
||
|
|
function normalizeTargetHost(hostOrId?: SnippetTargetHost | string): SnippetTargetHost | undefined {
|
||
|
|
if (typeof hostOrId === 'string') return { id: hostOrId };
|
||
|
|
return hostOrId;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Connectable hosts for manual run / save-and-run. */
|
||
|
|
export function getRunnableHostsForSnippet(
|
||
|
|
snippet: Pick<Snippet, 'targets' | 'targetGroups' | 'targetsAllHosts'>,
|
||
|
|
hosts: Host[],
|
||
|
|
): Host[] {
|
||
|
|
const connectable = hosts.filter((host) => host.protocol !== 'serial');
|
||
|
|
if (snippet.targetsAllHosts) return connectable;
|
||
|
|
const idSet = new Set(snippet.targets ?? []);
|
||
|
|
return connectable.filter(
|
||
|
|
(host) => idSet.has(host.id) || snippetTargetsHostGroup(snippet, host),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Whether a snippet/script applies to the given host based on targets or all-hosts flag. */
|
||
|
|
export function snippetAppliesToHost(
|
||
|
|
snippet: Pick<Snippet, 'targets' | 'targetGroups' | 'targetsAllHosts'>,
|
||
|
|
hostOrId?: SnippetTargetHost | string,
|
||
|
|
): boolean {
|
||
|
|
const host = normalizeTargetHost(hostOrId);
|
||
|
|
if (!host) return false;
|
||
|
|
if (snippet.targetsAllHosts) return true;
|
||
|
|
return snippetTargetsHostExplicitly(snippet, host.id)
|
||
|
|
|| snippetTargetsHostGroup(snippet, host);
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Whether a library/shortcut invocation may run in the current terminal. */
|
||
|
|
export function snippetCanRunInTerminal(
|
||
|
|
snippet: Pick<Snippet, 'targets' | 'targetGroups' | 'targetsAllHosts'>,
|
||
|
|
host: SnippetTargetHost,
|
||
|
|
): boolean {
|
||
|
|
if (snippet.targetsAllHosts) return true;
|
||
|
|
const hasScopedTargets = Boolean(
|
||
|
|
snippet.targets?.length || snippet.targetGroups !== undefined,
|
||
|
|
);
|
||
|
|
return !hasScopedTargets || snippetAppliesToHost(snippet, host);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* onOutput triggers listen on the active terminal session. When no explicit targets
|
||
|
|
* are configured, they apply to whichever host the session is connected to.
|
||
|
|
*/
|
||
|
|
export function snippetAppliesToOutputTrigger(
|
||
|
|
snippet: Pick<Snippet, 'trigger' | 'targets' | 'targetGroups' | 'targetsAllHosts'>,
|
||
|
|
hostOrId?: SnippetTargetHost | string,
|
||
|
|
): boolean {
|
||
|
|
if (snippet.trigger !== 'onOutput') return false;
|
||
|
|
const host = normalizeTargetHost(hostOrId);
|
||
|
|
if (!host) return false;
|
||
|
|
if (snippet.targetsAllHosts) return true;
|
||
|
|
if (snippet.targets?.length || snippet.targetGroups !== undefined) {
|
||
|
|
return snippetAppliesToHost(snippet, host);
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
/** Scripts explicitly linked to a host via targets (excludes all-hosts scripts). */
|
||
|
|
export function getScriptsLinkedToHost(snippets: Snippet[], hostId: string): Snippet[] {
|
||
|
|
return snippets.filter(
|
||
|
|
(snippet) => isScriptSnippet(snippet)
|
||
|
|
&& !snippet.targetsAllHosts
|
||
|
|
&& Boolean(snippet.targets?.includes(hostId)),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function linkHostToScript(snippet: Snippet, hostId: string): Snippet {
|
||
|
|
const targets = snippet.targets ?? [];
|
||
|
|
if (targets.includes(hostId)) {
|
||
|
|
return { ...snippet, targetsAllHosts: undefined };
|
||
|
|
}
|
||
|
|
return {
|
||
|
|
...snippet,
|
||
|
|
targets: [...targets, hostId],
|
||
|
|
targetsAllHosts: undefined,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
export function unlinkHostFromScript(snippet: Snippet, hostId: string): Snippet {
|
||
|
|
const targets = snippet.targets ?? [];
|
||
|
|
if (!targets.includes(hostId)) return snippet;
|
||
|
|
const nextTargets = targets.filter((id) => id !== hostId);
|
||
|
|
return { ...snippet, targets: nextTargets.length > 0 ? nextTargets : undefined };
|
||
|
|
}
|
||
|
|
|
||
|
|
export function unlinkHostFromScripts(snippets: Snippet[], hostId: string, scriptId: string): Snippet[] {
|
||
|
|
return snippets.map((snippet) => (
|
||
|
|
snippet.id === scriptId ? unlinkHostFromScript(snippet, hostId) : snippet
|
||
|
|
));
|
||
|
|
}
|