feat(dw): 🎸 parallel agent worktrees with isolated infra + test harness

bun dw spins up a fully isolated per-worktree dev stack: Neon DB branch,
Dragonfly + ElasticMQ via docker compose, portless aliases, tmux
session, and emulate.dev Google OAuth. Workers, dev server, vite,
checkout, and stripe-listen run as concurrently siblings. bun setup-test
auto-runs on first dw and seeds unit-test-org via
clearOrg+setupOrg+ensureDefaultStripeAccount with 13 features + team
invites for all 5 useautumn.com members. Hardcoded localhost URLs in
test scenarios now read AUTUMN_TEST_BASE_URL/AUTUMN_TEST_VITE_URL.
preload-env.ts loads .env.local so bun t/cm/setup-test route to the
worktree server. 1265-line scripts/dw.ts split into scripts/dw/ module
(index + commands + helpers). server/src/db/initDrizzle.ts reverted to
upstream (search_path concerns solved by per-branch Neon isolation).
Workers enabled in agent worktrees now that SQS is per-worktree via
ElasticMQ.
This commit is contained in:
amianthus
2026-05-16 01:59:15 +01:00
parent 6e53558520
commit 25e920bbd3
46 changed files with 1881 additions and 999 deletions

View File

@@ -1,14 +1,21 @@
import {
AppEnv,
invitation,
member,
type OrgConfig,
organizations,
user,
} from "@autumn/shared";
import type { DrizzleCli } from "@server/db/initDrizzle.js";
import { createHardcodedKey } from "@server/internal/dev/api-keys/apiKeyUtils.js";
import {
createHardcodedKey,
createKey,
} from "@server/internal/dev/api-keys/apiKeyUtils.js";
import chalk from "chalk";
import { eq } from "drizzle-orm";
import { and, eq, inArray } from "drizzle-orm";
import { clearOrgDbOnly } from "@tests/utils/setup/clearOrg.js";
import { setupOrg } from "@tests/utils/setup/setupOrg.js";
import { ensureDefaultStripeAccount } from "./ensureDefaultStripeAccount.js";
const TEST_ORG_CONFIG = {
id: "org_2sWv2S8LJ9iaTjLI6UtNsfL88Kt",
@@ -18,6 +25,22 @@ const TEST_ORG_CONFIG = {
created_at: 1738583937426,
};
// Synthetic inviter pinned to the test org; satisfies invitation.inviter_id
// NOT-NULL FK without needing a real human user in a fresh worktree branch.
const TEST_INVITER_USER = {
id: "user_setup_test_inviter",
name: "Setup Test Inviter",
email: "setup-test-inviter@autumn.test",
};
const TEAM_INVITE_EMAILS = [
"ayush@useautumn.com",
"jy@useautumn.com",
"tanvir@useautumn.com",
"charlie@useautumn.com",
"owen@useautumn.com",
];
/**
* Creates a test organization in the database and generates an API key
*/
@@ -33,11 +56,6 @@ export async function createTestOrg({
);
const TEST_API_KEY = process.env.UNIT_TEST_AUTUMN_SECRET_KEY;
if (!TEST_API_KEY) {
throw new Error(
"UNIT_TEST_AUTUMN_SECRET_KEY is not set (is infisical running?)",
);
}
// Check if org already exists
const existingOrg = await db.query.organizations.findFirst({
@@ -51,27 +69,52 @@ export async function createTestOrg({
),
);
// Create API key for existing org (will skip if already exists)
const { key, alreadyExists } = await createHardcodedKey({
await seedTeamInvites({ db });
await ensureDefaultStripeAccount({ db, orgId: TEST_ORG_CONFIG.id });
await clearOrgDbOnly({ db, orgId: TEST_ORG_CONFIG.id, env: AppEnv.Sandbox });
await setupOrg({ orgId: TEST_ORG_CONFIG.id, env: AppEnv.Sandbox });
if (TEST_API_KEY) {
const { key, alreadyExists } = await createHardcodedKey({
db,
env: AppEnv.Sandbox,
name: "Unit Test Key",
orgId: TEST_ORG_CONFIG.id,
hardcodedKey: TEST_API_KEY,
meta: {
createdBy: "setup-test-script",
createdAt: new Date().toISOString(),
},
});
console.log(
chalk.greenBright(
alreadyExists
? "✅ API key already exists in database"
: "✅ Created API key for existing organization",
),
);
return key;
}
// No hardcoded key in env; generate a fresh one for the existing org.
const generated = await createKey({
db,
env: AppEnv.Sandbox,
name: "Unit Test Key",
orgId: TEST_ORG_CONFIG.id,
hardcodedKey: TEST_API_KEY,
prefix: "am_sk_test_",
meta: {
createdBy: "setup-test-script",
createdAt: new Date().toISOString(),
autogenerated: true,
},
});
if (alreadyExists) {
console.log(chalk.greenBright("✅ API key already exists in database"));
} else {
console.log(
chalk.greenBright("✅ Created API key for existing organization"),
);
}
return key;
console.log(
chalk.greenBright(
"✅ Generated fresh API key (UNIT_TEST_AUTUMN_SECRET_KEY unset)",
),
);
return generated;
}
// Create the test organization
@@ -95,55 +138,135 @@ export async function createTestOrg({
),
);
// Get first 5 users from database and create memberships
const users = await db.select().from(user).limit(5);
if (users.length > 0) {
const { generateId } = await import("@server/utils/genUtils.js");
const memberships = users.map((u) => ({
id: generateId("mem"),
organizationId: TEST_ORG_CONFIG.id,
userId: u.id,
role: "owner",
createdAt: new Date(),
}));
await db.insert(member).values(memberships);
console.log(
chalk.greenBright(
`✅ Created ${memberships.length} membership(s) for test organization`,
),
);
} else {
console.log(
chalk.yellowBright(
"⚠ No users found in database. Skipping membership creation.",
),
);
}
await seedTeamInvites({ db });
await ensureDefaultStripeAccount({ db, orgId: TEST_ORG_CONFIG.id });
await clearOrgDbOnly({ db, orgId: TEST_ORG_CONFIG.id, env: AppEnv.Sandbox });
await setupOrg({ orgId: TEST_ORG_CONFIG.id, env: AppEnv.Sandbox });
// Create API key for the new org
const { key, alreadyExists } = await createHardcodedKey({
if (TEST_API_KEY) {
const { key, alreadyExists } = await createHardcodedKey({
db,
env: AppEnv.Sandbox,
name: "Unit Test Key",
orgId: TEST_ORG_CONFIG.id,
hardcodedKey: TEST_API_KEY,
meta: {
createdBy: "setup-test-script",
createdAt: new Date().toISOString(),
},
});
console.log(
chalk.greenBright(
alreadyExists
? "✅ API key already exists in database"
: "✅ Created API key for test organization",
),
);
return key;
}
const generated = await createKey({
db,
env: AppEnv.Sandbox,
name: "Unit Test Key",
orgId: TEST_ORG_CONFIG.id,
hardcodedKey: TEST_API_KEY,
prefix: "am_sk_test_",
meta: {
createdBy: "setup-test-script",
createdAt: new Date().toISOString(),
autogenerated: true,
},
});
if (alreadyExists) {
console.log(chalk.greenBright("✅ API key already exists in database"));
} else {
console.log(chalk.greenBright("✅ Created API key for test organization"));
}
return key;
console.log(
chalk.greenBright(
"✅ Generated fresh API key (UNIT_TEST_AUTUMN_SECRET_KEY unset)",
),
);
return generated;
}
export { TEST_ORG_CONFIG };
// Per-email: insert a member row if a user already exists, otherwise insert
// an invitation row. Both paths skip if a matching member/invitation already
// targets this org+email — safe to re-run. Note: better-auth does NOT auto-
// accept invitations on sign-in. Invitees must visit /accept?id=<inv_id> (or
// equivalent UI) after signing in to finish joining the org.
async function seedTeamInvites({ db }: { db: DrizzleCli }): Promise<void> {
const { generateId } = await import("@server/utils/genUtils.js");
// Ensure a synthetic inviter exists so invitation.inviter_id FK resolves.
await db
.insert(user)
.values({
id: TEST_INVITER_USER.id,
name: TEST_INVITER_USER.name,
email: TEST_INVITER_USER.email,
emailVerified: true,
createdAt: new Date(),
updatedAt: new Date(),
})
.onConflictDoNothing();
const existingUsers = await db
.select()
.from(user)
.where(inArray(user.email, TEAM_INVITE_EMAILS));
const userByEmail = new Map(existingUsers.map((u) => [u.email, u]));
const existingMembers = await db
.select()
.from(member)
.where(eq(member.organizationId, TEST_ORG_CONFIG.id));
const membershipUserIds = new Set(existingMembers.map((m) => m.userId));
const existingInvitesForOrg = await db
.select()
.from(invitation)
.where(
and(
eq(invitation.organizationId, TEST_ORG_CONFIG.id),
inArray(invitation.email, TEAM_INVITE_EMAILS),
),
);
const inviteEmails = new Set(existingInvitesForOrg.map((i) => i.email));
let membersCreated = 0;
let invitesCreated = 0;
const expiresAt = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000);
for (const email of TEAM_INVITE_EMAILS) {
const existingUser = userByEmail.get(email);
if (existingUser) {
if (membershipUserIds.has(existingUser.id)) continue;
await db.insert(member).values({
id: generateId("mem"),
organizationId: TEST_ORG_CONFIG.id,
userId: existingUser.id,
role: "owner",
createdAt: new Date(),
});
membersCreated++;
} else {
if (inviteEmails.has(email)) continue;
await db.insert(invitation).values({
id: generateId("inv"),
organizationId: TEST_ORG_CONFIG.id,
email,
role: "owner",
status: "pending",
createdAt: new Date(),
expiresAt,
inviterId: TEST_INVITER_USER.id,
});
invitesCreated++;
}
}
console.log(
chalk.greenBright(
`✅ Team seed: ${membersCreated} member(s) + ${invitesCreated} invitation(s) for ${TEAM_INVITE_EMAILS.length} email(s)`,
),
);
}

View File

@@ -0,0 +1,59 @@
import chalk from "chalk";
import type { DrizzleCli } from "@server/db/initDrizzle.js";
import { OrgService } from "@server/internal/orgs/OrgService.js";
import { createConnectAccount } from "@server/internal/orgs/orgUtils/createConnectAccount.js";
const DUMMY_USER = {
id: "setup-test-stripe-user",
email: "setup-test@autumn.test",
name: "Setup Test User",
};
/**
* Idempotently ensure the test org has a default Stripe Connect sandbox
* account. Creates one only if `test_stripe_connect.default_account_id`
* is missing.
*/
export async function ensureDefaultStripeAccount({
db,
orgId,
}: {
db: DrizzleCli;
orgId: string;
}): Promise<void> {
const org = await OrgService.get({ db, orgId });
const existingAccountId = org.test_stripe_connect?.default_account_id;
if (existingAccountId) {
console.log(
chalk.yellowBright(
`Stripe default account already connected (${existingAccountId}). Skipping.`,
),
);
return;
}
console.log(chalk.blue(" 🔄 Creating default Stripe sandbox account..."));
const newAccount = await createConnectAccount({
org: org as any,
user: DUMMY_USER as any,
});
await OrgService.update({
db,
orgId,
updates: {
test_stripe_connect: {
...org.test_stripe_connect,
default_account_id: newAccount.id,
},
},
});
console.log(
chalk.greenBright(
` ✅ Created default Stripe sandbox account: ${newAccount.id}`,
),
);
}