gemstack 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6b4da4865f171f049ffb5861de51b0f9e376e8e684dac8c897c760194b1269ad
4
- data.tar.gz: 678190d65346a0542c88f0e977227c477666d98c92bf252eb5d03cae23116796
3
+ metadata.gz: '08b7f22bd22a410de8638e6249f629fc05d73c55c451ee15aaf5060aaceed7e9'
4
+ data.tar.gz: c4729f7d3833d7645abf7f1ca70f7e281b9a4ec8406d0482baedf0f039668162
5
5
  SHA512:
6
- metadata.gz: 6d16bf203a45f8b40a37dd13cac7d31c983b5f71ea84c6ae90d5dc566f306b650b6ab3410edcfe1c21248f0accf3af9870db4f57567727a0395678beb65d6897
7
- data.tar.gz: 392c2b983fb86297436e835136c92fc37c19fe6649bb55b3ea5c7f9d0e058849e397634ebcd612806d264f27edaced07ed55689c4b1504e28d0abcfe5ed8c531
6
+ metadata.gz: 8892c3a10498fda83a04ab4323c86f29898eb250f9a4cae8f3c90aa2b32c83ae60f0c79e87459c28f6eaa7bdc99812d0acb675062a79de981ec942ec450052f1
7
+ data.tar.gz: b3d2dc8f1d33abb5eca014e4679e5aa8a6a37772c1fb30d5720fcc638923787902a5324aeaaa2652476e1ce0e9815693f22f149d3a8e8b33e28a4bd288868ed6
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.1
4
+
5
+ See the [GemStack changelog](https://github.com/gemstack-rb/gemstack/blob/main/CHANGELOG.md).
6
+
3
7
  ## 0.4.0
4
8
 
5
9
  See the [GemStack changelog](https://github.com/gemstack-rb/gemstack/blob/main/CHANGELOG.md).
@@ -7,7 +7,7 @@ module GemStack
7
7
  # as a Rack app. There is one per process, available as GemStack.application.
8
8
  #
9
9
  # Boot sequence (idempotent):
10
- # 1. load .env files (development/test) config.env_files
10
+ # 1. load .env files config.env_files
11
11
  # 2. load config/environments/<env>.rb if present
12
12
  # 3. run plugin hooks GemStack::Plugins
13
13
  # 4. set up Zeitwerk for every directory in app/ (+ config.autoload_paths)
@@ -13,7 +13,7 @@ module GemStack
13
13
  end
14
14
 
15
15
  KAMAL_GEM = %(gem "kamal", require: false, group: :development # bundle exec kamal deploy (config/deploy.yml)\n)
16
- # The image runs Next.js's standalone server (node frontend/server.js); the
16
+ # The image runs Next.js's standalone server (node -r … frontend/server.js); the
17
17
  # Dockerfile asks for that build with GEMSTACK_NEXT_OUTPUT=standalone.
18
18
  STANDALONE = %( output: process.env.GEMSTACK_NEXT_OUTPUT === "standalone" ? "standalone" : undefined,\n)
19
19
 
data/lib/gemstack/cli.rb CHANGED
@@ -241,7 +241,7 @@ module GemStack
241
241
  are updated, and for ones you edited you choose: overwrite, skip, see the diff, or FILE.new.
242
242
 
243
243
  gemstack update # the latest release
244
- gemstack update 0.4.0 # a specific version
244
+ gemstack update 0.4.1 # a specific version
245
245
  gemstack update --templates --dry-run # only the template step, preview
246
246
  gemstack update --templates --from 0.3.5
247
247
  DESC
data/lib/gemstack/core.rb CHANGED
@@ -20,10 +20,13 @@ module GemStack
20
20
  setting :name, default: -> { File.basename(root) }
21
21
  setting :root, default: -> { Dir.pwd }
22
22
 
23
- # .env files loaded at boot, earlier files win. Real ENV always wins.
23
+ # .env files loaded at boot from the app root, in every environment (one
24
+ # .env for the API and the frontend: next.config.ts reads the same files).
25
+ # Earlier files win; real ENV always wins. Missing files are skipped, so
26
+ # production containers configured through ENV are unaffected.
24
27
  setting :env_files, default: lambda {
25
28
  env = GemStack.env
26
- env.local? ? [".env.#{env}.local", ".env.local", ".env.#{env}", ".env"] : []
29
+ [".env.#{env}.local", ".env.local", ".env.#{env}", ".env"]
27
30
  }
28
31
 
29
32
  # Root secret for signatures (storage URLs, derived keys). Production must
@@ -70,7 +73,7 @@ module GemStack
70
73
  end
71
74
 
72
75
  # First call in config/app.rb: sets the application root and loads .env
73
- # files (development/test) before anything reads configuration or ENV.
76
+ # files before anything reads configuration or ENV.
74
77
  def setup(root:)
75
78
  config.root = root.to_s
76
79
  load_env_files!
@@ -2,5 +2,5 @@
2
2
 
3
3
  module GemStack
4
4
  # All GemStack gems are released together under a single version.
5
- VERSION = "0.4.0"
5
+ VERSION = "0.4.1"
6
6
  end
@@ -9,7 +9,7 @@ require "gemstack/jobs" # background jobs (app/jobs)
9
9
  require "gemstack/mail" # mailers (app/mailers)
10
10
  # require "gemstack/storage" # file uploads — `gemstack add storage` turns this on
11
11
 
12
- # Sets the application root and loads .env files (development/test).
12
+ # Sets the application root and loads the .env files there (the frontend reads them too).
13
13
  GemStack.setup(root: File.expand_path("..", __dir__))
14
14
 
15
15
  # Require the gems listed in the Gemfile for the current environment.
@@ -3,7 +3,7 @@
3
3
  #
4
4
  # One image runs every process; config/deploy.yml (Kamal) gives each role its command:
5
5
  <% if frontend? -%>
6
- # web node frontend/server.js Next.js
6
+ # web node -r … frontend/server.js Next.js (standalone server)
7
7
  <% end -%>
8
8
  # api bundle exec puma -C config/puma.rb Ruby API (bin/docker-entrypoint migrates first)
9
9
  <% if jobs? -%>
@@ -13,7 +13,8 @@
13
13
  # docker build -t <%= service %> .
14
14
  #
15
15
  # Configuration comes from the environment at runtime (SECRET_KEY_BASE,
16
- # DATABASE_URL, …); nothing secret is baked into the image.
16
+ # DATABASE_URL, …); nothing secret is baked into the image. The frontend's
17
+ # NEXT_PUBLIC_* values, public by design, are compiled in at build time.
17
18
 
18
19
  ARG RUBY_VERSION=<%= ruby_version %>
19
20
  <% if frontend? -%>
@@ -42,7 +43,10 @@ COPY frontend/package.json frontend/package-lock.json ./
42
43
  RUN npm ci
43
44
  COPY frontend/ ./
44
45
  ENV NEXT_TELEMETRY_DISABLED=1 GEMSTACK_NEXT_OUTPUT=standalone
45
- RUN npm run build \
46
+ # NEXT_PUBLIC_* values are compiled into the JavaScript here. They come from the
47
+ # root .env as a build secret (.kamal/secrets: GEMSTACK_PUBLIC_ENV), mounted where
48
+ # next.config.ts reads the root .env, for this step only: never stored in the image.
49
+ RUN --mount=type=secret,id=GEMSTACK_PUBLIC_ENV,target=/.env,required=false npm run build \
46
50
  && mkdir -p public && cp -r public .next/standalone/ && cp -r .next/static .next/standalone/.next/
47
51
  <% end -%>
48
52
 
@@ -57,6 +61,7 @@ COPY --from=gems --chown=app:app /app /app
57
61
  <% if frontend? -%>
58
62
  COPY --from=frontend /usr/local/bin/node /usr/local/bin/node
59
63
  COPY --from=frontend --chown=app:app /frontend/.next/standalone /app/frontend
64
+ COPY --from=frontend --chown=app:app /frontend/lib/gemstack/root-env.cjs /app/frontend/lib/gemstack/root-env.cjs
60
65
  <% end -%>
61
66
  ENV GEMSTACK_API_HOST=0.0.0.0 GEMSTACK_API_PORT=4000<% if frontend? %> NODE_ENV=production NEXT_TELEMETRY_DISABLED=1 PORT=3000<% end %>
62
67
  USER app
@@ -16,7 +16,7 @@ servers:
16
16
  web:
17
17
  hosts:
18
18
  - 192.168.0.1 # CHANGE: your server's IP or hostname
19
- cmd: node frontend/server.js
19
+ cmd: node -r ./frontend/lib/gemstack/root-env.cjs frontend/server.js
20
20
  env:
21
21
  clear:
22
22
  # Server Components call the API directly, through the alias below.
@@ -111,6 +111,11 @@ volumes:
111
111
 
112
112
  builder:
113
113
  arch: amd64
114
+ <% if frontend? -%>
115
+ # The root .env's NEXT_PUBLIC_* lines, for `next build` (see .kamal/secrets).
116
+ secrets:
117
+ - GEMSTACK_PUBLIC_ENV
118
+ <% end -%>
114
119
  <% if postgresql? || mysql? || redis? -%>
115
120
 
116
121
  accessories:
@@ -5,6 +5,11 @@
5
5
  # never a secret's value. `gemstack doctor` flags values written in place.
6
6
 
7
7
  KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD
8
+ <% if frontend? -%>
9
+ # The frontend's NEXT_PUBLIC_* settings from the root .env (.env.production first),
10
+ # compiled into the JavaScript at build time — public by design, nothing else is read.
11
+ GEMSTACK_PUBLIC_ENV=$(grep -hsE '^(export )?NEXT_PUBLIC_' .env.production .env)
12
+ <% end -%>
8
13
  SECRET_KEY_BASE=$SECRET_KEY_BASE
9
14
  <% if postgresql? -%>
10
15
  POSTGRES_PASSWORD=$POSTGRES_PASSWORD
@@ -0,0 +1,44 @@
1
+ // One .env for the whole app: the root one, which the Ruby API reads too.
2
+ // Loads it into process.env when required — by next.config.ts (next dev,
3
+ // next build, next start) and, for Next.js's standalone server, which doesn't
4
+ // run next.config.ts, as a preload:
5
+ //
6
+ // node -r ./lib/gemstack/root-env.cjs .next/standalone/server.js
7
+ //
8
+ // Same files and order as GemStack: .env.<env>.local, .env.local, .env.<env>,
9
+ // .env (earlier files win); the real environment always wins. <env> is
10
+ // GEMSTACK_ENV, else NODE_ENV, else production (a bare `node server.js`).
11
+ const { readFileSync } = require("node:fs");
12
+ const { resolve } = require("node:path");
13
+
14
+ const ESCAPES = { n: "\n", t: "\t", r: "\r" };
15
+
16
+ function parseValue(value) {
17
+ if (value.startsWith('"')) {
18
+ const body = /^"((?:[^"\\]|\\.)*)"/.exec(value)?.[1] ?? value.slice(1);
19
+ return body.replace(/\\(.)/g, (_, char) => ESCAPES[char] ?? char);
20
+ }
21
+ if (value.startsWith("'")) return /^'([^']*)'/.exec(value)?.[1] ?? value.slice(1);
22
+ return value.replace(/\s+#.*$/, "");
23
+ }
24
+
25
+ function loadRootEnv(root = resolve(__dirname, "../../..")) {
26
+ const env = process.env.GEMSTACK_ENV || process.env.NODE_ENV || "production";
27
+ for (const file of [`.env.${env}.local`, ".env.local", `.env.${env}`, ".env"]) {
28
+ let source;
29
+ try {
30
+ source = readFileSync(resolve(root, file), "utf8");
31
+ } catch {
32
+ continue;
33
+ }
34
+ for (const line of source.split(/\r?\n/)) {
35
+ const match = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_.]*)\s*=\s*(.*)$/.exec(line);
36
+ if (!match || process.env[match[1]] !== undefined) continue;
37
+ process.env[match[1]] = parseValue(match[2].trim());
38
+ }
39
+ }
40
+ }
41
+
42
+ loadRootEnv();
43
+
44
+ module.exports = { loadRootEnv };
@@ -1,5 +1,10 @@
1
1
  import type { NextConfig } from "next";
