Skip to content
Merged
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
71 changes: 20 additions & 51 deletions .github/workflows/cli-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,70 +91,39 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Set up Node.js
- name: Set up Node.js (for the CLI only)
uses: actions/setup-node@v4
with:
node-version: '24'

- name: Set up .NET
if: matrix.backend == 'dotnet'
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'

- name: Install Dependencies
run: |
npm install
npm install -D @playwright/test wait-on

- name: Install Playwright Browsers
run: npx playwright install --with-deps chromium
- name: Install CLI Dependencies
run: npm ci

- name: Scaffold App (${{ matrix.frontend }} + ${{ matrix.backend }})
run: node cli/index.js test-app --frontend ${{ matrix.frontend }} --backend ${{ matrix.backend }}

- name: Run lint.sh
run: cd test-app && ./lint.sh
- name: Build and start all containers
run: cd test-app && ./start.sh --no-open -d

- name: Run test.sh
run: cd test-app && ./test.sh

- name: Start Servers and Run Integration Test
env:
FRONTEND: ${{ matrix.frontend }}
- name: Multiply API + database end-to-end check
run: |
cd test-app
# Start the backend and frontend in the background
nohup ./start.sh > start.log 2>&1 &

# Wait for the backend to be healthy
npx wait-on tcp:127.0.0.1:8000 -t 120000

# Wait for the frontend to be reachable
case "$FRONTEND" in
nextjs)
npx wait-on http-get://127.0.0.1:3000 -t 120000
;;
angular)
npx wait-on http-get://127.0.0.1:4200 -t 120000
;;
*)
npx wait-on http-get://127.0.0.1:5173 -t 120000
;;
case "${{ matrix.frontend }}" in
angular) PORT=4200 ;;
vue|svelte) PORT=5173 ;;
*) PORT=3000 ;;
esac

# Run the Playwright integration test from the root directory
cd ..
npx playwright test tests/integration.spec.js --project=chromium
tests/container-e2e.sh test-app "$PORT"

- name: Print start.log on failure
- name: Print container logs on failure
if: failure()
run: cat test-app/start.log || true
run: |
cd test-app
docker compose ps -a || true
docker compose logs --tail=100 || true

- name: Tear down
if: always()
run: cd test-app && docker compose down -v || true

auto-merge:
needs: e2e
Expand Down
10 changes: 10 additions & 0 deletions .github/workflows/update-frameworks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,16 @@ jobs:
fi
done

