Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ jobs:
with:
node-version-file: '.nvmrc'

- name: Install Bun
uses: oven-sh/setup-bun@v2

- name: Install pnpm
uses: pnpm/action-setup@v4

Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ jobs:
with:
node-version-file: '.nvmrc'

- name: Install Bun
uses: oven-sh/setup-bun@v2

- uses: browser-actions/setup-chrome@v1
- run: chrome --version

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,4 @@ Thumbs.db

packages/registry/chain-registry
packages/registry/community-assetlist
packages/create-sei/test-output-*
4 changes: 1 addition & 3 deletions packages/create-sei/.gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
node_modules
dist

test
```
test-output-*
23 changes: 12 additions & 11 deletions packages/create-sei/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,24 @@
"module": "dist/main.js",
"type": "module",
"bin": "./dist/main.js",
"exports": {
".": {
"import": "./dist/main.js"
}
},
"scripts": {
"build": "rm -rf dist && tsc && chmod +x dist/main.js && rsync -av --exclude-from=.rsyncignore ./templates/ ./dist/templates/ && rsync -av --exclude-from=.rsyncignore ./extensions/ ./dist/extensions/",
"dev": "node --loader ts-node/esm src/main.ts",
"test": "jest"
"build": "tsc -b --force && chmod +x dist/main.js && rsync -av --exclude-from=.rsyncignore ./templates/ ./dist/templates/ && rsync -av --exclude-from=.rsyncignore ./extensions/ ./dist/extensions/",
"dev": "tsc --watch",
"clean": "rm -rf dist",
Comment on lines +18 to +20
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The build script no longer clears dist before compiling (tsc -b --force won’t remove outputs for deleted/renamed source files). This can leave stale artifacts under dist/ and potentially ship outdated files. Consider restoring rm -rf dist at the start of build (or tsc -b --clean before build) to ensure a clean output.

Copilot uses AI. Check for mistakes.
"test": "bun test"
Comment on lines 17 to +21
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

build/dev scripts rely on tsc, but this package no longer declares typescript in its own devDependencies. If someone runs this package’s scripts outside the monorepo tooling context (or if hoisting changes), these scripts can fail. Consider adding typescript (and any other required tooling) back to devDependencies, or document/enforce that the workspace root provides them.

Copilot uses AI. Check for mistakes.
},
"dependencies": {
"boxen": "^7.1.1",
"commander": "^12.1.0",
"inquirer": "^9.2.15"
},
"devDependencies": {
"@jest/globals": "^29.7.0",
"@types/jest": "^29.5.12",
"@types/node": "^20.14.10",
"jest": "^29.7.0",
"ts-jest": "^29.1.2",
"ts-node": "^10.9.2",
"typescript": "^5.5.3"

"publishConfig": {
"access": "public"
}
}
275 changes: 275 additions & 0 deletions packages/create-sei/src/__tests__/e2e.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
import { afterAll, beforeAll, describe, expect, test } from "bun:test";
import * as fs from "node:fs";
import path from "node:path";

const packageDir = path.resolve(import.meta.dir, "../..");
const cliPath = path.join(packageDir, "dist", "main.js");
const e2eDir = path.join(packageDir, "test-output-e2e");
const e2eTmpDir = path.join(e2eDir, ".tmp");
const baseProjectName = "e2e-basic";
const precompilesProjectName = "e2e-precompiles";
const e2eTmpDirEnv = `${e2eTmpDir}${path.sep}`;
const e2eSpawnEnv = {
...process.env,
TMPDIR: e2eTmpDirEnv,
BUN_TMPDIR: e2eTmpDirEnv,
};

async function runCli(
args: string[],
cwd: string,
): Promise<{ stdout: string; stderr: string; exitCode: number }> {
const proc = Bun.spawn(["node", cliPath, ...args], {
cwd,
stdout: "pipe",
stderr: "pipe",
env: { ...process.env, NO_COLOR: "1" },
});
Comment on lines +11 to +27
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

e2eSpawnEnv sets TMPDIR/BUN_TMPDIR, but runCli/ensureCliBuilt don’t use it (they build their own env object). If the goal is to keep all subprocess temp output under test-output-e2e/.tmp, consider using e2eSpawnEnv consistently (and layering NO_COLOR on top) so the CLI build/run doesn’t write temp files elsewhere.

Copilot uses AI. Check for mistakes.

const [stdout, stderr] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
]);
const exitCode = await proc.exited;

return { stdout, stderr, exitCode };
}

async function pathExists(targetPath: string): Promise<boolean> {
return fs.promises
.access(targetPath)
.then(() => true)
.catch(() => false);
}

async function ensureCliBuilt(): Promise<void> {
if (await pathExists(cliPath)) {
return;
}

const proc = Bun.spawn(["bun", "run", "build"], {
cwd: packageDir,
stdout: "pipe",
stderr: "pipe",
env: { ...process.env, NO_COLOR: "1" },
});
const [stdout, stderr] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
]);
await proc.exited;
if (proc.exitCode !== 0 || !(await pathExists(cliPath))) {
throw new Error(
`Failed to build create-sei CLI before e2e tests.\nstdout:\n${stdout}\nstderr:\n${stderr}`,
);
}
}

async function ensureProject(
projectName: string,
args: string[] = [],
): Promise<void> {
const projectDir = path.join(e2eDir, projectName);
if (await pathExists(projectDir)) {
return;
}

const { exitCode, stderr } = await runCli(
["app", "--name", projectName, ...args],
e2eDir,
);
if (exitCode !== 0) {
throw new Error(
`Failed to create fixture project '${projectName}'.\nstderr:\n${stderr}`,
);
}
}

async function installDeps(projectDir: string): Promise<void> {
const proc = Bun.spawn(["bun", "install"], {
cwd: projectDir,
stdout: "pipe",
stderr: "pipe",
env: e2eSpawnEnv,
});
const [stdout, stderr] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
]);
await proc.exited;
if (proc.exitCode !== 0) {
throw new Error(
`bun install failed in '${projectDir}'.\nstdout:\n${stdout}\nstderr:\n${stderr}`,
);
}
}

describe("create-sei CLI e2e", () => {
beforeAll(async () => {
await fs.promises.rm(e2eDir, { recursive: true, force: true });
await fs.promises.mkdir(e2eDir, { recursive: true });
await fs.promises.mkdir(e2eTmpDir, { recursive: true });
await ensureCliBuilt();
}, 120_000);

afterAll(async () => {
await fs.promises.rm(e2eDir, { recursive: true, force: true });
}, 120_000);

test("app --name creates a project directory", async () => {
const projectName = "e2e-create-check";
const { exitCode } = await runCli(["app", "--name", projectName], e2eDir);
expect(exitCode).toBe(0);

const projectDir = path.join(e2eDir, projectName);
const exists = await pathExists(projectDir);
expect(exists).toBe(true);
});

test("generated project has valid package.json", async () => {
await ensureProject(baseProjectName);
const pkgPath = path.join(e2eDir, baseProjectName, "package.json");
const raw = await fs.promises.readFile(pkgPath, "utf-8");
const pkg = JSON.parse(raw);

expect(pkg.scripts).toBeDefined();
expect(pkg.scripts.dev).toBe("next dev");
expect(pkg.scripts.build).toBe("next build");
expect(pkg.dependencies).toBeDefined();
expect(pkg.dependencies.next).toBeDefined();
expect(pkg.dependencies.react).toBeDefined();
expect(pkg.dependencies.viem).toBeDefined();
});

test("generated project has expected file structure", async () => {
await ensureProject(baseProjectName);
const projectDir = path.join(e2eDir, baseProjectName);
const expectedFiles = [
"package.json",
"tsconfig.json",
"next.config.mjs",
"src",
];

for (const file of expectedFiles) {
const exists = await pathExists(path.join(projectDir, file));
expect(exists).toBe(true);
}
});

test("generated project can install dependencies", async () => {
await ensureProject(baseProjectName);
const projectDir = path.join(e2eDir, baseProjectName);
await installDeps(projectDir);

// node_modules should exist
const nmExists = await pathExists(path.join(projectDir, "node_modules"));
expect(nmExists).toBe(true);
}, 60_000);

test("generated project can build successfully", async () => {
await ensureProject(baseProjectName);
const projectDir = path.join(e2eDir, baseProjectName);
await installDeps(projectDir);

const proc = Bun.spawn(["bun", "run", "build"], {
cwd: projectDir,
Comment on lines +160 to +176
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The e2e suite runs bun install repeatedly for the same fixture project across multiple tests (e.g., install + build tests both call installDeps). This can significantly slow CI and increase flakiness. Consider installing once per fixture (in beforeAll) or skipping install when node_modules already exists.

Copilot uses AI. Check for mistakes.
stdout: "pipe",
stderr: "pipe",
env: e2eSpawnEnv,
});

const [stdout, stderr] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
]);
await proc.exited;
const exitCode = proc.exitCode;

if (exitCode !== 0) {
console.error("Build stdout:", stdout);
console.error("Build stderr:", stderr);
}
expect(exitCode).toBe(0);
}, 120_000);

test("app --name --extension precompiles creates project with extension", async () => {
const projectName = "e2e-precompiles-create-check";
const { exitCode, stdout } = await runCli(
["app", "--name", projectName, "--extension", "precompiles"],
e2eDir,
);
expect(exitCode).toBe(0);
expect(stdout).toContain("Applied extension: precompiles");

// Extension should have overwritten package.json
const pkgPath = path.join(e2eDir, projectName, "package.json");
const pkg = JSON.parse(await fs.promises.readFile(pkgPath, "utf-8"));
expect(pkg.name).toBe("template-next-create-sei-app-precompiles");
});

test("extension project can install dependencies", async () => {
await ensureProject(precompilesProjectName, ["--extension", "precompiles"]);
const projectDir = path.join(e2eDir, precompilesProjectName);
await installDeps(projectDir);
}, 60_000);

test("extension project can build successfully", async () => {
await ensureProject(precompilesProjectName, ["--extension", "precompiles"]);
const projectDir = path.join(e2eDir, precompilesProjectName);
await installDeps(projectDir);

const proc = Bun.spawn(["bun", "run", "build"], {
cwd: projectDir,
Comment on lines +211 to +223
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The e2e suite also installs dependencies multiple times for the extension fixture (installDeps is called in both the “can install dependencies” and “can build successfully” tests). Consider reusing the same installed project (or checking for existing node_modules) to reduce runtime.

Copilot uses AI. Check for mistakes.
stdout: "pipe",
stderr: "pipe",
env: e2eSpawnEnv,
});

const [stdout, stderr] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
]);
await proc.exited;

if (proc.exitCode !== 0) {
console.error("Build stdout:", stdout);
console.error("Build stderr:", stderr);
}
expect(proc.exitCode).toBe(0);
}, 120_000);

test("list-extensions command outputs available extensions", async () => {
const { exitCode, stdout } = await runCli(["list-extensions"], e2eDir);
expect(exitCode).toBe(0);
expect(stdout).toContain("Available extensions:");
expect(stdout).toContain("precompiles");
});

test("app with invalid name does not create directory", async () => {
const { exitCode, stdout } = await runCli(
["app", "--name", "INVALID NAME!"],
e2eDir,
);
expect(exitCode).toBe(0);
expect(stdout).toContain("Invalid package name");

const exists = await pathExists(path.join(e2eDir, "INVALID NAME!"));
expect(exists).toBe(false);
});

test("app with nonexistent extension falls back to base template", async () => {
const { exitCode, stdout } = await runCli(
["app", "--name", "e2e-fallback", "--extension", "does-not-exist"],
e2eDir,
);
expect(exitCode).toBe(0);
expect(stdout).toContain("Warning");
expect(stdout).toContain("does-not-exist");

// Should still have created the project from base template
const pkgPath = path.join(e2eDir, "e2e-fallback", "package.json");
const pkg = JSON.parse(await fs.promises.readFile(pkgPath, "utf-8"));
expect(pkg.scripts.dev).toBe("next dev");
});
});
Loading
Loading