diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3cf0ea7..e50fdfe 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,10 +25,15 @@ jobs: - name: Checkout code uses: actions/checkout@v6 - - name: Install Crystal + - name: Install Crystal (Linux) + if: matrix.target == 'linux-x86_64' uses: crystal-lang/install-crystal@v1 with: crystal: latest + + - name: Install Crystal and OpenSSL (macOS) + if: matrix.target == 'darwin-arm64' + run: brew install crystal openssl@3 - name: Cache shards uses: actions/cache@v4 @@ -70,6 +75,16 @@ jobs: ./amber new --help ./amber-lsp --help + - name: Reject retired OpenSSL linkage (macOS) + if: matrix.target == 'darwin-arm64' + run: | + otool -L amber + otool -L amber-lsp + ! otool -L amber amber-lsp | grep -F 'openssl@1.1' + + - name: Smoke test generated Amber V2 web app + run: scripts/smoke_generated_web.sh ./amber + - name: Upload build artifacts uses: actions/upload-artifact@v7 if: github.event_name == 'workflow_dispatch' diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0bd728b..c5f4833 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,6 +40,9 @@ jobs: - name: Install dependencies run: shards install + - name: Check Amber V2 beta contract + run: scripts/check_beta_contract.sh + - name: Check code formatting run: crystal tool format --check src spec @@ -95,6 +98,16 @@ jobs: with: crystal: latest + - name: Restore tested shard cache + uses: actions/cache@v4 + with: + path: | + ~/.cache/shards + ./lib + key: ${{ runner.os }}-shards-${{ hashFiles('shard.lock') }} + restore-keys: | + ${{ runner.os }}-shards- + - name: Install dependencies run: shards install diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 38275fd..237f8ab 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,10 +35,25 @@ jobs: with: ref: ${{ github.event.release.tag_name || github.event.inputs.ref }} - - name: Install Crystal + - name: Install Crystal (Linux) + if: matrix.target == 'linux-x86_64' uses: crystal-lang/install-crystal@v1 with: crystal: latest + + - name: Install Crystal and OpenSSL (macOS) + if: matrix.target == 'darwin-arm64' + run: brew install crystal openssl@3 + + - name: Verify release versions agree + run: | + shard_version="$(awk '/^version:/ { print $2; exit }' shard.yml)" + cli_version="$(sed -n 's/.*VERSION = "\([^"]*\)".*/\1/p' src/amber_cli.cr | head -1)" + test "$shard_version" = "$cli_version" + requested_ref="${{ github.event.release.tag_name || github.event.inputs.ref }}" + if [[ "$requested_ref" == v* ]]; then + test "v$shard_version" = "$requested_ref" + fi - name: Install dependencies run: | @@ -67,6 +82,16 @@ jobs: file amber-lsp test -x amber-lsp + - name: Reject retired OpenSSL linkage (macOS) + if: matrix.target == 'darwin-arm64' + run: | + otool -L amber + otool -L amber-lsp + ! otool -L amber amber-lsp | grep -F 'openssl@1.1' + + - name: Smoke test generated Amber V2 web app + run: scripts/smoke_generated_web.sh ./amber + - name: Create archive run: | mkdir -p dist diff --git a/.gitignore b/.gitignore index 72ebefb..348fcec 100644 --- a/.gitignore +++ b/.gitignore @@ -20,7 +20,7 @@ Thumbs.db # Generated documentation (keep structure, ignore generated content) /docs/** -!/docs/README.md +!/docs/*.md !/docs/.gitkeep # Build artifacts @@ -29,6 +29,4 @@ Thumbs.db /target/ /dist/ -# Libraries don't need dependency lock -# Dependencies will be locked in applications that use them -/shard.lock +# The CLI is a distributed executable; commit shard.lock for reproducible builds. diff --git a/README.md b/README.md index ed395e0..bc2eb9c 100644 --- a/README.md +++ b/README.md @@ -1,314 +1,127 @@ # Amber CLI -[![Crystal CI](https://github.com/amberframework/amber_cli/actions/workflows/crystal.yml/badge.svg)](https://github.com/amberframework/amber_cli/actions/workflows/crystal.yml) [![GitHub release](https://img.shields.io/github/release/amberframework/amber_cli.svg)](https://github.com/amberframework/amber_cli/releases) [![Docs](https://img.shields.io/badge/docs-available-brightgreen.svg)](https://amberframework.github.io/amber_cli/) -A powerful command-line tool for managing Crystal web applications built with the [Amber Framework](https://amberframework.org). This CLI provides generators, database management, development utilities, and more to streamline your Amber development workflow. +Amber CLI is the standalone command-line companion for Amber V2. CLI `2.0.2` +creates the supported Amber `2.0.0-beta.2` ECR web application and includes +development, generator, database, and LSP tooling. -## 📖 Documentation +Amber V2 is a beta. The release-gated path is a web application on Apple +Silicon macOS or x86_64 Linux. See [Generator support](docs/GENERATOR_SUPPORT.md) +before relying on persistence, authentication, API-resource, or native output. -**[→ Complete Documentation](https://amberframework.github.io/amber_cli/)** +## Install -The comprehensive documentation includes detailed guides, examples, and API references generated from the codebase. +Prerequisites: Crystal 1.20 or newer (but earlier than 2.0), `shards`, and Git. -## 🚀 Quick Start +### Homebrew on macOS or Linux -### Installation +The tap name contains an underscore; the formula is named `amber`: -**macOS & Linux via Homebrew:** ```bash -brew tap amberframework/amber-cli -brew install amber-cli +brew tap amberframework/amber_cli +brew install amber_cli +amber --version ``` -**Direct Binary Download (macOS ARM64 / Linux x86_64):** +### Direct release archive -Download the pre-built tarball from the [releases page](https://github.com/amberframework/amber_cli/releases) and verify the sha256 before extracting: +Use `darwin-arm64` on Apple Silicon macOS or `linux-x86_64` on x86_64 Linux: ```bash -# macOS ARM64 example — replace VERSION and PLATFORM as needed -VERSION=2.0.1 -PLATFORM=darwin-arm64 # or linux-x86_64 - -curl -LO "https://github.com/amberframework/amber_cli/releases/download/v${VERSION}/amber_cli-${PLATFORM}.tar.gz" -curl -LO "https://github.com/amberframework/amber_cli/releases/download/v${VERSION}/amber_cli-${PLATFORM}.tar.gz.sha256" - -# Verify checksum (macOS: shasum -a 256; Linux: sha256sum) -shasum -a 256 -c "amber_cli-${PLATFORM}.tar.gz.sha256" - -# Extract and install -tar -xzf "amber_cli-${PLATFORM}.tar.gz" -sudo mv amber amber-lsp /usr/local/bin/ +version=v2.0.2 +platform=darwin-arm64 +asset="amber_cli-${platform}.tar.gz" + +curl -fLO "https://github.com/amberframework/amber_cli/releases/download/${version}/${asset}" +curl -fLO "https://github.com/amberframework/amber_cli/releases/download/${version}/${asset}.sha256" +shasum -a 256 -c "${asset}.sha256" +tar -xzf "${asset}" +install -m 0755 amber amber-lsp /usr/local/bin/ +amber --version ``` -**From Source:** -```bash -git clone https://github.com/amberframework/amber_cli.git -cd amber_cli -shards install -crystal build src/amber_cli.cr -o amber --release -crystal build src/amber_lsp.cr -o amber-lsp --release -sudo mv amber amber-lsp /usr/local/bin/ -``` - -**Windows:** -Use WSL2 or a virtual machine. Native Windows support is not currently available. +On Linux, use `sha256sum -c` for the checksum. Prefix only the `install` +command with `sudo` if `/usr/local/bin` is not writable. -### Create Your First App +## Create and verify a web app ```bash -# Create a new Amber application -amber new my_blog - -# Navigate to your app -cd my_blog - -# Set up the database -amber database create -amber database migrate - -# Start the development server +amber new my_app --type web +cd my_app +crystal spec +crystal build src/my_app.cr -o bin/my_app amber watch ``` -Your application will be available at `http://localhost:3000` - -## ⚡ Core Commands - -| Command | Description | Example | -|---------|-------------|---------| -| `new` | Create a new Amber application | `amber new my_app -d pg -t slang` | -| `generate` | Generate application components | `amber generate model User name:String` | -| `database` | Database operations and migrations | `amber database migrate` | -| `watch` | Development server with auto-reload | `amber watch` | -| `routes` | Display application routes | `amber routes --json` | -| `exec` | Execute Crystal code in app context | `amber exec 'puts User.count'` | -| `encrypt` | Manage encrypted environment files | `amber encrypt production` | -| `pipelines` | Show pipeline configuration | `amber pipelines` | -| `setup:lsp` | Configure the Amber LSP for Claude Code | `amber setup:lsp` | - -Run `amber --help` or `amber [command] --help` for detailed usage information. - -## 🔧 Key Features - -### **Flexible Code Generation** -- Built-in generators for models, controllers, views, and more -- Configurable custom generators with YAML/JSON configuration -- Intelligent word transformations (snake_case, PascalCase, pluralization) -- Template-based file generation with variable substitution - -### **Database Management** -- Full migration support with rollback capabilities -- Multi-database support (PostgreSQL, MySQL, SQLite) -- Database seeding and status reporting -- Environment-specific configuration - -### **Development Tools** -- File watching with automatic rebuild and restart -- Interactive code execution within application context -- Route analysis and pipeline inspection -- Environment file encryption for security +`amber new` installs shards by default; pass `--no-deps` when an offline or CI +workflow needs to run `shards install` later. Open and +. -### **Amber LSP — AI-Assisted Development** -- Built-in Language Server Protocol (LSP) server for Claude Code integration -- 15 convention rules that catch framework mistakes as you type -- Custom YAML-based rules for project-specific conventions -- One command to set up: `amber setup:lsp` +The web template is deliberately small: -### **Extensible Architecture** -- Plugin system for extending functionality -- Command registration system for custom commands -- Template engine for flexible file generation -- Configuration-driven behavior +- Amber from `amberframework/amber`, pinned to `2.0.0-beta.2` +- ECR views (Slang and Kilt are not supported in Amber V2) +- typed development, test, and production YAML +- homepage, controller spec, and static CSS/JavaScript +- no ORM or database driver by default -## 🏗️ Architecture Highlights +The `-d pg|mysql|sqlite` option records metadata and suggested URLs for future +persistence tooling. It does not add an ORM or driver to the core web app. -### **Zero External Dependencies** -- Built entirely with Crystal's standard library -- No external CLI frameworks or template engines -- Fast compilation and lightweight binary +## Commands -### **Clean Command Structure** -- Base command class for consistent behavior -- Command registry for easy extension -- Built-in option parsing and validation -- Comprehensive error handling +| Command | Status | Purpose | +|---|---|---| +| `amber new APP --type web` | Supported | Create the beta web application | +| `amber watch` | Supported | Rebuild and restart during development | +| `amber routes` | Supported | Inspect application routes | +| `amber pipelines` | Supported | Inspect configured pipelines | +| `amber generate` | Mixed | Core generators supported; dependency-backed generators preview | +| `amber database` | Preview for new apps | Requires an explicitly configured persistence stack | +| `amber new APP --type native` | Preview | Not part of the beta platform guarantee | +| `amber setup:lsp` | Available | Configure the bundled diagnostics LSP | -### **Smart Template System** -- ECR-based template processing -- Variable substitution with transformations -- Conditional file generation -- Post-generation command execution +Run `amber --help` or `amber COMMAND --help` for command syntax. The detailed +[web-app walkthrough](docs/BETA_WEB_APP.md) and +[generator table](docs/GENERATOR_SUPPORT.md) define what is release-gated. -## 🤖 Amber LSP — The Default Development Workflow - -Amber ships with a diagnostics-only Language Server that integrates with [Claude Code](https://claude.ai/claude-code). When you develop with Claude Code, the LSP runs in the background and automatically catches framework convention violations — wrong controller names, missing methods, bad inheritance, file naming issues, and more. Claude sees these diagnostics and self-corrects without you having to notice or intervene. - -**This is the recommended way to develop with Amber.** The LSP turns Claude Code from a general-purpose coding assistant into one that understands Amber's conventions natively. - -### Quick Setup +## Update and troubleshoot ```bash -# From your Amber project directory: -amber setup:lsp -``` - -This creates three files: - -| File | Purpose | -|------|---------| -| `.lsp.json` | Tells Claude Code where the LSP binary is and what files it handles | -| `.claude-plugin/plugin.json` | Plugin manifest so Claude Code discovers the LSP | -| `.amber-lsp.yml` | Rule configuration — customize severity, disable rules, add custom rules | - -Then open Claude Code in your project. The LSP activates automatically. - -### What It Checks - -The LSP ships with 15 built-in rules covering controllers, jobs, channels, pipes, mailers, schemas, routing, file naming, directory structure, and more. Every rule maps to an Amber convention — if Claude generates a controller that doesn't end with `Controller`, or a job without a `perform` method, the LSP flags it immediately. - -### Custom Rules - -You can define project-specific rules in `.amber-lsp.yml` using regex patterns. No recompilation needed: - -```yaml -custom_rules: - - id: "project/no-puts" - description: "Do not use puts in production code" - severity: warning - applies_to: ["src/**"] - pattern: "^\\s*puts\\b" - message: "Avoid 'puts' in production code. Use Log.info instead." +brew update +brew upgrade amber_cli +type -a amber +amber --version ``` -### Building the LSP Binary - -If `amber-lsp` is not on your PATH, the `setup:lsp` command will offer to build it: +If an older Amber V1 executable appears first, remove or rename it or put the +new installation directory earlier in `PATH`. On macOS, include +`otool -L "$(command -v amber)"` in install bug reports; release binaries must +not require the retired `openssl@1.1` library. -```bash -cd ~/open_source_coding_projects/amber_cli -crystal build src/amber_lsp.cr -o bin/amber-lsp --release -``` +Report CLI, template, or binary problems at +. Include OS/architecture, +`crystal --version`, `amber --version`, install method, command, and complete +output. -For full documentation on all 15 rules, configuration options, and custom rule syntax, see the [LSP Setup Guide](https://github.com/amberframework/amber/blob/v2-dev/docs/guides/lsp-setup.md). +## LSP -## 📚 Examples +The release archive includes `amber-lsp`. From an Amber project: -### Generate a Blog Post Resource ```bash -# Create model, controller, views, and migration -amber generate scaffold Post title:String content:Text published:Bool - -# Or generate individually -amber generate model Post title:String content:Text -amber generate controller Posts -amber generate migration add_published_to_posts published:Bool -``` - -### Custom Development Workflow -```bash -# Watch with custom build commands -amber watch --build "crystal build src/app.cr --release" --run "./app" - -# Execute code in application context -amber exec 'Post.where(published: true).count' - -# Encrypt production environment -amber encrypt production --editor vim -``` - -### Database Operations -```bash -# Create and set up database -amber database create -amber database migrate -amber database seed - -# Check migration status -amber database status - -# Rollback last migration -amber database rollback -``` - -## 🔍 Configuration - -Amber CLI uses several configuration files: - -- **`.amber.yml`** - Project-specific settings -- **`config/environments/`** - Environment configurations -- **`generator_configs/`** - Custom generator definitions - -Example `.amber.yml`: -```yaml -database: pg -language: slang -model: granite -watch: - run: - build_commands: - - "crystal build ./src/my_app.cr -o bin/my_app" - run_commands: - - "bin/my_app" - include: - - "./config/**/*.cr" - - "./src/**/*.cr" +amber setup:lsp ``` -## 🤝 Contributing - -We welcome contributions! Please read our [Contributing Guide](CONTRIBUTING.md) for details. - -1. Fork the repository -2. Create your feature branch (`git checkout -b my-new-feature`) -3. Write tests for your changes -4. Ensure all tests pass (`crystal spec`) -5. Follow Crystal's code formatting (`crystal tool format`) -6. Commit your changes (`git commit -am 'Add some feature'`) -7. Push to the branch (`git push origin my-new-feature`) -8. Create a Pull Request +See the [LSP setup guide](https://github.com/amberframework/amber/blob/v2.0.0-beta.2/docs/guides/lsp-setup.md). -## 📋 Requirements +## Contributing -- **Crystal** 1.20+ (latest stable recommended) -- **Git** (for project templates) -- **Database** (PostgreSQL, MySQL, or SQLite) - -## 🐛 Troubleshooting - -Common issues and solutions: - -**Database connection errors:** -```bash -# Verify database is running and check config -amber database status -``` - -**Generation failures:** -```bash -# Check template availability -amber generate --list -``` - -**Watch mode not working:** ```bash -# Show current configuration -amber watch --info +shards install +crystal tool format --check src spec +crystal spec ``` -For more detailed troubleshooting, see the [full documentation](https://amberframework.github.io/amber_cli/). - -## 📄 License - -This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. - -## 🌟 Acknowledgments - -- [Amber Framework](https://amberframework.org) - The Crystal web framework -- [Crystal Language](https://crystal-lang.org) - The programming language -- All the amazing [contributors](https://github.com/amberframework/amber_cli/contributors) - ---- - -**[→ View Complete Documentation](https://amberframework.github.io/amber_cli/)** +See [CONTRIBUTING.md](CONTRIBUTING.md) for the project workflow. diff --git a/RELEASE_NOTES_V2.0.2.md b/RELEASE_NOTES_V2.0.2.md new file mode 100644 index 0000000..63391eb --- /dev/null +++ b/RELEASE_NOTES_V2.0.2.md @@ -0,0 +1,47 @@ +# Amber CLI 2.0.2 + +Amber CLI 2.0.2 is the supported standalone CLI for Amber +`2.0.0-beta.2`. + +## Install + +```bash +brew tap amberframework/amber_cli +brew install amber_cli +amber --version +``` + +Direct assets are provided for Apple Silicon macOS and x86_64 Linux. Download +the matching `.tar.gz` and `.sha256` files and verify the checksum before +installing `amber` and `amber-lsp`. + +## What changed + +- Web apps now pin `amberframework/amber` at `2.0.0-beta.2`. +- The generated web app is ECR-only and uses typed V2 environment YAML. +- Personal fork, implicit Grant/Gemma, asset pipeline, and all-database-driver + dependencies were removed from the core template. +- Static CSS and JavaScript use an explicit wildcard static route. +- Generated app specs no longer start the server or use invalid dynamic + includes. +- Absolute destination paths resolve correctly. +- Dependencies install by default; `--no-deps` keeps offline/CI control. +- CLI and generator help label native and persistence-backed output as preview. +- Release CI rejects macOS binaries linked to `openssl@1.1` and exercises the + complete generated web-app path on both supported platforms. + +## Supported beta path + +```bash +amber new my_app --type web +cd my_app +crystal spec +crystal build src/my_app.cr -o bin/my_app +amber watch +``` + +See the [web-app guide](https://github.com/amberframework/amber_cli/blob/v2.0.2/docs/BETA_WEB_APP.md) +and [generator support table](https://github.com/amberframework/amber_cli/blob/v2.0.2/docs/GENERATOR_SUPPORT.md). + +Model, scaffold, API-resource, auth, and native app generators remain preview +surfaces in this release. diff --git a/docs/BETA_WEB_APP.md b/docs/BETA_WEB_APP.md new file mode 100644 index 0000000..61dffe9 --- /dev/null +++ b/docs/BETA_WEB_APP.md @@ -0,0 +1,75 @@ +# Amber V2 Beta Web App + +This guide is the consumer smoke test for Amber CLI `2.0.2` and Amber +`2.0.0-beta.2`. It is expected to pass on Apple Silicon macOS and x86_64 Linux. + +## 1. Verify the toolchain + +```bash +crystal --version +shards --version +amber --version +``` + +Crystal must be at least 1.20 and earlier than 2.0. Amber CLI must be 2.0.2 or +newer. + +## 2. Generate the web app + +```bash +amber new amber_beta_smoke --type web +cd amber_beta_smoke +``` + +The command installs dependencies unless `--no-deps` is passed. Inspect the +contract before continuing: + +```bash +grep -A2 'amber:' shard.yml +grep 'template:' .amber.yml +``` + +The dependency must be `amberframework/amber` version `2.0.0-beta.2`; the +template must be `ecr`. A newly generated app must not contain a personal fork, +Grant, Gemma, Slang, or all three database drivers. + +## 3. Test and build + +```bash +crystal spec +crystal build src/amber_beta_smoke.cr -o bin/amber_beta_smoke +``` + +No running database is required for this core application. + +## 4. Start and probe it + +```bash +amber watch +``` + +From another terminal: + +```bash +curl --fail http://127.0.0.1:3000/ +curl --fail http://127.0.0.1:3000/css/app.css +``` + +The first request validates routing, the controller, and ECR rendering. The +second validates the static pipeline. + +## 5. Try core generators + +```bash +amber generate controller Posts index show +amber generate schema Post title:string:required body:text +amber generate job PublishPost --queue=default +amber generate mailer Digest --actions=weekly +amber generate channel Updates --topics=posts +amber generate migration CreatePosts +``` + +Review generated files before adding them to an application. Run `crystal tool +format --check` and `crystal spec` after generation. These six core generator +paths run in the release smoke test on both supported platforms. See +[Generator support](GENERATOR_SUPPORT.md) for dependency-backed preview types. diff --git a/docs/GENERATOR_SUPPORT.md b/docs/GENERATOR_SUPPORT.md new file mode 100644 index 0000000..021bd2f --- /dev/null +++ b/docs/GENERATOR_SUPPORT.md @@ -0,0 +1,26 @@ +# Generator Support for the Amber V2 Beta + +The support label describes whether output can be evaluated in the core web app +without adding an unreleased ecosystem dependency. + +| Command | Beta status | Notes | +|---|---|---| +| `generate controller` | Supported | Controller, ECR action views, and specs | +| `generate schema` | Supported | Uses Amber's built-in Schema API | +| `generate job` | Supported | Uses Amber's built-in job system | +| `generate mailer` | Supported | Uses Amber's built-in mailer API | +| `generate channel` | Supported | Uses Amber WebSockets | +| `generate migration` | Supported output | Produces SQL migration files; applying them requires database tooling | +| `generate model` | Preview | Currently emits Grant-based output; Grant is not in the core app | +| `generate scaffold` | Preview | Includes Grant persistence and resource output | +| `generate api` | Preview | Includes a persistence-backed model | +| `generate auth` | Preview | Requires a compatible persistence/auth stack | +| `new --type native` | Preview | Has a separate multi-platform dependency and validation matrix | + +Preview does not mean removed. It means the CLI may generate the files, but the +Amber beta release does not promise that a clean web application will compile +them without additional work. The CLI prints a warning before generating a +dependency-backed preview surface. + +Amber V2 supports ECR only. The CLI ignores legacy Slang settings for new +output; migrate old `.slang` files before using V2 generators. diff --git a/docs/RELEASE_CHECKLIST.md b/docs/RELEASE_CHECKLIST.md new file mode 100644 index 0000000..2b69bf6 --- /dev/null +++ b/docs/RELEASE_CHECKLIST.md @@ -0,0 +1,40 @@ +# Amber V2 Beta Release Checklist + +Release order matters because generated apps pin a framework version and the +Homebrew formula pins CLI archives and checksums. + +## 1. Framework prerelease + +1. Confirm `shard.yml`, `src/amber/version.cr`, and the changelog all say + `2.0.0-beta.2`. +2. Run framework specs and formatting on macOS and Linux. +3. Tag the reviewed `v2-dev` commit as `v2.0.0-beta.2`. +4. Publish it as a GitHub prerelease with migration and support-matrix links. + +## 2. CLI release + +1. Confirm `shard.yml` and `AmberCLI::VERSION` both say `2.0.2`. +2. Generate a web app and verify it pins the framework prerelease. +3. On both supported platforms, install shards, run app specs, build the app, + start it, and request `/` plus `/css/app.css`. +4. On macOS, reject any binary linked to `openssl@1.1`. +5. Tag `v2.0.2`, publish the release, and wait for both archives and checksum + files to upload. + +## 3. Homebrew + +1. Update the underscore tap, `amberframework/homebrew-amber_cli`. +2. Verify both archive checksums in the formula. +3. Run `brew install amberframework/amber_cli/amber_cli` on macOS and Linux. +4. Repeat the complete generated-web-app smoke test from the installed formula. + +## 4. Documentation + +1. Publish framework, CLI, and website guides with the same command names, + versions, platform matrix, and preview labels. +2. Check that no onboarding page uses `amber-cli`, `brew install amber-cli`, a + personal fork, a moving `v2-dev` dependency, or Slang for a new V2 app. +3. Verify all release/download links and commands from a clean shell. + +Do not mark the beta complete if either supported platform cannot install the +CLI and build the generated web application. diff --git a/scripts/check_beta_contract.sh b/scripts/check_beta_contract.sh new file mode 100755 index 0000000..f4d47ac --- /dev/null +++ b/scripts/check_beta_contract.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +set -euo pipefail + +shard_version="$(awk '/^version:/ { print $2; exit }' shard.yml)" +cli_version="$(sed -n 's/.*VERSION = "\([^"]*\)".*/\1/p' src/amber_cli.cr | head -1)" +test "$shard_version" = "2.0.2" +test "$cli_version" = "$shard_version" + +grep -F 'github: amberframework/amber' src/amber_cli/commands/new.cr +grep -F 'version: 2.0.0-beta.2' src/amber_cli/commands/new.cr +grep -F 'template: ecr' src/amber_cli/commands/new.cr +grep -F 'brew tap amberframework/amber_cli' README.md +grep -F 'brew install amber_cli' README.md + +if grep -Eir 'crimson-knight/(amber|grant|gemma)' src/amber_cli/templates/app src/amber_cli/commands/new.cr; then + echo "supported web template contains a personal dependency" >&2 + exit 1 +fi + +if find src/amber_cli/templates/app -iname '*slang*' -print | grep -q .; then + echo "supported web template still contains a Slang template" >&2 + exit 1 +fi + +echo "Amber CLI beta contract checks passed" diff --git a/scripts/smoke_generated_web.sh b/scripts/smoke_generated_web.sh new file mode 100755 index 0000000..9349673 --- /dev/null +++ b/scripts/smoke_generated_web.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ $# -ne 1 ]]; then + echo "usage: $0 /path/to/amber" >&2 + exit 64 +fi + +cli_path="$1" +if [[ "$cli_path" != /* ]]; then + cli_path="$(cd "$(dirname "$cli_path")" && pwd)/$(basename "$cli_path")" +fi + +smoke_root="$(mktemp -d "${TMPDIR:-/tmp}/amber-v2-web-smoke.XXXXXX")" +app_path="$smoke_root/amber_beta_smoke" +server_pid="" + +cleanup() { + if [[ -n "$server_pid" ]] && kill -0 "$server_pid" 2>/dev/null; then + kill "$server_pid" 2>/dev/null || true + wait "$server_pid" 2>/dev/null || true + fi + rm -rf "$smoke_root" +} +trap cleanup EXIT + +"$cli_path" --version | grep -F "Amber CLI v2.0.2" +"$cli_path" new "$app_path" --type web --no-deps + +grep -F "github: amberframework/amber" "$app_path/shard.yml" +grep -F "version: 2.0.0-beta.2" "$app_path/shard.yml" +grep -F "template: ecr" "$app_path/.amber.yml" +if grep -Eiq 'crimson-knight|grant:|gemma:|slang' "$app_path/shard.yml" "$app_path/.amber.yml"; then + echo "generated app contains an unsupported beta dependency or template" >&2 + exit 1 +fi + +cd "$app_path" +shards install +crystal spec + +"$cli_path" generate controller Posts index show +"$cli_path" generate schema Post title:string:required body:text +"$cli_path" generate job PublishPost --queue=default +"$cli_path" generate mailer Digest --actions=weekly +"$cli_path" generate channel Updates --topics=posts +"$cli_path" generate migration CreatePosts + +if find src spec -iname '*slang*' -print | grep -q .; then + echo "a core generator emitted a Slang file" >&2 + exit 1 +fi +crystal tool format --check src spec +crystal spec +crystal build src/amber_beta_smoke.cr -o bin/amber_beta_smoke + +AMBER_SERVER_PORT=3210 ./bin/amber_beta_smoke >server.log 2>&1 & +server_pid="$!" + +for _ in {1..30}; do + if curl --fail --silent http://127.0.0.1:3210/ >homepage.html; then + break + fi + sleep 1 +done + +curl --fail --silent http://127.0.0.1:3210/ | grep -F "Amber V2 application is running successfully" +curl --fail --silent http://127.0.0.1:3210/css/app.css | grep -F "Application styles" + +kill "$server_pid" +wait "$server_pid" || true +server_pid="" + +echo "Amber V2 beta web-app smoke test passed" diff --git a/shard.lock b/shard.lock new file mode 100644 index 0000000..b49cf0e --- /dev/null +++ b/shard.lock @@ -0,0 +1,42 @@ +version: 2.0 +shards: + ameba: + git: https://github.com/crystal-ameba/ameba.git + version: 1.7.0-dev+git.commit.cdd58b34b0d8a9c785d67183a7a8f07541549e55 + + asset_pipeline: + git: https://github.com/amberframework/asset_pipeline.git + version: 0.36.0+git.commit.ded6af312d667b93db29e678ac4b8ff8dc26774e + + backtracer: + git: https://github.com/sija/backtracer.cr.git + version: 1.2.4 + + compiled_license: + git: https://github.com/elorest/compiled_license.git + version: 1.2.2 + + db: + git: https://github.com/crystal-lang/crystal-db.git + version: 0.13.1 + + exception_page: + git: https://github.com/crystal-loot/exception_page.git + version: 0.2.2 + + micrate: + git: https://github.com/amberframework/micrate.git + version: 0.15.1+git.commit.647fac0490a522956fef305c720e8ebf1422a87d + + mysql: + git: https://github.com/crystal-lang/crystal-mysql.git + version: 0.16.0 + + pg: + git: https://github.com/will/crystal-pg.git + version: 0.29.0 + + sqlite3: + git: https://github.com/crystal-lang/crystal-sqlite3.git + version: 0.21.0 + diff --git a/shard.yml b/shard.yml index 3ca693d..b133b29 100644 --- a/shard.yml +++ b/shard.yml @@ -1,5 +1,5 @@ name: amber_cli -version: 2.0.1 +version: 2.0.2 authors: - crimson-knight @@ -50,4 +50,5 @@ dependencies: development_dependencies: ameba: github: crystal-ameba/ameba - version: ~> 1.6.4 + # Includes upstream Crystal 1.21+ compatibility pending Ameba 1.7.0. + commit: cdd58b34b0d8a9c785d67183a7a8f07541549e55 diff --git a/spec/amber_cli_spec.cr b/spec/amber_cli_spec.cr index 4180292..3e849a9 100644 --- a/spec/amber_cli_spec.cr +++ b/spec/amber_cli_spec.cr @@ -58,6 +58,7 @@ require "./core/generator_config_spec" require "./core/template_engine_spec" require "./commands/base_command_spec" require "./commands/new_command_spec" +require "./commands/generate_command_spec" require "./commands/exec_command_spec" require "./native/capability_manifest_spec" require "./generators/native_app_spec" diff --git a/spec/commands/generate_command_spec.cr b/spec/commands/generate_command_spec.cr new file mode 100644 index 0000000..58d6f70 --- /dev/null +++ b/spec/commands/generate_command_spec.cr @@ -0,0 +1,31 @@ +require "../amber_cli_spec" +require "../../src/amber_cli/commands/generate" + +describe AmberCLI::Commands::GenerateCommand do + it "uses ECR and does not double-pluralize controller route guidance" do + SpecHelper.within_temp_directory do + File.write(".amber.yml", "template: ecr\n") + + command = AmberCLI::Commands::GenerateCommand.new("generate") + command.parse_and_execute(["controller", "Posts", "index", "show"]) + + File.exists?("src/views/posts/index.ecr").should be_true + spec = File.read("spec/controllers/posts_controller_spec.cr") + spec.should contain("GET /posts") + spec.should contain("GET /posts/1") + spec.should_not contain("postses") + end + end + + it "generates mailer ECR rendering against the Amber V2 mailer API" do + SpecHelper.within_temp_directory do + command = AmberCLI::Commands::GenerateCommand.new("generate") + command.parse_and_execute(["mailer", "Digest", "--actions=weekly"]) + + mailer = File.read("src/mailers/digest_mailer.cr") + mailer.should contain(%(ECR.render("src/views/digest_mailer/weekly.ecr"))) + mailer.should_not contain(" render(\"src/views") + File.exists?("src/views/digest_mailer/weekly.ecr").should be_true + end + end +end diff --git a/spec/commands/new_command_spec.cr b/spec/commands/new_command_spec.cr index 4cba004..a81c917 100644 --- a/spec/commands/new_command_spec.cr +++ b/spec/commands/new_command_spec.cr @@ -45,9 +45,9 @@ describe AmberCLI::Commands::NewCommand do command.app_type.should eq("web") end - it "preserves database and template flags alongside --type" do + it "preserves database and ECR flags alongside --type" do command = AmberCLI::Commands::NewCommand.new("new") - args = ["my_app", "-d", "sqlite", "-t", "slang", "--type=web"] + args = ["my_app", "-d", "sqlite", "-t", "ecr", "--type=web"] command.option_parser.unknown_args do |unknown_args, _| command.remaining_arguments.concat(unknown_args) @@ -55,7 +55,7 @@ describe AmberCLI::Commands::NewCommand do command.option_parser.parse(args) command.database.should eq("sqlite") - command.template.should eq("slang") + command.template.should eq("ecr") command.app_type.should eq("web") end @@ -72,4 +72,37 @@ describe AmberCLI::Commands::NewCommand do command.no_deps.should be_true end end + + describe "#execute" do + it "creates the supported web template at an absolute path" do + SpecHelper.within_temp_directory do |temp_dir| + destination = File.join(temp_dir, "outside", "beta_smoke") + command = AmberCLI::Commands::NewCommand.new("new") + + command.parse_and_execute([destination, "--type=web", "--no-deps", "-d", "sqlite"]) + + File.exists?(File.join(destination, "src/beta_smoke.cr")).should be_true + Dir.exists?(File.join(destination, "bin")).should be_true + + shard = File.read(File.join(destination, "shard.yml")) + shard.should contain("github: amberframework/amber") + shard.should contain("version: 2.0.0-beta.2") + shard.should_not contain("crimson-knight") + shard.should_not contain("grant:") + shard.should_not contain("slang") + + config = YAML.parse(File.read(File.join(destination, "config/environments/development.yml"))) + config["server"]["port"].as_i.should eq(3000) + config["database"]["url"].as_s.should contain("sqlite3:") + + routes = File.read(File.join(destination, "config/routes.cr")) + routes.should contain("pipeline :static") + routes.should contain("Amber::Pipe::Static.new") + routes.should contain(%(get "/*", Amber::Controller::Static, :index)) + + File.exists?(File.join(destination, "src/views/home/index.ecr")).should be_true + File.exists?(File.join(destination, "src/views/home/index.slang")).should be_false + end + end + end end diff --git a/spec/integration/end_to_end_spec.cr b/spec/integration/end_to_end_spec.cr index 2780575..1c1d847 100644 --- a/spec/integration/end_to_end_spec.cr +++ b/spec/integration/end_to_end_spec.cr @@ -50,7 +50,7 @@ describe "End-to-End Integration Tests" do # Create model template model_template = <<-CRYSTAL require "./application_record" - + module {{namespace}} class {{class_name}} < {{base_class}} # {{class_name}} model @@ -68,7 +68,7 @@ describe "End-to-End Integration Tests" do # Create spec template spec_template = <<-CRYSTAL require "../spec_helper" - + describe {{namespace}}::{{class_name}} do describe "{{spec_name}}" do it "should be valid" do @@ -171,7 +171,7 @@ describe "End-to-End Integration Tests" do service_template = <<-CRYSTAL require "../interfaces/i_{{snake_case}}_service" require "../repositories/{{snake_case}}_repository" - + module {{company_namespace}}::Services # {{service_name}}Service # Maintained by: {{team}} diff --git a/src/amber_cli.cr b/src/amber_cli.cr index 074025a..3d816a0 100644 --- a/src/amber_cli.cr +++ b/src/amber_cli.cr @@ -38,7 +38,7 @@ end Log.builder.bind "*", :info, backend module AmberCLI - VERSION = "2.0.1" + VERSION = "2.0.2" def self.run(args = ARGV) if args.empty? diff --git a/src/amber_cli/commands/generate.cr b/src/amber_cli/commands/generate.cr index 008f02e..219615d 100644 --- a/src/amber_cli/commands/generate.cr +++ b/src/amber_cli/commands/generate.cr @@ -34,7 +34,8 @@ require "../core/base_command" # ``` module AmberCLI::Commands class GenerateCommand < AmberCLI::Core::BaseCommand - VALID_TYPES = %w[model controller scaffold migration mailer job schema channel api auth] + VALID_TYPES = %w[model controller scaffold migration mailer job schema channel api auth] + PREVIEW_TYPES = %w[model scaffold api auth] FIELD_TYPE_MAP = { "string" => "String", @@ -187,6 +188,15 @@ module AmberCLI::Commands end def execute + if PREVIEW_TYPES.includes?(generator_type) + warning "#{generator_type} generation is a preview surface in the Amber V2 beta." + warning "The generated output requires a compatible persistence stack that is not included in new web apps." + end + + if File.exists?(".amber.yml") && File.read(".amber.yml").includes?("template: slang") + warning "Amber V2 supports ECR only; generating ECR output despite the legacy Slang setting." + end + case generator_type when "model" generate_model @@ -362,7 +372,7 @@ SPEC # Renders the #{action} email HTML body. # Template: src/views/#{file_name}_mailer/#{action}.ecr def #{action}_html_body : String? - render("src/views/#{file_name}_mailer/#{action}.ecr") + ECR.render("src/views/#{file_name}_mailer/#{action}.ecr") end METHOD end.join("\n\n") @@ -492,6 +502,7 @@ SCHEMA private def schema_spec_template # Build valid test data from fields + schema_field_width = schema_fields.map { |field| field[0].size }.max? || 0 valid_data_entries = schema_fields.map do |field_name, field_type, _| value = case field_type when "string", "text", "uuid" then "\"test_value\"" @@ -504,11 +515,13 @@ SCHEMA when "time", "timestamp" then "\"2024-01-01T00:00:00Z\"" else "\"test_value\"" end - " \"#{field_name}\" => JSON::Any.new(#{value})," + padding = " " * (schema_field_width - field_name.size) + " \"#{field_name}\"#{padding} => JSON::Any.new(#{value})," end.join("\n") # Fall back to regular fields if schema_fields is empty if valid_data_entries.empty? && !fields.empty? + field_width = fields.map { |field| field[0].size }.max? || 0 valid_data_entries = fields.map do |field_name, field_type| value = case field_type when "string", "text", "uuid" then "\"test_value\"" @@ -521,7 +534,8 @@ SCHEMA when "time", "timestamp" then "\"2024-01-01T00:00:00Z\"" else "\"test_value\"" end - " \"#{field_name}\" => JSON::Any.new(#{value})," + padding = " " * (field_width - field_name.size) + " \"#{field_name}\"#{padding} => JSON::Any.new(#{value})," end.join("\n") end @@ -783,23 +797,18 @@ VIEW end path = case action - when "index" then "/#{plural_name}" - when "show" then "/#{plural_name}/1" - when "new" then "/#{plural_name}/new" - when "edit" then "/#{plural_name}/1/edit" - when "create" then "/#{plural_name}" - when "update" then "/#{plural_name}/1" - when "destroy" then "/#{plural_name}/1" - else "/#{plural_name}" + when "index" then "/#{controller_resource_name}" + when "show" then "/#{controller_resource_name}/1" + when "new" then "/#{controller_resource_name}/new" + when "edit" then "/#{controller_resource_name}/1/edit" + when "create" then "/#{controller_resource_name}" + when "update" then "/#{controller_resource_name}/1" + when "destroy" then "/#{controller_resource_name}/1" + else "/#{controller_resource_name}" end <<-SPEC_BLOCK - describe "#{verb} #{path}" do - it "responds successfully" do - response = #{verb.downcase}("#{path}") - assert_response_success(response) - end - end + pending "add #{verb} #{path} to config/routes.cr, then enable its request spec" SPEC_BLOCK end.join("\n\n") @@ -807,9 +816,6 @@ SPEC_BLOCK require "../spec_helper" describe #{controller_name} do - include Amber::Testing::RequestHelpers - include Amber::Testing::Assertions - #{action_specs} end SPEC @@ -979,9 +985,6 @@ CONTROLLER require "../spec_helper" describe #{controller_name} do - include Amber::Testing::RequestHelpers - include Amber::Testing::Assertions - describe "GET /#{plural_name}" do it "responds successfully" do response = get("/#{plural_name}") @@ -1186,9 +1189,6 @@ CONTROLLER require "../spec_helper" describe Api::#{controller_name} do - include Amber::Testing::RequestHelpers - include Amber::Testing::Assertions - describe "GET /api/#{plural_name}" do it "responds with JSON" do response = get("/api/#{plural_name}") @@ -1632,6 +1632,12 @@ VIEW pluralize(name.underscore) end + # Controller names are conventionally plural (for example `Posts`). Avoid + # turning an already-plural resource into `postses` in route guidance. + private def controller_resource_name + file_name.ends_with?("s") ? file_name : plural_name + end + private def default_actions %w[index show new create edit update destroy] end @@ -1666,16 +1672,7 @@ VIEW end private def detect_template_extension - if File.exists?(".amber.yml") - content = File.read(".amber.yml") - if content.includes?("template: slang") - "slang" - else - "ecr" - end - else - "ecr" - end + "ecr" end private def create_file(path : String, content : String) @@ -1685,7 +1682,7 @@ VIEW if File.exists?(path) warning "Skipped (exists): #{path}" else - File.write(path, content) + File.write(path, content.ends_with?("\n") ? content : "#{content}\n") info "Created: #{path}" end end diff --git a/src/amber_cli/commands/new.cr b/src/amber_cli/commands/new.cr index 1ea126c..fba1cb0 100644 --- a/src/amber_cli/commands/new.cr +++ b/src/amber_cli/commands/new.cr @@ -6,12 +6,12 @@ require "../generators/native_app" # # ## Usage # ``` -# amber new [app_name] -d [pg | mysql | sqlite] -t [ecr | slang] --type [web | native] --no-deps +# amber new [app_name] -d [pg | mysql | sqlite] -t ecr --type [web | native] --no-deps # ``` # # ## Options # - `-d, --database` - Database type (pg, mysql, sqlite) -# - `-t, --template` - Template language (ecr, slang) +# - `-t, --template` - Template language (ECR is the only V2 engine) # - `--type` - Application type: web (default) or native (cross-platform desktop/mobile) # - `--no-deps` - Skip dependency installation # @@ -20,8 +20,8 @@ require "../generators/native_app" # # Create a new web app with PostgreSQL and ECR (defaults) # amber new my_blog # -# # Create app with MySQL and Slang templates -# amber new my_blog -d mysql -t slang +# # Record MySQL as the database for future persistence tooling +# amber new my_blog -d mysql # # # Create a native cross-platform app (macOS, iOS, Android) # amber new my_native_app --type native @@ -32,6 +32,8 @@ require "../generators/native_app" module AmberCLI::Commands class NewCommand < AmberCLI::Core::BaseCommand VALID_APP_TYPES = %w[web native] + VALID_DATABASES = %w[pg mysql sqlite] + VALID_TEMPLATES = %w[ecr] getter database : String = "pg" getter template : String = "ecr" @@ -46,11 +48,19 @@ module AmberCLI::Commands def setup_command_options option_parser.on("-d DATABASE", "--database=DATABASE", "Select the database engine (pg, mysql, sqlite)") do |db| + unless VALID_DATABASES.includes?(db) + error "Invalid database '#{db}'. Valid databases: #{VALID_DATABASES.join(", ")}" + exit(1) + end @parsed_options["database"] = db @database = db end - option_parser.on("-t TEMPLATE", "--template=TEMPLATE", "Select template engine (ecr, slang)") do |tmpl| + option_parser.on("-t TEMPLATE", "--template=TEMPLATE", "Template engine (ecr; the only V2 engine)") do |tmpl| + unless VALID_TEMPLATES.includes?(tmpl) + error "Invalid template '#{tmpl}'. Amber V2 supports ECR only." + exit(1) + end @parsed_options["template"] = tmpl @template = tmpl end @@ -79,13 +89,13 @@ module AmberCLI::Commands option_parser.separator "" option_parser.separator "App types:" option_parser.separator " web Web application with HTTP server, routes, views (default)" - option_parser.separator " native Cross-platform native app (macOS, iOS, Android)" + option_parser.separator " native Preview: cross-platform native app (macOS, iOS, Android)" option_parser.separator " Uses Asset Pipeline UI, FSDD process managers," option_parser.separator " crystal-audio, and platform build scripts." option_parser.separator "" option_parser.separator "Examples:" option_parser.separator " amber new my_app" - option_parser.separator " amber new my_app -d mysql -t slang" + option_parser.separator " amber new my_app -d mysql -t ecr" option_parser.separator " amber new my_native_app --type native" option_parser.separator " amber new . -d sqlite" end @@ -105,7 +115,7 @@ module AmberCLI::Commands full_path_name = Dir.current else project_name = File.basename(name) - full_path_name = File.join(Dir.current, name) + full_path_name = File.expand_path(name, Dir.current) end if full_path_name =~ /\s+/ @@ -124,6 +134,7 @@ module AmberCLI::Commands private def execute_native(full_path_name : String, project_name : String) info "Creating new Amber V2 native application: #{project_name}" + warning "Native application generation is a preview surface in this beta." info "Type: native (cross-platform: macOS, iOS, Android)" info "Location: #{full_path_name}" @@ -160,6 +171,8 @@ module AmberCLI::Commands create_project_structure(full_path_name, project_name) + install_dependencies(full_path_name) unless no_deps + # Encrypt production.yml by default if File.exists?(File.join(full_path_name, "config", "environments", "production.yml")) cwd = Dir.current @@ -173,10 +186,33 @@ module AmberCLI::Commands puts "" info "To get started:" info " cd #{name}" unless name == "." - info " shards install" unless no_deps - info " amber database create" - info " amber database migrate" + info " shards install" if no_deps + info " crystal spec" info " amber watch" + puts "" + info "Persistence, auth, API-resource, and native generators are preview surfaces." + info "The generated web app intentionally has no ORM or database driver." + end + + private def install_dependencies(path : String) + info "Installing dependencies..." + status = Process.run( + "shards", + ["install"], + chdir: path, + input: Process::Redirect::Inherit, + output: Process::Redirect::Inherit, + error: Process::Redirect::Inherit + ) + return if status.success? + + warning "Dependency installation failed; the project files were kept." + warning "Run 'shards install' in #{path} after resolving the error." + exit!(error: true) + rescue ex : File::NotFoundError + warning "The 'shards' executable was not found; the project files were kept." + warning "Install Crystal and run 'shards install' in #{path}." + exit!(error: true) end private def create_project_structure(path : String, name : String) @@ -186,8 +222,8 @@ module AmberCLI::Commands "config", "config/environments", "config/initializers", # Database "db", "db/migrations", - # Public assets - "public", "public/css", "public/js", "public/img", + # Build output and public assets + "bin", "public", "public/css", "public/js", "public/img", # Spec directories "spec", "spec/controllers", "spec/models", "spec/schemas", "spec/jobs", "spec/mailers", "spec/channels", "spec/requests", @@ -230,7 +266,7 @@ version: 0.1.0 authors: - Your Name -crystal: ">= 1.10.0" +crystal: ">= 1.20.0, < 2.0" license: UNLICENSED @@ -239,53 +275,27 @@ targets: main: src/#{name}.cr dependencies: - # Amber Framework V2 amber: - github: crimson-knight/amber - branch: master - - # Grant ORM (ActiveRecord-style, replaces Granite in V2) - grant: - github: crimson-knight/grant - branch: main - - # Asset Pipeline (native ESM, no Webpack/npm required) - asset_pipeline: - github: amberframework/asset_pipeline - - # File uploads (optional) - gemma: - github: crimson-knight/gemma - - # Database adapters (all required by Grant at compile time) - pg: - github: will/crystal-pg - mysql: - github: crystal-lang/crystal-mysql - sqlite3: - github: crystal-lang/crystal-sqlite3 - -development_dependencies: - ameba: - github: crystal-ameba/ameba - version: ~> 1.6.4 + github: amberframework/amber + version: 2.0.0-beta.2 SHARD - File.write(File.join(path, "shard.yml"), shard_content) + write_text(File.join(path, "shard.yml"), shard_content) end private def create_amber_yml(path : String, name : String) amber_content = <<-AMBER app: #{name} +type: web author: Your Name email: your.email@example.com database: #{database} language: crystal -model: grant -template: #{template} +model: none +template: ecr AMBER - File.write(File.join(path, ".amber.yml"), amber_content) + write_text(File.join(path, ".amber.yml"), amber_content) end private def create_gitignore(path : String) @@ -308,9 +318,9 @@ Thumbs.db .idea/ .vscode/ -# Environment files (encrypted versions are safe to commit) -/config/environments/*.yml -!/config/environments/*.yml.enc +# Local secrets +.env +/config/environments/*.local.yml # Dependencies /node_modules/ @@ -319,7 +329,7 @@ Thumbs.db /tmp/ GITIGNORE - File.write(File.join(path, ".gitignore"), gitignore_content) + write_text(File.join(path, ".gitignore"), gitignore_content) end private def create_main_file(path : String, name : String) @@ -335,21 +345,15 @@ require "./channels/**" Amber::Server.start MAIN - File.write(File.join(path, "src/#{name}.cr"), main_content) + write_text(File.join(path, "src/#{name}.cr"), main_content) end private def create_config_files(path : String, name : String) app_config = <<-CONFIG require "amber" - -Amber::Server.configure do |settings| - settings.name = "#{name}" - settings.port = ENV["PORT"]?.try(&.to_i) || 3000 - settings.secret_key_base = ENV["SECRET_KEY_BASE"]? || "#{Random::Secure.hex(64)}" -end CONFIG - File.write(File.join(path, "config/application.cr"), app_config) + write_text(File.join(path, "config/application.cr"), app_config) end private def create_application_controller(path : String) @@ -362,7 +366,7 @@ class ApplicationController < Amber::Controller::Base end CONTROLLER - File.write(File.join(path, "src/controllers/application_controller.cr"), controller_content) + write_text(File.join(path, "src/controllers/application_controller.cr"), controller_content) end private def create_routes_file(path : String, name : String) @@ -376,6 +380,11 @@ Amber::Server.configure do plug Amber::Pipe::CSRF.new end + pipeline :static do + plug Amber::Pipe::Error.new + plug Amber::Pipe::Static.new("./public") + end + pipeline :api do plug Amber::Pipe::Error.new plug Amber::Pipe::Logger.new @@ -385,51 +394,61 @@ Amber::Server.configure do get "/", HomeController, :index end + routes :static do + get "/*", Amber::Controller::Static, :index + end + # routes :api do # end end ROUTES - File.write(File.join(path, "config/routes.cr"), routes_content) + write_text(File.join(path, "config/routes.cr"), routes_content) end private def create_environment_files(path : String, name : String) - dev_config = <<-YML -database_url: postgres://localhost:5432/#{name}_development -YML + database_urls = case database + when "mysql" + {"mysql://localhost:3306/#{name}_development", "mysql://localhost:3306/#{name}_test"} + when "sqlite" + {"sqlite3:./db/#{name}_development.db", "sqlite3:./db/#{name}_test.db"} + else + {"postgres://localhost:5432/#{name}_development", "postgres://localhost:5432/#{name}_test"} + end + + dev_config = environment_config(name, database_urls[0], "debug") + test_config = environment_config(name, database_urls[1], "warn") + prod_config = environment_config(name, "", "info", host: "0.0.0.0", secret: "") + + write_text(File.join(path, "config/environments/development.yml"), dev_config) + write_text(File.join(path, "config/environments/test.yml"), test_config) + write_text(File.join(path, "config/environments/production.yml"), prod_config) + end - test_config = <<-YML -database_url: postgres://localhost:5432/#{name}_test -YML + private def environment_config(name : String, database_url : String, severity : String, + host : String = "127.0.0.1", + secret : String = Random::Secure.hex(32)) : String + <<-YML +name: #{name} - prod_config = <<-YML -database_url: <%= ENV["DATABASE_URL"] %> -YML +server: + host: #{host} + port: 3000 + secret_key_base: "#{secret}" - # Adjust database URLs based on selected database - case database - when "mysql" - dev_config = <<-YML -database_url: mysql://localhost:3306/#{name}_development -YML - test_config = <<-YML -database_url: mysql://localhost:3306/#{name}_test -YML - when "sqlite" - dev_config = <<-YML -database_url: sqlite3:./db/#{name}_development.db -YML - test_config = <<-YML -database_url: sqlite3:./db/#{name}_test.db -YML - prod_config = <<-YML -database_url: sqlite3:./db/#{name}_production.db -YML - end +database: + url: "#{database_url}" + +session: + key: "#{name}.session" + store: "signed_cookie" + adapter: "memory" + expires: 0 - File.write(File.join(path, "config/environments/development.yml"), dev_config) - File.write(File.join(path, "config/environments/test.yml"), test_config) - File.write(File.join(path, "config/environments/production.yml"), prod_config) +logging: + severity: "#{severity}" + colorize: true +YML end private def create_home_controller(path : String, name : String) @@ -441,45 +460,11 @@ class HomeController < ApplicationController end CONTROLLER - File.write(File.join(path, "src/controllers/home_controller.cr"), home_controller) + write_text(File.join(path, "src/controllers/home_controller.cr"), home_controller) end private def create_views(path : String, name : String) - if template == "slang" - layout_content = <<-LAYOUT -doctype html -html - head - meta charset="utf-8" - meta name="viewport" content="width=device-width, initial-scale=1" - title #{name} - link rel="stylesheet" href="/css/app.css" - body - == content - script src="/js/app.js" -LAYOUT - File.write(File.join(path, "src/views/layouts/application.slang"), layout_content) - - index_content = <<-VIEW -.welcome - h1 = "Welcome to \#{Amber.settings.name}!" - p Your Amber V2 application is running successfully. - - h2 Getting Started - ul - li - | Edit this page: - code src/views/home/index.slang - li - | Add routes: - code config/routes.cr - li - | Generate a resource: - code amber generate scaffold Post title:string body:text -VIEW - File.write(File.join(path, "src/views/home/index.slang"), index_content) - else - layout_content = <<-LAYOUT + layout_content = <<-LAYOUT @@ -494,9 +479,9 @@ VIEW LAYOUT - File.write(File.join(path, "src/views/layouts/application.ecr"), layout_content) + write_text(File.join(path, "src/views/layouts/application.ecr"), layout_content) - index_content = <<-VIEW + index_content = <<-VIEW

Welcome to <%= Amber.settings.name %>!

Your Amber V2 application is running successfully.

@@ -505,20 +490,23 @@ LAYOUT
  • Edit this page: src/views/home/index.ecr
  • Add routes: config/routes.cr
  • -
  • Generate a resource: amber generate scaffold Post title:string body:text
  • +
  • Add a controller: amber generate controller Posts
VIEW - File.write(File.join(path, "src/views/home/index.ecr"), index_content) - end + write_text(File.join(path, "src/views/home/index.ecr"), index_content) end private def create_spec_helper(path : String, name : String) spec_helper = <<-SPEC require "spec" -require "../config/application" -require "../config/routes" -require "../src/**" +require "../config/*" +require "../src/controllers/**" +require "../src/models/**" +require "../src/schemas/**" +require "../src/jobs/**" +require "../src/mailers/**" +require "../src/channels/**" # Amber Testing Framework require "amber/testing/testing" @@ -528,7 +516,7 @@ include Amber::Testing::RequestHelpers include Amber::Testing::Assertions SPEC - File.write(File.join(path, "spec/spec_helper.cr"), spec_helper) + write_text(File.join(path, "spec/spec_helper.cr"), spec_helper) end private def create_home_controller_spec(path : String) @@ -536,9 +524,6 @@ SPEC require "../spec_helper" describe HomeController do - include Amber::Testing::RequestHelpers - include Amber::Testing::Assertions - describe "GET /" do it "responds successfully" do response = get("/") @@ -548,7 +533,7 @@ describe HomeController do end SPEC - File.write(File.join(path, "spec/controllers/home_controller_spec.cr"), spec_content) + write_text(File.join(path, "spec/controllers/home_controller_spec.cr"), spec_content) end private def create_seeds_file(path : String) @@ -570,7 +555,7 @@ puts "Seeding database..." puts "Done!" SEEDS - File.write(File.join(path, "db/seeds.cr"), seeds_content) + write_text(File.join(path, "db/seeds.cr"), seeds_content) end private def create_keep_files(path : String) @@ -682,7 +667,7 @@ th { } CSS - File.write(File.join(path, "public/css/app.css"), css_content) + write_text(File.join(path, "public/css/app.css"), css_content) # JavaScript js_content = <<-JS @@ -690,7 +675,7 @@ CSS console.log("Amber V2 application loaded"); JS - File.write(File.join(path, "public/js/app.js"), js_content) + write_text(File.join(path, "public/js/app.js"), js_content) # robots.txt robots_content = <<-ROBOTS @@ -698,7 +683,7 @@ User-agent: * Disallow: ROBOTS - File.write(File.join(path, "public/robots.txt"), robots_content) + write_text(File.join(path, "public/robots.txt"), robots_content) # Placeholder favicon File.write(File.join(path, "public/favicon.ico"), "") @@ -706,6 +691,10 @@ ROBOTS # .keep for img File.write(File.join(path, "public/img/.keep"), "") end + + private def write_text(path : String, content : String) + File.write(path, content.ends_with?("\n") ? content : "#{content}\n") + end end end diff --git a/src/amber_cli/config.cr b/src/amber_cli/config.cr index 652e528..3e018cd 100644 --- a/src/amber_cli/config.cr +++ b/src/amber_cli/config.cr @@ -27,8 +27,8 @@ module Amber::CLI alias WatchOptions = Hash(String, Hash(String, Array(String))) property database : String = "pg" - property language : String = "slang" - property model : String = "grant" + property language : String = "ecr" + property model : String = "none" property watch : WatchOptions? def initialize @@ -53,7 +53,7 @@ module Amber::CLI "include" => [ "./config/**/*.cr", "./src/**/*.cr", - "./src/views/**/*.slang", + "./src/views/**/*.ecr", ], }, } diff --git a/src/amber_cli/documentation.cr b/src/amber_cli/documentation.cr index 25eb624..7ba038d 100644 --- a/src/amber_cli/documentation.cr +++ b/src/amber_cli/documentation.cr @@ -24,9 +24,7 @@ module AmberCLI::Documentation # ```bash # amber new my_app # cd my_app - # shards install - # amber database create - # amber database migrate + # crystal spec # amber watch # ``` # @@ -57,7 +55,8 @@ module AmberCLI::Documentation # ### Options # # - `-d, --database=DATABASE` - Database engine (pg, mysql, sqlite) - # - `-t, --template=TEMPLATE` - Template engine (ecr, slang) + # - `-t, --template=TEMPLATE` - Template engine (`ecr` only in V2) + # - `--type=TYPE` - Application type (`web` supported, `native` preview) # - `-y, --assume-yes` - Skip interactive prompts # - `--no-deps` - Don't install dependencies # @@ -68,9 +67,9 @@ module AmberCLI::Documentation # amber new my_blog # ``` # - # Create with specific database and template: + # Record a database choice for future persistence tooling: # ```bash - # amber new my_api -d mysql -t slang + # amber new my_api -d mysql -t ecr # ``` # # Create in current directory: @@ -89,6 +88,10 @@ module AmberCLI::Documentation # - **shard.yml** - Dependency configuration # - **.amber.yml** - Project configuration # - **.gitignore** - Git ignore rules + # + # The web template pins Amber `2.0.0-beta.2`, uses ECR, and intentionally + # does not add an ORM or database driver. Model, scaffold, API-resource, + # authentication, and native generators are preview surfaces during the beta. class NewCommand end @@ -443,7 +446,7 @@ module AmberCLI::Documentation # **Problem**: Template not found errors # **Solution**: # 1. Verify template files exist in expected locations - # 2. Check `.amber.yml` for correct template setting (ecr or slang) + # 2. Check `.amber.yml` for `template: ecr` (Amber V2 does not support Slang) # 3. Ensure template variables are properly defined # # ### Getting More Help diff --git a/src/amber_cli/templates/app/.amber.yml.ecr b/src/amber_cli/templates/app/.amber.yml.ecr index 100b050..d61ad80 100644 --- a/src/amber_cli/templates/app/.amber.yml.ecr +++ b/src/amber_cli/templates/app/.amber.yml.ecr @@ -1,7 +1,8 @@ type: app database: <%= @database %> -language: <%= @language %> -model: <%= @model %> +language: crystal +model: none +template: ecr # list of tasks to be run by `amber watch` watch: @@ -18,9 +19,6 @@ watch: - ./config/environments/*.yml - ./src/**/*.cr - ./src/**/*.ecr - <%- if @language == "slang" -%> - - ./src/views/**/*.slang - <%- end -%> - ./src/locales/*.yml # exclude: # NOTE simplistic implementation: (1) enumerate all includes and excludes; (2) return (includes - excludes) # - ./src/some_irrelevant_file.cr diff --git a/src/amber_cli/templates/app/.gitignore.ecr b/src/amber_cli/templates/app/.gitignore.ecr index 78b0de6..0ece77a 100644 --- a/src/amber_cli/templates/app/.gitignore.ecr +++ b/src/amber_cli/templates/app/.gitignore.ecr @@ -6,7 +6,7 @@ /tmp/ .env .encryption_key -production.yml +/config/environments/*.local.yml .DS_Store /bin/ /node_modules diff --git a/src/amber_cli/templates/app/Dockerfile.ecr b/src/amber_cli/templates/app/Dockerfile.ecr deleted file mode 100644 index 2efcf16..0000000 --- a/src/amber_cli/templates/app/Dockerfile.ecr +++ /dev/null @@ -1,10 +0,0 @@ -FROM amberframework/amber:<%= Amber::VERSION %> - -WORKDIR /app - -COPY shard.* /app/ -RUN shards install - -COPY . /app - -CMD amber watch diff --git a/src/amber_cli/templates/app/README.md.ecr b/src/amber_cli/templates/app/README.md.ecr index d274725..a2c8949 100644 --- a/src/amber_cli/templates/app/README.md.ecr +++ b/src/amber_cli/templates/app/README.md.ecr @@ -1,50 +1,31 @@ # <%= @name %> -[![Amber Framework](https://img.shields.io/badge/using-amber_framework-orange.svg)](https://amberframework.org) +An ECR web application generated by Amber CLI for Amber `2.0.0-beta.2`. -This is a project written using [Amber](https://amberframework.org). Enjoy! +## Run it -## Getting Started - -These instructions will get a copy of this project running on your machine for development and testing purposes. - -Please see [deployment](https://docs.amberframework.org/amber/deployment) for notes on deploying the project in production. - -## Prerequisites - -This project requires [Crystal](https://crystal-lang.org/) ([installation guide](https://crystal-lang.org/docs/installation/)). - -## Development - -To start your Amber server: +```bash +shards install +crystal spec +amber watch +``` -1. Install dependencies with `shards install` -2. Build executables with `shards build` -3. Create and migrate your database with `bin/amber db create migrate`. Also see [creating the database](https://docs.amberframework.org/amber/guides/create-new-app#creating-the-database). -4. Start Amber server with `bin/amber watch` +Open and +. -Now you can visit http://localhost:3000/ from your browser. +The generated application intentionally includes no ORM or database driver. +Persistence, authentication, API-resource, and native generators are preview +surfaces during this beta. Add those dependencies only by following their own +release documentation. -Getting an error message you need help decoding? Check the [Amber troubleshooting guide](https://docs.amberframework.org/amber/troubleshooting), post a [tagged message on Stack Overflow](https://stackoverflow.com/questions/tagged/amber-framework), or visit [Amber on Gitter](https://gitter.im/amberframework/amber). +## Production configuration -Using Docker? Please check [Amber Docker guides](https://docs.amberframework.org/amber/guides/docker). +Set at least `AMBER_SERVER_SECRET_KEY_BASE`. Set `AMBER_DATABASE_URL` after an +ORM and database driver have been added. See the +[Amber V2 beta guide](https://github.com/amberframework/amber/blob/v2.0.0-beta.2/docs/beta-installation.md). ## Tests -To run the test suite: - -``` +```bash crystal spec ``` - -## Contributing - -1. Fork it ( https://github.com/<%= @github_name %>/<%= @name %>/fork ) -2. Create your feature branch ( `git checkout -b my-new-feature` ) -3. Commit your changes ( `git commit -am 'Add some feature'` ) -4. Push to the branch ( `git push origin my-new-feature` ) -5. Create a new Pull Request - -## Contributors - -- [<%= @github_name %>](https://github.com/<%= @github_name %>) <%= @author %> - creator, maintainer diff --git a/src/amber_cli/templates/app/config/application.cr.ecr b/src/amber_cli/templates/app/config/application.cr.ecr index 4d13b7e..318939c 100644 --- a/src/amber_cli/templates/app/config/application.cr.ecr +++ b/src/amber_cli/templates/app/config/application.cr.ecr @@ -1,25 +1,4 @@ -# About Application.cr File -# -# This is Amber application main entry point. This file is responsible for loading -# initializers, classes, and all application related code in order to have -# Amber::Server boot up. -# -# > We recommend not modifying the order of the requires since the order will -# affect the behavior of the application. - require "amber" -require "./settings" -require "./logger" -require "./i18n" -require "./database" -require "./initializers/**" - -# uncomment these 4 lines to enable plugins -# require "../plugins/plugins" - -# Start Generator Dependencies: Don't modify. -# End Generator Dependencies - require "../src/controllers/application_controller" require "../src/controllers/**" require "./routes" diff --git a/src/amber_cli/templates/app/config/database.cr.ecr b/src/amber_cli/templates/app/config/database.cr.ecr deleted file mode 100644 index 6c3a3c6..0000000 --- a/src/amber_cli/templates/app/config/database.cr.ecr +++ /dev/null @@ -1,3 +0,0 @@ -require "granite/adapter/<%= @database %>" - -Granite::Connections << Granite::Adapter::<%= @database.capitalize %>.new(name: "<%= @database %>", url: ENV["DATABASE_URL"]? || Amber.settings.database_url) diff --git a/src/amber_cli/templates/app/config/environments/development.yml.ecr b/src/amber_cli/templates/app/config/environments/development.yml.ecr index 787ede9..14220f2 100644 --- a/src/amber_cli/templates/app/config/environments/development.yml.ecr +++ b/src/amber_cli/templates/app/config/environments/development.yml.ecr @@ -1,43 +1,26 @@ -secret_key_base: <%= Random::Secure.urlsafe_base64(32) %> -port: 3000 name: <%= @name %> -logging: - severity: debug - colorize: true - filter: - - password - - confirm_password +server: + host: 127.0.0.1 + port: 3000 + secret_key_base: <%= Random::Secure.hex(32) %> -host: 0.0.0.0 -port_reuse: true -process_count: 1 -# ssl_key_file: -# ssl_cert_file: -redis_url: "redis://localhost:6379" +database: <% case @database when "mysql" -%> -database_url: mysql://root@localhost:3306/<%= database_name %>_development -<% when "pg" -%> -database_url: <%= "postgres://postgres:#{ENV["POSTGRES_PASSWORD"]? || "password"}@localhost:5432/#{database_name}_development" %> + url: mysql://localhost:3306/<%= database_name %>_development <% when "sqlite" -%> -database_url: sqlite3:./db/<%= database_name %>_development.db -<% else -end -%> -auto_reload: true + url: sqlite3:./db/<%= database_name %>_development.db +<% else -%> + url: postgres://localhost:5432/<%= database_name %>_development +<% end -%> session: - key: amber.session + key: <%= @name %>.session store: signed_cookie + adapter: memory expires: 0 -smtp: - enabled: false - -pipes: - static: - headers: - "Cache-Control": "no-store" - -secrets: - description: Store your development secrets credentials and settings here. +logging: + severity: debug + colorize: true diff --git a/src/amber_cli/templates/app/config/environments/production.yml.ecr b/src/amber_cli/templates/app/config/environments/production.yml.ecr index e03c3d9..14309d4 100644 --- a/src/amber_cli/templates/app/config/environments/production.yml.ecr +++ b/src/amber_cli/templates/app/config/environments/production.yml.ecr @@ -1,47 +1,20 @@ -secret_key_base: <%= Random::Secure.urlsafe_base64(32) %> -port: 8080 name: <%= @name %> -logging: - severity: debug - colorize: true - filter: - - password - - confirm_password +# Set AMBER_SERVER_SECRET_KEY_BASE and AMBER_DATABASE_URL in production. +server: + host: 0.0.0.0 + port: 3000 + secret_key_base: "" -host: 0.0.0.0 -port_reuse: true -process_count: 4 -# ssl_key_file: -# ssl_cert_file: -redis_url: "redis://localhost:6379" -<% case @database -when "mysql" -%> -database_url: mysql://root@localhost:3306/<%= database_name %> -<% when "pg" -%> -database_url: <%= "postgres://postgres:#{ENV["POSTGRES_PASSWORD"]? || "password"}@localhost:5432/#{database_name}" %> -<% when "sqlite" -%> -database_url: sqlite3:./db/<%= database_name %>.db -<% else -end -%> -auto_reload: false +database: + url: "" session: - key: amber.session + key: <%= @name %>.session store: signed_cookie + adapter: memory expires: 0 -smtp: - host: smtp.sendgrid.com - port: 25 - tls: true - username: api_key - enabled: false - -pipes: - static: - headers: - "Cache-Control": "private, max-age=3600" - -secrets: - description: Store your production secrets credentials and settings here. +logging: + severity: info + colorize: true diff --git a/src/amber_cli/templates/app/config/environments/test.yml.ecr b/src/amber_cli/templates/app/config/environments/test.yml.ecr index f440f87..37d796b 100644 --- a/src/amber_cli/templates/app/config/environments/test.yml.ecr +++ b/src/amber_cli/templates/app/config/environments/test.yml.ecr @@ -1,38 +1,26 @@ -secret_key_base: <%= Random::Secure.urlsafe_base64(32) %> -port: 3000 name: <%= @name %> -logging: - severity: debug - colorize: true - filter: - - password - - confirm_password - -host: 0.0.0.0 -port_reuse: false -process_count: 1 -# ssl_key_file: -# ssl_cert_file: -redis_url: "redis://localhost:6379" +server: + host: 127.0.0.1 + port: 3000 + secret_key_base: <%= Random::Secure.hex(32) %> + +database: <% case @database when "mysql" -%> -database_url: mysql://root@localhost:3306/<%= database_name %>_test -<% when "pg" -%> -database_url: <%= "postgres://postgres:#{ENV["POSTGRES_PASSWORD"]? || "password"}@localhost:5432/#{database_name}_test" %> + url: mysql://localhost:3306/<%= database_name %>_test <% when "sqlite" -%> -database_url: sqlite3:./db/<%= database_name %>_test.db -<% else -end -%> -auto_reload: false + url: sqlite3:./db/<%= database_name %>_test.db +<% else -%> + url: postgres://localhost:5432/<%= database_name %>_test +<% end -%> session: - key: amber.session + key: <%= @name %>.session store: signed_cookie + adapter: memory expires: 0 -smtp: - enabled: false - -secrets: - description: Store your development secrets credentials and settings here. +logging: + severity: warn + colorize: true diff --git a/src/amber_cli/templates/app/config/i18n.cr.ecr b/src/amber_cli/templates/app/config/i18n.cr.ecr deleted file mode 100644 index 1b005b2..0000000 --- a/src/amber_cli/templates/app/config/i18n.cr.ecr +++ /dev/null @@ -1,24 +0,0 @@ -require "citrine-i18n" - -Citrine::I18n.configure do |settings| - # Backend storage (as supported by i18n.cr) - # settings.backend = I18n::Backend::Yaml.new - - # Default locale (defaults to "en" and "./src/locales/**/en.yml"). - # For a new default locale to be accepted, it must be found by the - # backend storage and reported in "settings.available_locales". - # settings.default_locale = "en" - - # Separator between sublevels of data (defaults to '.') - # e.g. I18n.translate("some/thing") instead of "some.thing" - # settings.default_separator = '.' - - # Returns the current exception handler. Defaults to an instance of - # I18n::ExceptionHandler. - # settings.exception_handler = ExceptionHandler.new - - # The path from where the translations should be loaded - settings.load_path += ["./src/locales"] -end - -I18n.init diff --git a/src/amber_cli/templates/app/config/initializers/mailer.cr.ecr b/src/amber_cli/templates/app/config/initializers/mailer.cr.ecr deleted file mode 100644 index 1044d37..0000000 --- a/src/amber_cli/templates/app/config/initializers/mailer.cr.ecr +++ /dev/null @@ -1,18 +0,0 @@ -require "quartz_mailer" - -Quartz.config do |c| - c.smtp_enabled = Amber.settings.smtp.enabled - - c.smtp_address = ENV["SMTP_ADDRESS"]? || Amber.settings.smtp.host - c.smtp_port = ENV["SMTP_PORT"]? || Amber.settings.smtp.port - c.username = ENV["SMTP_USERNAME"]? || Amber.settings.smtp.username - c.password = ENV["SMTP_PASSWORD"]? || Amber.settings.smtp.password - - c.use_authentication = !c.password.blank? - c.use_tls = EMail::Client::TLSMode::NONE - # c.use_tls = EMail::Client::TLSMode::STARTTLS - # c.use_tls = EMail::Client::TLSMode::SMTPS -end - -require "../../src/mailers/application_mailer" -require "../../src/mailers/**" diff --git a/src/amber_cli/templates/app/config/logger.cr.ecr b/src/amber_cli/templates/app/config/logger.cr.ecr deleted file mode 100644 index d31de0f..0000000 --- a/src/amber_cli/templates/app/config/logger.cr.ecr +++ /dev/null @@ -1,50 +0,0 @@ -require "log" - -# About logger.cr File -# -# Amber is using the crystal standard library Log -# You can read details here: https://crystal-lang.org/api/0.35.0/Log.html - -# Using environment settings: -Colorize.enabled = Amber.settings.logging.colorize -backend = Log::IOBackend.new(STDOUT) - -# Custom formatter -# This is a good place to change the time from UTC - -# if you want the systems local time or hard code a timezone, uncomment -# one of the following lines and update the formatter accordingly -# time_zone = Time::Location.local -# time_zone = Time::Location.load("America/Buenos_Aires") - -backend.formatter = Log::Formatter.new do |entry, io| - io << entry.timestamp.to_s("%I:%M:%S") - # io << entry.timestamp.in(time_zone).to_s("%I:%M:%S") - io << " " - io << entry.source - io << " |" - io << " (#{entry.severity})" if entry.severity > Log::Severity::Debug - io << " " - io << entry.message -end - -Log.builder.clear -Log.builder.bind "*", Amber.settings.logging.severity, backend - -# Using crystal's standard environment variables: -# CRYSTAL_LOG_LEVEL=INFO -# CRYSTAL_LOG_SOURCES=* -# Logs are emitted to STDOUT -# Log.setup_from_env - -# Using more advanced options: -# backend = Log::IOBackend.new -# Log.builder.bind "*", :warn, backend -# Log.builder.bind "request", :debug, backend -# Log.builder.bind "headers", :debug, backend -# Log.builder.bind "cookies", :debug, backend -# Log.builder.bind "params", :debug, backend -# Log.builder.bind "session", :debug, backend -# Log.builder.bind "errors", :warn, backend -# Log.builder.bind "granite.*", :info, backend -# Log.builder.bind "*", :error, ElasticSearchBackend.new("http://localhost:9200") diff --git a/src/amber_cli/templates/app/config/routes.cr.ecr b/src/amber_cli/templates/app/config/routes.cr.ecr index f706e06..c3343f8 100644 --- a/src/amber_cli/templates/app/config/routes.cr.ecr +++ b/src/amber_cli/templates/app/config/routes.cr.ecr @@ -1,10 +1,5 @@ Amber::Server.configure do pipeline :web do - # Plug is the method to use connect a pipe (middleware) - # A plug accepts an instance of HTTP::Handler - # plug Amber::Pipe::PoweredByAmber.new - # plug Amber::Pipe::ClientIp.new(["X-Forwarded-For"]) - plug Citrine::I18n::Handler.new plug Amber::Pipe::Error.new plug Amber::Pipe::Logger.new plug Amber::Pipe::Session.new @@ -12,17 +7,7 @@ Amber::Server.configure do plug Amber::Pipe::CSRF.new end - pipeline :api do - # plug Amber::Pipe::PoweredByAmber.new - plug Amber::Pipe::Error.new - plug Amber::Pipe::Logger.new - plug Amber::Pipe::Session.new - plug Amber::Pipe::CORS.new - end - - # All static content will run these transformations pipeline :static do - # plug Amber::Pipe::PoweredByAmber.new plug Amber::Pipe::Error.new plug Amber::Pipe::Static.new("./public") end @@ -31,12 +16,7 @@ Amber::Server.configure do get "/", HomeController, :index end - routes :api do - end - routes :static do - # Each route is defined as follow - # verb resource : String, controller : Symbol, action : Symbol get "/*", Amber::Controller::Static, :index end end diff --git a/src/amber_cli/templates/app/config/settings.cr.ecr b/src/amber_cli/templates/app/config/settings.cr.ecr deleted file mode 100644 index dc3d57f..0000000 --- a/src/amber_cli/templates/app/config/settings.cr.ecr +++ /dev/null @@ -1,106 +0,0 @@ -# About settings.cr File -# -# With `Amber::Server.configure` block you can redefine the Server configuration -# settings and use ENVIRONMENT variables and/or values evaluated at runtime. -# -# > Important! Yaml configurations are first class citizen and are loaded first before -# this file, we recommend to use yaml configurations before changing any settings here. -# Any uncommented setting here will override the YAML with the value set here. - -Amber::Server.configure do |settings| - # Use your environment variables settings here. - # - # Name: A name that identifies this application. This is not internally - # used by the framework. - # - # settings.name = "<%= display_name %> web application." - # - # - # Colorize Logging: specifies whether or not to use ANSI color codes - # when logging information, display the time and/or to display the severity level. - # Defaults to true. - # - # settings.logging.severity = "info" - # settings.logging.colorize = true - # settings.logging.color = "white" - # settings.logging.filter = %w(password confirm_password) - # settings.logging.skip = %w() - # - # - # Secret Key Base: is used for specifying a key which allows sessions - # for the application to be verified against a known secure key to - # prevent tampering. Applications get Amber.secret_key - # initialized to a random key present in `ENV["AMBER_SECRET_KEY"]` or - # `.amber_secret_key` in this order. - # - # settings.secret_key_base= <%= Random::Secure.urlsafe_base64(32) %> - # - # - # Host: is the application server host address or ip address. Useful for when - # deploying Amber to a PAAS and likely the assigned server IP is either - # known or unknown. Defaults to an environment variable HOST - # - # settings.host = ENV["HOST"] if ENV["HOST"]? - # - # - # Port Reuse: Amber supports clustering mode which allows to spin - # multiple app instances per core. This setting allows to bind the different - # instances to the same port. Default this setting to true if the number or process - # is greater than 1. - # - # > Read more about Linux PORT REUSE https://lwn.net/Articles/542629/ - # - # settings.port_reuse = true - # - # - # Process Count: This will enable Amber to be used in cluster mode, - # spinning an instance for each number of process specified here. - # Rule of thumb, always leave at least 1 core available for system processes/resources. - # - # settings.process_count = ENV["PROCESS_COUNT"].to_i if ENV["PROCESS_COUNT"]? - # - # - # PORT: This is the port that you're application will run on. Examples would be (80, 443, 3000, 8080) - # - settings.port = ENV["PORT"].to_i if ENV["PORT"]? - # - # - # Redis URL: Redis is an in memory key value storage. Amber utilizes redis as - # a storing option for session information. - # - # settings.redis_url = ENV["REDIS_URL"] if ENV["REDIS_URL"]? - # - # - # Database URL: This is the database connection string or data file url. - # The connection string contains the information to establish a connection to the - # database or the data file. Defaults to the database provider you chose at - # at app generation. - # - # settings.database_url = ENV["DATABASE_URL"] if ENV["DATABASE_URL"]? - # - # - # SSL Key File: The private key is a text file used initially to generate a - # Certificate Signing Request (CSR), and later to secure and verify connections - # using the certificate created per that request. The private key is used to create - # a digital signature as you might imagine from the name, the private key should be - # ``closely guarded. - # - # settings.ssl_key_file = ENV["SSL_KEY_FILE"] if ENV["SSL_KEY_FILE"]? - # - # - # SSL Cert File: This represents the signed certificate file. SSL Certificates are - # small data files that digitally bind a cryptographic key to an organization's - # details. When installed on a web server, it activates the padlock and the https - # protocol and allows secure connections from a web server to a browser. - # - # settings.ssl_cert_file = ENV["SSL_CERT_FILE"] if ENV["SSL_CERT_FILE"]? - # - # - # Session: A Hash that specifies the session storage mechanism, expiration and key to be used - # for the application. The `key` specifies the name of the cookie to be used defaults to - # "amber.session". The store can be `encrypted_cookie`, `signed_cookie` or `redis`. Expires - # when set to 0 means this is indefinitely and is expressed in seconds. - # - # settings.session = { "key" => "amber.session", "store" => "signed_cookie", "expires" => 0 } - # -end diff --git a/src/amber_cli/templates/app/docker-compose.yml.ecr b/src/amber_cli/templates/app/docker-compose.yml.ecr deleted file mode 100644 index 3d28f41..0000000 --- a/src/amber_cli/templates/app/docker-compose.yml.ecr +++ /dev/null @@ -1,73 +0,0 @@ -<% if @database == "pg" - @db_url = "postgres://postgres:password@db:5432/#{database_name}_development" - @wait_for = "while ! nc -q 1 db 5432 -version: '2' - -services: - app: - build: . - image: <%= @name %> - command: amber watch - environment: - DATABASE_URL: <%= @db_url %> - ports: - - 3000:3000 - links: - - db - volumes: - - .:/app - - shards:/app/lib - - migrate: - build: . - image: <%= @name %> - command: bash -c '<%= @wait_for %>amber db migrate seed' - environment: - DATABASE_URL: <%= @db_url %> - volumes: - - .:/app - links: - - db - -<% if @database == "pg" -%> - db: - image: postgres - ports: - - 5432:5432 - environment: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: password - POSTGRES_DB: <%= "#{database_name}_development" %> - volumes: - - db:/var/lib/postgres/data -<% elsif @database == "mysql" -%> - db: - image: mysql - ports: - - 3306:3306 - environment: - MYSQL_USER: admin - MYSQL_PASSWORD: password - MYSQL_DATABASE: <%= "#{database_name}_development" %> - MYSQL_RANDOM_ROOT_PASSWORD: 'yes' - volumes: - - db:/var/lib/mysql -<% elsif @database == "sqlite" -%> - db: - image: <%= @name %> - command: 'bash -c "ls > /dev/null"' - volumes: - - db:/app/local/db -<% end -%> - -volumes: - db: - shards: diff --git a/src/amber_cli/templates/app/shard.yml.ecr b/src/amber_cli/templates/app/shard.yml.ecr index a91f1cb..a8ce201 100644 --- a/src/amber_cli/templates/app/shard.yml.ecr +++ b/src/amber_cli/templates/app/shard.yml.ecr @@ -4,7 +4,7 @@ version: 0.1.0 authors: - {{author_name | "Your Name "}} -crystal: ">= 1.10.0" +crystal: ">= 1.20.0, < 2.0" license: UNLICENSED @@ -13,33 +13,6 @@ targets: main: src/{{snake_case}}.cr dependencies: - # Amber Framework V2 amber: - github: crimson-knight/amber - branch: master - - # Grant ORM (ActiveRecord-style, replaces Granite in V2) - grant: - github: crimson-knight/grant - branch: main - - # Asset Pipeline (native ESM, no Webpack/npm required) - asset_pipeline: - github: amberframework/asset_pipeline - - # File uploads (optional) - gemma: - github: crimson-knight/gemma - - # Database adapters (all required by Grant at compile time) - pg: - github: will/crystal-pg - mysql: - github: crystal-lang/crystal-mysql - sqlite3: - github: crystal-lang/crystal-sqlite3 - -development_dependencies: - ameba: - github: crystal-ameba/ameba - version: ~> 1.4.3 + github: amberframework/amber + version: 2.0.0-beta.2 diff --git a/src/amber_cli/templates/app/spec/request_helper.cr.ecr b/src/amber_cli/templates/app/spec/request_helper.cr.ecr deleted file mode 100644 index ca67a4b..0000000 --- a/src/amber_cli/templates/app/spec/request_helper.cr.ecr +++ /dev/null @@ -1,30 +0,0 @@ -require "http" - -module RequestHelper - macro included - {% http_read_verbs = %w(get head options trace connect) %} - {% http_write_verbs = %w(post put patch delete) %} - {% http_verbs = http_read_verbs + http_write_verbs %} - - {% for method in http_verbs %} - def {{method.id}}(path, headers : HTTP::Headers? = nil, body : String? = nil) - request = HTTP::Request.new("{{method.id}}".upcase, path, headers, body ) - {% if http_write_verbs.includes? method %} - request.headers["Content-Type"] ||= "application/x-www-form-urlencoded" - {% end %} - process_request(request) - end - {% end %} - end - - private def process_request(request) - io = IO::Memory.new - response = HTTP::Server::Response.new(io) - context = HTTP::Server::Context.new(request, response) - handler.call context - response.close - io.rewind - client_response = HTTP::Client::Response.from_io(io, decompress: false) - client_response - end -end diff --git a/src/amber_cli/templates/app/spec/spec_helper.cr.ecr b/src/amber_cli/templates/app/spec/spec_helper.cr.ecr index 2c6ce5d..d875808 100644 --- a/src/amber_cli/templates/app/spec/spec_helper.cr.ecr +++ b/src/amber_cli/templates/app/spec/spec_helper.cr.ecr @@ -1,10 +1,8 @@ ENV["AMBER_ENV"] ||= "test" require "spec" -require "micrate" - require "../config/application" +require "amber/testing/testing" -Micrate::DB.connection_url = ENV["DATABASE_URL"]? || Amber.settings.database_url -# Automatically run migrations on the test database -Micrate::Cli.run_up +include Amber::Testing::RequestHelpers +include Amber::Testing::Assertions diff --git a/src/amber_cli/templates/app/src/controllers/application_controller.cr.ecr b/src/amber_cli/templates/app/src/controllers/application_controller.cr.ecr index a9b2a7c..71bbade 100644 --- a/src/amber_cli/templates/app/src/controllers/application_controller.cr.ecr +++ b/src/amber_cli/templates/app/src/controllers/application_controller.cr.ecr @@ -1,6 +1,3 @@ -require "jasper_helpers" - class ApplicationController < Amber::Controller::Base - include JasperHelpers - LAYOUT = "application.<%= @language %>" + LAYOUT = "application.ecr" end diff --git a/src/amber_cli/templates/app/src/controllers/home_controller.cr.ecr b/src/amber_cli/templates/app/src/controllers/home_controller.cr.ecr index 7a66cb2..581e54c 100644 --- a/src/amber_cli/templates/app/src/controllers/home_controller.cr.ecr +++ b/src/amber_cli/templates/app/src/controllers/home_controller.cr.ecr @@ -1,5 +1,5 @@ class HomeController < ApplicationController def index - render("index.<%= @language %>") + render("index.ecr") end end diff --git a/src/amber_cli/templates/app/src/mailers/application_mailer.cr.ecr b/src/amber_cli/templates/app/src/mailers/application_mailer.cr.ecr deleted file mode 100644 index 52e2e22..0000000 --- a/src/amber_cli/templates/app/src/mailers/application_mailer.cr.ecr +++ /dev/null @@ -1,7 +0,0 @@ -require "quartz_mailer" - -class ApplicationMailer < Quartz::Composer - def sender - address name: "Email Sender", email: "from@example.com" - end -end diff --git a/src/amber_cli/templates/app/src/views/home/index.ecr.ecr b/src/amber_cli/templates/app/src/views/home/index.ecr.ecr index 200cb15..d24e2bb 100644 --- a/src/amber_cli/templates/app/src/views/home/index.ecr.ecr +++ b/src/amber_cli/templates/app/src/views/home/index.ecr.ecr @@ -1,11 +1,9 @@ -
-
-

<%= "<" %>%= t "welcome_to_amber" %>

-

Thank you for trying out the Amber Framework. We are working hard to provide a super fast and reliable framework that provides all the productivity tools you are used to without sacrificing the speed.

- -
+
+

Welcome to <%= "<" %>%= Amber.settings.name %>!

+

Your Amber V2 beta web application is running.

+
    +
  • Edit src/views/home/index.ecr
  • +
  • Add routes in config/routes.cr
  • +
  • Run crystal spec before committing
  • +
diff --git a/src/amber_cli/templates/app/src/views/home/index.slang.ecr b/src/amber_cli/templates/app/src/views/home/index.slang.ecr deleted file mode 100644 index 7379816..0000000 --- a/src/amber_cli/templates/app/src/views/home/index.slang.ecr +++ /dev/null @@ -1,8 +0,0 @@ -.row.justify-content-center - .col-sm-12.col-md-6 - h2 = t "welcome_to_amber" - p Thank you for trying out the Amber Framework. We are working hard to provide a super fast and reliable framework that provides all the productivity tools you are used to without sacrificing the speed. - .list-group - a.list-group-item.list-group-item-action target="_blank" href="https://docs.amberframework.org" Getting Started with Amber Framework - a.list-group-item.list-group-item-action target="_blank" href="https://github.com/veelenga/awesome-crystal" List of Awesome Crystal projects and shards - a.list-group-item.list-group-item-action target="_blank" href="https://discord.gg/vwvP5zakSn" Join the Amber Discord! diff --git a/src/amber_cli/templates/app/src/views/layouts/_nav.ecr.ecr b/src/amber_cli/templates/app/src/views/layouts/_nav.ecr.ecr deleted file mode 100644 index d0891bd..0000000 --- a/src/amber_cli/templates/app/src/views/layouts/_nav.ecr.ecr +++ /dev/null @@ -1,4 +0,0 @@ -<%="<"%>%- active = context.request.path == "/" ? "active" : "" %> - diff --git a/src/amber_cli/templates/app/src/views/layouts/_nav.slang.ecr b/src/amber_cli/templates/app/src/views/layouts/_nav.slang.ecr deleted file mode 100644 index ffc86ca..0000000 --- a/src/amber_cli/templates/app/src/views/layouts/_nav.slang.ecr +++ /dev/null @@ -1,3 +0,0 @@ -- active = context.request.path == "/" ? "active" : "" -li.nav-item class=active - a.nav-link href="/" Home diff --git a/src/amber_cli/templates/app/src/views/layouts/_session.ecr.ecr b/src/amber_cli/templates/app/src/views/layouts/_session.ecr.ecr deleted file mode 100644 index e69de29..0000000 diff --git a/src/amber_cli/templates/app/src/views/layouts/_session.slang.ecr b/src/amber_cli/templates/app/src/views/layouts/_session.slang.ecr deleted file mode 100644 index e69de29..0000000 diff --git a/src/amber_cli/templates/app/src/views/layouts/application.ecr.ecr b/src/amber_cli/templates/app/src/views/layouts/application.ecr.ecr index fe092e6..2535dc3 100644 --- a/src/amber_cli/templates/app/src/views/layouts/application.ecr.ecr +++ b/src/amber_cli/templates/app/src/views/layouts/application.ecr.ecr @@ -1,46 +1,15 @@ - + - <%= display_name %> using Amber - - - - - - - - + + + <%= display_name %> + - - -
- <%="<"%>%- flash.each do |key, value| %> -
%= key %>" role="alert"> - <%="<"%>%= flash[key] %> -
- <%="<"%>%- end %> - -
- <%="<"%>%= content %> -
-
- - - +
+ <%= "<" %>%= content %> +
- <%="<"%>%- if Amber.settings.auto_reload? -%><%="<"%>%- end -%> diff --git a/src/amber_cli/templates/app/src/views/layouts/application.slang.ecr b/src/amber_cli/templates/app/src/views/layouts/application.slang.ecr deleted file mode 100644 index 4e2c211..0000000 --- a/src/amber_cli/templates/app/src/views/layouts/application.slang.ecr +++ /dev/null @@ -1,35 +0,0 @@ -doctype html -html - head - title <%= display_name %> using Amber - meta charset="utf-8" - meta http-equiv="X-UA-Compatible" content="IE=edge" - meta name="viewport" content="width=device-width, initial-scale=1" - link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" - link rel="stylesheet" href="/css/main.css" - link rel="apple-touch-icon" href="/favicon.png" - link rel="icon" href="/favicon.png" - link rel="icon" type="image/x-icon" href="/favicon.ico" - - body - nav.navbar.navbar-expand.navbar-dark.bg-primary.mb-3 - .container - a.navbar-brand href="/" - img src="/img/logo.svg" height="30" alt="Amber logo" - ul.navbar-nav.mr-auto - == render(partial: "layouts/_nav.slang") - ul.navbar-nav - == render(partial: "layouts/_session.slang") - - .container - - flash.each do |key, value| - div class="alert alert-#{key}" role="alert" - = flash[key] - - .main== content - - script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" - - script type="module" src="/js/amber.js" - - if Amber.settings.auto_reload? - script src="/js/client_reload.js" diff --git a/src/amber_cli/templates/app/src/views/layouts/mailer.slang b/src/amber_cli/templates/app/src/views/layouts/mailer.slang deleted file mode 100644 index c447d0a..0000000 --- a/src/amber_cli/templates/app/src/views/layouts/mailer.slang +++ /dev/null @@ -1,6 +0,0 @@ -html - head - body - table - tr - td == content diff --git a/src/amber_cli/templates/app/src/{{name}}.cr.ecr b/src/amber_cli/templates/app/src/{{name}}.cr.ecr index bd86d09..9701068 100644 --- a/src/amber_cli/templates/app/src/{{name}}.cr.ecr +++ b/src/amber_cli/templates/app/src/{{name}}.cr.ecr @@ -1,4 +1,3 @@ require "../config/application" -Amber::Support::ClientReload.new if Amber.settings.auto_reload? Amber::Server.start diff --git a/src/amber_cli/vendor/inflector/ai_transformer.cr b/src/amber_cli/vendor/inflector/ai_transformer.cr index a01d92f..c54555d 100644 --- a/src/amber_cli/vendor/inflector/ai_transformer.cr +++ b/src/amber_cli/vendor/inflector/ai_transformer.cr @@ -97,42 +97,42 @@ module AmberCLI::Vendor::Inflector::AITransformer <<-PROMPT Transform this English word to its plural form. Return only the plural form, nothing else. - + Word: #{word} - + Examples: - mouse → mice - child → children - person → people - foot → feet - datum → data - + Plural: PROMPT when "singular" <<-PROMPT Transform this English word to its singular form. Return only the singular form, nothing else. - + Word: #{word} - + Examples: - mice → mouse - children → child - people → person - feet → foot - data → datum - + Singular: PROMPT else <<-PROMPT Transform this English word using the transformation: #{transformation} Return only the transformed word, nothing else. - + Word: #{word} Transformation: #{transformation} - + Result: PROMPT end