From f13477ccd938f3cb7f9471e8909680ebbe130758 Mon Sep 17 00:00:00 2001 From: crimson-knight Date: Tue, 11 Aug 2026 23:13:51 -0400 Subject: [PATCH 1/8] Add manifest-backed assets to generated web apps --- .github/ISSUE_TEMPLATE/release-checklist.md | 9 +- .github/workflows/release.yml | 8 - README.md | 53 +++- RELEASE_SETUP.md | 26 +- docs/BETA_WEB_APP.md | 75 ++++- docs/RELEASE_CHECKLIST.md | 22 +- scripts/check_beta_contract.sh | 17 +- scripts/read_asset_paths.cr | 15 + scripts/smoke_generated_web.ps1 | 114 +++++++ scripts/smoke_generated_web.sh | 60 +++- shard.lock | 2 +- shard.yml | 4 +- spec/amber_cli_spec.cr | 2 + spec/commands/assets_command_spec.cr | 25 ++ spec/commands/new_command_spec.cr | 51 +++- spec/commands/watch_command_spec.cr | 15 + src/amber_cli.cr | 3 + src/amber_cli/commands/assets.cr | 60 ++++ src/amber_cli/commands/new.cr | 99 ++++-- src/amber_cli/commands/watch.cr | 15 + src/amber_cli/helpers/process_runner.cr | 31 +- src/amber_cli/main_command.cr | 1 + src/amber_cli/static_assets.cr | 36 +++ src/amber_cli/templates/app/.gitignore.ecr | 2 +- src/amber_cli/templates/app/README.md.ecr | 8 +- .../templates/app/app/assets/files/.gitkeep | 1 + .../templates/app/app/assets/fonts/.gitkeep | 1 + .../app/app/assets/images/amber-crystal.svg | 7 + .../app/app/assets/images/favicon.svg | 4 + .../js => app/assets/javascript}/app.js | 2 +- .../css => app/assets/stylesheets}/app.css | 20 +- .../templates/app/config/application.cr.ecr | 1 + .../templates/app/config/assets.cr.ecr | 1 + .../templates/app/public/crossdomain.xml | 15 - .../templates/app/public/css/main.css | 7 - .../templates/app/public/fonts/.gitkeep | 0 .../templates/app/public/img/.gitkeep | 0 .../templates/app/public/img/logo.svg | 1 - .../templates/app/public/js/amber.js | 289 ------------------ .../templates/app/public/js/client_reload.js | 52 ---- src/amber_cli/templates/app/shard.yml.ecr | 3 + .../app/src/views/home/index.ecr.ecr | 2 +- .../app/src/views/layouts/application.ecr.ecr | 7 +- 43 files changed, 697 insertions(+), 469 deletions(-) create mode 100644 scripts/read_asset_paths.cr create mode 100644 spec/commands/assets_command_spec.cr create mode 100644 spec/commands/watch_command_spec.cr create mode 100644 src/amber_cli/commands/assets.cr create mode 100644 src/amber_cli/static_assets.cr create mode 100644 src/amber_cli/templates/app/app/assets/files/.gitkeep create mode 100644 src/amber_cli/templates/app/app/assets/fonts/.gitkeep create mode 100644 src/amber_cli/templates/app/app/assets/images/amber-crystal.svg create mode 100644 src/amber_cli/templates/app/app/assets/images/favicon.svg rename src/amber_cli/templates/app/{public/js => app/assets/javascript}/app.js (51%) rename src/amber_cli/templates/app/{public/css => app/assets/stylesheets}/app.css (90%) create mode 100644 src/amber_cli/templates/app/config/assets.cr.ecr delete mode 100644 src/amber_cli/templates/app/public/crossdomain.xml delete mode 100644 src/amber_cli/templates/app/public/css/main.css delete mode 100644 src/amber_cli/templates/app/public/fonts/.gitkeep delete mode 100644 src/amber_cli/templates/app/public/img/.gitkeep delete mode 100644 src/amber_cli/templates/app/public/img/logo.svg delete mode 100644 src/amber_cli/templates/app/public/js/amber.js delete mode 100644 src/amber_cli/templates/app/public/js/client_reload.js diff --git a/.github/ISSUE_TEMPLATE/release-checklist.md b/.github/ISSUE_TEMPLATE/release-checklist.md index 2cc4ab1..94e21c4 100644 --- a/.github/ISSUE_TEMPLATE/release-checklist.md +++ b/.github/ISSUE_TEMPLATE/release-checklist.md @@ -24,6 +24,8 @@ assignees: [] - [ ] Run `gh workflow run release.yml --repo amberframework/amber_cli --ref -f ref=` - [ ] Confirm `Build darwin-arm64` passed - [ ] Confirm `Build linux-x86_64` passed +- [ ] Confirm `Build linux-arm64` passed +- [ ] Record Windows x86-64 generated-app compile status (compatibility signal; not a beta gate) ## Publish @@ -46,8 +48,11 @@ assignees: [] - [ ] macOS binaries do not link to `openssl@1.1` - [ ] `amber new smoke_app --type web -y --no-deps` - [ ] Generated app pins the reviewed Amber version and ECR template -- [ ] `shards install`, app specs, and app build pass -- [ ] Built app starts and serves `/` plus `/css/app.css` +- [ ] Generated app pins the reviewed Asset Pipeline revision; no placeholder pins remain +- [ ] `shards install`, `amber assets build`, `amber assets check`, app specs, and app build pass +- [ ] Built app serves `/` plus its manifest-rendered CSS, JavaScript, SVG, and favicon URLs +- [ ] Fingerprinted responses prove SRI, MIME, immutable caching, `nosniff`, and gzip negotiation +- [ ] CSS contains the rewritten fingerprinted image URL; no raw `public/css` or `public/js` entry remains ## Post Release diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ed65547..497f6f0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -179,11 +179,3 @@ jobs: repository: amberframework/homebrew-amber_cli event-type: release-published client-payload: '{"version": "${{ github.event.release.tag_name }}"}' - - - name: Dispatch to hyphen tap (homebrew-amber-cli) - uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v4 - with: - token: ${{ secrets.HOMEBREW_TAP_TOKEN }} - repository: amberframework/homebrew-amber-cli - event-type: release-published - client-payload: '{"version": "${{ github.event.release.tag_name }}"}' diff --git a/README.md b/README.md index 7e40a55..c961e57 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,10 @@ creates the supported Amber `2.0.0-beta.3` ECR web application and includes development, generator, database, and LSP tooling. Amber V2 is a beta. The release-gated path is a web application on Apple -Silicon macOS or x86_64 Linux. Linux ARM64 generated-app compilation is checked -in CI; its first direct archive will ship with the next CLI release. See [Generator support](docs/GENERATOR_SUPPORT.md) -before relying on authentication, API-resource, or native output. +Silicon macOS, x86_64 Linux, or ARM64 Linux. Windows x86-64 generated-app +compilation is checked in CI for compatibility, but Windows does not block this +beta release. See [Generator support](docs/GENERATOR_SUPPORT.md) before relying +on authentication, API-resource, or native output. ## Install @@ -56,21 +57,24 @@ command with `sudo` if `/usr/local/bin` is not writable. ```bash amber new my_app --type web cd my_app +amber assets check crystal spec crystal build src/my_app.cr -o bin/my_app amber watch ``` -`amber new` installs shards by default; pass `--no-deps` when an offline or CI -workflow needs to run `shards install` later. Open and -. +`amber new` installs shards by default and compiles the starter assets. Pass +`--no-deps` when an offline or CI workflow needs to run `shards install` later. +`amber watch` recompiles assets before the application whenever an ECR template +or a file under `app/assets/` changes. Open . The web template is deliberately small: - Amber from `amberframework/amber`, pinned to `2.0.0-beta.3` - ECR views (Slang and Kilt are not supported in Amber V2) - typed development, test, and production YAML -- branded homepage, controller spec, and static CSS/JavaScript +- branded homepage, controller spec, and fingerprinted CSS, JavaScript, SVG, + font, image, and general static-file support - a browser-native import map with a local JavaScript module entry point - Grant ORM, Micrate-powered migration commands, and the selected database driver - SQLite by default, so the first persisted feature needs no database server @@ -79,6 +83,39 @@ The `-d pg|mysql|sqlite` option selects the generated driver, connection, and development/test URLs. SQLite is the default; PostgreSQL and MySQL expect their respective local servers or a `DATABASE_URL`. +### Static assets: source versus generated output + +Write application-owned files in these directories: + +```text +app/assets/ +├── stylesheets/ # CSS; starter entry: app.css +├── javascript/ # browser modules; starter entry: app.js +├── images/ # SVG, PNG, JPEG, WebP, AVIF, and icons +├── fonts/ # WOFF, WOFF2, TTF, and OTF +└── files/ # PDFs, web manifests, and other downloads +``` + +Run the compiler after an authored asset changes outside watch mode: + +```bash +amber assets build +amber assets check +``` + +The build fingerprints every file into `public/assets/`, rewrites local CSS and +JavaScript references, writes SRI and response metadata to +`public/assets/manifest.json`, and creates deterministic gzip siblings for +compressible files. `public/assets/` is generated and gitignored; do not edit or +commit it. Keep stable root files such as `public/robots.txt` in `public/`. + +In `src/views/layouts/application.ecr`, resolve authored logical names through +`stylesheet_link_tag`, `javascript_importmap_tag`, `image_tag`, and +`favicon_tag`. In CSS, references are relative to that CSS source file; for +example, `app/assets/stylesheets/app.css` uses +`url("../images/amber-crystal.svg")`. The compiler replaces that reference with +the image's fingerprinted URL. + Create the first complete resource and its database table: ```bash @@ -102,6 +139,8 @@ SQL migration to `db/migrations/`, and the resource route to `config/routes.cr`. | `amber pipelines` | Supported | Inspect configured pipelines | | `amber generate` | Mixed | Model, scaffold, migration, and core generators supported; auth and API preview | | `amber database` | Supported | Apply, roll back, inspect, redo, and seed the generated database | +| `amber assets build` | Supported | Fingerprint `app/assets/` into generated `public/assets/` output | +| `amber assets check` | Supported | Verify manifest, bytes, integrity, MIME, and compressed output without changing it | | `amber new APP --type native` | Preview | Not part of the beta platform guarantee | | `amber setup:lsp` | Available | Configure the bundled diagnostics LSP | diff --git a/RELEASE_SETUP.md b/RELEASE_SETUP.md index 9c61823..5c0d5f0 100644 --- a/RELEASE_SETUP.md +++ b/RELEASE_SETUP.md @@ -13,13 +13,14 @@ A successful release means all of the following happen without manual file editi 5. The tap explicitly dispatches its install validation after the bot pushes the formula. This is required because pushes made by `GITHUB_TOKEN` do not start push-triggered workflows. -6. On Apple Silicon macOS and x86_64 Linux, that validation proves a clean +6. On Apple Silicon macOS, x86_64 Linux, and ARM64 Linux, validation proves a clean machine can: - `brew install amberframework/amber_cli/amber_cli` - `brew test amber_cli` - create the ECR web template with `amber new smoke_app --type web` - - install shards, run specs, and build the generated app - - launch the built app and request `/` plus `/css/app.css` + - install shards, build and verify assets, run specs, and build the generated app + - launch the built app and request `/` plus its manifest-rendered + fingerprinted CSS, JavaScript, image, and favicon URLs The fully qualified Homebrew command trusts only the `amber_cli` formula under Homebrew's third-party tap trust model. Do not replace it with a separate @@ -73,7 +74,9 @@ From the CLI repo: That should produce: -- `dist/amber_cli-darwin-arm64.tar.gz` or `dist/amber_cli-linux-x86_64.tar.gz` +- `dist/amber_cli-darwin-arm64.tar.gz`, + `dist/amber_cli-linux-x86_64.tar.gz`, or + `dist/amber_cli-linux-arm64.tar.gz` - matching `.sha256` output ### 3. Dry-run the GitHub build matrix @@ -114,6 +117,7 @@ In `amberframework/amber_cli`, the release workflow must be green for: - `Build darwin-arm64` - `Build linux-x86_64` +- `Build linux-arm64` - `Upload Release Assets` - `Notify Homebrew Tap` @@ -138,12 +142,16 @@ brew test amber_cli amber new smoke_app --type web -y --no-deps cd smoke_app shards install +amber assets build +amber assets check crystal spec crystal build src/smoke_app.cr -o bin/smoke_app ``` -It then starts the built application and probes the homepage and generated CSS. -The macOS job also rejects binaries linked to `openssl@1.1`. +It then starts the built application and probes the homepage plus the +manifest-rendered CSS, JavaScript, image, and favicon URLs. It verifies asset +MIME types, immutable cache headers, SRI, and gzip negotiation. The macOS job +also rejects binaries linked to `openssl@1.1`. ## Manual Recovery @@ -164,8 +172,8 @@ If the release build fails before the tap update: ## Current Packaging Direction The Homebrew tap and matching release archives are the supported install paths -for Apple Silicon macOS and x86_64 Linux today. Other operating systems and -architectures remain preview or contributor build-from-source targets until -they have published artifacts and the same end-to-end release gates. +for Apple Silicon macOS, x86_64 Linux, and ARM64 Linux today. Windows x86-64 is +compiled in CI as a compatibility check but does not gate this beta and does not +yet have a release archive. For eventual `homebrew/core` inclusion, we should plan for a source-building formula and a clean `brew audit --new --formula amber_cli` story. The current tap keeps release onboarding fast, while the source-build path is the more likely route for upstream Homebrew acceptance. diff --git a/docs/BETA_WEB_APP.md b/docs/BETA_WEB_APP.md index 61efa02..9ef4abd 100644 --- a/docs/BETA_WEB_APP.md +++ b/docs/BETA_WEB_APP.md @@ -32,23 +32,51 @@ grep -E '^(template|database|model):' .amber.yml The dependency must be `amberframework/amber` version `2.0.0-beta.3`; the template must be `ecr`, the database must be `sqlite`, and the model layer must -be `grant`. `shard.yml` must include Grant and `crystal-sqlite3`, but it must not -include Gemma, Slang, PostgreSQL, or MySQL unless you selected that database. +be `grant`. `shard.yml` must include Grant, Asset Pipeline, and +`crystal-sqlite3`, but it must not include Gemma, Slang, PostgreSQL, or MySQL +unless you selected that database. The first page carries a compact version of Amber's V2 visual language: warm -paper colors, a CSS faceted-crystal mark, editorial heading scale, status chips, -and a responsive first-edits panel. It should begin with “Your new idea starts -here” and list the page, route, and controller entry points. Its local +paper colors, a fingerprinted SVG crystal, editorial heading scale, status +chips, and a responsive first-edits panel. It should begin with “Your new idea +starts here” and list the page, route, and controller entry points. Its local JavaScript entry point is resolved through a browser-native import map. The -starter uses no remote font, image, JavaScript package, or front-end build step. +starter uses no remote font, image, JavaScript package, Node.js, or external +front-end bundler. + +The source and output locations are intentionally different: + +```text +app/assets/ +├── stylesheets/app.css +├── javascript/app.js +├── images/amber-crystal.svg +├── images/favicon.svg +├── fonts/ +└── files/ + +public/ +├── assets/ # generated fingerprints, gzip siblings, and manifest.json +└── robots.txt # stable root URL; authored directly +``` + +Never edit `public/assets/`; it is gitignored and regenerated. Put WOFF2 fonts +in `app/assets/fonts/`, images in `app/assets/images/`, and PDFs or other +downloads in `app/assets/files/`. ## 3. Test and build ```bash +amber assets build +amber assets check crystal spec crystal build src/amber_beta_smoke.cr -o bin/amber_beta_smoke ``` +`amber new` already performs the first asset build. Running both commands here +proves a clean rebuild and verifies every emitted byte, SHA-256/SRI value, MIME +type, and expected gzip file against `public/assets/manifest.json`. + No database server is required: the default development and test databases are local SQLite files under `db/`. @@ -62,18 +90,43 @@ 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. Confirm the deployed scaffold, rather -than an older cached template, with: +The request validates routing, the controller, ECR rendering, and the manifest +helpers. Confirm the deployed scaffold and find the emitted stylesheet URL with: ```bash curl --fail http://127.0.0.1:3000/ | grep 'Your new idea' -curl --fail http://127.0.0.1:3000/css/app.css | grep -- '--amber-accent: #e96918' +curl --fail http://127.0.0.1:3000/ | grep -o '/assets/stylesheets/app-[a-f0-9]*\.css' +``` + +That URL is content-addressed and changes when the compiled CSS changes. The +release smoke follows the URL rendered by the page, then verifies the CSS, +JavaScript, and SVG responses, immutable caching, portable MIME types, SRI, and +gzip negotiation. + +To add a font, place it at a concrete source path such as +`app/assets/fonts/brand.woff2`, then reference it from +`app/assets/stylesheets/app.css`: + +```css +@font-face { + font-family: "Brand"; + src: url("../fonts/brand.woff2") format("woff2"); + font-display: swap; +} ``` +To add an image, place it under `app/assets/images/` and use a relative URL in +CSS or a logical path in ECR: + +```ecr +<%= image_tag("images/product-mark.svg", alt: "Product mark") %> +``` + +`config/assets.cr` configures the strict runtime manifest lookup. Missing +logical assets fail clearly instead of silently falling back to a stale raw URL. + ## 5. Generate and migrate a real resource ```bash diff --git a/docs/RELEASE_CHECKLIST.md b/docs/RELEASE_CHECKLIST.md index e89fcbc..24cf32f 100644 --- a/docs/RELEASE_CHECKLIST.md +++ b/docs/RELEASE_CHECKLIST.md @@ -15,10 +15,19 @@ Homebrew formula pins CLI archives and checksums. 1. Confirm `shard.yml` and `AmberCLI::VERSION` both say `2.0.4`. 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.4`, publish the release, and wait for all archives and checksum +3. Verify the generated app pins the reviewed Asset Pipeline revision and has + no placeholder dependency revisions. +4. On macOS, x86-64 Linux, and ARM64 Linux, install shards; run + `amber assets build` and `amber assets check`; run app specs; build and start + the app; then follow the manifest-rendered CSS, JavaScript, SVG, and favicon + URLs from `/`. +5. For fingerprinted responses, verify MIME, SRI in rendered tags, immutable + caching, `nosniff`, and gzip negotiation. Confirm CSS rewrites its authored + image URL and that `public/assets/` contains no source-owned raw entry point. +6. On macOS, reject any binary linked to `openssl@1.1`. +7. Confirm the Windows x86-64 generated app builds in CI. This is a compatibility + check, not a beta release gate. +8. Tag `v2.0.4`, publish the release, and wait for all archives and checksum files to upload. ## 3. Homebrew @@ -38,5 +47,6 @@ Homebrew formula pins CLI archives and checksums. 3. Verify all release/download links and commands from a clean shell. Do not mark the beta complete if macOS, x86-64 Linux, or ARM64 Linux cannot -install the CLI and build the generated web application, or if the Windows -generated-app compile gate fails. +install the CLI and complete the generated web-app and asset smoke. Record a +Windows CI failure as a known compatibility issue; Windows does not gate this +beta. diff --git a/scripts/check_beta_contract.sh b/scripts/check_beta_contract.sh index 31a2720..9e0a519 100755 --- a/scripts/check_beta_contract.sh +++ b/scripts/check_beta_contract.sh @@ -12,13 +12,26 @@ grep -F 'template: ecr' src/amber_cli/commands/new.cr grep -F 'model: grant' src/amber_cli/commands/new.cr grep -F 'database: #{database}' src/amber_cli/commands/new.cr grep -F 'github: crimson-knight/grant' src/amber_cli/commands/new.cr +grep -F 'github: amberframework/asset_pipeline' src/amber_cli/commands/new.cr +grep -F 'github: amberframework/asset_pipeline' src/amber_cli/templates/app/shard.yml.ecr grep -F 'github: amberframework/micrate' shard.yml test -s src/amber_cli/templates/app/config/database.cr.ecr +test -s src/amber_cli/templates/app/config/assets.cr.ecr grep -F 'Your new idea' src/amber_cli/commands/new.cr grep -F -- '--amber-accent: #e96918' src/amber_cli/commands/new.cr grep -F 'Your new idea' src/amber_cli/templates/app/src/views/home/index.ecr.ecr -test -s src/amber_cli/templates/app/public/css/app.css -test -s src/amber_cli/templates/app/public/js/app.js +test -s src/amber_cli/templates/app/app/assets/stylesheets/app.css +test -s src/amber_cli/templates/app/app/assets/javascript/app.js +test -s src/amber_cli/templates/app/app/assets/images/amber-crystal.svg +test -s src/amber_cli/templates/app/app/assets/images/favicon.svg +test ! -e src/amber_cli/templates/app/public/css/app.css +test ! -e src/amber_cli/templates/app/public/js/app.js +grep -F 'url("../images/amber-crystal.svg")' src/amber_cli/templates/app/app/assets/stylesheets/app.css +grep -F 'stylesheet_link_tag("stylesheets/app.css")' src/amber_cli/templates/app/src/views/layouts/application.ecr.ecr +grep -F 'javascript_importmap_tag({"app" => "javascript/app.js"}' src/amber_cli/templates/app/src/views/layouts/application.ecr.ecr +grep -F 'favicon_tag("images/favicon.svg")' src/amber_cli/templates/app/src/views/layouts/application.ecr.ecr +grep -F 'image_tag("images/amber-crystal.svg"' src/amber_cli/templates/app/src/views/home/index.ecr.ecr +grep -F '/public/assets/' src/amber_cli/templates/app/.gitignore.ecr grep -F 'brew install amberframework/amber_cli/amber_cli' README.md files=( diff --git a/scripts/read_asset_paths.cr b/scripts/read_asset_paths.cr new file mode 100644 index 0000000..7caf8ef --- /dev/null +++ b/scripts/read_asset_paths.cr @@ -0,0 +1,15 @@ +require "json" + +if ARGV.size < 2 + STDERR.puts "usage: read_asset_paths MANIFEST LOGICAL_PATH [LOGICAL_PATH ...]" + exit 64 +end + +manifest_path = ARGV.shift +manifest = JSON.parse(File.read(manifest_path)) +assets = manifest["assets"].as_h + +ARGV.each do |logical_path| + entry = assets[logical_path]? || raise "Asset #{logical_path.inspect} is missing from #{manifest_path}" + puts entry["path"].as_s +end diff --git a/scripts/smoke_generated_web.ps1 b/scripts/smoke_generated_web.ps1 index 15c9985..5ec1236 100644 --- a/scripts/smoke_generated_web.ps1 +++ b/scripts/smoke_generated_web.ps1 @@ -43,6 +43,9 @@ if (-not $manifest.Contains("github: crimson-knight/grant")) { if (-not $manifest.Contains("github: crystal-lang/crystal-sqlite3")) { throw "Generated shard.yml does not include SQLite" } +if (-not $manifest.Contains("github: amberframework/asset_pipeline")) { + throw "Generated shard.yml does not include Asset Pipeline" +} if (-not $amberConfig.Contains("database: sqlite") -or -not $amberConfig.Contains("model: grant")) { throw "Generated .amber.yml does not select SQLite and Grant" } @@ -64,7 +67,39 @@ if ($FrameworkCommit) { Push-Location $appPath try { + Invoke-Checked -Command $cliPath -Arguments @("assets", "check") Invoke-Checked -Command "shards" -Arguments @("install") + Invoke-Checked -Command $cliPath -Arguments @("assets", "build") + Invoke-Checked -Command $cliPath -Arguments @("assets", "check") + + $assetManifestPath = Join-Path $appPath "public/assets/manifest.json" + if (-not (Test-Path $assetManifestPath)) { + throw "Generated app did not produce an asset manifest" + } + $assetManifest = [System.IO.File]::ReadAllText($assetManifestPath) | ConvertFrom-Json + $css = $assetManifest.assets.'stylesheets/app.css' + $javascript = $assetManifest.assets.'javascript/app.js' + $logo = $assetManifest.assets.'images/amber-crystal.svg' + $favicon = $assetManifest.assets.'images/favicon.svg' + foreach ($entry in @($css, $javascript, $logo, $favicon)) { + if (-not $entry.path -or -not $entry.integrity.StartsWith("sha256-")) { + throw "Asset manifest entry is missing its path or integrity" + } + $relativeAssetPath = $entry.path.TrimStart([char[]]@('/')) + $compiledPath = Join-Path (Join-Path $appPath "public") $relativeAssetPath + if (-not (Test-Path $compiledPath)) { + throw "Compiled asset is missing: $compiledPath" + } + } + $relativeCssPath = $css.path.TrimStart([char[]]@('/')) + $compiledCssPath = Join-Path (Join-Path $appPath "public") $relativeCssPath + if (-not ([System.IO.File]::ReadAllText($compiledCssPath).Contains($logo.path))) { + throw "Compiled CSS did not rewrite the authored logo URL" + } + if (Test-Path (Join-Path $appPath "public/css/app.css")) { + throw "Generated app still contains a raw public CSS entry point" + } + Invoke-Checked -Command "crystal" -Arguments @("spec") Invoke-Checked -Command $cliPath -Arguments @( "generate", @@ -94,6 +129,8 @@ try { Invoke-Checked -Command $cliPath -Arguments @("database", "migrate") Invoke-Checked -Command "crystal" -Arguments @("spec") + Invoke-Checked -Command $cliPath -Arguments @("assets", "build") + Invoke-Checked -Command $cliPath -Arguments @("assets", "check") New-Item -ItemType Directory -Force bin | Out-Null Invoke-Checked -Command "crystal" -Arguments @( "build", @@ -101,6 +138,83 @@ try { "-o", "bin/amber_beta_smoke.exe" ) + + $server = $null + $previousServerPort = $env:AMBER_SERVER_PORT + try { + $env:AMBER_SERVER_PORT = "3210" + $serverPath = Join-Path $appPath "bin/amber_beta_smoke.exe" + $serverLog = Join-Path $appPath "server.log" + $serverErrorLog = Join-Path $appPath "server-error.log" + $server = Start-Process ` + -FilePath $serverPath ` + -PassThru ` + -RedirectStandardOutput $serverLog ` + -RedirectStandardError $serverErrorLog + + $homepage = $null + for ($attempt = 0; $attempt -lt 30; $attempt++) { + try { + $homepage = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:3210/" + break + } catch { + Start-Sleep -Seconds 1 + } + } + if (-not $homepage) { + $serverOutput = if (Test-Path $serverErrorLog) { [System.IO.File]::ReadAllText($serverErrorLog) } else { "" } + throw "Generated Windows app did not serve its homepage. $serverOutput" + } + foreach ($expected in @("Your new idea", $css.path, $javascript.path, $logo.path, $favicon.path, 'integrity="sha256-')) { + if (-not $homepage.Content.Contains($expected)) { + throw "Generated homepage did not contain expected asset/page content: $expected" + } + } + + $cssResponse = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:3210$($css.path)" + if (-not $cssResponse.Content.Contains("--amber-accent: #e96918") -or -not $cssResponse.Content.Contains($logo.path)) { + throw "Fingerprint CSS response did not contain the starter style and rewritten image URL" + } + if ($cssResponse.Headers["Content-Type"] -notmatch "text/css") { + throw "Fingerprint CSS response has the wrong MIME type" + } + if ($cssResponse.Headers["Cache-Control"] -notmatch "max-age=31536000" -or $cssResponse.Headers["Cache-Control"] -notmatch "immutable") { + throw "Fingerprint CSS response is missing immutable caching" + } + + $javascriptResponse = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:3210$($javascript.path)" + if ($javascriptResponse.Content -notmatch 'dataset\.javascript = "ready"' -or $javascriptResponse.Headers["Content-Type"] -notmatch "javascript") { + throw "Fingerprint JavaScript response has the wrong body or MIME type" + } + + $logoResponse = Invoke-WebRequest -UseBasicParsing -Uri "http://127.0.0.1:3210$($logo.path)" + if ($logoResponse.Content -notmatch "&2 @@ -49,10 +55,13 @@ if [[ -n "$framework_commit" ]]; then fi cd "$app_path" +"$cli_path" assets check env GIT_CONFIG_COUNT=1 \ GIT_CONFIG_KEY_0=core.hooksPath \ GIT_CONFIG_VALUE_0=/dev/null \ shards install +"$cli_path" assets build +"$cli_path" assets check crystal spec "$cli_path" generate scaffold Pet name:string:required species:string:required adopted:bool @@ -78,8 +87,26 @@ if find src spec -iname '*slang*' -print | grep -q .; then fi crystal tool format --check src spec crystal spec +"$cli_path" assets build +"$cli_path" assets check crystal build src/amber_beta_smoke.cr -o bin/amber_beta_smoke +asset_paths="$(crystal run "$script_dir/read_asset_paths.cr" -- \ + public/assets/manifest.json \ + stylesheets/app.css \ + javascript/app.js \ + images/amber-crystal.svg \ + images/favicon.svg)" +css_url="$(printf '%s\n' "$asset_paths" | sed -n '1p')" +js_url="$(printf '%s\n' "$asset_paths" | sed -n '2p')" +logo_url="$(printf '%s\n' "$asset_paths" | sed -n '3p')" +favicon_url="$(printf '%s\n' "$asset_paths" | sed -n '4p')" +test -n "$css_url" +test -n "$js_url" +test -n "$logo_url" +test -n "$favicon_url" +grep -F "$logo_url" "public/${css_url#/}" + AMBER_SERVER_PORT=3210 ./bin/amber_beta_smoke >server.log 2>&1 & server_pid="$!" @@ -93,9 +120,36 @@ done curl --fail --silent http://127.0.0.1:3210/ | grep -F "Your new idea" curl --fail --silent http://127.0.0.1:3210/ | grep -F "Ready to customize" curl --fail --silent http://127.0.0.1:3210/ | grep -F 'type="importmap"' -curl --fail --silent http://127.0.0.1:3210/ | grep -F '"app":"/js/app.js"' -curl --fail --silent http://127.0.0.1:3210/css/app.css | grep -F "Amber V2 starter styles" -curl --fail --silent http://127.0.0.1:3210/css/app.css | grep -F -- "--amber-accent: #e96918" +curl --fail --silent http://127.0.0.1:3210/ | grep -F "$css_url" +curl --fail --silent http://127.0.0.1:3210/ | grep -F "$js_url" +curl --fail --silent http://127.0.0.1:3210/ | grep -F "$logo_url" +curl --fail --silent http://127.0.0.1:3210/ | grep -F "$favicon_url" +curl --fail --silent http://127.0.0.1:3210/ | grep -F 'integrity="sha256-' + +css_headers="$smoke_root/css-headers.txt" +css_body="$smoke_root/css-body.css" +curl --fail --silent --dump-header "$css_headers" --output "$css_body" "http://127.0.0.1:3210$css_url" +grep -F "Amber V2 starter styles" "$css_body" +grep -F -- "--amber-accent: #e96918" "$css_body" +grep -i -F "Content-Type: text/css" "$css_headers" +grep -i -F "Cache-Control: public, max-age=31536000, immutable" "$css_headers" +grep -i -F "X-Content-Type-Options: nosniff" "$css_headers" + +gzip_headers="$smoke_root/css-gzip-headers.txt" +curl --fail --silent --header "Accept-Encoding: gzip" --dump-header "$gzip_headers" --output /dev/null "http://127.0.0.1:3210$css_url" +grep -i -F "Content-Encoding: gzip" "$gzip_headers" +grep -i -F "Content-Type: text/css" "$gzip_headers" +grep -i -F "Vary: Accept-Encoding" "$gzip_headers" + +js_headers="$smoke_root/js-headers.txt" +curl --fail --silent --dump-header "$js_headers" "http://127.0.0.1:3210$js_url" | grep -F 'dataset.javascript = "ready"' +grep -i -E "Content-Type: (text/javascript|application/javascript)" "$js_headers" +grep -i -F "Cache-Control: public, max-age=31536000, immutable" "$js_headers" + +image_headers="$smoke_root/image-headers.txt" +curl --fail --silent --dump-header "$image_headers" "http://127.0.0.1:3210$logo_url" | grep -F ")) + File.write("app/assets/stylesheets/app.css", %(.mark { background: url("../images/mark.svg"); })) + + AmberCLI::Commands::AssetsCommand.new("assets").parse_and_execute(["build"]) + manifest = AssetPipeline::StaticAssets::Manifest.load("public/assets/manifest.json") + manifest.assets.keys.sort.should eq(["images/mark.svg", "stylesheets/app.css"]) + + AmberCLI::Commands::AssetsCommand.new("assets").parse_and_execute(["check"]) + + css_path = Path["public"].join(manifest.path("stylesheets/app.css").lchop('/')) + File.write(css_path, "tampered") + expect_raises(AssetPipeline::StaticAssets::VerificationError) do + AmberCLI::StaticAssets.check + end + end + end +end diff --git a/spec/commands/new_command_spec.cr b/spec/commands/new_command_spec.cr index fdeb64b..3051418 100644 --- a/spec/commands/new_command_spec.cr +++ b/spec/commands/new_command_spec.cr @@ -89,6 +89,8 @@ describe AmberCLI::Commands::NewCommand do shard.should contain("version: 2.0.0-beta.3") shard.should contain("grant:") shard.should contain("github: crimson-knight/grant") + shard.should contain("asset_pipeline:") + shard.should contain("github: amberframework/asset_pipeline") shard.should contain("github: crystal-lang/crystal-sqlite3") shard.should_not contain("slang") @@ -101,6 +103,12 @@ describe AmberCLI::Commands::NewCommand do database_config.should contain(%(name: "primary")) database_config.should contain(%(ENV["DATABASE_URL"]? || Amber.settings.database_url)) + application_config = File.read(File.join(destination, "config/application.cr")) + application_config.should contain(%(require "./assets")) + assets_config = File.read(File.join(destination, "config/assets.cr")) + assets_config.should eq(%(Amber::Assets.configure(manifest_path: "public/assets/manifest.json")\n)) + assets_config.should_not contain("AssetPipeline::StaticAssets::Compiler") + 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:") @@ -117,26 +125,61 @@ describe AmberCLI::Commands::NewCommand do index.should contain("amber generate controller Posts") index.should_not contain("class=\"welcome\"") - stylesheet = File.read(File.join(destination, "public/css/app.css")) + stylesheet = File.read(File.join(destination, "app/assets/stylesheets/app.css")) stylesheet.should contain("--amber-accent: #e96918") stylesheet.should contain(".starter-main") stylesheet.should contain(".starter-crystal") + stylesheet.should contain(%(url("../images/amber-crystal.svg"))) layout = File.read(File.join(destination, "src/views/layouts/application.ecr")) - layout.should contain(%(type="importmap")) - layout.should contain(%("app":"/js/app.js")) + layout.should contain(%(favicon_tag("images/favicon.svg"))) + layout.should contain(%(stylesheet_link_tag("stylesheets/app.css"))) + layout.should contain("javascript_importmap_tag({\"app\" => \"javascript/app.js\"}") layout.should contain(%(type="module">import "app";)) layout.should_not contain(%( + <%= javascript_importmap_tag({"app" => "javascript/app.js"}, preload: ["javascript/app.js"]) %> @@ -543,7 +571,7 @@ LAYOUT
- + <%= image_tag("images/amber-crystal.svg", class: "starter-crystal", alt: "") %> <%= Amber.settings.name %> Amber V2 beta @@ -679,7 +707,7 @@ SEEDS --amber-gold: #f3a348; --amber-green: #377a55; --amber-shadow: 0 22px 70px rgba(74, 42, 23, 0.13); - font-family: Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; line-height: 1.5; color: var(--amber-ink); background: var(--amber-paper); @@ -737,8 +765,7 @@ a { width: 4.5rem; height: 6rem; opacity: 0.17; - background: linear-gradient(145deg, var(--amber-gold), var(--amber-accent)); - clip-path: polygon(50% 0, 92% 24%, 78% 80%, 50% 100%, 18% 80%, 5% 24%); + background: center / contain no-repeat url("../images/amber-crystal.svg"); transform: rotate(14deg); } @@ -770,24 +797,14 @@ a { } .starter-crystal { - position: relative; display: inline-block; width: 23px; height: 31px; flex: 0 0 auto; - background: linear-gradient(145deg, #ffc36b 0 28%, var(--amber-accent) 29% 68%, var(--amber-accent-deep) 69%); - clip-path: polygon(50% 0, 93% 24%, 78% 79%, 50% 100%, 18% 79%, 5% 24%); + object-fit: contain; filter: drop-shadow(0 4px 6px rgba(169, 61, 8, 0.24)); } -.starter-crystal::after { - position: absolute; - inset: 22% 25% 17% 49%; - background: rgba(255, 248, 225, 0.52); - clip-path: polygon(0 0, 100% 8%, 70% 100%, 0 74%); - content: ""; -} - .starter-status { display: inline-flex; align-items: center; @@ -1051,16 +1068,35 @@ th { } CSS - write_text(File.join(path, "public/css/app.css"), css_content) + write_text(File.join(path, "app/assets/stylesheets/app.css"), css_content) # JavaScript js_content = <<-JS // Application JavaScript entry point. -// Add local modules under public/js and map stable names in the ECR layout. +// Add local modules under app/assets/javascript and map stable names in the ECR layout. document.documentElement.dataset.javascript = "ready"; JS - write_text(File.join(path, "public/js/app.js"), js_content) + write_text(File.join(path, "app/assets/javascript/app.js"), js_content) + + logo_svg = <<-SVG + + Amber crystal + + + + + +SVG + write_text(File.join(path, "app/assets/images/amber-crystal.svg"), logo_svg) + + favicon_svg = <<-SVG + + + + +SVG + write_text(File.join(path, "app/assets/images/favicon.svg"), favicon_svg) # robots.txt robots_content = <<-ROBOTS @@ -1070,11 +1106,18 @@ ROBOTS write_text(File.join(path, "public/robots.txt"), robots_content) - # Placeholder favicon - File.write(File.join(path, "public/favicon.ico"), "") + %w[app/assets/fonts app/assets/files].each do |directory| + File.write(File.join(path, directory, ".gitkeep"), "") + end + end - # .keep for img - File.write(File.join(path, "public/img/.keep"), "") + private def build_assets(path : String) + manifest = AmberCLI::StaticAssets.build(path) + info "Compiled #{manifest.assets.size} fingerprinted static assets" + rescue ex : AssetPipeline::StaticAssets::Error + error ex.message || "Asset compilation failed; the project files were kept." + info "Run 'amber assets build' in #{path} after resolving the error." + exit!(error: true) end private def write_text(path : String, content : String) diff --git a/src/amber_cli/commands/watch.cr b/src/amber_cli/commands/watch.cr index f70d9f7..2d95d0f 100644 --- a/src/amber_cli/commands/watch.cr +++ b/src/amber_cli/commands/watch.cr @@ -1,6 +1,7 @@ require "../core/base_command" require "../helpers/process_runner" require "../helpers/sentry" +require "../static_assets" # The `watch` command starts a development server that automatically reloads # your application when source files change. @@ -135,9 +136,11 @@ module AmberCLI::Commands private def get_default_watch_files [ "src/**/*.cr", + "src/**/*.ecr", "config/**/*.cr", "config/**/*.yml", "config/**/*.yaml", + "app/assets/**/*", ] end @@ -147,6 +150,7 @@ module AmberCLI::Commands name: #{@name} build: #{@build_command} run: #{@run_command} + assets: amber assets build (runs in-process before application compile) files: #{@watch_files.join(", ")} INFO end @@ -159,6 +163,7 @@ module AmberCLI::Commands process_runner = Sentry::ProcessRunner.new( process_name: @name, + before_build: -> { build_static_assets }, build_commands: build_commands, run_commands: run_commands, includes: includes, @@ -166,6 +171,7 @@ module AmberCLI::Commands ) info "Watching files: #{@watch_files.join(", ")}" + info "Asset step: amber assets build (before application compile)" info "Build command: #{@build_command}" info "Run command: #{@run_command}" info "Press Ctrl+C to stop" @@ -178,6 +184,15 @@ module AmberCLI::Commands process_runner.run end + + private def build_static_assets : Bool + manifest = AmberCLI::StaticAssets.build + info "Compiled #{manifest.assets.size} fingerprinted static assets" + true + rescue ex : AssetPipeline::StaticAssets::Error + error ex.message || "Static asset build failed" + false + end end end diff --git a/src/amber_cli/helpers/process_runner.cr b/src/amber_cli/helpers/process_runner.cr index 34c5ea0..6a22768 100644 --- a/src/amber_cli/helpers/process_runner.cr +++ b/src/amber_cli/helpers/process_runner.cr @@ -7,16 +7,18 @@ module Sentry property processes = Hash(String, Array(Process)).new property process_name : String - FILE_TIMESTAMPS = Hash(String, Int64).new def initialize( @process_name : String, + @before_build : Proc(Bool)? = nil, @build_commands = Hash(String, String).new, # { "task1" => [ ... ], "task2" => [ ... ] } @run_commands = Hash(String, String).new, # { "task1" => [ ... ], "task2" => [ ... ] } @includes = Hash(String, Array(String)).new, # { "task1" => [ ... ], "task2" => [ ... ] } @excludes = Hash(String, Array(String)).new, # { "task1" => [ ... ], "task2" => [ ... ] } ) @app_running = false + @file_timestamps = Hash(String, Int64).new + @file_tasks = Hash(String, Array(String)).new end def run @@ -50,9 +52,10 @@ module Sentry end all_files.each do |file, tasks| + @file_tasks[file] = tasks timestamp = get_timestamp(file) - if FILE_TIMESTAMPS[file]? != timestamp - FILE_TIMESTAMPS[file] = timestamp + if @file_timestamps[file]? != timestamp + @file_timestamps[file] = timestamp unless no_actions log :scan, "File changed: #{file.colorize(:light_gray)} (will notify: #{tasks.join(", ")})" changed_files << file @@ -60,6 +63,17 @@ module Sentry end end + deleted_files = @file_timestamps.keys.reject { |file| all_files.has_key?(file) } + deleted_files.each do |file| + tasks = @file_tasks.delete(file) || [] of String + @file_timestamps.delete(file) + next if no_actions + + log :scan, "File removed: #{file.colorize(:light_gray)} (will notify: #{tasks.join(", ")})" + all_files[file] = tasks + changed_files << file + end + return if no_actions || changed_files.empty? tasks_to_run = Hash(String, Int32).new @@ -120,6 +134,15 @@ module Sentry if skip_build ok_to_run = true else + if before_build = @before_build + log :run, "Building static assets..." + unless before_build.call + log :run, "Static asset build failed; application compile skipped.", :red + exit 1 unless @app_running + return + end + end + log :run, "Building..." time = Time.instant build_result = Amber::CLI::Helpers.run(build_command_run) @@ -187,7 +210,7 @@ module Sentry end private def get_timestamp(file : String) - File.info(file).modification_time.to_unix + File.info(file).modification_time.to_unix_ms end private def project_name diff --git a/src/amber_cli/main_command.cr b/src/amber_cli/main_command.cr index 134f451..a6ae364 100644 --- a/src/amber_cli/main_command.cr +++ b/src/amber_cli/main_command.cr @@ -27,6 +27,7 @@ module AmberCLI option_parser.separator " watch Watch and reload application" option_parser.separator " exec [command] Execute commands in the context of the application" option_parser.separator " database [command] Database migration and seeding tasks" + option_parser.separator " assets [command] Build or verify fingerprinted static assets" option_parser.separator " encrypt [command] Encryption utilities" option_parser.separator " pipelines Pipeline management" option_parser.separator " plugin [command] Plugin management" diff --git a/src/amber_cli/static_assets.cr b/src/amber_cli/static_assets.cr new file mode 100644 index 0000000..54c2f5f --- /dev/null +++ b/src/amber_cli/static_assets.cr @@ -0,0 +1,36 @@ +require "asset_pipeline/static_assets" + +module AmberCLI + # Shared, convention-based access to a web application's authored and + # compiled static assets. Keeping this outside the command class lets project + # generation and watch mode use the compiler in-process instead of spawning a + # second CLI executable. + module StaticAssets + SOURCE_ROOT = Path["app/assets"] + OUTPUT_ROOT = Path["public/assets"] + PUBLIC_PATH = "/assets" + MANIFEST_NAME = "manifest.json" + + def self.compiler(project_root : Path | String = Path["."]) : AssetPipeline::StaticAssets::Compiler + root = Path[project_root.to_s].expand.normalize + AssetPipeline::StaticAssets::Compiler.new( + source_root: root.join(SOURCE_ROOT), + output_root: root.join(OUTPUT_ROOT), + public_path: PUBLIC_PATH, + manifest_name: MANIFEST_NAME + ) + end + + def self.build(project_root : Path | String = Path["."]) : AssetPipeline::StaticAssets::Manifest + compiler(project_root).build + end + + def self.check(project_root : Path | String = Path["."]) : AssetPipeline::StaticAssets::Manifest + root = Path[project_root.to_s].expand.normalize + AssetPipeline::StaticAssets::Compiler.check( + output_root: root.join(OUTPUT_ROOT), + manifest_name: MANIFEST_NAME + ) + end + end +end diff --git a/src/amber_cli/templates/app/.gitignore.ecr b/src/amber_cli/templates/app/.gitignore.ecr index 0ece77a..b6bd1d7 100644 --- a/src/amber_cli/templates/app/.gitignore.ecr +++ b/src/amber_cli/templates/app/.gitignore.ecr @@ -10,4 +10,4 @@ .DS_Store /bin/ /node_modules -/public/dist +/public/assets/ diff --git a/src/amber_cli/templates/app/README.md.ecr b/src/amber_cli/templates/app/README.md.ecr index 5ad3751..638a7c4 100644 --- a/src/amber_cli/templates/app/README.md.ecr +++ b/src/amber_cli/templates/app/README.md.ecr @@ -6,14 +6,18 @@ An ECR web application generated by Amber CLI for Amber `2.0.0-beta.3`. ```bash shards install +amber assets build +amber assets check crystal spec amber generate scaffold Pet name:string:required species:string:required amber database migrate amber watch ``` -Open and -. +Open . Authored CSS, JavaScript, images, fonts, and other +files live under `app/assets/`. `amber assets build` fingerprints them into the +generated, gitignored `public/assets/` tree and writes `manifest.json`; edit the +source files, never the generated copies. The generated application includes Grant ORM and the selected database driver. SQLite is the zero-setup default; choose `-d pg` or `-d mysql` when generating diff --git a/src/amber_cli/templates/app/app/assets/files/.gitkeep b/src/amber_cli/templates/app/app/assets/files/.gitkeep new file mode 100644 index 0000000..6690fad --- /dev/null +++ b/src/amber_cli/templates/app/app/assets/files/.gitkeep @@ -0,0 +1 @@ +Keep this authored static-file directory in version control. diff --git a/src/amber_cli/templates/app/app/assets/fonts/.gitkeep b/src/amber_cli/templates/app/app/assets/fonts/.gitkeep new file mode 100644 index 0000000..cf8a48e --- /dev/null +++ b/src/amber_cli/templates/app/app/assets/fonts/.gitkeep @@ -0,0 +1 @@ +Keep this authored font directory in version control. diff --git a/src/amber_cli/templates/app/app/assets/images/amber-crystal.svg b/src/amber_cli/templates/app/app/assets/images/amber-crystal.svg new file mode 100644 index 0000000..904876d --- /dev/null +++ b/src/amber_cli/templates/app/app/assets/images/amber-crystal.svg @@ -0,0 +1,7 @@ + + Amber crystal + + + + + diff --git a/src/amber_cli/templates/app/app/assets/images/favicon.svg b/src/amber_cli/templates/app/app/assets/images/favicon.svg new file mode 100644 index 0000000..86812f8 --- /dev/null +++ b/src/amber_cli/templates/app/app/assets/images/favicon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/amber_cli/templates/app/public/js/app.js b/src/amber_cli/templates/app/app/assets/javascript/app.js similarity index 51% rename from src/amber_cli/templates/app/public/js/app.js rename to src/amber_cli/templates/app/app/assets/javascript/app.js index ad6418a..c23a467 100644 --- a/src/amber_cli/templates/app/public/js/app.js +++ b/src/amber_cli/templates/app/app/assets/javascript/app.js @@ -1,3 +1,3 @@ // Application JavaScript entry point. -// Add local modules under public/js and map stable names in the ECR layout. +// Add local modules under app/assets/javascript and map stable names in the ECR layout. document.documentElement.dataset.javascript = "ready"; diff --git a/src/amber_cli/templates/app/public/css/app.css b/src/amber_cli/templates/app/app/assets/stylesheets/app.css similarity index 90% rename from src/amber_cli/templates/app/public/css/app.css rename to src/amber_cli/templates/app/app/assets/stylesheets/app.css index 9cbcc98..d7945c4 100644 --- a/src/amber_cli/templates/app/public/css/app.css +++ b/src/amber_cli/templates/app/app/assets/stylesheets/app.css @@ -9,7 +9,7 @@ --amber-accent-deep: #a93d08; --amber-gold: #f3a348; --amber-green: #377a55; - font-family: Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + font-family: ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; color: var(--amber-ink); background: var(--amber-paper); } @@ -28,15 +28,17 @@ a { color: inherit; } linear-gradient(145deg, #fffdf9, var(--amber-paper) 48%, var(--amber-paper-deep)); } -.amber-starter::before { +.amber-starter::after { position: absolute; - top: 8rem; - right: -13rem; - width: 38rem; - height: 38rem; - border: 1px solid rgba(169, 61, 8, .12); - border-radius: 50%; + top: 15rem; + right: 9%; + width: 4.5rem; + height: 6rem; + opacity: .17; + background: center / contain no-repeat url("../images/amber-crystal.svg"); content: ""; + pointer-events: none; + transform: rotate(14deg); } .starter-nav, .starter-main, .starter-footer { @@ -54,7 +56,7 @@ a { color: inherit; } } .starter-brand { display: inline-flex; align-items: center; gap: 11px; font: 800 1.06rem ui-serif, Georgia, serif; text-decoration: none; } -.starter-crystal { width: 23px; height: 31px; background: linear-gradient(145deg, #ffc36b 0 28%, var(--amber-accent) 29% 68%, var(--amber-accent-deep) 69%); clip-path: polygon(50% 0, 93% 24%, 78% 79%, 50% 100%, 18% 79%, 5% 24%); } +.starter-crystal { width: 23px; height: 31px; object-fit: contain; filter: drop-shadow(0 4px 6px rgba(169, 61, 8, .24)); } .starter-status, .starter-proof span { border: 1px solid var(--amber-line); border-radius: 999px; background: rgba(255, 253, 249, .76); color: var(--amber-muted); font-size: .72rem; font-weight: 800; } .starter-status { display: inline-flex; align-items: center; gap: 7px; padding: 7px 11px; letter-spacing: .08em; text-transform: uppercase; } .starter-status > span { width: 7px; height: 7px; border-radius: 50%; background: var(--amber-green); } diff --git a/src/amber_cli/templates/app/config/application.cr.ecr b/src/amber_cli/templates/app/config/application.cr.ecr index 318939c..417d775 100644 --- a/src/amber_cli/templates/app/config/application.cr.ecr +++ b/src/amber_cli/templates/app/config/application.cr.ecr @@ -1,4 +1,5 @@ require "amber" +require "./assets" require "../src/controllers/application_controller" require "../src/controllers/**" require "./routes" diff --git a/src/amber_cli/templates/app/config/assets.cr.ecr b/src/amber_cli/templates/app/config/assets.cr.ecr new file mode 100644 index 0000000..7b16371 --- /dev/null +++ b/src/amber_cli/templates/app/config/assets.cr.ecr @@ -0,0 +1 @@ +Amber::Assets.configure(manifest_path: "public/assets/manifest.json") diff --git a/src/amber_cli/templates/app/public/crossdomain.xml b/src/amber_cli/templates/app/public/crossdomain.xml deleted file mode 100644 index 0c16a7a..0000000 --- a/src/amber_cli/templates/app/public/crossdomain.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - diff --git a/src/amber_cli/templates/app/public/css/main.css b/src/amber_cli/templates/app/public/css/main.css deleted file mode 100644 index 91b7d27..0000000 --- a/src/amber_cli/templates/app/public/css/main.css +++ /dev/null @@ -1,7 +0,0 @@ -.main { - padding-top: 20px; -} - -.bg-primary { - background-color: #f4994b !important -} diff --git a/src/amber_cli/templates/app/public/fonts/.gitkeep b/src/amber_cli/templates/app/public/fonts/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/amber_cli/templates/app/public/img/.gitkeep b/src/amber_cli/templates/app/public/img/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/src/amber_cli/templates/app/public/img/logo.svg b/src/amber_cli/templates/app/public/img/logo.svg deleted file mode 100644 index 1edac32..0000000 --- a/src/amber_cli/templates/app/public/img/logo.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/amber_cli/templates/app/public/js/amber.js b/src/amber_cli/templates/app/public/js/amber.js deleted file mode 100644 index d66fdbb..0000000 --- a/src/amber_cli/templates/app/public/js/amber.js +++ /dev/null @@ -1,289 +0,0 @@ -const EVENTS = { - join: 'join', - leave: 'leave', - message: 'message' -} -const STALE_CONNECTION_THRESHOLD_SECONDS = 100 -const SOCKET_POLLING_RATE = 10000 - -/** - * Returns a numeric value for the current time - */ -let now = () => { - return new Date().getTime() -} - -/** - * Returns the difference between the current time and passed `time` in seconds - * @param {Number|Date} time - A numeric time or date object - */ -let secondsSince = (time) => { - return (now() - time) / 1000 -} - -/** - * Class for channel related functions (joining, leaving, subscribing and sending messages) - */ -export class Channel { - /** - * @param {String} topic - topic to subscribe to - * @param {Socket} socket - A Socket instance - */ - constructor(topic, socket) { - this.topic = topic - this.socket = socket - this.onMessageHandlers = [] - } - - /** - * Join a channel, subscribe to all channels messages - */ - join() { - try { - this.socket.ws.send(JSON.stringify({ event: EVENTS.join, topic: this.topic })) - } catch { - this._reconnect() - } - } - - /** - * Leave a channel, stop subscribing to channel messages - */ - leave() { - try { - this.socket.ws.send(JSON.stringify({ event: EVENTS.leave, topic: this.topic })) - } catch { - this._reconnect() - } - } - - /** - * Calls all message handlers with a matching subject - */ - handleMessage(msg) { - this.onMessageHandlers.forEach((handler) => { - if (handler.subject === msg.subject) handler.callback(msg.payload) - }) - } - - /** - * Subscribe to a channel subject - * @param {String} subject - subject to listen for: `msg:new` - * @param {function} callback - callback function when a new message arrives - */ - on(subject, callback) { - this.onMessageHandlers.push({ subject: subject, callback: callback }) - } - - /** - * Send a new message to the channel - * @param {String} subject - subject to send message to: `msg:new` - * @param {Object} payload - payload object: `{message: 'hello'}` - */ - push(subject, payload) { - try { - this.socket.ws.send(JSON.stringify({ event: EVENTS.message, topic: this.topic, subject: subject, payload: payload })) - } catch { - this._reconnect() - } - } -} - -/** - * Class for maintaining connection with server and maintaining channels list - */ -export class Socket { - /** - * @param {String} endpoint - Websocket endpont used in routes.cr file - */ - constructor(endpoint) { - this.endpoint = endpoint - this.ws = null - this.channels = [] - this.lastPing = now() - this.reconnectTries = 0 - this.attemptReconnect = true - } - - /** - * Returns whether or not the last received ping has been past the threshold - */ - _connectionIsStale() { - return secondsSince(this.lastPing) > STALE_CONNECTION_THRESHOLD_SECONDS - } - - /** - * Tries to reconnect to the websocket server using a recursive timeout - */ - _reconnect() { - clearTimeout(this.reconnectTimeout) - this.reconnectTimeout = setTimeout(() => { - this.reconnectTries++ - this.connect(this.params) - this._reconnect() - }, this._reconnectInterval()) - } - - /** - * Returns an incrementing timeout interval based around the number of reconnection retries - */ - _reconnectInterval() { - return [1000, 2000, 5000, 10000][this.reconnectTries] || 10000 - } - - /** - * Sets a recursive timeout to check if the connection is stale - */ - _poll() { - this.pollingTimeout = setTimeout(() => { - if (this._connectionIsStale()) { - this._reconnect() - } else { - this._poll() - } - }, SOCKET_POLLING_RATE) - } - - /** - * Clear polling timeout and start polling - */ - _startPolling() { - clearTimeout(this.pollingTimeout) - this._poll() - } - - /** - * Sets `lastPing` to the curent time - */ - _handlePing() { - this.lastPing = now() - } - - /** - * Clears reconnect timeout, resets variables an starts polling - */ - _reset() { - clearTimeout(this.reconnectTimeout) - this.reconnectTries = 0 - this.attemptReconnect = true - this._startPolling() - } - - /** - * Connect the socket to the server, and binds to native ws functions - * @param {Object} params - Optional parameters - * @param {String} params.location - Hostname to connect to, defaults to `window.location.hostname` - * @param {String} parmas.port - Port to connect to, defaults to `window.location.port` - * @param {String} params.protocol - Protocol to use, either 'wss' or 'ws' - */ - connect(params) { - this.params = params - - let opts = { - location: window.location.hostname, - port: window.location.port, - protocol: window.location.protocol === 'https:' ? 'wss:' : 'ws:', - } - - if (params) Object.assign(opts, params) - if (opts.port) opts.location += `:${opts.port}` - - return new Promise((resolve, reject) => { - this.ws = new WebSocket(`${opts.protocol}//${opts.location}${this.endpoint}`) - this.ws.onmessage = (msg) => { this.handleMessage(msg) } - this.ws.onclose = () => { - if (this.attemptReconnect) this._reconnect() - } - this.ws.onopen = () => { - this._reset() - resolve() - } - }) - } - - /** - * Closes the socket connection permanently - */ - disconnect() { - this.attemptReconnect = false - clearTimeout(this.pollingTimeout) - clearTimeout(this.reconnectTimeout) - this.ws.close() - } - - /** - * Adds a new channel to the socket channels list - * @param {String} topic - Topic for the channel: `chat_room:123` - */ - channel(topic) { - let channel = new Channel(topic, this) - this.channels.push(channel) - return channel - } - - /** - * Message handler for messages received - * @param {MessageEvent} msg - Message received from ws - */ - handleMessage(msg) { - if (msg.data === "ping") return this._handlePing() - - let parsed_msg = JSON.parse(msg.data) - this.channels.forEach((channel) => { - if (channel.topic === parsed_msg.topic) channel.handleMessage(parsed_msg) - }) - } -} - -export default { - Channel, - Socket -}; - -/** - * Allows delete links to post for security and ease of use similar to Rails jquery_ujs - */ -document.addEventListener("DOMContentLoaded", () => { - let elements = document.querySelectorAll("a[data-method='delete']"); - for (let i = 0; i < elements.length; i++) { - elements[i].addEventListener("click", (e) => { - e.preventDefault(); - let message = elements[i].getAttribute("data-confirm") || "Are you sure?"; - if (confirm(message)) { - let form = document.createElement("form"); - let input = document.createElement("input"); - form.setAttribute("action", elements[i].getAttribute("href")); - form.setAttribute("method", "POST"); - input.setAttribute("type", "hidden"); - input.setAttribute("name", "_method"); - input.setAttribute("value", "DELETE"); - form.appendChild(input); - document.body.appendChild(form); - form.submit(); - } - return false; - }) - } -}); - -if (!Date.prototype.toGranite) { - (function() { - - function pad(number) { - if (number < 10) { - return '0' + number; - } - return number; - } - - Date.prototype.toGranite = function() { - return this.getUTCFullYear() + - '-' + pad(this.getUTCMonth() + 1) + - '-' + pad(this.getUTCDate()) + - ' ' + pad(this.getUTCHours()) + - ':' + pad(this.getUTCMinutes()) + - ':' + pad(this.getUTCSeconds()) ; - }; - - }()); -} diff --git a/src/amber_cli/templates/app/public/js/client_reload.js b/src/amber_cli/templates/app/public/js/client_reload.js deleted file mode 100644 index cf0e04d..0000000 --- a/src/amber_cli/templates/app/public/js/client_reload.js +++ /dev/null @@ -1,52 +0,0 @@ -if ('WebSocket' in window) { - (function () { - /** - * Allows to reload the browser when the server connection is lost - */ - function tryReload() { - var request = new XMLHttpRequest(); - request.open('GET', window.location.href, true); - request.onreadystatechange = function () { - if (request.readyState == 4) { - if (request.status == 0) { - setTimeout(function () { - tryReload(); - }, 1000) - } else { - window.location.reload(); - } - } - }; - request.send(); - } - - /** - * Listen server file reload - */ - function refreshCSS() { - var sheets = [].slice.call(document.getElementsByTagName('link')); - var head = document.getElementsByTagName('head')[0]; - for (var i = 0; i < sheets.length; ++i) { - var elem = sheets[i]; - var rel = elem.rel; - if (elem.href && typeof rel != 'string' || rel.length == 0 || rel.toLowerCase() == 'stylesheet') { - head.removeChild(elem); - var url = elem.href.replace(/(&|\\?)_cacheOverride=\\d+/, ''); - elem.href = url + (url.indexOf('?') >= 0 ? '&' : '?') + '_cacheOverride=' + (new Date().valueOf()); - head.appendChild(elem); - } - } - } - - var protocol = window.location.protocol === 'http:' ? 'ws://' : 'wss://'; - var address = protocol + window.location.host + '/client-reload'; - var socket = new WebSocket(address); - socket.onmessage = function (msg) { - if (msg.data == 'reload') { - tryReload(); - } else if (msg.data == 'refreshcss') { - refreshCSS(); - } - }; - })(); -} diff --git a/src/amber_cli/templates/app/shard.yml.ecr b/src/amber_cli/templates/app/shard.yml.ecr index efd58e0..0819a18 100644 --- a/src/amber_cli/templates/app/shard.yml.ecr +++ b/src/amber_cli/templates/app/shard.yml.ecr @@ -19,6 +19,9 @@ dependencies: grant: github: crimson-knight/grant commit: 2665a978b43ac608c68cde9243821f8f8f053372 + asset_pipeline: + github: amberframework/asset_pipeline + commit: 67659880b11ef6cb91aa890d6f13fbc98000996a <% case @database when "pg" -%> pg: 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 66ba1ce..e3a3635 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,7 +1,7 @@
%= Amber.settings.name %> home"> - + <%= "<" %>%= image_tag("images/amber-crystal.svg", class: "starter-crystal", alt: "") %> <%= "<" %>%= Amber.settings.name %> Amber V2 beta 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 633360b..927e6f2 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 @@ -4,13 +4,12 @@ <%= display_name %> - + <%= "<" %>%= favicon_tag("images/favicon.svg") %> + <%= "<" %>%= stylesheet_link_tag("stylesheets/app.css") %> <%= "<" %>%= content %> - + <%= "<" %>%= javascript_importmap_tag({"app" => "javascript/app.js"}, preload: ["javascript/app.js"]) %> From 6fe45dd8b2eac1c858bd3ffaa7280c9591a20ef6 Mon Sep 17 00:00:00 2001 From: crimson-knight Date: Tue, 11 Aug 2026 23:22:25 -0400 Subject: [PATCH 2/8] Prepare Amber CLI 2.0.5 --- README.md | 12 +++--- RELEASE_NOTES_V2.0.5.md | 45 +++++++++++++++++++++++ docs/BETA_WEB_APP.md | 8 ++-- docs/RELEASE_CHECKLIST.md | 8 ++-- scripts/check_beta_contract.sh | 5 ++- scripts/smoke_generated_web.ps1 | 2 +- scripts/smoke_generated_web.sh | 2 +- shard.yml | 2 +- spec/commands/new_command_spec.cr | 2 +- src/amber_cli.cr | 2 +- src/amber_cli/commands/generate.cr | 12 +++--- src/amber_cli/commands/new.cr | 6 +-- src/amber_cli/commands/setup_lsp.cr | 2 +- src/amber_cli/documentation.cr | 2 +- src/amber_cli/templates/app/README.md.ecr | 4 +- src/amber_cli/templates/app/shard.yml.ecr | 2 +- 16 files changed, 81 insertions(+), 35 deletions(-) create mode 100644 RELEASE_NOTES_V2.0.5.md diff --git a/README.md b/README.md index c961e57..e906b91 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,8 @@ [![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/) -Amber CLI is the standalone command-line companion for Amber V2. CLI `2.0.4` -creates the supported Amber `2.0.0-beta.3` ECR web application and includes +Amber CLI is the standalone command-line companion for Amber V2. CLI `2.0.5` +creates the supported Amber `2.0.0-beta.4` ECR web application and includes development, generator, database, and LSP tooling. Amber V2 is a beta. The release-gated path is a web application on Apple @@ -32,12 +32,12 @@ the `amber_cli` formula. ### Direct release archive -CLI `2.0.4` publishes `darwin-arm64`, `linux-x86_64`, and `linux-arm64` +CLI `2.0.5` publishes `darwin-arm64`, `linux-x86_64`, and `linux-arm64` archives. Windows x86-64 is compiled in CI but does not yet have a release archive. ```bash -version=v2.0.4 +version=v2.0.5 platform=darwin-arm64 asset="amber_cli-${platform}.tar.gz" @@ -70,7 +70,7 @@ or a file under `app/assets/` changes. Open . The web template is deliberately small: -- Amber from `amberframework/amber`, pinned to `2.0.0-beta.3` +- Amber from `amberframework/amber`, pinned to `2.0.0-beta.4` - ECR views (Slang and Kilt are not supported in Amber V2) - typed development, test, and production YAML - branded homepage, controller spec, and fingerprinted CSS, JavaScript, SVG, @@ -175,7 +175,7 @@ The release archive includes `amber-lsp`. From an Amber project: amber setup:lsp ``` -See the [LSP setup guide](https://github.com/amberframework/amber/blob/v2.0.0-beta.3/docs/guides/lsp-setup.md). +See the [LSP setup guide](https://github.com/amberframework/amber/blob/v2.0.0-beta.4/docs/guides/lsp-setup.md). ## Contributing diff --git a/RELEASE_NOTES_V2.0.5.md b/RELEASE_NOTES_V2.0.5.md new file mode 100644 index 0000000..e90295d --- /dev/null +++ b/RELEASE_NOTES_V2.0.5.md @@ -0,0 +1,45 @@ +# Amber CLI 2.0.5 + +Amber CLI 2.0.5 makes production-safe static assets part of the same supported +Amber V2 web-app path as Grant, Micrate, and SQLite. A fresh application starts +with editable CSS, JavaScript, SVG, and font locations; a deterministic asset +manifest; manifest-aware ECR helpers; and browser-correct production headers. + +## Start a complete web app + +```bash +amber new pet_tracker --type web +cd pet_tracker +shards install +amber assets check +amber generate scaffold Pet name:string:required species:string:required adopted:bool +amber database migrate +crystal spec +amber watch +``` + +Open and . The homepage +uses the same Amber design language as the framework site and references +fingerprinted files from `public/assets/manifest.json`. + +## What changed + +- Added `amber assets build` for deterministic fingerprinting and + `amber assets check` for strict release verification. +- Moved authored browser files to `app/assets/`; generated files under + `public/assets/` are ignored and may be rebuilt at any time. +- Rewrites local CSS URLs and JavaScript module references to their + fingerprinted image, font, stylesheet, and module targets. +- Generates manifest-aware stylesheet, import-map, preload, image, and favicon + tags, including Subresource Integrity metadata where browsers support it. +- Rebuilds assets before application compilation in `amber watch`, including + file additions, changes, and deletions. +- Boots generated applications in Unix and Windows CI and requests the real + HTML, CSS, JavaScript, SVG, and compressed-asset paths instead of treating a + successful compile as sufficient evidence. +- Publishes CLI archives for Apple Silicon macOS, x86_64 Linux, and ARM64 Linux; + Windows x86_64 remains a source-build path with a release-gated app smoke. + +Grant, SQLite, Micrate migrations, and generated HTML resource CRUD remain the +default persistence contract from CLI 2.0.4. Native applications, +authentication generators, and generated APIs remain preview surfaces. diff --git a/docs/BETA_WEB_APP.md b/docs/BETA_WEB_APP.md index 9ef4abd..9009374 100644 --- a/docs/BETA_WEB_APP.md +++ b/docs/BETA_WEB_APP.md @@ -1,7 +1,7 @@ # Amber V2 Beta Web App -This guide is the consumer smoke test for Amber CLI `2.0.4` and Amber -`2.0.0-beta.3`. It is expected to pass on Apple Silicon macOS, x86_64 Linux, +This guide is the consumer smoke test for Amber CLI `2.0.5` and Amber +`2.0.0-beta.4`. It is expected to pass on Apple Silicon macOS, x86_64 Linux, and ARM64 Linux. Windows x86-64 compilation is checked in CI. ## 1. Verify the toolchain @@ -12,7 +12,7 @@ shards --version amber --version ``` -Crystal must be at least 1.20 and earlier than 2.0. Amber CLI must be 2.0.4 or +Crystal must be at least 1.20 and earlier than 2.0. Amber CLI must be 2.0.5 or newer. ## 2. Generate the web app @@ -30,7 +30,7 @@ grep -A2 'amber:' shard.yml grep -E '^(template|database|model):' .amber.yml ``` -The dependency must be `amberframework/amber` version `2.0.0-beta.3`; the +The dependency must be `amberframework/amber` version `2.0.0-beta.4`; the template must be `ecr`, the database must be `sqlite`, and the model layer must be `grant`. `shard.yml` must include Grant, Asset Pipeline, and `crystal-sqlite3`, but it must not include Gemma, Slang, PostgreSQL, or MySQL diff --git a/docs/RELEASE_CHECKLIST.md b/docs/RELEASE_CHECKLIST.md index 24cf32f..04a25c7 100644 --- a/docs/RELEASE_CHECKLIST.md +++ b/docs/RELEASE_CHECKLIST.md @@ -6,14 +6,14 @@ 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.3`. + `2.0.0-beta.4`. 2. Run framework specs and formatting on macOS and Linux. -3. Tag the reviewed `v2-dev` commit as `v2.0.0-beta.3`. +3. Tag the reviewed `v2-dev` commit as `v2.0.0-beta.4`. 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.4`. +1. Confirm `shard.yml` and `AmberCLI::VERSION` both say `2.0.5`. 2. Generate a web app and verify it pins the framework prerelease. 3. Verify the generated app pins the reviewed Asset Pipeline revision and has no placeholder dependency revisions. @@ -27,7 +27,7 @@ Homebrew formula pins CLI archives and checksums. 6. On macOS, reject any binary linked to `openssl@1.1`. 7. Confirm the Windows x86-64 generated app builds in CI. This is a compatibility check, not a beta release gate. -8. Tag `v2.0.4`, publish the release, and wait for all archives and checksum +8. Tag `v2.0.5`, publish the release, and wait for all archives and checksum files to upload. ## 3. Homebrew diff --git a/scripts/check_beta_contract.sh b/scripts/check_beta_contract.sh index 9e0a519..43c4f69 100755 --- a/scripts/check_beta_contract.sh +++ b/scripts/check_beta_contract.sh @@ -3,11 +3,11 @@ 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.4" +test "$shard_version" = "2.0.5" test "$cli_version" = "$shard_version" grep -F 'github: amberframework/amber' src/amber_cli/commands/new.cr -grep -F 'version: 2.0.0-beta.3' src/amber_cli/commands/new.cr +grep -F 'version: 2.0.0-beta.4' src/amber_cli/commands/new.cr grep -F 'template: ecr' src/amber_cli/commands/new.cr grep -F 'model: grant' src/amber_cli/commands/new.cr grep -F 'database: #{database}' src/amber_cli/commands/new.cr @@ -38,6 +38,7 @@ files=( README.md RELEASE_NOTES_V2.0.3.md RELEASE_NOTES_V2.0.4.md + RELEASE_NOTES_V2.0.5.md RELEASE_SETUP.md .github/ISSUE_TEMPLATE/release-checklist.md docs/*.md diff --git a/scripts/smoke_generated_web.ps1 b/scripts/smoke_generated_web.ps1 index 5ec1236..e6abc64 100644 --- a/scripts/smoke_generated_web.ps1 +++ b/scripts/smoke_generated_web.ps1 @@ -51,7 +51,7 @@ if (-not $amberConfig.Contains("database: sqlite") -or -not $amberConfig.Contain } if ($FrameworkCommit) { - $releasedFramework = " version: 2.0.0-beta.3" + $releasedFramework = " version: 2.0.0-beta.4" if (-not $manifest.Contains($releasedFramework)) { throw "Generated shard.yml does not contain the expected Amber beta pin" } diff --git a/scripts/smoke_generated_web.sh b/scripts/smoke_generated_web.sh index fad2d11..7da0685 100755 --- a/scripts/smoke_generated_web.sh +++ b/scripts/smoke_generated_web.sh @@ -30,7 +30,7 @@ trap cleanup EXIT "$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.3" "$app_path/shard.yml" +grep -F "version: 2.0.0-beta.4" "$app_path/shard.yml" grep -F "github: crimson-knight/grant" "$app_path/shard.yml" grep -F "github: amberframework/asset_pipeline" "$app_path/shard.yml" grep -F "github: crystal-lang/crystal-sqlite3" "$app_path/shard.yml" diff --git a/shard.yml b/shard.yml index 4b59842..29464d1 100644 --- a/shard.yml +++ b/shard.yml @@ -1,5 +1,5 @@ name: amber_cli -version: 2.0.4 +version: 2.0.5 authors: - crimson-knight diff --git a/spec/commands/new_command_spec.cr b/spec/commands/new_command_spec.cr index 3051418..fa3e908 100644 --- a/spec/commands/new_command_spec.cr +++ b/spec/commands/new_command_spec.cr @@ -86,7 +86,7 @@ describe AmberCLI::Commands::NewCommand do shard = File.read(File.join(destination, "shard.yml")) shard.should contain("github: amberframework/amber") - shard.should contain("version: 2.0.0-beta.3") + shard.should contain("version: 2.0.0-beta.4") shard.should contain("grant:") shard.should contain("github: crimson-knight/grant") shard.should contain("asset_pipeline:") diff --git a/src/amber_cli.cr b/src/amber_cli.cr index 2a4c598..e78527a 100644 --- a/src/amber_cli.cr +++ b/src/amber_cli.cr @@ -40,7 +40,7 @@ end Log.builder.bind "*", :info, backend module AmberCLI - VERSION = "2.0.4" + VERSION = "2.0.5" 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 31bd2f4..456ccf9 100644 --- a/src/amber_cli/commands/generate.cr +++ b/src/amber_cli/commands/generate.cr @@ -292,7 +292,7 @@ RETRIES # #{class_name}.new.enqueue(delay: 5.minutes) # #{class_name}.new.enqueue(queue: "critical") # -# See: https://github.com/amberframework/amber/blob/v2.0.0-beta.3/docs/guides/background-jobs.md +# See: https://github.com/amberframework/amber/blob/v2.0.0-beta.4/docs/guides/background-jobs.md class #{class_name} < Amber::Jobs::Job include JSON::Serializable @@ -390,7 +390,7 @@ METHOD # .subject("Welcome!") # .deliver # -# See: https://github.com/amberframework/amber/blob/v2.0.0-beta.3/docs/guides/mailer.md +# See: https://github.com/amberframework/amber/blob/v2.0.0-beta.4/docs/guides/mailer.md class #{class_name}Mailer < Amber::Mailer::Base def initialize(@user_name : String, @user_email : String) end @@ -494,7 +494,7 @@ SPEC # # Handle errors: result.errors # end # -# See: https://github.com/amberframework/amber/blob/v2.0.0-beta.3/docs/guides/schema-api.md +# See: https://github.com/amberframework/amber/blob/v2.0.0-beta.4/docs/guides/schema-api.md class #{class_name}Schema < Amber::Schema::Definition #{field_definitions} end @@ -599,7 +599,7 @@ SPEC # Clients subscribe to this channel through a ClientSocket. # Messages sent to this channel are handled by `handle_message`. # -# See: https://github.com/amberframework/amber/blob/v2.0.0-beta.3/docs/guides/websockets.md +# See: https://github.com/amberframework/amber/blob/v2.0.0-beta.4/docs/guides/websockets.md class #{class_name}Channel < Amber::WebSockets::Channel # Called when a client subscribes to this channel. # Use this for authorization or sending initial state. @@ -630,7 +630,7 @@ CHANNEL # Configure in config/routes.cr: # websocket "/#{file_name}", #{class_name}Socket # -# See: https://github.com/amberframework/amber/blob/v2.0.0-beta.3/docs/guides/websockets.md +# See: https://github.com/amberframework/amber/blob/v2.0.0-beta.4/docs/guides/websockets.md struct #{class_name}Socket < Amber::WebSockets::ClientSocket channel "#{file_name}:*", #{class_name}Channel @@ -859,7 +859,7 @@ SPEC # # Used by #{controller_name} for request validation. # -# See: https://github.com/amberframework/amber/blob/v2.0.0-beta.3/docs/guides/schema-api.md +# See: https://github.com/amberframework/amber/blob/v2.0.0-beta.4/docs/guides/schema-api.md class #{class_name}Schema < Amber::Schema::Definition #{field_definitions} end diff --git a/src/amber_cli/commands/new.cr b/src/amber_cli/commands/new.cr index fc57132..70c8533 100644 --- a/src/amber_cli/commands/new.cr +++ b/src/amber_cli/commands/new.cr @@ -284,7 +284,7 @@ targets: dependencies: amber: github: amberframework/amber - version: 2.0.0-beta.3 + version: 2.0.0-beta.4 grant: github: crimson-knight/grant commit: 2665a978b43ac608c68cde9243821f8f8f053372 @@ -316,7 +316,7 @@ AMBER readme_content = <<-README # #{name} -An ECR web application generated by Amber CLI for Amber `2.0.0-beta.3`. +An ECR web application generated by Amber CLI for Amber `2.0.0-beta.4`. ## Run it @@ -353,7 +353,7 @@ server database. Set `DATABASE_URL` to override the environment YAML URL. ## Production configuration Set at least `AMBER_SERVER_SECRET_KEY_BASE` and `DATABASE_URL`. See the -[Amber V2 beta guide](https://github.com/amberframework/amber/blob/v2.0.0-beta.3/docs/beta-installation.md). +[Amber V2 beta guide](https://github.com/amberframework/amber/blob/v2.0.0-beta.4/docs/beta-installation.md). README write_text(File.join(path, "README.md"), readme_content) diff --git a/src/amber_cli/commands/setup_lsp.cr b/src/amber_cli/commands/setup_lsp.cr index 8560272..55d6d30 100644 --- a/src/amber_cli/commands/setup_lsp.cr +++ b/src/amber_cli/commands/setup_lsp.cr @@ -215,7 +215,7 @@ module AmberCLI::Commands content = <<-YAML # Amber LSP Configuration - # See: https://github.com/amberframework/amber/blob/v2.0.0-beta.3/docs/guides/lsp-setup.md + # See: https://github.com/amberframework/amber/blob/v2.0.0-beta.4/docs/guides/lsp-setup.md # Override built-in rule settings # rules: diff --git a/src/amber_cli/documentation.cr b/src/amber_cli/documentation.cr index d8b2d9e..4eb1775 100644 --- a/src/amber_cli/documentation.cr +++ b/src/amber_cli/documentation.cr @@ -89,7 +89,7 @@ module AmberCLI::Documentation # - **.amber.yml** - Project configuration # - **.gitignore** - Git ignore rules # - # The web template pins Amber `2.0.0-beta.3`, uses ECR, and includes Grant, + # The web template pins Amber `2.0.0-beta.4`, uses ECR, and includes Grant, # Micrate-powered database commands, and the selected database driver. Model, # scaffold, and migration generators are supported. API-resource, # authentication, and native generators are preview surfaces during the beta. diff --git a/src/amber_cli/templates/app/README.md.ecr b/src/amber_cli/templates/app/README.md.ecr index 638a7c4..c1b4f9b 100644 --- a/src/amber_cli/templates/app/README.md.ecr +++ b/src/amber_cli/templates/app/README.md.ecr @@ -1,6 +1,6 @@ # <%= @name %> -An ECR web application generated by Amber CLI for Amber `2.0.0-beta.3`. +An ECR web application generated by Amber CLI for Amber `2.0.0-beta.4`. ## Run it @@ -26,7 +26,7 @@ the app to target a server database. ## Production configuration Set at least `AMBER_SERVER_SECRET_KEY_BASE` and `AMBER_DATABASE_URL`. See the -[Amber V2 beta guide](https://github.com/amberframework/amber/blob/v2.0.0-beta.3/docs/beta-installation.md). +[Amber V2 beta guide](https://github.com/amberframework/amber/blob/v2.0.0-beta.4/docs/beta-installation.md). ## Tests diff --git a/src/amber_cli/templates/app/shard.yml.ecr b/src/amber_cli/templates/app/shard.yml.ecr index 0819a18..ce305b3 100644 --- a/src/amber_cli/templates/app/shard.yml.ecr +++ b/src/amber_cli/templates/app/shard.yml.ecr @@ -15,7 +15,7 @@ targets: dependencies: amber: github: amberframework/amber - version: 2.0.0-beta.3 + version: 2.0.0-beta.4 grant: github: crimson-knight/grant commit: 2665a978b43ac608c68cde9243821f8f8f053372 From 273a650a6009ed0b990f62f239ad64119848f910 Mon Sep 17 00:00:00 2001 From: crimson-knight Date: Tue, 11 Aug 2026 23:47:13 -0400 Subject: [PATCH 3/8] Verify beta assets across release platforms --- .github/workflows/build.yml | 3 ++- .github/workflows/platform-compile.yml | 6 ++++-- scripts/smoke_generated_web.ps1 | 3 --- scripts/smoke_generated_web.sh | 1 - shard.lock | 2 +- shard.yml | 2 +- src/amber_cli/commands/new.cr | 2 +- 7 files changed, 9 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b2e6b49..ebb6dec 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -83,7 +83,8 @@ jobs: ! 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 + # Exercise the clean beta.4 candidate before its version tag exists. + run: scripts/smoke_generated_web.sh ./amber a57b74c5842298f06dff58a1732442efc131a177 - name: Upload build artifacts uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 diff --git a/.github/workflows/platform-compile.yml b/.github/workflows/platform-compile.yml index 21e7258..3fd0e1b 100644 --- a/.github/workflows/platform-compile.yml +++ b/.github/workflows/platform-compile.yml @@ -27,7 +27,8 @@ jobs: run: crystal build src/amber_cli.cr --no-debug -o amber - name: Compile generated Amber V2 web app - run: scripts/smoke_generated_web.sh ./amber + # Exercise the clean beta.4 candidate before its version tag exists. + run: scripts/smoke_generated_web.sh ./amber a57b74c5842298f06dff58a1732442efc131a177 windows-x86-64: name: Windows x86_64 generated web app @@ -57,4 +58,5 @@ jobs: - name: Compile generated Amber V2 web app shell: pwsh - run: scripts/smoke_generated_web.ps1 ./amber.exe + # Exercise the clean beta.4 candidate before its version tag exists. + run: scripts/smoke_generated_web.ps1 ./amber.exe a57b74c5842298f06dff58a1732442efc131a177 diff --git a/scripts/smoke_generated_web.ps1 b/scripts/smoke_generated_web.ps1 index e6abc64..3774b3e 100644 --- a/scripts/smoke_generated_web.ps1 +++ b/scripts/smoke_generated_web.ps1 @@ -56,9 +56,6 @@ if ($FrameworkCommit) { throw "Generated shard.yml does not contain the expected Amber beta pin" } $manifest = $manifest.Replace( - " github: amberframework/amber", - " github: crimson-knight/amber" - ).Replace( $releasedFramework, " commit: $FrameworkCommit" ) diff --git a/scripts/smoke_generated_web.sh b/scripts/smoke_generated_web.sh index 7da0685..a422288 100755 --- a/scripts/smoke_generated_web.sh +++ b/scripts/smoke_generated_web.sh @@ -49,7 +49,6 @@ if grep -Eiq 'gemma:|slang' "$app_path/shard.yml" "$app_path/.amber.yml"; then fi if [[ -n "$framework_commit" ]]; then - sed -i.bak "s/github: amberframework\/amber/github: crimson-knight\/amber/" "$app_path/shard.yml" sed -i.bak -E "s/ version: 2\.0\.0-beta\.[0-9]+/ commit: ${framework_commit}/" "$app_path/shard.yml" rm -f "$app_path/shard.yml.bak" fi diff --git a/shard.lock b/shard.lock index 0bfe232..9a50e13 100644 --- a/shard.lock +++ b/shard.lock @@ -6,7 +6,7 @@ shards: asset_pipeline: git: https://github.com/amberframework/asset_pipeline.git - version: 0.37.0+git.commit.67659880b11ef6cb91aa890d6f13fbc98000996a + version: 0.37.0+git.commit.4700a4bb0ecd87ea533d89ebf2e6b7a90183bc5c backtracer: git: https://github.com/sija/backtracer.cr.git diff --git a/shard.yml b/shard.yml index 29464d1..bbebb36 100644 --- a/shard.yml +++ b/shard.yml @@ -19,7 +19,7 @@ dependencies: # Release candidate includes the native UI surface and static asset compiler. asset_pipeline: github: amberframework/asset_pipeline - commit: 67659880b11ef6cb91aa890d6f13fbc98000996a + commit: 4700a4bb micrate: github: amberframework/micrate diff --git a/src/amber_cli/commands/new.cr b/src/amber_cli/commands/new.cr index 70c8533..8ca2531 100644 --- a/src/amber_cli/commands/new.cr +++ b/src/amber_cli/commands/new.cr @@ -290,7 +290,7 @@ dependencies: commit: 2665a978b43ac608c68cde9243821f8f8f053372 asset_pipeline: github: amberframework/asset_pipeline - commit: 67659880b11ef6cb91aa890d6f13fbc98000996a + commit: 4700a4bb #{database_shard_dependency} SHARD From a9da67b3205fc2fbae8f1a459ada2b17161afef2 Mon Sep 17 00:00:00 2001 From: crimson-knight Date: Tue, 11 Aug 2026 23:49:29 -0400 Subject: [PATCH 4/8] Test the final beta 4 candidate --- .github/workflows/build.yml | 2 +- .github/workflows/platform-compile.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ebb6dec..63bca4a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -84,7 +84,7 @@ jobs: - name: Smoke test generated Amber V2 web app # Exercise the clean beta.4 candidate before its version tag exists. - run: scripts/smoke_generated_web.sh ./amber a57b74c5842298f06dff58a1732442efc131a177 + run: scripts/smoke_generated_web.sh ./amber c4d08e67ead2f160b31b86945af57f0c13478d61 - name: Upload build artifacts uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 diff --git a/.github/workflows/platform-compile.yml b/.github/workflows/platform-compile.yml index 3fd0e1b..2d9fe23 100644 --- a/.github/workflows/platform-compile.yml +++ b/.github/workflows/platform-compile.yml @@ -28,7 +28,7 @@ jobs: - name: Compile generated Amber V2 web app # Exercise the clean beta.4 candidate before its version tag exists. - run: scripts/smoke_generated_web.sh ./amber a57b74c5842298f06dff58a1732442efc131a177 + run: scripts/smoke_generated_web.sh ./amber c4d08e67ead2f160b31b86945af57f0c13478d61 windows-x86-64: name: Windows x86_64 generated web app @@ -59,4 +59,4 @@ jobs: - name: Compile generated Amber V2 web app shell: pwsh # Exercise the clean beta.4 candidate before its version tag exists. - run: scripts/smoke_generated_web.ps1 ./amber.exe a57b74c5842298f06dff58a1732442efc131a177 + run: scripts/smoke_generated_web.ps1 ./amber.exe c4d08e67ead2f160b31b86945af57f0c13478d61 From 0701009f1e211648eb8a6736a11ff3cd148fd8aa Mon Sep 17 00:00:00 2001 From: crimson-knight Date: Wed, 12 Aug 2026 00:12:05 -0400 Subject: [PATCH 5/8] Verify generated apps with Windows runtime fix --- .github/workflows/build.yml | 2 +- .github/workflows/platform-compile.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 63bca4a..06073df 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -84,7 +84,7 @@ jobs: - name: Smoke test generated Amber V2 web app # Exercise the clean beta.4 candidate before its version tag exists. - run: scripts/smoke_generated_web.sh ./amber c4d08e67ead2f160b31b86945af57f0c13478d61 + run: scripts/smoke_generated_web.sh ./amber 51da178fb8fb364498fc1f106727a487ac2c505a - name: Upload build artifacts uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 diff --git a/.github/workflows/platform-compile.yml b/.github/workflows/platform-compile.yml index 2d9fe23..017129d 100644 --- a/.github/workflows/platform-compile.yml +++ b/.github/workflows/platform-compile.yml @@ -28,7 +28,7 @@ jobs: - name: Compile generated Amber V2 web app # Exercise the clean beta.4 candidate before its version tag exists. - run: scripts/smoke_generated_web.sh ./amber c4d08e67ead2f160b31b86945af57f0c13478d61 + run: scripts/smoke_generated_web.sh ./amber 51da178fb8fb364498fc1f106727a487ac2c505a windows-x86-64: name: Windows x86_64 generated web app @@ -59,4 +59,4 @@ jobs: - name: Compile generated Amber V2 web app shell: pwsh # Exercise the clean beta.4 candidate before its version tag exists. - run: scripts/smoke_generated_web.ps1 ./amber.exe c4d08e67ead2f160b31b86945af57f0c13478d61 + run: scripts/smoke_generated_web.ps1 ./amber.exe 51da178fb8fb364498fc1f106727a487ac2c505a From 6da0c483936b1bc23f0163ecdf2ea0c699707bbd Mon Sep 17 00:00:00 2001 From: crimson-knight Date: Wed, 12 Aug 2026 00:19:27 -0400 Subject: [PATCH 6/8] Pin generated apps to release dependencies --- scripts/check_beta_contract.sh | 13 +++++++++++ scripts/smoke_generated_web.ps1 | 3 +++ scripts/smoke_generated_web.sh | 1 + shard.lock | 2 +- shard.yml | 4 ++-- spec/generators/native_app_spec.cr | 13 ++++++++--- src/amber_cli/commands/new.cr | 2 +- src/amber_cli/generators/native_app.cr | 27 ++++++++++++++--------- src/amber_cli/templates/app/shard.yml.ecr | 2 +- 9 files changed, 49 insertions(+), 18 deletions(-) diff --git a/scripts/check_beta_contract.sh b/scripts/check_beta_contract.sh index 43c4f69..20777c7 100755 --- a/scripts/check_beta_contract.sh +++ b/scripts/check_beta_contract.sh @@ -13,7 +13,20 @@ grep -F 'model: grant' src/amber_cli/commands/new.cr grep -F 'database: #{database}' src/amber_cli/commands/new.cr grep -F 'github: crimson-knight/grant' src/amber_cli/commands/new.cr grep -F 'github: amberframework/asset_pipeline' src/amber_cli/commands/new.cr +grep -F 'version: ~> 0.37.0' src/amber_cli/commands/new.cr grep -F 'github: amberframework/asset_pipeline' src/amber_cli/templates/app/shard.yml.ecr +grep -F 'version: ~> 0.37.0' src/amber_cli/templates/app/shard.yml.ecr +grep -F 'github: amberframework/amber' src/amber_cli/generators/native_app.cr +grep -F 'version: 2.0.0-beta.4' src/amber_cli/generators/native_app.cr +grep -F 'version: ~> 0.37.0' src/amber_cli/generators/native_app.cr +if grep -F 'branch:' src/amber_cli/generators/native_app.cr; then + echo "native app template contains a mutable dependency branch" >&2 + exit 1 +fi +if grep -F 'shards install || true' src/amber_cli/generators/native_app.cr; then + echo "native app setup masks dependency installation failures" >&2 + exit 1 +fi grep -F 'github: amberframework/micrate' shard.yml test -s src/amber_cli/templates/app/config/database.cr.ecr test -s src/amber_cli/templates/app/config/assets.cr.ecr diff --git a/scripts/smoke_generated_web.ps1 b/scripts/smoke_generated_web.ps1 index 3774b3e..19aefe0 100644 --- a/scripts/smoke_generated_web.ps1 +++ b/scripts/smoke_generated_web.ps1 @@ -46,6 +46,9 @@ if (-not $manifest.Contains("github: crystal-lang/crystal-sqlite3")) { if (-not $manifest.Contains("github: amberframework/asset_pipeline")) { throw "Generated shard.yml does not include Asset Pipeline" } +if (-not $manifest.Contains("version: ~> 0.37.0")) { + throw "Generated shard.yml does not pin the supported Asset Pipeline release" +} if (-not $amberConfig.Contains("database: sqlite") -or -not $amberConfig.Contains("model: grant")) { throw "Generated .amber.yml does not select SQLite and Grant" } diff --git a/scripts/smoke_generated_web.sh b/scripts/smoke_generated_web.sh index a422288..9f22dae 100755 --- a/scripts/smoke_generated_web.sh +++ b/scripts/smoke_generated_web.sh @@ -33,6 +33,7 @@ grep -F "github: amberframework/amber" "$app_path/shard.yml" grep -F "version: 2.0.0-beta.4" "$app_path/shard.yml" grep -F "github: crimson-knight/grant" "$app_path/shard.yml" grep -F "github: amberframework/asset_pipeline" "$app_path/shard.yml" +grep -F "version: ~> 0.37.0" "$app_path/shard.yml" grep -F "github: crystal-lang/crystal-sqlite3" "$app_path/shard.yml" grep -F "template: ecr" "$app_path/.amber.yml" grep -F "database: sqlite" "$app_path/.amber.yml" diff --git a/shard.lock b/shard.lock index 9a50e13..febdfca 100644 --- a/shard.lock +++ b/shard.lock @@ -6,7 +6,7 @@ shards: asset_pipeline: git: https://github.com/amberframework/asset_pipeline.git - version: 0.37.0+git.commit.4700a4bb0ecd87ea533d89ebf2e6b7a90183bc5c + version: 0.37.0 backtracer: git: https://github.com/sija/backtracer.cr.git diff --git a/shard.yml b/shard.yml index bbebb36..6fee04b 100644 --- a/shard.yml +++ b/shard.yml @@ -16,10 +16,10 @@ targets: dependencies: - # Release candidate includes the native UI surface and static asset compiler. + # Stable static-asset compiler used by the CLI and generated applications. asset_pipeline: github: amberframework/asset_pipeline - commit: 4700a4bb + version: ~> 0.37.0 micrate: github: amberframework/micrate diff --git a/spec/generators/native_app_spec.cr b/spec/generators/native_app_spec.cr index 8fc56eb..ce0f1c8 100644 --- a/spec/generators/native_app_spec.cr +++ b/spec/generators/native_app_spec.cr @@ -30,11 +30,15 @@ describe AmberCLI::Generators::NativeApp do # Must have amber (patterns only) shard_content.should contain("amber:") - shard_content.should contain("crimson-knight/amber") + shard_content.should contain("github: amberframework/amber") + shard_content.should contain("version: 2.0.0-beta.4") - # Must have asset_pipeline with cross-platform branch + # Must have the released asset_pipeline with cross-platform UI support shard_content.should contain("asset_pipeline:") - shard_content.should contain("feature/utility-first-css-asset-pipeline") + shard_content.should contain("version: ~> 0.37.0") + + # Release manifests must be reproducible; no mutable dependency branches. + shard_content.should_not contain("branch:") # Must have crystal-audio shard_content.should contain("crystal-audio:") @@ -128,6 +132,9 @@ describe AmberCLI::Generators::NativeApp do makefile_content.should contain("macos-release:") makefile_content.should contain("setup:") makefile_content.should contain("spec:") + makefile_content.should contain("command -v shards-alpha") + makefile_content.should contain("$(CRYSTAL) spec spec/ -Dmacos") + makefile_content.should_not contain("shards install || true") end end diff --git a/src/amber_cli/commands/new.cr b/src/amber_cli/commands/new.cr index 8ca2531..5c168c7 100644 --- a/src/amber_cli/commands/new.cr +++ b/src/amber_cli/commands/new.cr @@ -290,7 +290,7 @@ dependencies: commit: 2665a978b43ac608c68cde9243821f8f8f053372 asset_pipeline: github: amberframework/asset_pipeline - commit: 4700a4bb + version: ~> 0.37.0 #{database_shard_dependency} SHARD diff --git a/src/amber_cli/generators/native_app.cr b/src/amber_cli/generators/native_app.cr index ca07901..9b4b0f6 100644 --- a/src/amber_cli/generators/native_app.cr +++ b/src/amber_cli/generators/native_app.cr @@ -106,7 +106,7 @@ version: 0.1.0 authors: - Your Name -crystal: ">= 1.15.0" +crystal: ">= 1.20.0, < 2.0" license: UNLICENSED @@ -117,36 +117,39 @@ targets: dependencies: # Amber Framework V2 (patterns only, NO HTTP server for native apps) amber: - github: crimson-knight/amber - branch: master + github: amberframework/amber + version: 2.0.0-beta.4 # Grant ORM (ActiveRecord-style, replaces Granite in V2) grant: github: crimson-knight/grant - branch: main + commit: 2665a978b43ac608c68cde9243821f8f8f053372 # Asset Pipeline (cross-platform UI: AppKit, UIKit, Android Views) - # IMPORTANT: Must use the feature branch for cross-platform UI support asset_pipeline: github: amberframework/asset_pipeline - branch: feature/utility-first-css-asset-pipeline + version: ~> 0.37.0 # Audio recording, playback, and transcription crystal-audio: github: crimson-knight/crystal-audio + commit: e99105e46a5d5793fe4fcc69c7e3cf3fc65c0966 # Database adapters (all required by Grant at compile time) pg: github: will/crystal-pg + version: 0.30.0 mysql: - github: crystal-lang/crystal-mysql + github: crimson-knight/crystal-mysql + commit: c061324dcef89a200a7a3f86a59b2ebf03f83602 sqlite3: github: crystal-lang/crystal-sqlite3 + version: 0.23.0 development_dependencies: ameba: github: crystal-ameba/ameba - version: ~> 1.6.4 + commit: cdd58b34b0d8a9c785d67183a7a8f07541549e55 SHARD File.write(File.join(path, "shard.yml"), content) @@ -243,7 +246,11 @@ all: macos # --- First-time setup --- setup: - shards-alpha install || shards install || true + @if command -v shards-alpha >/dev/null 2>&1; then \ + shards-alpha install; \ + else \ + shards install; \ + fi @# crystal-audio shard name has a hyphen but source uses underscore @# Crystal's require resolution needs the underscore directory @if [ ! -e lib/crystal_audio ]; then \\ @@ -292,7 +299,7 @@ run: macos # --- Tests --- spec: - crystal-alpha spec spec/ -Dmacos + $(CRYSTAL) spec spec/ -Dmacos # --- Clean --- diff --git a/src/amber_cli/templates/app/shard.yml.ecr b/src/amber_cli/templates/app/shard.yml.ecr index ce305b3..3059fda 100644 --- a/src/amber_cli/templates/app/shard.yml.ecr +++ b/src/amber_cli/templates/app/shard.yml.ecr @@ -21,7 +21,7 @@ dependencies: commit: 2665a978b43ac608c68cde9243821f8f8f053372 asset_pipeline: github: amberframework/asset_pipeline - commit: 67659880b11ef6cb91aa890d6f13fbc98000996a + version: ~> 0.37.0 <% case @database when "pg" -%> pg: From 1db4988447366df80ce3c2a54b7a8e556809cebc Mon Sep 17 00:00:00 2001 From: crimson-knight Date: Wed, 12 Aug 2026 00:24:36 -0400 Subject: [PATCH 7/8] Test generated apps against published releases --- .github/workflows/build.yml | 4 ++-- .github/workflows/platform-compile.yml | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 06073df..854b284 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -83,8 +83,8 @@ jobs: ! otool -L amber amber-lsp | grep -F 'openssl@1.1' - name: Smoke test generated Amber V2 web app - # Exercise the clean beta.4 candidate before its version tag exists. - run: scripts/smoke_generated_web.sh ./amber 51da178fb8fb364498fc1f106727a487ac2c505a + # Exercise the exact framework and asset releases emitted by the template. + run: scripts/smoke_generated_web.sh ./amber - name: Upload build artifacts uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 diff --git a/.github/workflows/platform-compile.yml b/.github/workflows/platform-compile.yml index 017129d..347e471 100644 --- a/.github/workflows/platform-compile.yml +++ b/.github/workflows/platform-compile.yml @@ -27,8 +27,8 @@ jobs: run: crystal build src/amber_cli.cr --no-debug -o amber - name: Compile generated Amber V2 web app - # Exercise the clean beta.4 candidate before its version tag exists. - run: scripts/smoke_generated_web.sh ./amber 51da178fb8fb364498fc1f106727a487ac2c505a + # Exercise the exact framework and asset releases emitted by the template. + run: scripts/smoke_generated_web.sh ./amber windows-x86-64: name: Windows x86_64 generated web app @@ -58,5 +58,5 @@ jobs: - name: Compile generated Amber V2 web app shell: pwsh - # Exercise the clean beta.4 candidate before its version tag exists. - run: scripts/smoke_generated_web.ps1 ./amber.exe 51da178fb8fb364498fc1f106727a487ac2c505a + # Exercise the exact framework and asset releases emitted by the template. + run: scripts/smoke_generated_web.ps1 ./amber.exe From e2714c1c5750cb8e718d79335b18acbec7620201 Mon Sep 17 00:00:00 2001 From: crimson-knight Date: Wed, 12 Aug 2026 00:31:11 -0400 Subject: [PATCH 8/8] Document native preview dependency contract --- RELEASE_NOTES_V2.0.5.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RELEASE_NOTES_V2.0.5.md b/RELEASE_NOTES_V2.0.5.md index e90295d..4c7a09b 100644 --- a/RELEASE_NOTES_V2.0.5.md +++ b/RELEASE_NOTES_V2.0.5.md @@ -39,6 +39,10 @@ fingerprinted files from `public/assets/manifest.json`. successful compile as sufficient evidence. - Publishes CLI archives for Apple Silicon macOS, x86_64 Linux, and ARM64 Linux; Windows x86_64 remains a source-build path with a release-gated app smoke. +- Replaces the native preview generator's old personal and feature branches + with the canonical Amber beta, released Asset Pipeline, and exact reviewed + dependency revisions. Its documented `make setup` now fails visibly when + dependencies cannot install instead of hiding the error. Grant, SQLite, Micrate migrations, and generated HTML resource CRUD remain the default persistence contract from CLI 2.0.4. Native applications,