Document that package.json aliases may target only direct subfolders of src #21

Merged
temeddix merged 27 commits from alias-targets into main 2026-10-18 14:37:50 +00:00
5 changed files with 40 additions and 59 deletions
Showing only changes of commit cd1864e1a9 - Show all commits
+5 -10
View File
@@ -60,15 +60,6 @@ export async function retryInvalidToken<T extends { status: { success: boolean }
return result; return result;
} }
function hasCodexTokens(tokens: unknown): boolean {
return (
isObject(tokens) &&
["access_token", "refresh_token", "id_token", "account_id"].every(
(name) => typeof tokens[name] === "string" && tokens[name] !== "",
)
);
}
async function readCodexAuth(path: string): Promise<string> { async function readCodexAuth(path: string): Promise<string> {
const stat = await lstat(path); const stat = await lstat(path);
if (!stat.isFile() || stat.isSymbolicLink()) { if (!stat.isFile() || stat.isSymbolicLink()) {
@@ -84,11 +75,15 @@ async function readCodexAuth(path: string): Promise<string> {
} catch { } catch {
throw new Error("invalid Codex login"); throw new Error("invalid Codex login");
} }
const tokens = isObject(value) ? value.tokens : undefined;
if ( if (
!isObject(value) || !isObject(value) ||
typeof value.auth_mode !== "string" || typeof value.auth_mode !== "string" ||
value.auth_mode === "" || value.auth_mode === "" ||
!hasCodexTokens(value.tokens) !isObject(tokens) ||
!["access_token", "refresh_token", "id_token", "account_id"].every(
(name) => typeof tokens[name] === "string" && tokens[name] !== "",
)
) { ) {
throw new Error("invalid Codex login"); throw new Error("invalid Codex login");
} }
+5 -11
View File
@@ -49,16 +49,6 @@ async function profileProcessIds(uid: number): Promise<number[]> {
.map(Number); .map(Number);
} }
function killProcess(pid: number): void {
try {
process.kill(pid, "SIGKILL");
} catch (error) {
if (!isNotFound(error)) {
throw error;
}
}
}
// Kills the UID's processes every 10 ms until none is left, for up to 100 rounds. // Kills the UID's processes every 10 ms until none is left, for up to 100 rounds.
export async function killProfileProcesses(uid: number, attempt = 0): Promise<void> { export async function killProfileProcesses(uid: number, attempt = 0): Promise<void> {
if (attempt === 100) { if (attempt === 100) {
@@ -69,7 +59,11 @@ export async function killProfileProcesses(uid: number, attempt = 0): Promise<vo
return; return;
} }
for (const pid of ids) { for (const pid of ids) {
killProcess(pid); try {
process.kill(pid, "SIGKILL");
} catch (error) {
if (!isNotFound(error)) throw error;
}
} }
await sleep(10); await sleep(10);
await killProfileProcesses(uid, attempt + 1); await killProfileProcesses(uid, attempt + 1);
+6 -2
View File
@@ -5,8 +5,8 @@ import process from "node:process";
import { isNotFound } from "./errors.ts"; import { isNotFound } from "./errors.ts";
import { isObject } from "./json.ts"; import { isObject } from "./json.ts";
import { failureFooter, isFailureReason, isProfileType, withProfile } from "./profiles.ts"; import { failureFooter, withProfile } from "./profiles.ts";
import type { ProfileFailure } from "./profiles.ts"; import type { FailureReason, ProfileFailure } from "./profiles.ts";
import { readRuntimeManifest } from "./runtime.ts"; import { readRuntimeManifest } from "./runtime.ts";
import type { RuntimeManifest, RuntimeOutcome } from "./runtime.ts"; import type { RuntimeManifest, RuntimeOutcome } from "./runtime.ts";
@@ -30,6 +30,10 @@ const VERDICTS: Record<string, [event: string, mark: string]> = {
Approved: ["APPROVED", "✅"], Approved: ["APPROVED", "✅"],
"Changes requested": ["REQUEST_CHANGES", "🛑"], "Changes requested": ["REQUEST_CHANGES", "🛑"],
}; };
const PROFILE_TYPES: Set<unknown> = new Set(["claude", "codex", "opencode"]);
const FAILURE_REASONS: Set<unknown> = new Set(["sign-in failed", "usage exhausted", "failed"]);
const isProfileType = (value: unknown): value is ProfileFailure["type"] => PROFILE_TYPES.has(value);
const isFailureReason = (value: unknown): value is FailureReason => FAILURE_REASONS.has(value);
const failureComment = (failures: ProfileFailure[]): string => const failureComment = (failures: ProfileFailure[]): string =>
`AI bot failed.\n\n${failureFooter(failures)}`; `AI bot failed.\n\n${failureFooter(failures)}`;
+1 -14
View File
@@ -1,18 +1,9 @@
export type ProfileType = "claude" | "codex" | "opencode";
export interface BotProfile { export interface BotProfile {
type: ProfileType; type: "claude" | "codex" | "opencode";
model: string; model: string;
token?: string; token?: string;
} }
const PROFILE_TYPES: Set<unknown> = new Set(["claude", "codex", "opencode"]);
const FAILURE_REASONS: Set<unknown> = new Set(["sign-in failed", "usage exhausted", "failed"]);
export function isProfileType(value: unknown): value is ProfileType {
return PROFILE_TYPES.has(value);
}
export function profileFooter(profile: Pick<BotProfile, "type" | "model">): string { export function profileFooter(profile: Pick<BotProfile, "type" | "model">): string {
return `> Profile: \`${profile.type}\` · \`${profile.model}\``; return `> Profile: \`${profile.type}\` · \`${profile.model}\``;
} }
@@ -23,10 +14,6 @@ export function withProfile(body: string, profile: Pick<BotProfile, "type" | "mo
export type FailureReason = "sign-in failed" | "usage exhausted" | "failed"; export type FailureReason = "sign-in failed" | "usage exhausted" | "failed";
export function isFailureReason(value: unknown): value is FailureReason {
return FAILURE_REASONS.has(value);
}
export type ProfileFailure = Pick<BotProfile, "type" | "model"> & { export type ProfileFailure = Pick<BotProfile, "type" | "model"> & {
reason: FailureReason; reason: FailureReason;
}; };
+23 -22
View File
@@ -39,24 +39,6 @@ async function writePrivate(path: string, value: string, uid: number, gid: numbe
await chown(path, uid, gid); await chown(path, uid, gid);
} }
async function prepareProfile(
runtimeDir: string,
profile: BotProfile,
index: number,
): Promise<RuntimeProfile> {
const uid = FIRST_PROFILE_UID + index;
const home = join(runtimeDir, `profile-${index}`);
await mkdir(home, { mode: 0o700 });
await chmod(home, 0o700);
await chown(home, uid, uid);
const tokenFile =
profile.token === undefined || profile.token === "" ? null : join(home, "token");
if (tokenFile !== null && profile.token !== undefined) {
await writePrivate(tokenFile, profile.token, uid, uid);
}
return { index, type: profile.type, model: profile.model, uid, gid: uid, home, tokenFile };
}
export async function prepareRuntime( export async function prepareRuntime(
actionPath: string, actionPath: string,
runtimeRoot: string, runtimeRoot: string,
@@ -74,10 +56,29 @@ export async function prepareRuntime(
// One profile at a time, in order, as each claims the next UID. // One profile at a time, in order, as each claims the next UID.
const profiles = await credentials.profiles.reduce<Promise<RuntimeProfile[]>>( const profiles = await credentials.profiles.reduce<Promise<RuntimeProfile[]>>(
async (previous, profile, index) => [ async (previous, profile, index) => {
...(await previous), const prepared = await previous;
await prepareProfile(runtimeDir, profile, index), const uid = FIRST_PROFILE_UID + index;
], const home = join(runtimeDir, `profile-${index}`);
await mkdir(home, { mode: 0o700 });
await chmod(home, 0o700);
await chown(home, uid, uid);
const tokenFile =
profile.token === undefined || profile.token === "" ? null : join(home, "token");
if (tokenFile !== null && profile.token !== undefined) {
await writePrivate(tokenFile, profile.token, uid, uid);
}
prepared.push({
index,
type: profile.type,
model: profile.model,
uid,
gid: uid,
home,
tokenFile,
});
return prepared;
},
Promise.resolve([]), Promise.resolve([]),
); );