@rudderhq/agent-runtime-opencode-local 0.6.6-canary.8 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/skills/app-builder/SKILL.md +104 -0
- package/skills/app-builder/agents/openai.yaml +14 -0
- package/skills/app-builder/assets/scaffold/.env.example +3 -0
- package/skills/app-builder/assets/scaffold/app/api/%5F%5Frudder/health/route.ts +20 -0
- package/skills/app-builder/assets/scaffold/app/api/contacts/[id]/route.ts +39 -0
- package/skills/app-builder/assets/scaffold/app/api/contacts/route.ts +31 -0
- package/skills/app-builder/assets/scaffold/app/api/data/export/route.ts +19 -0
- package/skills/app-builder/assets/scaffold/app/api/data/import/route.ts +40 -0
- package/skills/app-builder/assets/scaffold/app/globals.css +32 -0
- package/skills/app-builder/assets/scaffold/app/layout.tsx +15 -0
- package/skills/app-builder/assets/scaffold/app/page.tsx +9 -0
- package/skills/app-builder/assets/scaffold/components/contacts-workspace.tsx +292 -0
- package/skills/app-builder/assets/scaffold/components/ui/button.tsx +42 -0
- package/skills/app-builder/assets/scaffold/components/ui/card.tsx +19 -0
- package/skills/app-builder/assets/scaffold/components/ui/input.tsx +17 -0
- package/skills/app-builder/assets/scaffold/components/ui/label.tsx +6 -0
- package/skills/app-builder/assets/scaffold/data/.gitkeep +1 -0
- package/skills/app-builder/assets/scaffold/drizzle.config.ts +18 -0
- package/skills/app-builder/assets/scaffold/instrumentation.ts +5 -0
- package/skills/app-builder/assets/scaffold/lib/data-transfer.ts +44 -0
- package/skills/app-builder/assets/scaffold/lib/db/client.ts +78 -0
- package/skills/app-builder/assets/scaffold/lib/db/schema.ts +35 -0
- package/skills/app-builder/assets/scaffold/lib/domain.ts +18 -0
- package/skills/app-builder/assets/scaffold/lib/jobs/runner.ts +56 -0
- package/skills/app-builder/assets/scaffold/lib/utils.ts +6 -0
- package/skills/app-builder/assets/scaffold/migrations/0000_app_builder_foundation.sql +27 -0
- package/skills/app-builder/assets/scaffold/migrations/meta/_journal.json +13 -0
- package/skills/app-builder/assets/scaffold/next-env.d.ts +6 -0
- package/skills/app-builder/assets/scaffold/next.config.ts +8 -0
- package/skills/app-builder/assets/scaffold/package.json +47 -0
- package/skills/app-builder/assets/scaffold/playwright.config.ts +30 -0
- package/skills/app-builder/assets/scaffold/pnpm-lock.yaml +2687 -0
- package/skills/app-builder/assets/scaffold/postcss.config.mjs +5 -0
- package/skills/app-builder/assets/scaffold/rudder.app.json +32 -0
- package/skills/app-builder/assets/scaffold/scripts/migrate.ts +21 -0
- package/skills/app-builder/assets/scaffold/scripts/seed.ts +31 -0
- package/skills/app-builder/assets/scaffold/scripts/snapshot.ts +17 -0
- package/skills/app-builder/assets/scaffold/tests/e2e/app.spec.ts +50 -0
- package/skills/app-builder/assets/scaffold/tests/unit/data-transfer.test.ts +28 -0
- package/skills/app-builder/assets/scaffold/tests/unit/domain.test.ts +23 -0
- package/skills/app-builder/assets/scaffold/tsconfig.json +41 -0
- package/skills/app-builder/assets/scaffold/vitest.config.ts +14 -0
- package/skills/app-builder/evals/evals.json +65 -0
- package/skills/app-builder/references/data-safety.md +38 -0
- package/skills/app-builder/references/design-guidelines.md +20 -0
- package/skills/app-builder/references/migrations-and-promotion.md +36 -0
- package/skills/app-builder/references/scaffold-contract.md +77 -0
- package/skills/app-builder/references/verification.md +23 -0
- package/skills/app-builder/scripts/scaffold.mjs +58 -0
- package/skills/app-builder/scripts/validate-manifest.mjs +63 -0
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { cn } from "@/lib/utils";
|
|
2
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
|
|
6
|
+
const buttonVariants = cva(
|
|
7
|
+
"inline-flex min-h-10 items-center justify-center gap-2 rounded-lg px-4 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--ring)] disabled:pointer-events-none disabled:opacity-50",
|
|
8
|
+
{
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default: "bg-[var(--primary)] text-[var(--primary-foreground)] hover:brightness-95",
|
|
12
|
+
outline: "border bg-[var(--card)] hover:bg-[var(--muted)]",
|
|
13
|
+
ghost: "hover:bg-[var(--muted)]",
|
|
14
|
+
destructive: "bg-[var(--destructive)] text-white hover:brightness-95",
|
|
15
|
+
},
|
|
16
|
+
size: {
|
|
17
|
+
default: "h-10",
|
|
18
|
+
sm: "h-9 px-3",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
defaultVariants: {
|
|
22
|
+
variant: "default",
|
|
23
|
+
size: "default",
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
export interface ButtonProps
|
|
29
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
30
|
+
VariantProps<typeof buttonVariants> {
|
|
31
|
+
asChild?: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function Button({ className, variant, size, asChild, ...props }: ButtonProps) {
|
|
35
|
+
const Component = asChild ? Slot : "button";
|
|
36
|
+
return (
|
|
37
|
+
<Component
|
|
38
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
39
|
+
{...props}
|
|
40
|
+
/>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { cn } from "@/lib/utils";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
|
|
4
|
+
export function Card({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
5
|
+
return (
|
|
6
|
+
<section
|
|
7
|
+
className={cn("rounded-xl border bg-[var(--card)] text-[var(--card-foreground)] shadow-sm", className)}
|
|
8
|
+
{...props}
|
|
9
|
+
/>
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function CardHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
14
|
+
return <div className={cn("border-b px-5 py-4", className)} {...props} />;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function CardContent({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
18
|
+
return <div className={cn("p-5", className)} {...props} />;
|
|
19
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { cn } from "@/lib/utils";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
|
|
4
|
+
export const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
|
|
5
|
+
({ className, type, ...props }, ref) => (
|
|
6
|
+
<input
|
|
7
|
+
ref={ref}
|
|
8
|
+
type={type}
|
|
9
|
+
className={cn(
|
|
10
|
+
"flex h-10 w-full rounded-lg border bg-white px-3 py-2 text-sm outline-none placeholder:text-[var(--muted-foreground)] focus:ring-2 focus:ring-[var(--ring)] disabled:opacity-50",
|
|
11
|
+
className,
|
|
12
|
+
)}
|
|
13
|
+
{...props}
|
|
14
|
+
/>
|
|
15
|
+
),
|
|
16
|
+
);
|
|
17
|
+
Input.displayName = "Input";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineConfig } from "drizzle-kit";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
const dataDir = process.env.RUDDER_APP_DATA_DIR
|
|
5
|
+
? path.resolve(process.env.RUDDER_APP_DATA_DIR)
|
|
6
|
+
: path.resolve("data");
|
|
7
|
+
const mode = process.env.RUDDER_APP_DATA_MODE === "production"
|
|
8
|
+
? "app.sqlite"
|
|
9
|
+
: "dev.sqlite";
|
|
10
|
+
|
|
11
|
+
export default defineConfig({
|
|
12
|
+
dialect: "sqlite",
|
|
13
|
+
schema: "./lib/db/schema.ts",
|
|
14
|
+
out: "./migrations",
|
|
15
|
+
dbCredentials: {
|
|
16
|
+
url: path.join(dataDir, mode),
|
|
17
|
+
},
|
|
18
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { contactStatusSchema } from "./domain";
|
|
3
|
+
|
|
4
|
+
export const exportedContactSchema = z.object({
|
|
5
|
+
id: z.string().uuid(),
|
|
6
|
+
name: z.string().min(1).max(120),
|
|
7
|
+
email: z.email().max(320),
|
|
8
|
+
company: z.string().max(160),
|
|
9
|
+
status: contactStatusSchema,
|
|
10
|
+
createdAt: z.iso.datetime(),
|
|
11
|
+
updatedAt: z.iso.datetime(),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export const importEnvelopeSchema = z.object({
|
|
15
|
+
format: z.literal("rudder-app-data/v1"),
|
|
16
|
+
exportedAt: z.iso.datetime(),
|
|
17
|
+
data: z.object({
|
|
18
|
+
contacts: z.array(exportedContactSchema).max(100_000),
|
|
19
|
+
}),
|
|
20
|
+
}).strict();
|
|
21
|
+
|
|
22
|
+
type ContactRow = {
|
|
23
|
+
id: string;
|
|
24
|
+
name: string;
|
|
25
|
+
email: string;
|
|
26
|
+
company: string;
|
|
27
|
+
status: "new" | "contacted" | "replied" | "paused";
|
|
28
|
+
createdAt: Date;
|
|
29
|
+
updatedAt: Date;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export function buildExportEnvelope(rows: ContactRow[]) {
|
|
33
|
+
return {
|
|
34
|
+
format: "rudder-app-data/v1" as const,
|
|
35
|
+
exportedAt: new Date().toISOString(),
|
|
36
|
+
data: {
|
|
37
|
+
contacts: rows.map((row) => ({
|
|
38
|
+
...row,
|
|
39
|
+
createdAt: row.createdAt.toISOString(),
|
|
40
|
+
updatedAt: row.updatedAt.toISOString(),
|
|
41
|
+
})),
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {
|
|
2
|
+
drizzle,
|
|
3
|
+
type SqliteRemoteDatabase,
|
|
4
|
+
} from "drizzle-orm/sqlite-proxy";
|
|
5
|
+
import { mkdirSync } from "node:fs";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { DatabaseSync, type SQLInputValue } from "node:sqlite";
|
|
8
|
+
import * as schema from "./schema";
|
|
9
|
+
|
|
10
|
+
type DatabaseHandle = {
|
|
11
|
+
sqlite: DatabaseSync;
|
|
12
|
+
db: SqliteRemoteDatabase<typeof schema>;
|
|
13
|
+
filePath: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const handles = new Map<string, DatabaseHandle>();
|
|
17
|
+
|
|
18
|
+
export function dataMode() {
|
|
19
|
+
return process.env.RUDDER_APP_DATA_MODE === "production"
|
|
20
|
+
? "production"
|
|
21
|
+
: "development";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function dataDirectory() {
|
|
25
|
+
return process.env.RUDDER_APP_DATA_DIR
|
|
26
|
+
? path.resolve(process.env.RUDDER_APP_DATA_DIR)
|
|
27
|
+
: path.resolve("data");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function databasePath() {
|
|
31
|
+
if (process.env.RUDDER_APP_DATA_DIR) {
|
|
32
|
+
return path.join(
|
|
33
|
+
dataDirectory(),
|
|
34
|
+
dataMode() === "production" ? "app.sqlite" : "dev.sqlite",
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
return path.join(
|
|
38
|
+
dataDirectory(),
|
|
39
|
+
dataMode() === "production" ? "production/app.sqlite" : "development/dev.sqlite",
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function getDatabase(): DatabaseHandle {
|
|
44
|
+
const filePath = databasePath();
|
|
45
|
+
const cached = handles.get(filePath);
|
|
46
|
+
if (cached) return cached;
|
|
47
|
+
|
|
48
|
+
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
49
|
+
const sqlite = new DatabaseSync(filePath);
|
|
50
|
+
sqlite.exec("PRAGMA journal_mode = WAL");
|
|
51
|
+
sqlite.exec("PRAGMA foreign_keys = ON");
|
|
52
|
+
sqlite.exec("PRAGMA busy_timeout = 5000");
|
|
53
|
+
const db = drizzle(async (query, parameters, method): Promise<{ rows: unknown[] }> => {
|
|
54
|
+
const statement = sqlite.prepare(query);
|
|
55
|
+
statement.setReturnArrays(true);
|
|
56
|
+
const values = parameters as SQLInputValue[];
|
|
57
|
+
if (method === "run") {
|
|
58
|
+
statement.run(...values);
|
|
59
|
+
return { rows: [] };
|
|
60
|
+
}
|
|
61
|
+
if (method === "get") {
|
|
62
|
+
return { rows: statement.get(...values) as unknown as unknown[] };
|
|
63
|
+
}
|
|
64
|
+
return { rows: statement.all(...values) as unknown as unknown[][] };
|
|
65
|
+
}, { schema });
|
|
66
|
+
const handle: DatabaseHandle = {
|
|
67
|
+
sqlite,
|
|
68
|
+
db,
|
|
69
|
+
filePath,
|
|
70
|
+
};
|
|
71
|
+
handles.set(filePath, handle);
|
|
72
|
+
return handle;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function closeDatabases() {
|
|
76
|
+
for (const handle of handles.values()) handle.sqlite.close();
|
|
77
|
+
handles.clear();
|
|
78
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { integer, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
|
|
2
|
+
|
|
3
|
+
export const contacts = sqliteTable("contacts", {
|
|
4
|
+
id: text("id").primaryKey(),
|
|
5
|
+
name: text("name").notNull(),
|
|
6
|
+
email: text("email").notNull(),
|
|
7
|
+
company: text("company").notNull().default(""),
|
|
8
|
+
status: text("status", { enum: ["new", "contacted", "replied", "paused"] })
|
|
9
|
+
.notNull()
|
|
10
|
+
.default("new"),
|
|
11
|
+
createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull(),
|
|
12
|
+
updatedAt: integer("updated_at", { mode: "timestamp_ms" }).notNull(),
|
|
13
|
+
}, (table) => [
|
|
14
|
+
uniqueIndex("contacts_email_uq").on(table.email),
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
export const jobs = sqliteTable("jobs", {
|
|
18
|
+
id: text("id").primaryKey(),
|
|
19
|
+
kind: text("kind").notNull(),
|
|
20
|
+
idempotencyKey: text("idempotency_key").notNull(),
|
|
21
|
+
payloadJson: text("payload_json").notNull().default("{}"),
|
|
22
|
+
status: text("status", { enum: ["pending", "running", "completed", "failed", "missed"] })
|
|
23
|
+
.notNull()
|
|
24
|
+
.default("pending"),
|
|
25
|
+
catchUpPolicy: text("catch_up_policy", { enum: ["run", "skip", "prompt"] })
|
|
26
|
+
.notNull()
|
|
27
|
+
.default("prompt"),
|
|
28
|
+
scheduledFor: integer("scheduled_for", { mode: "timestamp_ms" }).notNull(),
|
|
29
|
+
attempts: integer("attempts").notNull().default(0),
|
|
30
|
+
lastError: text("last_error"),
|
|
31
|
+
createdAt: integer("created_at", { mode: "timestamp_ms" }).notNull(),
|
|
32
|
+
updatedAt: integer("updated_at", { mode: "timestamp_ms" }).notNull(),
|
|
33
|
+
}, (table) => [
|
|
34
|
+
uniqueIndex("jobs_idempotency_key_uq").on(table.idempotencyKey),
|
|
35
|
+
]);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const contactStatusSchema = z.enum(["new", "contacted", "replied", "paused"]);
|
|
4
|
+
|
|
5
|
+
export const contactCreateSchema = z.object({
|
|
6
|
+
name: z.string().trim().min(1).max(120),
|
|
7
|
+
email: z.email().max(320),
|
|
8
|
+
company: z.string().trim().max(160).default(""),
|
|
9
|
+
status: contactStatusSchema.default("new"),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export const contactUpdateSchema = contactCreateSchema.partial().refine(
|
|
13
|
+
(value) => Object.keys(value).length > 0,
|
|
14
|
+
"At least one contact field is required",
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
export type ContactInput = z.infer<typeof contactCreateSchema>;
|
|
18
|
+
export type ContactStatus = z.infer<typeof contactStatusSchema>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { getDatabase } from "@/lib/db/client";
|
|
2
|
+
|
|
3
|
+
type RunnerState = {
|
|
4
|
+
timer: NodeJS.Timeout | null;
|
|
5
|
+
ticking: boolean;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const globalState = globalThis as typeof globalThis & {
|
|
9
|
+
__rudderAppJobRunner?: RunnerState;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function state(): RunnerState {
|
|
13
|
+
globalState.__rudderAppJobRunner ??= { timer: null, ticking: false };
|
|
14
|
+
return globalState.__rudderAppJobRunner;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function tick() {
|
|
18
|
+
const current = state();
|
|
19
|
+
if (current.ticking) return;
|
|
20
|
+
current.ticking = true;
|
|
21
|
+
try {
|
|
22
|
+
const { sqlite } = getDatabase();
|
|
23
|
+
const now = Date.now();
|
|
24
|
+
sqlite.prepare(`
|
|
25
|
+
update jobs
|
|
26
|
+
set status = case catch_up_policy
|
|
27
|
+
when 'skip' then 'missed'
|
|
28
|
+
when 'run' then 'pending'
|
|
29
|
+
else 'missed'
|
|
30
|
+
end,
|
|
31
|
+
updated_at = ?
|
|
32
|
+
where status = 'pending' and scheduled_for < ?
|
|
33
|
+
`).run(now, now - 60_000);
|
|
34
|
+
// Domain-specific handlers should claim one pending job transactionally,
|
|
35
|
+
// execute it with the persisted idempotency key, and then complete/fail it.
|
|
36
|
+
} catch {
|
|
37
|
+
// The health endpoint remains authoritative. A missing pre-migration table
|
|
38
|
+
// must not create an unhandled background rejection during startup.
|
|
39
|
+
} finally {
|
|
40
|
+
current.ticking = false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function startJobRunner() {
|
|
45
|
+
const current = state();
|
|
46
|
+
if (current.timer) return;
|
|
47
|
+
current.timer = setInterval(() => void tick(), 15_000);
|
|
48
|
+
current.timer.unref();
|
|
49
|
+
void tick();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function stopJobRunner() {
|
|
53
|
+
const current = state();
|
|
54
|
+
if (current.timer) clearInterval(current.timer);
|
|
55
|
+
current.timer = null;
|
|
56
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
CREATE TABLE `contacts` (
|
|
2
|
+
`id` text PRIMARY KEY NOT NULL,
|
|
3
|
+
`name` text NOT NULL,
|
|
4
|
+
`email` text NOT NULL,
|
|
5
|
+
`company` text DEFAULT '' NOT NULL,
|
|
6
|
+
`status` text DEFAULT 'new' NOT NULL,
|
|
7
|
+
`created_at` integer NOT NULL,
|
|
8
|
+
`updated_at` integer NOT NULL
|
|
9
|
+
);
|
|
10
|
+
--> statement-breakpoint
|
|
11
|
+
CREATE UNIQUE INDEX `contacts_email_uq` ON `contacts` (`email`);
|
|
12
|
+
--> statement-breakpoint
|
|
13
|
+
CREATE TABLE `jobs` (
|
|
14
|
+
`id` text PRIMARY KEY NOT NULL,
|
|
15
|
+
`kind` text NOT NULL,
|
|
16
|
+
`idempotency_key` text NOT NULL,
|
|
17
|
+
`payload_json` text DEFAULT '{}' NOT NULL,
|
|
18
|
+
`status` text DEFAULT 'pending' NOT NULL,
|
|
19
|
+
`catch_up_policy` text DEFAULT 'prompt' NOT NULL,
|
|
20
|
+
`scheduled_for` integer NOT NULL,
|
|
21
|
+
`attempts` integer DEFAULT 0 NOT NULL,
|
|
22
|
+
`last_error` text,
|
|
23
|
+
`created_at` integer NOT NULL,
|
|
24
|
+
`updated_at` integer NOT NULL
|
|
25
|
+
);
|
|
26
|
+
--> statement-breakpoint
|
|
27
|
+
CREATE UNIQUE INDEX `jobs_idempotency_key_uq` ON `jobs` (`idempotency_key`);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rudder-app",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=22.5.0"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"dev": "next dev --hostname 127.0.0.1",
|
|
11
|
+
"build": "next build",
|
|
12
|
+
"start": "next start --hostname 127.0.0.1",
|
|
13
|
+
"typecheck": "tsc --noEmit",
|
|
14
|
+
"test": "vitest run",
|
|
15
|
+
"test:e2e": "playwright test",
|
|
16
|
+
"db:generate": "drizzle-kit generate",
|
|
17
|
+
"db:migrate": "tsx scripts/migrate.ts",
|
|
18
|
+
"db:seed": "tsx scripts/seed.ts",
|
|
19
|
+
"data:snapshot": "tsx scripts/snapshot.ts",
|
|
20
|
+
"verify": "tsc --noEmit && vitest run && next build",
|
|
21
|
+
"predev": "tsx scripts/migrate.ts && tsx scripts/seed.ts"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@radix-ui/react-slot": "1.2.4",
|
|
25
|
+
"class-variance-authority": "0.7.1",
|
|
26
|
+
"clsx": "2.1.1",
|
|
27
|
+
"drizzle-orm": "0.45.2",
|
|
28
|
+
"lucide-react": "1.27.0",
|
|
29
|
+
"next": "16.2.12",
|
|
30
|
+
"react": "19.2.8",
|
|
31
|
+
"react-dom": "19.2.8",
|
|
32
|
+
"tailwind-merge": "3.6.0",
|
|
33
|
+
"zod": "4.4.3"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@playwright/test": "1.58.2",
|
|
37
|
+
"@tailwindcss/postcss": "4.3.3",
|
|
38
|
+
"@types/node": "24.12.0",
|
|
39
|
+
"@types/react": "19.2.7",
|
|
40
|
+
"@types/react-dom": "19.2.3",
|
|
41
|
+
"drizzle-kit": "0.31.10",
|
|
42
|
+
"tailwindcss": "4.3.3",
|
|
43
|
+
"tsx": "4.23.1",
|
|
44
|
+
"typescript": "5.9.3",
|
|
45
|
+
"vitest": "3.2.7"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { defineConfig, devices } from "@playwright/test";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
testDir: "./tests/e2e",
|
|
5
|
+
fullyParallel: false,
|
|
6
|
+
retries: 0,
|
|
7
|
+
reporter: "list",
|
|
8
|
+
use: {
|
|
9
|
+
baseURL: process.env.RUDDER_APP_BASE_URL ?? "http://127.0.0.1:3000",
|
|
10
|
+
trace: "retain-on-failure",
|
|
11
|
+
},
|
|
12
|
+
webServer: process.env.RUDDER_APP_BASE_URL
|
|
13
|
+
? undefined
|
|
14
|
+
: {
|
|
15
|
+
command: "pnpm exec next dev --hostname 127.0.0.1 --port 3000",
|
|
16
|
+
url: "http://127.0.0.1:3000/api/__rudder/health",
|
|
17
|
+
reuseExistingServer: true,
|
|
18
|
+
timeout: 120_000,
|
|
19
|
+
},
|
|
20
|
+
projects: [
|
|
21
|
+
{ name: "desktop", use: { ...devices["Desktop Chrome"] } },
|
|
22
|
+
{
|
|
23
|
+
name: "mobile",
|
|
24
|
+
use: {
|
|
25
|
+
viewport: { width: 390, height: 844 },
|
|
26
|
+
isMobile: true,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
});
|