[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
This commit is contained in:
118
domain/snippetPackage.ts
Normal file
118
domain/snippetPackage.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
export const SNIPPET_PACKAGE_NAME_PATTERN = /^[\w\p{L}\p{N}-]+$/u;
|
||||
|
||||
export const SNIPPET_PACKAGE_PATH_CHANGE_EVENT = "netcatty:snippets:package-path-change";
|
||||
|
||||
export type SnippetPackagePathChange = {
|
||||
from: string;
|
||||
to: string | null;
|
||||
};
|
||||
|
||||
export type SnippetPackageRenameError = "empty" | "invalidChars" | "duplicate";
|
||||
|
||||
type PackagedSnippet = { package?: string };
|
||||
|
||||
export function isSnippetPackagePathAtOrBelow(path: string, root: string): boolean {
|
||||
return path === root || path.startsWith(`${root}/`);
|
||||
}
|
||||
|
||||
export function getSnippetPackageAncestors(path: string): string[] {
|
||||
const normalized = path.trim().replace(/\/+$/g, "");
|
||||
if (!normalized) return [];
|
||||
const isAbsolute = normalized.startsWith("/");
|
||||
const parts = normalized.split("/").filter(Boolean);
|
||||
return parts.map((_, index) => {
|
||||
const joined = parts.slice(0, index + 1).join("/");
|
||||
return isAbsolute ? `/${joined}` : joined;
|
||||
});
|
||||
}
|
||||
|
||||
/** Persisted packages plus inferred ancestors from packages and snippet.package. */
|
||||
export function collectSnippetPackageTreePaths(
|
||||
packages: string[],
|
||||
snippets: PackagedSnippet[] = [],
|
||||
): string[] {
|
||||
const paths = new Set<string>();
|
||||
const add = (raw: string | undefined) => {
|
||||
if (!raw) return;
|
||||
getSnippetPackageAncestors(raw).forEach((ancestor) => paths.add(ancestor));
|
||||
};
|
||||
packages.forEach(add);
|
||||
snippets.forEach((snippet) => add(snippet.package));
|
||||
return Array.from(paths);
|
||||
}
|
||||
|
||||
export const rewriteSnippetPackagePath = (value: string, from: string, to: string): string => {
|
||||
if (value === from) return to;
|
||||
if (value.startsWith(`${from}/`)) return to + value.slice(from.length);
|
||||
return value;
|
||||
};
|
||||
|
||||
export function applySnippetPackagePathChange(
|
||||
value: string | undefined,
|
||||
change: SnippetPackagePathChange,
|
||||
): string {
|
||||
const packagePath = value || "";
|
||||
if (!packagePath || !isSnippetPackagePathAtOrBelow(packagePath, change.from)) {
|
||||
return packagePath;
|
||||
}
|
||||
return change.to === null
|
||||
? ""
|
||||
: rewriteSnippetPackagePath(packagePath, change.from, change.to);
|
||||
}
|
||||
|
||||
/** Remove a package and descendants; keep snippets and clear their package path. */
|
||||
export function deleteSnippetPackage<T extends PackagedSnippet>(
|
||||
packages: string[],
|
||||
snippets: T[],
|
||||
path: string,
|
||||
): { packages: string[]; snippets: T[] } {
|
||||
return {
|
||||
packages: packages.filter((item) => !isSnippetPackagePathAtOrBelow(item, path)),
|
||||
snippets: snippets.map((snippet) => {
|
||||
const packagePath = snippet.package || "";
|
||||
if (!packagePath || !isSnippetPackagePathAtOrBelow(packagePath, path)) return snippet;
|
||||
return { ...snippet, package: "" };
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
export function renameSnippetPackage<T extends PackagedSnippet>(
|
||||
packages: string[],
|
||||
snippets: T[],
|
||||
path: string,
|
||||
newName: string,
|
||||
):
|
||||
| { ok: true; packages: string[]; snippets: T[]; newPath: string }
|
||||
| { ok: false; error: SnippetPackageRenameError } {
|
||||
const trimmed = newName.trim();
|
||||
if (!trimmed) return { ok: false, error: "empty" };
|
||||
if (!SNIPPET_PACKAGE_NAME_PATTERN.test(trimmed)) return { ok: false, error: "invalidChars" };
|
||||
|
||||
const parts = path.split("/");
|
||||
parts[parts.length - 1] = trimmed;
|
||||
const newPath = parts.join("/");
|
||||
|
||||
if (newPath === path) {
|
||||
return { ok: true, packages, snippets, newPath };
|
||||
}
|
||||
|
||||
const occupied = collectSnippetPackageTreePaths(packages, snippets);
|
||||
const duplicate = occupied.some(
|
||||
(item) => item !== path && item.toLowerCase() === newPath.toLowerCase(),
|
||||
);
|
||||
if (duplicate) return { ok: false, error: "duplicate" };
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
newPath,
|
||||
packages: Array.from(new Set(
|
||||
packages.map((item) => rewriteSnippetPackagePath(item, path, newPath)),
|
||||
)),
|
||||
snippets: snippets.map((snippet) => {
|
||||
const packagePath = snippet.package || "";
|
||||
if (!packagePath) return snippet;
|
||||
const nextPath = rewriteSnippetPackagePath(packagePath, path, newPath);
|
||||
return nextPath === packagePath ? snippet : { ...snippet, package: nextPath };
|
||||
}),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user