2
2
 
3
+ // The root .env (shared with the Ruby API) — here so `next build` and
4
+ // `next start` see it wherever they run: NEXT_PUBLIC_* values are compiled
5
+ // into the JavaScript at build time. See lib/gemstack/root-env.cjs.
6
+ require("./lib/gemstack/root-env.cjs");
7
+
3
8
  // One public origin, in every environment:
4
9
  // - `gemstack dev`: the GemStack gateway routes /api/* to Ruby before requests reach Next.js.
5
10
  // - Production with Kamal (gemstack generate deploy): kamal-proxy routes /api/* to Ruby.
@@ -6,6 +6,7 @@
6
6
  "dev": "next dev",
7
7
  "build": "next build",
8
8
  "start": "next start",
9
+ "start:standalone": "node -r ./lib/gemstack/root-env.cjs .next/standalone/server.js",
9
10
  "typecheck": "tsc --noEmit"
10
11
  },
11
12
  "dependencies": {
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gemstack
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adware Technologies
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - '='
46
46
  - !ruby/object:Gem::Version
47
- version: 0.4.0
47
+ version: 0.4.1
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - '='
53
53
  - !ruby/object:Gem::Version
54
- version: 0.4.0
54
+ version: 0.4.1
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: json
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -346,6 +346,7 @@ files:
346
346
  - templates/frontend/app/page.tsx.tt
347
347
  - templates/frontend/app/providers.tsx
348
348
  - templates/frontend/lib/gemstack/client.ts
349
+ - templates/frontend/lib/gemstack/root-env.cjs
349
350
  - templates/frontend/next-env.d.ts
350
351
  - templates/frontend/next.config.ts
351
352
  - templates/frontend/package.json.tt