gemstack 0.4.2 → 0.4.3

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: 470fa3d30b1554cfa0aa75c71518f3ec47807a2f09f634c964720133c3cbe0d8
4
- data.tar.gz: 1b11a77cbac038b67fc22c1b746ef2dd8011bf729a3523b7d99bb32857c62ea5
3
+ metadata.gz: 6310feb0f5388f4664b5d3cc6a628487dae761765a4b8ee8c09c36e3096da3e8
4
+ data.tar.gz: ae643ed6d28ef9403ccc15b351adb77fddc3cb8e2573ffb27a75dd6207549a2f
5
5
  SHA512:
6
- metadata.gz: 7f52cbd61e22fc289cc33b7eaa9097ad5b3f8271a5b284599a164bd688547ccbda631268256d78c8f0bee9eb253f3b9b3adc74a86ac085292ffcd6896294f516
7
- data.tar.gz: ff4886ab02478e6a1355f47fbe0d072886b49be8c8c33060a06b706137f47836dfe31f2ee52db0335c71de9b82a46f980728b1a9a26e5f0614e01c8dd584b741
6
+ metadata.gz: d4b0d241f841248a9ffd8e9fa0947e60114299ae8d01e72cee926a48aec24e469fe47ff1dc00f9d75043453e4e90a898c2a728065fcdac1d57a89db596489e1a
7
+ data.tar.gz: 1c5f793e4228c3182bffb2162bf8d94ff38fc01e1aaea6b9fde397977cfcbf15d1905f1d9f962e7412a052023b8d8102fc39bba98eb92eae4eec1ad6653931b8
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.3
4
+
5
+ See the [GemStack changelog](https://github.com/gemstack-rb/gemstack/blob/main/CHANGELOG.md).
6
+
3
7
  ## 0.4.2
4
8
 
5
9
  See the [GemStack changelog](https://github.com/gemstack-rb/gemstack/blob/main/CHANGELOG.md).
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+
5
+ module GemStack
6
+ class CLI < Thor
7
+ # `gemstack deploy [KAMAL ARGS]` — Kamal with the app's root .env loaded,
8
+ # so .kamal/secrets ($SECRET_KEY_BASE, $DATABASE_URL, …) and the frontend's
9
+ # NEXT_PUBLIC_* build values come from the same file as everything else.
10
+ # Kamal doesn't read .env itself.
11
+ class DeployCommand
12
+ # Production values first; .env.local is left out on purpose: on a
13
+ # developer's machine it holds development overrides (a local
14
+ # DATABASE_URL…) that must never reach production.
15
+ ENV_FILES = %w[.env.production.local .env.production .env].freeze
16
+ # Kamal builds the image from a clone of the last commit: uncommitted
17
+ # changes wouldn't be deployed.
18
+ BUILDS = %w[deploy setup redeploy build].freeze
19
+
20
+ def initialize(root:, env: ENV, output: $stderr, exec: ->(*command, **opts) { Kernel.exec(*command, **opts) })
21
+ @root = root
22
+ @env = env
23
+ @output = output
24
+ @exec = exec
25
+ end
26
+
27
+ def run(args)
28
+ unless File.file?(File.join(@root, "config/deploy.yml"))
29
+ raise Thor::Error, "No config/deploy.yml: run `gemstack generate deploy` first (docs/deployment.md)"
30
+ end
31
+
32
+ args = ["deploy"] if args.empty?
33
+ refuse_uncommitted_changes! if BUILDS.include?(args.first)
34
+ load_env_files
35
+ @exec.call("bundle", "exec", "kamal", *args, chdir: @root)
36
+ end
37
+
38
+ private
39
+
40
+ def load_env_files
41
+ files = ENV_FILES.map { |name| File.join(@root, name) }.select { |path| File.file?(path) }
42
+ if files.empty?
43
+ return @output.puts("gemstack deploy: no .env.production or .env; .kamal/secrets reads the environment")
44
+ end
45
+
46
+ loaded = Dotenv.load(files, env: @env)
47
+ names = files.map { |path| File.basename(path) }.join(", ")
48
+ @output.puts("gemstack deploy: #{loaded.size} settings from #{names} (the environment wins)")
49
+ end
50
+
51
+ def refuse_uncommitted_changes!
52
+ changes, status = Open3.capture2("git", "-C", @root, "status", "--porcelain")
53
+ return unless status.success? && !changes.strip.empty?
54
+
55
+ raise Thor::Error, "Uncommitted changes: Kamal builds the image from the last commit, so they wouldn't " \
56
+ "be deployed. Commit (or stash) them first."
57
+ rescue SystemCallError
58
+ nil # no git: nothing to check
59
+ end
60
+ end
61
+
62
+ desc "deploy [KAMAL ARGS]", "Deploy with Kamal, the root .env.production/.env loaded (or any Kamal command)"
63
+ long_desc <<~DESC
64
+ Runs `bundle exec kamal` with the app's root .env files loaded (.env.production.local,
65
+ .env.production, .env — earlier files win, real environment variables win over all), so
66
+ .kamal/secrets resolves its $VARIABLES from them. Without arguments it runs `kamal deploy`,
67
+ and refuses when there are uncommitted changes (Kamal builds from the last commit).
68
+
69
+ gemstack deploy # build, push and deploy the last commit
70
+ gemstack deploy setup # the first deploy: Docker, kamal-proxy, accessories
71
+ gemstack deploy logs # any Kamal command or alias: console, migrate, rollback VERSION…
72
+ DESC
73
+ stop_on_unknown_option! :deploy
74
+ def deploy(*args)
75
+ DeployCommand.new(root: Project.root!).run(args)
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+
5
+ module GemStack
6
+ class CLI < Thor
7
+ # The deploy half of `gemstack update --templates`: the files
8
+ # `gemstack generate deploy` wrote (Dockerfile, config/deploy.yml,
9
+ # .kamal/secrets, bin/…). .gemstack/generators.json records each one's
10
+ # content as generated: a file that still matches was never edited and is
11
+ # updated; for one you edited, the release whose render matches is the base
12
+ # the merge starts from. Files a release adds (bin/deploy) are created.
13
+ module DeployFiles
14
+ DEPLOY_OWNER = "deploy:app"
15
+ # What DeployGenerator reads to decide what to write (frontend? database? jobs? mail?).
16
+ DEPLOY_INPUTS = %w[Gemfile Gemfile.lock config/app.rb config/database.yml .ruby-version .tool-versions
17
+ frontend/package.json frontend/next.config.ts].freeze
18
+
19
+ private
20
+
21
+ def update_deploy_files
22
+ @manifest = GenerationManifest.new(@root)
23
+ tracked = @manifest.files.select { |_, entry| entry["owner"] == DEPLOY_OWNER }
24
+ return unless tracked.any? || File.file?(File.join(@root, "config/deploy.yml"))
25
+
26
+ Dir.mktmpdir("gemstack-deploy-templates") do |cache|
27
+ @deploy_cache = cache
28
+ tree = render_deploy(Generator::TEMPLATES)
29
+ @output.puts(" Deploy files: couldn't render this version's deploy templates; skipped") unless tree
30
+ tree&.each { |path, file| apply_deploy(path, file, tracked[path]) }
31
+ end
32
+ @manifest.save unless @dry_run
33
+ end
34
+
35
+ def apply_deploy(path, file, entry)
36
+ mine_path = File.join(@root, path)
37
+ mine = File.binread(mine_path) if File.file?(mine_path)
38
+ return if mine == file.content
39
+ return (entry ? @result.removed << path : record_deploy(path, file) { create(path, file) }) if mine.nil?
40
+ if entry && Digest::SHA256.hexdigest(mine) == entry["sha256"] # never edited
41
+ return record_deploy(path, file) { update(path, file, :updated) }
42
+ end
43
+
44
+ base = deploy_base(path, entry)
45
+ return if base == file.content # this release didn't change it: your edits are yours
46
+
47
+ bases = base ? [base] : deploy_bases(path)
48
+ reason = "is new in this version, and you have your own" if entry.nil? && bases.empty?
49
+ record_deploy(path, file) { conflict(path, file, mine, bases, **{ reason: reason }.compact) }
50
+ end
51
+
52
+ # Runs the write, then records what the file now derives from: the new
53
+ # template, unless you kept yours (skip / FILE.new).
54
+ def record_deploy(path, file)
55
+ yield
56
+ written = (@result.created + @result.updated + @result.merged + @result.marked).include?(path)
57
+ @manifest.record_content(path, owner: DEPLOY_OWNER, content: file.content) if written && !@dry_run
58
+ end
59
+
60
+ # The template this file was generated from: the release render whose
61
+ # content matches the recorded checksum.
62
+ def deploy_base(path, entry)
63
+ return nil unless entry
64
+
65
+ deploy_releases.each do |source|
66
+ content = deploy_render(source)&.dig(path)&.content
67
+ return content if content && Digest::SHA256.hexdigest(content) == entry["sha256"]
68
+ end
69
+ nil
70
+ end
71
+
72
+ # Without a recorded base: every earlier release's version of the file
73
+ # (this release's isn't a base — merging with it would just keep yours).
74
+ def deploy_bases(path)
75
+ deploy_releases.drop(1).filter_map { |source| deploy_render(source)&.dig(path)&.content }.uniq
76
+ end
77
+
78
+ # This release (what a merged file was recorded against), the recorded
79
+ # version, then every earlier release, newest first.
80
+ def deploy_releases
81
+ @deploy_releases ||= [Generator::TEMPLATES] +
82
+ if @old_templates then Array(@old_templates)
83
+ else ([@from] + TemplateSource.releases_before(GemStack::VERSION).reverse).compact.uniq
84
+ end
85
+ end
86
+
87
+ def deploy_render(source)
88
+ (@deploy_renders ||= {}).fetch(source) do
89
+ templates = File.directory?(source.to_s) ? source : TemplateSource.locate(source, cache: @deploy_cache)
90
+ @deploy_renders[source] = templates && render_deploy(templates)
91
+ end
92
+ end
93
+
94
+ # `gemstack generate deploy` for this app (its database, frontend, jobs,
95
+ # pinned versions and any lib/templates/gemstack/deploy overrides), in a
96
+ # scratch copy of the files it reads.
97
+ def render_deploy(templates)
98
+ Dir.mktmpdir("gemstack-render-deploy") do |dir|
99
+ sandbox = File.join(dir, File.basename(@root))
100
+ copy_deploy_inputs(sandbox)
101
+ generator = DeployGenerator.new(root: sandbox, output: StringIO.new, templates: templates)
102
+ generator.run
103
+ generator.created.filter_map do |full|
104
+ rel = full.delete_prefix("#{sandbox}/")
105
+ [rel, TemplateUpdate::AppFile.new(File.binread(full), File.stat(full).mode)] unless rel == "vendor/.keep"
106
+ end.to_h
107
+ end
108
+ rescue StandardError
109
+ nil # a release whose deploy templates this version can't render: compare without it
110
+ end
111
+
112
+ def copy_deploy_inputs(sandbox)
113
+ globs = DEPLOY_INPUTS + %w[app/jobs/*.rb app/mailers/*.rb lib/templates/gemstack/deploy/**/*]
114
+ globs.flat_map { |glob| Dir.glob(glob, File::FNM_DOTMATCH, base: @root) }.each do |rel|
115
+ source = File.join(@root, rel)
116
+ next unless File.file?(source)
117
+
118
+ FileUtils.mkdir_p(File.dirname(File.join(sandbox, rel)))
119
+ FileUtils.cp(source, File.join(sandbox, rel))
120
+ end
121
+ File.write(File.join(sandbox, "Gemfile"), "") unless File.file?(File.join(sandbox, "Gemfile"))
122
+ end
123
+ end
124
+ end
125
+ end
@@ -7,11 +7,18 @@ module GemStack
7
7
  # to Ruby and handles HTTPS; database accessories), .kamal/secrets,
8
8
  # bin/docker-entrypoint and .dockerignore. Nothing is deployed anywhere.
9
9
  class DeployGenerator < Generator
10
- def initialize(root:, output: $stdout, force: false)
10
+ # templates: another release's templates/ directory (gemstack update
11
+ # renders old releases' deploy files to compare with).
12
+ def initialize(root:, output: $stdout, force: false, templates: nil)
11
13
  super(output: output, force: force)
12
14
  @root = root
15
+ @templates = templates
13
16
  end
14
17
 
18
+ # Bundler 4 lockfiles list gem checksums; gems installed from the local
19
+ # cache can be added without one, so fill them in before building.
20
+ CHECKSUMS_HINT = ", then bundle lock --add-checksums (Gemfile.lock lists gem checksums)"
21
+
15
22
  KAMAL_GEM = %(gem "kamal", require: false, group: :development # bundle exec kamal deploy (config/deploy.yml)\n)
16
23
  # The image runs Next.js's standalone server (node -r … frontend/server.js); the
17
24
  # Dockerfile asks for that build with GEMSTACK_NEXT_OUTPUT=standalone.
@@ -103,7 +110,12 @@ module GemStack
103
110
  return status("identical", path, "kamal") if gemfile.match?(/^\s*gem ["']kamal["']/)
104
111
 
105
112
  File.write(path, "#{gemfile.chomp}\n\n#{KAMAL_GEM}")
106
- status("gem", path, "kamal — run bundle install")
113
+ status("gem", path, "kamal — run bundle install#{CHECKSUMS_HINT if lockfile_checksums?}")
114
+ end
115
+
116
+ def lockfile_checksums?
117
+ lock = File.join(@root, "Gemfile.lock")
118
+ File.file?(lock) && File.read(lock).match?(/^CHECKSUMS$/)
107
119
  end
108
120
 
109
121
  def enable_standalone_frontend
@@ -14,7 +14,8 @@ module GemStack
14
14
  # Kinds that change the API, so the TypeScript contract is regenerated after them.
15
15
  CONTRACT_KINDS = %w[model controller resource].freeze
16
16
  # What `gemstack generate deploy` writes at the app's root.
17
- DEPLOY_FILES = %w[Dockerfile .dockerignore config/deploy.yml .kamal/secrets bin/docker-entrypoint].freeze
17
+ DEPLOY_FILES = %w[Dockerfile .dockerignore config/deploy.yml .kamal/secrets bin/docker-entrypoint
18
+ bin/deploy].freeze
18
19
  SHARED = (Generator::BASE_CLASSES.values + %w[
19
20
  app/controllers/application_controller.rb test/test_helper.rb
20
21
  frontend/lib/format.ts frontend/lib/gemstack/client.ts
@@ -56,6 +56,14 @@ module GemStack
56
56
  @dirty = true
57
57
  end
58
58
 
59
+ # Records a file as generated from `content` — what a later update
60
+ # compares with — whatever the file holds now (e.g. after a merge).
61
+ def record_content(relative, owner:, content:)
62
+ absolute(relative)
63
+ files[relative] = { "owner" => owner, "sha256" => Digest::SHA256.hexdigest(content) }
64
+ @dirty = true
65
+ end
66
+
59
67
  def record_route(line, owner:)
60
68
  entry = { "owner" => owner, "line" => line }
61
69
  return if routes.include?(entry)
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "digest"
3
4
  require "fileutils"
4
5
  require "json"
5
6
  require "net/http"
@@ -82,11 +83,19 @@ module GemStack
82
83
  # template are applied to your copy, keeping your edits. Where both changed
83
84
  # the same lines the file gets <<<<<<< conflict markers to resolve.
84
85
  #
86
+ # The files `gemstack generate deploy` wrote (Dockerfile, config/deploy.yml,
87
+ # .kamal/secrets, bin/…) are updated the same way. .gemstack/generators.json
88
+ # records each one's content as generated: a file that still matches was
89
+ # never edited; for one you edited, the release whose render matches is the
90
+ # base the merge starts from. Files a release adds (bin/deploy) are created.
91
+ #
85
92
  # Apps record the templates they're on in .gemstack/version. Older apps don't,
86
93
  # so each file is compared with every earlier release's template: matching
87
94
  # one means you never edited it (it's just out of date). Without any old
88
95
  # templates (offline), files that differ are offered, never updated.
89
96
  class TemplateUpdate
97
+ include DeployFiles
98
+
90
99
  VERSION_FILE = ".gemstack/version"
91
100
  # Managed elsewhere: the Gemfile by `gemstack update`/Bundler. Migrations
92
101
  # (db/migrations/) are the app's history and never part of an update.
@@ -121,6 +130,7 @@ module GemStack
121
130
  new_tree = render(Generator::TEMPLATES, version: GemStack::VERSION)
122
131
  old_trees = old_trees_for_comparison
123
132
  new_tree.each { |path, file| apply(path, file, old_trees.map { |tree| tree[path]&.content }) }
133
+ update_deploy_files
124
134
  report
125
135
  record_version unless @dry_run || @result.conflicts.any?
126
136
  @result
@@ -230,11 +240,11 @@ module GemStack
230
240
  status("update", path)
231
241
  end
232
242
 
233
- def conflict(path, file, mine, bases)
243
+ def conflict(path, file, mine, bases, reason: "changed in this version and you edited it")
234
244
  return record_conflict(path) unless @interactive
235
245
 
236
246
  loop do
237
- case @prompt.call("#{path} changed in this version and you edited it — [m]erge (keeps your edits), " \
247
+ case @prompt.call("#{path} #{reason} — [m]erge (keeps your edits), " \
238
248
  "[o]verwrite, [s]kip, [d]iff, save as [n]ew file?")
239
249
  when "m"
240
250
  return if merge(path, file, mine, bases)
data/lib/gemstack/cli.rb CHANGED
@@ -25,9 +25,11 @@ module GemStack
25
25
  require_relative "cli/deploy_generator"
26
26
  require_relative "cli/add_generator"
27
27
  require_relative "cli/update_generator"
28
+ require_relative "cli/deploy_files"
28
29
  require_relative "cli/template_update"
29
30
  require_relative "cli/commands/db"
30
31
  require_relative "cli/commands/jobs"
32
+ require_relative "cli/commands/deploy"
31
33
  require_relative "cli/console_methods"
32
34
  require_relative "cli/helpers"
33
35
 
@@ -238,10 +240,10 @@ module GemStack
238
240
  "~> VERSION" and runs bundle update for them. Without VERSION, uses the latest release on
239
241
  rubygems.org. Then updates the files `gemstack new` wrote (config, bin/, frontend setup…):
240
242
  files the release didn't change are left alone, new ones are created, ones you never edited
241
- are updated, and for ones you edited you choose: overwrite, skip, see the diff, or FILE.new.
243
+ are updated, and for ones you edited you choose: merge, overwrite, skip, see the diff, or FILE.new.
242
244
 
243
245
  gemstack update # the latest release
244
- gemstack update 0.4.2 # a specific version
246
+ gemstack update 0.4.3 # a specific version
245
247
  gemstack update --templates --dry-run # only the template step, preview
246
248
  gemstack update --templates --from 0.3.5
247
249
  DESC
@@ -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.2"
5
+ VERSION = "0.4.3"
6
6
  end
@@ -0,0 +1,9 @@
1
+ #!/bin/sh
2
+ # Deploys with Kamal, the root .env.production / .env loaded (docs/deployment.md):
3
+ #
4
+ # bin/deploy # build, push and deploy the last commit
5
+ # bin/deploy setup # the first deploy: Docker, kamal-proxy, accessories
6
+ # bin/deploy logs # any Kamal command or alias: console, migrate, rollback VERSION…
7
+ set -e
8
+ cd "$(dirname "$0")/.."
9
+ exec bundle exec gemstack deploy "$@"
@@ -1,10 +1,12 @@
1
1
  # Kamal deploy configuration for <%= app_name %> (gemstack generate deploy — docs/deployment.md).
2
2
  #
3
- # bundle exec kamal setup # first deploy: Docker, kamal-proxy (HTTPS) and accessories on the server
4
- # bundle exec kamal deploy # every deploy after that, with zero downtime
3
+ # bin/deploy setup # first deploy: Docker, kamal-proxy (HTTPS) and accessories on the server
4
+ # bin/deploy # every deploy after that (the last commit), with zero downtime
5
+ # bin/deploy logs # any Kamal command or alias below: console, migrate, rollback VERSION…
5
6
  #
6
- # Replace the placeholders marked CHANGE. Secrets are read from .kamal/secrets,
7
- # which only references environment variables, never their values.
7
+ # bin/deploy (gemstack deploy) runs Kamal with the root .env.production and .env
8
+ # loaded. Replace the placeholders marked CHANGE. Secrets are read from
9
+ # .kamal/secrets, which only references those variables, never their values.
8
10
 
9
11
  service: <%= service %>
10
12
 
@@ -17,6 +19,8 @@ servers:
17
19
  hosts:
18
20
  - 192.168.0.1 # CHANGE: your server's IP or hostname
19
21
  cmd: node -r ./frontend/lib/gemstack/root-env.cjs frontend/server.js
22
+ # Not the primary role (see primary_role below), so the proxy is enabled explicitly.
23
+ proxy: true
20
24
  env:
21
25
  clear:
22
26
  # Server Components call the API directly, through the alias below.
@@ -57,12 +61,21 @@ servers:
57
61
  cmd: bundle exec gemstack jobs
58
62
  <% end -%>
59
63
 
60
- primary_role: <%= frontend? ? "web" : "api" %>
64
+ <% if frontend? -%>
65
+ # The API boots first: the web role's healthcheck (/) renders pages that call it,
66
+ # which fails on the first deploy if the web role boots before any API is running.
67
+ <% end -%>
68
+ primary_role: api
61
69
 
62
70
  # HTTPS with a Let's Encrypt certificate: point the domain's DNS at the server first.
63
71
  proxy:
64
72
  ssl: true
65
73
  host: app.example.com # CHANGE: your domain
74
+ # Serving www and the bare domain: list both (each gets a certificate) and
75
+ # redirect one to the other in frontend/next.config.ts (docs/deployment.md).
76
+ # hosts:
77
+ # - www.example.com
78
+ # - example.com
66
79
  <% if frontend? -%>
67
80
  app_port: 3000
68
81
  healthcheck:
@@ -74,7 +87,7 @@ proxy:
74
87
  <% end -%>
75
88
 
76
89
  registry:
77
- # server: ghcr.io # for GitHub's registry; Docker Hub is the default
90
+ # server: ghcr.io # for GitHub's registry (password: see .kamal/secrets); Docker Hub is the default
78
91
  username: your-user # CHANGE
79
92
  password:
80
93
  - KAMAL_REGISTRY_PASSWORD
@@ -109,6 +122,10 @@ volumes:
109
122
  - "<%= service %>_data:/data"
110
123
  <% end -%>
111
124
 
125
+ # Kamal connects as root; set the user your server logs in with (e.g. ubuntu on AWS).
126
+ # ssh:
127
+ # user: ubuntu
128
+
112
129
  builder:
113
130
  arch: amd64
114
131
  <% if frontend? -%>
@@ -153,7 +170,7 @@ accessories:
153
170
  <% end -%>
154
171
  <% end -%>
155
172
 
156
- # bundle exec kamal console | logs | migrate | shell
173
+ # bin/deploy console | logs | migrate | shell
157
174
  aliases:
158
175
  console: app exec --interactive --reuse --roles=api "bundle exec gemstack console"
159
176
  logs: app logs --follow
@@ -3,12 +3,17 @@
3
3
  # This file is committed to git: only ever write REFERENCES here — environment
4
4
  # variables ($NAME) or password-manager commands ($(kamal secrets fetch ...)) —
5
5
  # never a secret's value. `gemstack doctor` flags values written in place.
6
+ #
7
+ # bin/deploy (gemstack deploy) loads the root .env.production.local, .env.production
8
+ # and .env (git-ignored) before Kamal reads this file: put the production values there.
6
9
 
10
+ # Docker Hub: an access token. GitHub's registry (ghcr.io): a token with write:packages,
11
+ # e.g. KAMAL_REGISTRY_PASSWORD=$(gh auth token) after `gh auth refresh -s write:packages`.
7
12
  KAMAL_REGISTRY_PASSWORD=$KAMAL_REGISTRY_PASSWORD
8
13
  <% if frontend? -%>
9
- # The frontend's NEXT_PUBLIC_* settings from the root .env (.env.production first),
14
+ # The frontend's NEXT_PUBLIC_* settings from the root .env files (production first),
10
15
  # 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)
16
+ GEMSTACK_PUBLIC_ENV=$(grep -hsE '^(export )?NEXT_PUBLIC_' .env.production.local .env.production .env)
12
17
  <% end -%>
13
18
  SECRET_KEY_BASE=$SECRET_KEY_BASE
14
19
  <% if postgresql? -%>
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.2
4
+ version: 0.4.3
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.2
47
+ version: 0.4.3
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.2
54
+ version: 0.4.3
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: json
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -175,9 +175,11 @@ files:
175
175
  - lib/gemstack/cli/add_next_steps.rb
176
176
  - lib/gemstack/cli/app_generator.rb
177
177
  - lib/gemstack/cli/commands/db.rb
178
+ - lib/gemstack/cli/commands/deploy.rb
178
179
  - lib/gemstack/cli/commands/jobs.rb
179
180
  - lib/gemstack/cli/console_methods.rb
180
181
  - lib/gemstack/cli/controller_generator.rb
182
+ - lib/gemstack/cli/deploy_files.rb
181
183
  - lib/gemstack/cli/deploy_generator.rb
182
184
  - lib/gemstack/cli/destroy_generator.rb
183
185
  - lib/gemstack/cli/destroy_generator/kinds.rb
@@ -336,6 +338,7 @@ files:
336
338
  - templates/controller/app/controllers/%file_name%_controller.rb.tt
337
339
  - templates/controller/test/controllers/%file_name%_controller_test.rb.tt
338
340
  - templates/deploy/Dockerfile.tt
341
+ - templates/deploy/bin/deploy
339
342
  - templates/deploy/bin/docker-entrypoint.tt
340
343
  - templates/deploy/config/deploy.yml.tt
341
344
  - templates/deploy/dot_dockerignore