- name: Update Docker and Pipelock Dependencies
run: |
# Fetch latest Pipelock release from GitHub
PIPELOCK_TAG=$(curl -s https://api.github.com/repos/luckyPipewrench/pipelock/releases/latest | jq -r '.tag_name // empty' | sed 's/^v//')
if [ -n "$PIPELOCK_TAG" ] && [ "$PIPELOCK_TAG" != "null" ]; then
echo "Latest Pipelock version found: $PIPELOCK_TAG"
sed -i.bak -E "s|(ghcr\.io/luckypipewrench/pipelock:)[0-9.]+|\1$PIPELOCK_TAG|g" templates/base/docker-compose.yml
rm -f templates/base/docker-compose.yml.bak
fi

- name: Update Hardcoded Versions in UI (README & CLI)
run: |
cat << 'EOF' > update-ui.js
Expand Down
22 changes: 22 additions & 0 deletions .releaserc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"branches": [
"main"
],
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/npm",
[
"@semantic-release/git",
{
"assets": [
"package.json",
"package-lock.json",
"cli/build-date.js"
],
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}
],
"@semantic-release/github"
]
}
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@ You will be prompted to choose a project name, your preferred frontend, and back
- **Migrations**: Alembic (v1.20.0), SQLAlchemy (v2.1.4), and Psycopg2 (v2.9.13) are used universally for database migrations across all frameworks.
- **Drivers**: FastAPI/Flask use SQLAlchemy/Psycopg2, Node.js Express uses `pg` (v8.23.0), and .NET uses `Npgsql` (v10.0.3).
- **Opinionated Defaults**: We bake in sensible, predefined choices (e.g., Angular uses `zone.js`, testing is unified under Playwright and framework-native test runners).
- **Unified Scripts**: `start.sh`, `test.sh`, and `lint.sh` available out of the box to manage both frontend and backend seamlessly.
- **Fullstack Containerization**: Every frontend and backend framework includes an optimized, production-ready `Dockerfile`. Orchestrate the entire three-tier architecture (frontend, backend, database + migrations) via Docker Compose (`./start.sh` or `docker compose up --build`).
- **Self-Contained AI Sandbox**: Run autonomous coding agents (Antigravity, Claude Code, OpenCode) in a strictly isolated container (`./sandbox.sh` or `.devcontainer/`). Host filesystem outside `/workspace` is completely inaccessible.
- **Secret Leak Prevention**: Developer secrets in `.env.local` or `.env.secrets` are masked with empty mounts inside the container, preventing the AI from reading them.
- **Pipelock AI Firewall & MCP Sentinel**: Integrated [Pipelock](https://github.com/luckyPipewrench/pipelock) proxy scans outbound AI traffic with 65+ DLP patterns, prevents secret exfiltration, blocks SSRF against cloud metadata, validates MCP tool calls, and generates verifiable signed audit receipts.
- **Unified Scripts**: `start.sh`, `sandbox.sh`, `test.sh`, and `lint.sh` available out of the box to manage both frontend and backend seamlessly.
- **CI/CD Ready**: Includes a pre-configured `.github/workflows/cli-e2e.yml` that tests both ends. _Tip: To enforce this, enable branch protection in your GitHub repository settings and require the "test" status check to pass._
- **Developer Experience**: Includes `.vscode/extensions.json` recommending the necessary linters and formatters, and an `AGENTS.md` file providing behavioral guidelines for AI agents. It is AI development friendly, including an extensive set of agent skills in `.agents/skills`.
<details>
Expand Down Expand Up @@ -122,6 +126,8 @@ This project redistributes boilerplate code and configuration files generated by
- [Psycopg2](https://www.psycopg.org/) (LGPL with exceptions)
- [node-postgres (pg)](https://node-postgres.com/) (MIT License)
- [Npgsql](https://www.npgsql.org/) (PostgreSQL License)
- [Docker](https://www.docker.com/) (Apache License 2.0)
- [Pipelock](https://github.com/luckyPipewrench/pipelock) (Apache License Version 2.0, January 2004)

## References

Expand Down
154 changes: 41 additions & 113 deletions cli/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,28 @@ const { execSync } = require("child_process");

async function main() {
const pkg = require("../package.json");
let version = pkg.version;
let publishDate = "Local build";
try {
publishDate = require("./build-date.js") + " (UTC)";
} catch (e) {}

try {
if (fs.existsSync(path.resolve(__dirname, "../.git"))) {
const gitTag = execSync("git describe --tags --abbrev=0 2>/dev/null", { encoding: "utf8" }).trim();
if (gitTag) {
version = gitTag.replace(/^v/, "");
}
const commitDate = execSync("git log -1 --format=%cd --date=format:'%Y-%m-%d %H:%M:%S' 2>/dev/null", { encoding: "utf8" }).trim();
if (commitDate) {
publishDate = `${commitDate} (local development)`;
}
}
} catch (e) {}

const { asciiArt } = require("./logo");
console.log(pc.cyan(asciiArt));
console.log(pc.cyan(`\nWelcome to create-fullstack-app! (v${pkg.version}, published on ${publishDate})\n`));
console.log(pc.cyan(`\nWelcome to create-fullstack-app! (v${version}, published on ${publishDate})\n`));

// Simple argument parsing
const args = process.argv.slice(2);
Expand Down Expand Up @@ -151,6 +165,26 @@ async function main() {
await fs.writeJson(pkgPath, pkg, { spaces: 2 });
}

// 5. Generate .env configuration
let frontendPort = 3000;
if (frontend === "angular") frontendPort = 4200;
if (frontend === "vue" || frontend === "svelte") frontendPort = 5173;

const envContent = [
"# Application environment configuration",
`FRONTEND_PORT=${frontendPort}`,
"BACKEND_PORT=8000",
"POSTGRES_PORT=5432",
"POSTGRES_USER=user",
"POSTGRES_PASSWORD=password",
"POSTGRES_DB=appdb",
"DATABASE_URL=postgresql://user:password@127.0.0.1:5432/appdb",
"PIPELOCK_MODE=balanced",
"PIPELOCK_PORT=8888",
"",
].join("\n");
await fs.writeFile(path.join(targetDir, ".env"), envContent);

// 5. Make all shell scripts executable
const makeExecutable = (dir) => {
const files = fs.readdirSync(dir);
Expand All @@ -165,117 +199,6 @@ async function main() {
};
makeExecutable(targetDir);

// 6. Install dependencies
console.log(
pc.blue("\nInstalling dependencies (this may take a minute)..."),
);

// Frontend installation
console.log(pc.cyan(" Installing frontend dependencies..."));
execSync("npm install --no-audit --no-fund ", { cwd: targetFrontend, stdio: "inherit" });

if (backend === "fastapi" || backend === "flask") {
console.log(pc.cyan(" Setting up backend virtual environment..."));
const pythonCmd = process.platform === "win32" ? "python" : "python3";
try {
execSync(`${pythonCmd} -m venv venv`, {
cwd: targetBackend,
stdio: "inherit",
});

const pipPath =
process.platform === "win32"
? path.join("venv", "Scripts", "pip")
: path.join("venv", "bin", "pip");

console.log(pc.cyan(" Installing backend dependencies..."));
execSync(`${pipPath} install -r requirements.txt`, {
cwd: targetBackend,
stdio: "inherit",
});
} catch (e) {
console.error(
pc.red(
"\n Failed to set up Python virtual environment or install dependencies."
)
);
console.error(
pc.yellow(
" TIP: We frequently update templates to use the latest framework versions."
)
);
console.error(
pc.yellow(
" If pip failed to find a matching version, your default 'python3' version might be too old."
)
);
console.error(
pc.yellow(
" Please try updating to the newest version of Python and run this again."
)
);
process.exit(1);
}
} else if (backend === "dotnet") {
// Check if .NET 9+ SDK is available
let dotnetMajor = 0;
try {
const version = execSync("dotnet --version", { stdio: "pipe" }).toString().trim();
dotnetMajor = parseInt(version.split(".")[0], 10);
} catch {
dotnetMajor = 0;
}

if (dotnetMajor < 10) {
console.log(pc.yellow(`\n .NET 10 SDK is required but ${dotnetMajor > 0 ? `v${dotnetMajor} was found` : "dotnet was not found"} on your system.`));

if (process.platform === "win32") {
console.log(pc.yellow(" Please install the .NET 10 SDK from: https://aka.ms/dotnet/download"));
} else {
const { installDotnet } = await prompts({
type: "confirm",
name: "installDotnet",
message: "Would you like to install the .NET 10 SDK automatically? (uses the official Microsoft install script)",
initial: true,
});

if (installDotnet) {
console.log(pc.cyan(" Installing .NET 10 SDK (this may take a few minutes)..."));
try {
execSync(
"curl -sSL https://dot.net/v1/dotnet-install.sh | bash -s -- --channel 10.0",
{ stdio: "inherit" }
);
// Make the newly installed dotnet available in the current process without a shell restart
const dotnetRoot = path.join(os.homedir(), ".dotnet");
process.env.DOTNET_ROOT = dotnetRoot;
process.env.PATH = `${dotnetRoot}:${process.env.PATH}`;
console.log(pc.green(" .NET 10 SDK installed successfully."));
console.log(pc.yellow(` Add ${dotnetRoot} to your PATH permanently by updating your shell profile (e.g. ~/.zshrc).`));
} catch (e) {
console.log(pc.yellow(" Could not install .NET SDK automatically. Please install it from https://aka.ms/dotnet/download"));
}
} else {
console.log(pc.yellow(" Skipping .NET SDK installation. Please install .NET 10 SDK from https://aka.ms/dotnet/download"));
}
}
}

console.log(pc.cyan(" Restoring .NET dependencies..."));
try {
execSync("dotnet restore backend.sln", { cwd: targetBackend, stdio: "inherit" });
} catch (e) {
console.log(
pc.yellow(
" Could not restore .NET dependencies automatically. Please make sure .NET 10 SDK is installed and in your PATH.",
),
);
}
} else if (backend === "nodejs") {
console.log(pc.cyan(" Installing Node.js backend dependencies..."));
execSync("npm install --no-audit --no-fund ", { cwd: targetBackend, stdio: "inherit" });
}

// 7. Initialize Git repository
console.log(pc.blue("\nInitializing Git repository..."));
try {
Expand All @@ -299,7 +222,12 @@ async function main() {

console.log(` ${pc.cyan("./start.sh")}`);
console.log(
" Starts both the backend and frontend development servers.\n",
" Starts all services in Docker and automatically reopens your IDE in a DevContainer.\n",
);

console.log(` ${pc.cyan("./sandbox.sh")}`);
console.log(
" Launches an isolated AI agent sandbox with Pipelock egress firewall & secret masking.\n",
);

console.log(` ${pc.cyan("./test.sh")}`);
Expand Down
Loading
Loading