gemstack-cli 0.2.0 → 0.2.2
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 +4 -4
- data/CHANGELOG.md +8 -0
- data/LICENSE.txt +1 -1
- data/README.md +2 -2
- data/lib/gemstack/cli/add_generator.rb +2 -0
- data/lib/gemstack/cli/app_generator.rb +3 -1
- data/lib/gemstack/cli/deploy_generator.rb +1 -2
- data/lib/gemstack/cli/doctor.rb +1 -1
- data/lib/gemstack/cli/generator.rb +22 -0
- data/lib/gemstack/cli/job_generator.rb +1 -0
- data/lib/gemstack/cli/resource_generator.rb +1 -0
- data/templates/app/Gemfile.tt +2 -0
- data/templates/app/app/jobs/application_job.rb +9 -0
- data/templates/app/app/mailers/application_mailer.rb +8 -0
- data/templates/app/app/mailers/templates/dot_keep +0 -0
- data/templates/app/app/models/application_model.rb +19 -0
- data/templates/app/app/serializers/application_serializer.rb +6 -0
- data/templates/app/config/environments/development.rb.tt +35 -0
- data/templates/app/config/environments/production.rb.tt +40 -0
- data/templates/app/config/environments/test.rb.tt +21 -0
- data/templates/auth/app/mailers/auth_mailer.rb +1 -1
- data/templates/auth/app/models/auth_token.rb +1 -1
- data/templates/auth/app/models/user.rb +1 -1
- data/templates/auth/app/serializers/api_token_serializer.rb +1 -1
- data/templates/auth/app/serializers/new_api_token_serializer.rb +1 -1
- data/templates/auth/app/serializers/user_serializer.rb +1 -1
- data/templates/frontend/app/page.module.css +315 -0
- data/templates/frontend/app/page.tsx.tt +171 -33
- data/templates/job/app/jobs/%file_name%.rb.tt +1 -1
- data/templates/resource/model/app/models/%file_name%.rb.tt +1 -1
- data/templates/resource/serializer/app/serializers/%file_name%_serializer.rb.tt +1 -1
- data/templates/storage/app/serializers/upload_serializer.rb +1 -1
- metadata +15 -8
- data/templates/app/config/environments/development.rb +0 -6
- data/templates/app/config/environments/production.rb +0 -8
- data/templates/app/config/environments/test.rb +0 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8f9ebe8567d9943d054f3baaf1427483fd75242677bc8a461034d63a826b197e
|
|
4
|
+
data.tar.gz: 816d6520c304822b290f30b417f574dfe8bd771586d5bc8a6df2b929ec144b4f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7068ec73cee6b4f0e87cb5c84a4f0531bbfd8444df9170a994e6223f8a32cf7d5b814ef089bc5c5854ce539e919bc9a5dde10118dcfcd035ac02099bff5e5e28
|
|
7
|
+
data.tar.gz: f1c8ddf36a3b0eb81f7a1a680b5c95bfa8df8328dbbf0804f927bfb85339f02fba020cb6e8d66e16f0c14fff3902a2183bd49a212dc1a39bf3ce650365b55b62
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.2
|
|
4
|
+
|
|
5
|
+
See the [GemStack changelog](https://github.com/gemstack-rb/gemstack/blob/main/CHANGELOG.md).
|
|
6
|
+
|
|
7
|
+
## 0.2.1
|
|
8
|
+
|
|
9
|
+
See the [GemStack changelog](https://github.com/gemstack-rb/gemstack/blob/main/CHANGELOG.md).
|
|
10
|
+
|
|
3
11
|
## 0.2.0
|
|
4
12
|
|
|
5
13
|
See the [GemStack changelog](https://github.com/gemstack-rb/gemstack/blob/main/CHANGELOG.md).
|
data/LICENSE.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2026
|
|
3
|
+
Copyright (c) 2026 Adware Technologies (https://www.adwaretech.com)
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
data/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
GemStack command-line interface and generators.
|
|
4
4
|
|
|
5
5
|
Part of [GemStack](https://github.com/gemstack-rb/gemstack), a modular Ruby API framework for Next.js
|
|
6
|
-
applications. All GemStack gems are developed together in that repository and released with the same
|
|
6
|
+
applications by [Adware Technologies](https://www.adwaretech.com). All GemStack gems are developed together in that repository and released with the same
|
|
7
7
|
version.
|
|
8
8
|
|
|
9
9
|
## Installation
|
|
@@ -21,4 +21,4 @@ Source, issues and pull requests: [gemstack-rb/gemstack](https://github.com/gems
|
|
|
21
21
|
|
|
22
22
|
## License
|
|
23
23
|
|
|
24
|
-
MIT —
|
|
24
|
+
Open source under the MIT License — © [Adware Technologies](https://www.adwaretech.com). See [LICENSE.txt](LICENSE.txt).
|
|
@@ -70,6 +70,7 @@ module GemStack
|
|
|
70
70
|
|
|
71
71
|
def add_storage
|
|
72
72
|
add_gem("gemstack-storage")
|
|
73
|
+
ensure_base_classes(@root, :serializer)
|
|
73
74
|
copy_feature_templates("storage")
|
|
74
75
|
add_routes(%(post "/uploads", to: "uploads#create" # direct uploads (gemstack add storage)\n),
|
|
75
76
|
marker: "uploads#create")
|
|
@@ -94,6 +95,7 @@ module GemStack
|
|
|
94
95
|
add_gem("gemstack-mail")
|
|
95
96
|
add_gem("gemstack-auth")
|
|
96
97
|
JobGenerator.install_migration(@root, output: @output) # emails are sent from background jobs
|
|
98
|
+
ensure_base_classes(@root, :model, :serializer, :mailer)
|
|
97
99
|
copy_feature_templates("auth", skip_migration: "*_create_auth_tables.rb")
|
|
98
100
|
include_in_application_controller("GemStack::Auth::Controller")
|
|
99
101
|
add_routes(AUTH_ROUTES, marker: "/auth/login")
|
|
@@ -54,7 +54,7 @@ module GemStack
|
|
|
54
54
|
database_adapter if database? # fail early on an unknown --database
|
|
55
55
|
@output.puts("Creating GemStack application #{name} in #{destination}")
|
|
56
56
|
render_directory("app", destination, skip: lambda { |rel|
|
|
57
|
-
!database? && (rel.start_with?("db/") || rel == "config/database.yml.tt")
|
|
57
|
+
!database? && (rel.start_with?("db/", "app/models/") || rel == "config/database.yml.tt")
|
|
58
58
|
})
|
|
59
59
|
render_directory("frontend", File.join(destination, "frontend")) if frontend?
|
|
60
60
|
install unless @options[:skip_install]
|
|
@@ -86,6 +86,8 @@ module GemStack
|
|
|
86
86
|
|
|
87
87
|
run_step("npm install", File.join(destination, "frontend"), "npm", "install", "--no-fund",
|
|
88
88
|
"--no-audit")
|
|
89
|
+
# The TypeScript contract, so frontend/lib/api/generated exists from the start.
|
|
90
|
+
run_step("gemstack contract", destination, "bin/gemstack", "contract", "--quiet") if bundled
|
|
89
91
|
end
|
|
90
92
|
|
|
91
93
|
# Best effort: a missing or password-protected database server shouldn't fail `new`.
|
|
@@ -18,8 +18,7 @@ module GemStack
|
|
|
18
18
|
def jobs?
|
|
19
19
|
return true if gemfile.match?(/^\s*gem "gemstack-auth"/)
|
|
20
20
|
|
|
21
|
-
gemfile.match?(/^\s*gem "gemstack-jobs"/) &&
|
|
22
|
-
%w[app/jobs/*.rb app/mailers/*.rb].any? { |glob| !Dir.glob(File.join(@root, glob)).empty? }
|
|
21
|
+
gemfile.match?(/^\s*gem "gemstack-jobs"/) && Generator.background_work?(@root)
|
|
23
22
|
end
|
|
24
23
|
|
|
25
24
|
def mail? = gemfile.match?(/^\s*gem "gemstack-(mail|auth)"/)
|
data/lib/gemstack/cli/doctor.rb
CHANGED
|
@@ -59,6 +59,28 @@ module GemStack
|
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
# Every template file for a generator, with app overrides applied.
|
|
62
|
+
# The app's base classes (written by `gemstack new` since 0.2.1). Apps
|
|
63
|
+
# created earlier get the ones a generator's code inherits from.
|
|
64
|
+
BASE_CLASSES = {
|
|
65
|
+
model: "app/models/application_model.rb", serializer: "app/serializers/application_serializer.rb",
|
|
66
|
+
job: "app/jobs/application_job.rb", mailer: "app/mailers/application_mailer.rb"
|
|
67
|
+
}.freeze
|
|
68
|
+
|
|
69
|
+
# Whether the app has jobs or mailers of its own (base classes don't count).
|
|
70
|
+
def self.background_work?(root)
|
|
71
|
+
%w[app/jobs/*.rb app/mailers/*.rb].any? do |glob|
|
|
72
|
+
Dir.glob(File.join(root, glob)).any? { |file| !File.basename(file).start_with?("application_") }
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def ensure_base_classes(root, *kinds)
|
|
77
|
+
kinds.each do |kind|
|
|
78
|
+
rel = BASE_CLASSES.fetch(kind)
|
|
79
|
+
target = File.join(root, rel)
|
|
80
|
+
write(target, File.read(File.join(template_root("app"), rel))) unless File.exist?(target)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
62
84
|
def template_files(name, override_root: nil)
|
|
63
85
|
files = relative_files(template_root(name)).to_h { |rel| [rel, File.join(template_root(name), rel)] }
|
|
64
86
|
if override_root
|
|
@@ -35,6 +35,7 @@ module GemStack
|
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def run
|
|
38
|
+
ensure_base_classes(@root, :job)
|
|
38
39
|
template_files("job", override_root: @root).each do |rel, source|
|
|
39
40
|
target = File.join(@root, rel.delete_suffix(".tt").gsub("%file_name%", file_name))
|
|
40
41
|
write(target, render(File.read(source), source))
|
data/templates/app/Gemfile.tt
CHANGED
|
@@ -14,6 +14,7 @@ path "<%= gemstack_path %>" do
|
|
|
14
14
|
gem "gemstack-db"
|
|
15
15
|
<% end -%>
|
|
16
16
|
gem "gemstack-jobs"
|
|
17
|
+
gem "gemstack-mail"
|
|
17
18
|
gem "gemstack-dev"
|
|
18
19
|
gem "gemstack-http"
|
|
19
20
|
gem "gemstack-schema"
|
|
@@ -24,6 +25,7 @@ gem "gemstack", "~> <%= version %>"
|
|
|
24
25
|
gem "gemstack-db", "~> <%= version %>"
|
|
25
26
|
<% end -%>
|
|
26
27
|
gem "gemstack-jobs", "~> <%= version %>"
|
|
28
|
+
gem "gemstack-mail", "~> <%= version %>"
|
|
27
29
|
<% end -%>
|
|
28
30
|
gem "puma", ">= 6.4"
|
|
29
31
|
<% if database? -%>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The base class for this app's background jobs (docs/background-jobs.md):
|
|
4
|
+
# retry and discard rules declared here apply to every job.
|
|
5
|
+
class ApplicationJob < GemStack::Job
|
|
6
|
+
# retry_on Net::ReadTimeout, attempts: 5, wait: 30 # seconds, :exponential or ->(attempt) { … }
|
|
7
|
+
# discard_on GemStack::NotFound # e.g. the record was deleted before the job ran
|
|
8
|
+
# queue :default
|
|
9
|
+
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The base class for this app's mailers (docs/mail.md). Templates live in
|
|
4
|
+
# app/mailers/templates/<mailer>/<action>.{text,html}.erb.
|
|
5
|
+
class ApplicationMailer < GemStack::Mailer
|
|
6
|
+
# default from: "My App <hello@example.com>" # otherwise config.mail.default_from (MAIL_FROM)
|
|
7
|
+
# default reply_to: "support@example.com"
|
|
8
|
+
end
|
|
File without changes
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The base class for this app's models — add plugins, validations and helpers
|
|
4
|
+
# every model should share here. `class Product < ApplicationModel` maps to the
|
|
5
|
+
# products table (docs/models.md).
|
|
6
|
+
#
|
|
7
|
+
# Created with Class.new on purpose: `class ApplicationModel < GemStack::Model`
|
|
8
|
+
# would make Sequel look for an "application_models" table.
|
|
9
|
+
ApplicationModel = Class.new(GemStack::Model) # rubocop:disable Style/EmptyClassDefinition
|
|
10
|
+
|
|
11
|
+
class ApplicationModel
|
|
12
|
+
# Sequel plugins for every model, e.g.:
|
|
13
|
+
# plugin :dirty # previous values after an update (column_changes)
|
|
14
|
+
|
|
15
|
+
# Shared query helpers:
|
|
16
|
+
# dataset_module do
|
|
17
|
+
# def recent = order(Sequel.desc(:created_at))
|
|
18
|
+
# end
|
|
19
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Development settings: applied after config/app.rb when GEMSTACK_ENV=development
|
|
4
|
+
# (the default). Each line shows the default value — uncomment one to change it.
|
|
5
|
+
# All settings: docs/configuration.md.
|
|
6
|
+
GemStack.configure do |config|
|
|
7
|
+
# ── Code ────────────────────────────────────────────────────────────────
|
|
8
|
+
# config.reload_code = true # reload app/ and config/routes.rb when they change
|
|
9
|
+
# config.eager_load = false # load app/ lazily, for a faster boot
|
|
10
|
+
# config.jit = nil # :yjit or :zjit to try Ruby's JIT locally
|
|
11
|
+
|
|
12
|
+
# ── Logging ─────────────────────────────────────────────────────────────
|
|
13
|
+
# config.logger.level = :debug # :debug, :info, :warn, :error (or GEMSTACK_LOG_LEVEL)
|
|
14
|
+
# config.logger.format = :pretty # readable lines; :json to see what production logs
|
|
15
|
+
|
|
16
|
+
# ── HTTP ────────────────────────────────────────────────────────────────
|
|
17
|
+
# config.http.show_exceptions = true # 500s carry the exception; browsers get an error page
|
|
18
|
+
# config.contract.docs = true # interactive API docs at /api/docs
|
|
19
|
+
|
|
20
|
+
# ── gemstack dev ────────────────────────────────────────────────────────
|
|
21
|
+
# config.dev.port = 3000 # the one public port (or PORT)
|
|
22
|
+
# config.dev.jobs_command = %w[bundle exec gemstack jobs] # nil: don't start a jobs worker
|
|
23
|
+
<% if database? -%>
|
|
24
|
+
|
|
25
|
+
# ── Database (connection: config/database.yml) ──────────────────────────
|
|
26
|
+
# config.db.log_queries = true # every SQL statement at DEBUG
|
|
27
|
+
# config.db.slow_query_ms = 500 # slower queries are logged at WARN
|
|
28
|
+
<% end -%>
|
|
29
|
+
|
|
30
|
+
# ── Cache ───────────────────────────────────────────────────────────────
|
|
31
|
+
# config.cache.store = :memory # :null to switch caching off, :redis to share it
|
|
32
|
+
|
|
33
|
+
# ── Mail ────────────────────────────────────────────────────────────────
|
|
34
|
+
# config.mail.delivery = :log # saved to tmp/mail (.eml + .html); :smtp really sends
|
|
35
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Production settings: applied after config/app.rb when GEMSTACK_ENV=production.
|
|
4
|
+
# Each line shows the default value — uncomment one to change it.
|
|
5
|
+
#
|
|
6
|
+
# Secrets and connections come from environment variables, never from files:
|
|
7
|
+
# SECRET_KEY_BASE, <% if database? %>DATABASE_URL, <% end %>SMTP_URL, MAIL_FROM, APP_URL, REDIS_URL…
|
|
8
|
+
# `gemstack doctor --production` checks what's missing (docs/deployment.md).
|
|
9
|
+
GemStack.configure do |config|
|
|
10
|
+
# ── Code ────────────────────────────────────────────────────────────────
|
|
11
|
+
# config.eager_load = true # load all code at boot (copy-on-write friendly with Puma workers)
|
|
12
|
+
# config.jit = :yjit # Ruby's JIT (or GEMSTACK_JIT=yjit|zjit|off)
|
|
13
|
+
|
|
14
|
+
# ── Logging ─────────────────────────────────────────────────────────────
|
|
15
|
+
# config.logger.level = :info
|
|
16
|
+
# config.logger.format = :json # one JSON object per line, with the request id
|
|
17
|
+
|
|
18
|
+
# ── HTTP ────────────────────────────────────────────────────────────────
|
|
19
|
+
# config.http.show_exceptions = false # never send exception details to clients
|
|
20
|
+
# config.http.hsts = "max-age=63072000; includeSubDomains" # on HTTPS requests; nil if your proxy sets it
|
|
21
|
+
# config.http.cors.origins = ["https://app.example.com"] # only if the frontend is on another domain
|
|
22
|
+
# config.http.max_body_size = 10 * 1024 * 1024 # larger requests get 413
|
|
23
|
+
<% if database? -%>
|
|
24
|
+
|
|
25
|
+
# ── Database ────────────────────────────────────────────────────────────
|
|
26
|
+
# config.db.pool_size = 5 # connections per process (GEMSTACK_MAX_THREADS)
|
|
27
|
+
# config.db.statement_timeout = 5_000 # ms; stop runaway queries (PostgreSQL, MySQL)
|
|
28
|
+
# config.db.slow_query_ms = 500 # slower queries are logged at WARN
|
|
29
|
+
<% end -%>
|
|
30
|
+
|
|
31
|
+
# ── Cache: :memory is per process — use Redis with several processes or servers ──
|
|
32
|
+
# config.cache.store = :redis # add gem "redis-client"; REDIS_URL
|
|
33
|
+
|
|
34
|
+
# ── Background jobs (`gemstack jobs`) ───────────────────────────────────
|
|
35
|
+
# config.jobs.concurrency = 5 # threads per worker (or GEMSTACK_JOB_CONCURRENCY)
|
|
36
|
+
|
|
37
|
+
# ── Mail ────────────────────────────────────────────────────────────────
|
|
38
|
+
# config.mail.delivery = :smtp # SMTP_URL=smtp://user:password@smtp.example.com:587
|
|
39
|
+
# config.mail.default_from = "App <hello@example.com>" # or MAIL_FROM
|
|
40
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Test settings: applied after config/app.rb when tests run (`gemstack test`).
|
|
4
|
+
# Each line shows the default value — uncomment one to change it.
|
|
5
|
+
GemStack.configure do |config|
|
|
6
|
+
# ── Logging ─────────────────────────────────────────────────────────────
|
|
7
|
+
# config.logger.level = :debug # logs are discarded in tests unless this (or GEMSTACK_LOG_LEVEL) is set
|
|
8
|
+
|
|
9
|
+
# ── HTTP ────────────────────────────────────────────────────────────────
|
|
10
|
+
# config.http.show_exceptions = true # a failing request's JSON includes the exception
|
|
11
|
+
<% if database? -%>
|
|
12
|
+
|
|
13
|
+
# ── Database (connection: the test section of config/database.yml, or TEST_DATABASE_URL) ──
|
|
14
|
+
# config.db.log_queries = false
|
|
15
|
+
<% end -%>
|
|
16
|
+
|
|
17
|
+
# ── Background work is recorded, not performed ──────────────────────────
|
|
18
|
+
# config.jobs.adapter = :test # assert_enqueued / perform_enqueued_jobs; :inline runs jobs at once
|
|
19
|
+
# config.mail.delivery = :test # assert_emails / last_email
|
|
20
|
+
# config.cache.store = :null # no caching, so tests can't leak into each other; :memory to test it
|
|
21
|
+
end
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Account emails. Templates: app/mailers/templates/auth_mailer/*.erb.
|
|
4
4
|
# Links point at the Next.js pages (config.auth.app_url, APP_URL in production).
|
|
5
|
-
class AuthMailer <
|
|
5
|
+
class AuthMailer < ApplicationMailer
|
|
6
6
|
def password_reset(user_id)
|
|
7
7
|
@user = User[user_id] or return
|
|
8
8
|
token, = GemStack::Auth::Tokens.issue(@user.id, purpose: "password_reset")
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# API tokens (and single-use reset/verification tokens) — see GemStack::Auth::Tokens.
|
|
4
|
-
class AuthToken <
|
|
4
|
+
class AuthToken < ApplicationModel
|
|
5
5
|
field :purpose, :string, null: false
|
|
6
6
|
field :name, :string, size: 100
|
|
7
7
|
field :last_used_at, :datetime
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Accounts. GemStack::Auth::User adds password hashing (Argon2id), email
|
|
4
4
|
# normalization, validations and User.authenticate_by(email:, password:).
|
|
5
|
-
class User <
|
|
5
|
+
class User < ApplicationModel
|
|
6
6
|
include GemStack::Auth::User
|
|
7
7
|
|
|
8
8
|
field :email, :string, null: false, size: 254
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# The response to creating an API token: the only time the token itself is shown.
|
|
4
|
-
class NewApiTokenSerializer <
|
|
4
|
+
class NewApiTokenSerializer < ApplicationSerializer
|
|
5
5
|
Result = Data.define(:token, :api_token)
|
|
6
6
|
|
|
7
7
|
attribute :token, :string
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/* The GemStack welcome page (app/page.tsx). Delete both files when you replace it. */
|
|
2
|
+
|
|
3
|
+
.shell {
|
|
4
|
+
--ruby: #e11d48;
|
|
5
|
+
--violet: #7c3aed;
|
|
6
|
+
--ink: #16181d;
|
|
7
|
+
--muted: #5b6170;
|
|
8
|
+
--surface: rgba(255, 255, 255, 0.72);
|
|
9
|
+
--border: rgba(22, 24, 29, 0.09);
|
|
10
|
+
--code: #f3f4f7;
|
|
11
|
+
--ok: #16a34a;
|
|
12
|
+
--pending: #d99a1e;
|
|
13
|
+
--error: #dc2626;
|
|
14
|
+
|
|
15
|
+
min-height: 100vh;
|
|
16
|
+
display: flex;
|
|
17
|
+
flex-direction: column;
|
|
18
|
+
color: var(--ink);
|
|
19
|
+
background:
|
|
20
|
+
radial-gradient(60rem 30rem at 10% -10%, rgba(225, 29, 72, 0.12), transparent 60%),
|
|
21
|
+
radial-gradient(50rem 30rem at 100% 0%, rgba(124, 58, 237, 0.12), transparent 60%),
|
|
22
|
+
#fbfbfd;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@media (prefers-color-scheme: dark) {
|
|
26
|
+
.shell {
|
|
27
|
+
--ink: #eceef4;
|
|
28
|
+
--muted: #9aa0ad;
|
|
29
|
+
--surface: rgba(24, 27, 36, 0.72);
|
|
30
|
+
--border: rgba(255, 255, 255, 0.08);
|
|
31
|
+
--code: rgba(255, 255, 255, 0.06);
|
|
32
|
+
--ok: #4ade80;
|
|
33
|
+
--pending: #fbbf24;
|
|
34
|
+
--error: #f87171;
|
|
35
|
+
|
|
36
|
+
background:
|
|
37
|
+
radial-gradient(60rem 30rem at 10% -10%, rgba(225, 29, 72, 0.22), transparent 60%),
|
|
38
|
+
radial-gradient(50rem 30rem at 100% 0%, rgba(124, 58, 237, 0.22), transparent 60%),
|
|
39
|
+
#0c0d12;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.main {
|
|
44
|
+
flex: 1;
|
|
45
|
+
width: 100%;
|
|
46
|
+
max-width: 68rem;
|
|
47
|
+
margin: 0 auto;
|
|
48
|
+
padding: clamp(3rem, 8vw, 6rem) 1.25rem 3rem;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/* Hero */
|
|
52
|
+
|
|
53
|
+
.hero {
|
|
54
|
+
text-align: center;
|
|
55
|
+
margin-bottom: clamp(2.5rem, 6vw, 4rem);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.logo {
|
|
59
|
+
width: 5rem;
|
|
60
|
+
height: 5rem;
|
|
61
|
+
filter: drop-shadow(0 12px 24px rgba(225, 29, 72, 0.35));
|
|
62
|
+
animation: float 6s ease-in-out infinite;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@keyframes float {
|
|
66
|
+
50% {
|
|
67
|
+
transform: translateY(-6px) rotate(-2deg);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@media (prefers-reduced-motion: reduce) {
|
|
72
|
+
.logo {
|
|
73
|
+
animation: none;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.eyebrow {
|
|
78
|
+
margin: 1rem 0 0.5rem;
|
|
79
|
+
font-size: 0.8rem;
|
|
80
|
+
font-weight: 700;
|
|
81
|
+
letter-spacing: 0.18em;
|
|
82
|
+
text-transform: uppercase;
|
|
83
|
+
color: var(--muted);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.title {
|
|
87
|
+
margin: 0;
|
|
88
|
+
font-size: clamp(2.2rem, 6vw, 3.6rem);
|
|
89
|
+
line-height: 1.1;
|
|
90
|
+
letter-spacing: -0.03em;
|
|
91
|
+
font-weight: 800;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.accent {
|
|
95
|
+
background: linear-gradient(120deg, #ff5f8f, var(--ruby) 45%, var(--violet));
|
|
96
|
+
-webkit-background-clip: text;
|
|
97
|
+
background-clip: text;
|
|
98
|
+
color: transparent;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.lead {
|
|
102
|
+
max-width: 38rem;
|
|
103
|
+
margin: 1.1rem auto 0;
|
|
104
|
+
font-size: clamp(1.02rem, 2.2vw, 1.2rem);
|
|
105
|
+
color: var(--muted);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.lead strong {
|
|
109
|
+
color: var(--ink);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* Live checks */
|
|
113
|
+
|
|
114
|
+
.checks {
|
|
115
|
+
display: flex;
|
|
116
|
+
flex-wrap: wrap;
|
|
117
|
+
justify-content: center;
|
|
118
|
+
gap: 0.6rem;
|
|
119
|
+
margin-top: 2rem;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.check {
|
|
123
|
+
display: inline-flex;
|
|
124
|
+
align-items: center;
|
|
125
|
+
gap: 0.55rem;
|
|
126
|
+
padding: 0.5rem 0.95rem;
|
|
127
|
+
border: 1px solid var(--border);
|
|
128
|
+
border-radius: 999px;
|
|
129
|
+
background: var(--surface);
|
|
130
|
+
backdrop-filter: blur(8px);
|
|
131
|
+
font-size: 0.92rem;
|
|
132
|
+
overflow-wrap: anywhere;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.check code {
|
|
136
|
+
font-size: 0.85em;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.dot {
|
|
140
|
+
width: 0.55rem;
|
|
141
|
+
height: 0.55rem;
|
|
142
|
+
flex: none;
|
|
143
|
+
border-radius: 50%;
|
|
144
|
+
background: currentColor;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.ok .dot {
|
|
148
|
+
background: var(--ok);
|
|
149
|
+
box-shadow: 0 0 0 4px color-mix(in srgb, var(--ok) 22%, transparent);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.pending .dot {
|
|
153
|
+
background: var(--pending);
|
|
154
|
+
animation: pulse 1.2s ease-in-out infinite;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.error .dot {
|
|
158
|
+
background: var(--error);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
@keyframes pulse {
|
|
162
|
+
50% {
|
|
163
|
+
opacity: 0.35;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/* Cards */
|
|
168
|
+
|
|
169
|
+
.grid {
|
|
170
|
+
display: grid;
|
|
171
|
+
grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
|
|
172
|
+
gap: 1rem;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.card {
|
|
176
|
+
position: relative;
|
|
177
|
+
display: flex;
|
|
178
|
+
flex-direction: column;
|
|
179
|
+
gap: 0.6rem;
|
|
180
|
+
padding: 1.4rem;
|
|
181
|
+
border: 1px solid var(--border);
|
|
182
|
+
border-radius: 16px;
|
|
183
|
+
background: var(--surface);
|
|
184
|
+
backdrop-filter: blur(8px);
|
|
185
|
+
transition:
|
|
186
|
+
transform 0.2s ease,
|
|
187
|
+
border-color 0.2s ease,
|
|
188
|
+
box-shadow 0.2s ease;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.card:hover {
|
|
192
|
+
transform: translateY(-2px);
|
|
193
|
+
border-color: color-mix(in srgb, var(--ruby) 35%, var(--border));
|
|
194
|
+
box-shadow: 0 16px 40px -24px rgba(225, 29, 72, 0.45);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.step {
|
|
198
|
+
display: grid;
|
|
199
|
+
place-items: center;
|
|
200
|
+
width: 1.9rem;
|
|
201
|
+
height: 1.9rem;
|
|
202
|
+
border-radius: 10px;
|
|
203
|
+
background: linear-gradient(135deg, var(--ruby), var(--violet));
|
|
204
|
+
color: #fff;
|
|
205
|
+
font-size: 0.85rem;
|
|
206
|
+
font-weight: 700;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.cardTitle {
|
|
210
|
+
margin: 0.3rem 0 0;
|
|
211
|
+
font-size: 1.08rem;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.cardText {
|
|
215
|
+
flex: 1;
|
|
216
|
+
margin: 0;
|
|
217
|
+
color: var(--muted);
|
|
218
|
+
font-size: 0.93rem;
|
|
219
|
+
line-height: 1.5;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.command {
|
|
223
|
+
display: flex;
|
|
224
|
+
align-items: flex-start;
|
|
225
|
+
gap: 0.5rem;
|
|
226
|
+
padding: 0.55rem 0.6rem 0.55rem 0.8rem;
|
|
227
|
+
border-radius: 10px;
|
|
228
|
+
background: var(--code);
|
|
229
|
+
font-size: 0.82rem;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.command code {
|
|
233
|
+
flex: 1;
|
|
234
|
+
min-width: 0;
|
|
235
|
+
padding: 0.15rem 0 0;
|
|
236
|
+
background: none;
|
|
237
|
+
line-height: 1.5;
|
|
238
|
+
overflow-wrap: anywhere; /* long commands wrap instead of being cut off */
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.prompt {
|
|
242
|
+
color: var(--ruby);
|
|
243
|
+
user-select: none;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.copy {
|
|
247
|
+
flex: none;
|
|
248
|
+
padding: 0.25rem 0.6rem;
|
|
249
|
+
border: 1px solid var(--border);
|
|
250
|
+
border-radius: 7px;
|
|
251
|
+
background: transparent;
|
|
252
|
+
color: var(--ink);
|
|
253
|
+
font: inherit;
|
|
254
|
+
font-size: 0.75rem;
|
|
255
|
+
cursor: pointer;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.copy:hover,
|
|
259
|
+
.copy:focus-visible {
|
|
260
|
+
border-color: var(--ruby);
|
|
261
|
+
color: var(--ruby);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.link {
|
|
265
|
+
align-self: flex-start;
|
|
266
|
+
color: var(--ruby);
|
|
267
|
+
font-weight: 600;
|
|
268
|
+
text-decoration: none;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.link:hover,
|
|
272
|
+
.link:focus-visible {
|
|
273
|
+
text-decoration: underline;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.edit {
|
|
277
|
+
margin: 2.5rem 0 0;
|
|
278
|
+
text-align: center;
|
|
279
|
+
color: var(--muted);
|
|
280
|
+
font-size: 0.92rem;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/* Footer */
|
|
284
|
+
|
|
285
|
+
.footer {
|
|
286
|
+
display: flex;
|
|
287
|
+
flex-wrap: wrap;
|
|
288
|
+
justify-content: space-between;
|
|
289
|
+
gap: 0.5rem 1.5rem;
|
|
290
|
+
width: 100%;
|
|
291
|
+
max-width: 68rem;
|
|
292
|
+
margin: 0 auto;
|
|
293
|
+
padding: 1.5rem 1.25rem 2rem;
|
|
294
|
+
border-top: 1px solid var(--border);
|
|
295
|
+
color: var(--muted);
|
|
296
|
+
font-size: 0.85rem;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.footer a {
|
|
300
|
+
color: var(--ink);
|
|
301
|
+
font-weight: 600;
|
|
302
|
+
text-decoration: none;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.footer a:hover,
|
|
306
|
+
.footer a:focus-visible {
|
|
307
|
+
color: var(--ruby);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
@media (max-width: 40rem) {
|
|
311
|
+
.footer {
|
|
312
|
+
justify-content: center;
|
|
313
|
+
text-align: center;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
@@ -1,45 +1,183 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { useQuery } from "@tanstack/react-query";
|
|
4
|
+
import { useState } from "react";
|
|
4
5
|
import { api, API_PATH } from "@/lib/gemstack/client";
|
|
6
|
+
import styles from "./page.module.css";
|
|
5
7
|
|
|
6
|
-
//
|
|
7
|
-
// Replace it with your
|
|
8
|
-
|
|
8
|
+
// The GemStack welcome page: shows that Next.js renders and that the browser
|
|
9
|
+
// reaches the Ruby API through the same origin. Replace it with your app —
|
|
10
|
+
// this file and app/page.module.css are yours to delete.
|
|
11
|
+
|
|
12
|
+
const DOCS = "https://github.com/gemstack-rb/gemstack/tree/main/docs";
|
|
13
|
+
|
|
14
|
+
export default function Welcome() {
|
|
9
15
|
const health = useQuery({
|
|
10
|
-
queryKey: ["health"],
|
|
11
|
-
queryFn: () =>
|
|
16
|
+
queryKey: ["gemstack", "health"],
|
|
17
|
+
queryFn: async () => {
|
|
18
|
+
const started = performance.now();
|
|
19
|
+
const body = await api.get<{ status: string }>("/health");
|
|
20
|
+
return { ...body, ms: Math.round(performance.now() - started) };
|
|
21
|
+
},
|
|
22
|
+
retry: 1,
|
|
12
23
|
});
|
|
24
|
+
const apiState = health.isPending ? "pending" : health.isError ? "error" : "ok";
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<div className={styles.shell}>
|
|
28
|
+
<main className={styles.main}>
|
|
29
|
+
<header className={styles.hero}>
|
|
30
|
+
<GemLogo className={styles.logo} />
|
|
31
|
+
<p className={styles.eyebrow}>GemStack</p>
|
|
32
|
+
<h1 className={styles.title}>
|
|
33
|
+
Yay! You're on <span className={styles.accent}>GemStack</span>.
|
|
34
|
+
</h1>
|
|
35
|
+
<p className={styles.lead}>
|
|
36
|
+
<strong><%= module_name %></strong> is running: a Next.js frontend and a Ruby API, served from one origin.
|
|
37
|
+
</p>
|
|
38
|
+
|
|
39
|
+
<div className={styles.checks} role="status" aria-live="polite">
|
|
40
|
+
<span className={`${styles.check} ${styles.ok}`}>
|
|
41
|
+
<span className={styles.dot} aria-hidden="true" /> Next.js rendering
|
|
42
|
+
</span>
|
|
43
|
+
<span className={`${styles.check} ${styles[apiState]}`}>
|
|
44
|
+
<span className={styles.dot} aria-hidden="true" />
|
|
45
|
+
{apiState === "pending" && "Connecting to the Ruby API…"}
|
|
46
|
+
{apiState === "ok" && (
|
|
47
|
+
<>
|
|
48
|
+
Ruby API connected · <code>GET {API_PATH}/health</code> in {health.data?.ms} ms
|
|
49
|
+
</>
|
|
50
|
+
)}
|
|
51
|
+
{apiState === "error" && <>Ruby API unreachable: {health.error?.message}</>}
|
|
52
|
+
</span>
|
|
53
|
+
</div>
|
|
54
|
+
</header>
|
|
13
55
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
56
|
+
<section className={styles.grid} aria-label="Next steps">
|
|
57
|
+
<Card
|
|
58
|
+
step="1"
|
|
59
|
+
title="Generate a resource"
|
|
60
|
+
text="Migration, model, API, typed TypeScript client and Next.js pages — in one command."
|
|
61
|
+
command="gemstack g resource Product name:string"
|
|
62
|
+
/>
|
|
63
|
+
<Card
|
|
64
|
+
step="2"
|
|
65
|
+
title="Explore your API"
|
|
66
|
+
text="Every endpoint with its types, and a form to try requests from the browser."
|
|
67
|
+
href={`${API_PATH}/docs`}
|
|
68
|
+
linkLabel={`Open ${API_PATH}/docs`}
|
|
69
|
+
/>
|
|
70
|
+
<Card
|
|
71
|
+
step="3"
|
|
72
|
+
title="Check your setup"
|
|
73
|
+
text="Ruby, Node.js, the database, migrations and the TypeScript contract — with the fix for anything off."
|
|
74
|
+
command="gemstack doctor"
|
|
75
|
+
/>
|
|
76
|
+
<Card
|
|
77
|
+
step="4"
|
|
78
|
+
title="Read the guides"
|
|
79
|
+
text="Models, controllers, jobs, realtime, authentication, storage and deployment."
|
|
80
|
+
href={DOCS}
|
|
81
|
+
linkLabel="Open the guides"
|
|
82
|
+
external
|
|
83
|
+
/>
|
|
84
|
+
</section>
|
|
85
|
+
|
|
86
|
+
<p className={styles.edit}>
|
|
87
|
+
Edit <code>frontend/app/page.tsx</code> to replace this page · API code lives in <code>app/</code>
|
|
88
|
+
</p>
|
|
89
|
+
</main>
|
|
90
|
+
|
|
91
|
+
<footer className={styles.footer}>
|
|
92
|
+
<span>
|
|
93
|
+
GemStack <%= version %> · Ruby <%= ruby_version %> · Next.js
|
|
94
|
+
</span>
|
|
95
|
+
<span>
|
|
96
|
+
Open source by{" "}
|
|
97
|
+
<a href="https://www.adwaretech.com" target="_blank" rel="noreferrer">
|
|
98
|
+
Adware Technologies
|
|
99
|
+
</a>
|
|
100
|
+
</span>
|
|
101
|
+
</footer>
|
|
102
|
+
</div>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function Card(props: {
|
|
107
|
+
step: string;
|
|
108
|
+
title: string;
|
|
109
|
+
text: string;
|
|
110
|
+
command?: string;
|
|
111
|
+
href?: string;
|
|
112
|
+
linkLabel?: string;
|
|
113
|
+
external?: boolean;
|
|
114
|
+
}) {
|
|
115
|
+
return (
|
|
116
|
+
<article className={styles.card}>
|
|
117
|
+
<span className={styles.step} aria-hidden="true">
|
|
118
|
+
{props.step}
|
|
119
|
+
</span>
|
|
120
|
+
<h2 className={styles.cardTitle}>{props.title}</h2>
|
|
121
|
+
<p className={styles.cardText}>{props.text}</p>
|
|
122
|
+
{props.command && <Command text={props.command} />}
|
|
123
|
+
{props.href && (
|
|
124
|
+
<a
|
|
125
|
+
className={styles.link}
|
|
126
|
+
href={props.href}
|
|
127
|
+
{...(props.external ? { target: "_blank", rel: "noreferrer" } : {})}
|
|
128
|
+
>
|
|
129
|
+
{props.linkLabel} <span aria-hidden="true">→</span>
|
|
130
|
+
</a>
|
|
131
|
+
)}
|
|
132
|
+
</article>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function Command({ text }: { text: string }) {
|
|
137
|
+
const [copied, setCopied] = useState(false);
|
|
138
|
+
async function copy() {
|
|
139
|
+
try {
|
|
140
|
+
await navigator.clipboard.writeText(text);
|
|
141
|
+
setCopied(true);
|
|
142
|
+
setTimeout(() => setCopied(false), 1500);
|
|
143
|
+
} catch {
|
|
144
|
+
// Clipboard unavailable (e.g. not a secure context): the command stays selectable.
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return (
|
|
148
|
+
<div className={styles.command}>
|
|
149
|
+
<code>
|
|
150
|
+
<span className={styles.prompt} aria-hidden="true">
|
|
151
|
+
$
|
|
152
|
+
</span>{" "}
|
|
153
|
+
{text}
|
|
154
|
+
</code>
|
|
155
|
+
<button type="button" className={styles.copy} onClick={copy} aria-label={`Copy: ${text}`}>
|
|
156
|
+
{copied ? "Copied" : "Copy"}
|
|
157
|
+
</button>
|
|
158
|
+
</div>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
20
161
|
|
|
162
|
+
function GemLogo({ className }: { className?: string }) {
|
|
21
163
|
return (
|
|
22
|
-
<
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
</
|
|
30
|
-
|
|
31
|
-
<
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
Edit this page: <code>frontend/app/page.tsx</code>
|
|
41
|
-
</li>
|
|
42
|
-
</ul>
|
|
43
|
-
</main>
|
|
164
|
+
<svg className={className} viewBox="0 0 64 64" role="img" aria-label="GemStack">
|
|
165
|
+
<defs>
|
|
166
|
+
<linearGradient id="gemstack-gem" x1="0" y1="0" x2="1" y2="1">
|
|
167
|
+
<stop offset="0" stopColor="#ff5f8f" />
|
|
168
|
+
<stop offset="0.55" stopColor="#e11d48" />
|
|
169
|
+
<stop offset="1" stopColor="#7c3aed" />
|
|
170
|
+
</linearGradient>
|
|
171
|
+
</defs>
|
|
172
|
+
<path d="M18 8h28l12 15-26 34L6 23z" fill="url(#gemstack-gem)" />
|
|
173
|
+
<path
|
|
174
|
+
d="M6 23h52M18 8l7 15 7-15 7 15 7-15M25 23l7 34 7-34"
|
|
175
|
+
fill="none"
|
|
176
|
+
stroke="rgba(255,255,255,0.55)"
|
|
177
|
+
strokeWidth="1.4"
|
|
178
|
+
strokeLinejoin="round"
|
|
179
|
+
/>
|
|
180
|
+
<path d="M18 8h28l-7 15H25z" fill="rgba(255,255,255,0.18)" />
|
|
181
|
+
</svg>
|
|
44
182
|
);
|
|
45
183
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Enqueue with <%= class_name %>.perform_later(...) — arguments must be JSON values (pass ids, not records).
|
|
4
4
|
# Runs in `gemstack jobs` (and in `gemstack dev`). Delivery is at-least-once: keep perform idempotent.
|
|
5
|
-
class <%= class_name %> <
|
|
5
|
+
class <%= class_name %> < ApplicationJob
|
|
6
6
|
<% if queue_name -%>
|
|
7
7
|
queue :<%= queue_name %>
|
|
8
8
|
<% end -%>
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
# What the API returns for a <%= class_name %>. Only listed attributes are exposed;
|
|
4
4
|
# their types (from <%= class_name %>'s fields) drive the generated TypeScript types.
|
|
5
|
-
class <%= class_name %>Serializer <
|
|
5
|
+
class <%= class_name %>Serializer < ApplicationSerializer
|
|
6
6
|
attributes :id, <%= fields.map { |f| ":#{f.column}" }.join(", ") %>, :created_at, :updated_at
|
|
7
7
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Where and how the browser sends the file (see UploadsController).
|
|
4
|
-
class UploadSerializer <
|
|
4
|
+
class UploadSerializer < ApplicationSerializer
|
|
5
5
|
Result = Data.define(:signed_id, :url, :http_method, :headers)
|
|
6
6
|
|
|
7
7
|
attribute :signed_id, :string
|
metadata
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gemstack-cli
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
|
+
- Adware Technologies
|
|
7
8
|
- Shoaib Malik
|
|
8
9
|
bindir: exe
|
|
9
10
|
cert_chain: []
|
|
@@ -15,28 +16,28 @@ dependencies:
|
|
|
15
16
|
requirements:
|
|
16
17
|
- - '='
|
|
17
18
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.2.
|
|
19
|
+
version: 0.2.2
|
|
19
20
|
type: :runtime
|
|
20
21
|
prerelease: false
|
|
21
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
23
|
requirements:
|
|
23
24
|
- - '='
|
|
24
25
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.2.
|
|
26
|
+
version: 0.2.2
|
|
26
27
|
- !ruby/object:Gem::Dependency
|
|
27
28
|
name: gemstack-dev
|
|
28
29
|
requirement: !ruby/object:Gem::Requirement
|
|
29
30
|
requirements:
|
|
30
31
|
- - '='
|
|
31
32
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: 0.2.
|
|
33
|
+
version: 0.2.2
|
|
33
34
|
type: :runtime
|
|
34
35
|
prerelease: false
|
|
35
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
37
|
requirements:
|
|
37
38
|
- - '='
|
|
38
39
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: 0.2.
|
|
40
|
+
version: 0.2.2
|
|
40
41
|
- !ruby/object:Gem::Dependency
|
|
41
42
|
name: thor
|
|
42
43
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -80,13 +81,18 @@ files:
|
|
|
80
81
|
- templates/app/Gemfile.tt
|
|
81
82
|
- templates/app/README.md.tt
|
|
82
83
|
- templates/app/app/controllers/application_controller.rb
|
|
84
|
+
- templates/app/app/jobs/application_job.rb
|
|
85
|
+
- templates/app/app/mailers/application_mailer.rb
|
|
86
|
+
- templates/app/app/mailers/templates/dot_keep
|
|
87
|
+
- templates/app/app/models/application_model.rb
|
|
88
|
+
- templates/app/app/serializers/application_serializer.rb
|
|
83
89
|
- templates/app/bin/gemstack
|
|
84
90
|
- templates/app/config.ru
|
|
85
91
|
- templates/app/config/app.rb.tt
|
|
86
92
|
- templates/app/config/database.yml.tt
|
|
87
|
-
- templates/app/config/environments/development.rb
|
|
88
|
-
- templates/app/config/environments/production.rb
|
|
89
|
-
- templates/app/config/environments/test.rb
|
|
93
|
+
- templates/app/config/environments/development.rb.tt
|
|
94
|
+
- templates/app/config/environments/production.rb.tt
|
|
95
|
+
- templates/app/config/environments/test.rb.tt
|
|
90
96
|
- templates/app/config/puma.rb
|
|
91
97
|
- templates/app/config/routes.rb
|
|
92
98
|
- templates/app/db/migrations/dot_keep
|
|
@@ -134,6 +140,7 @@ files:
|
|
|
134
140
|
- templates/deploy/dot_dockerignore
|
|
135
141
|
- templates/frontend/app/globals.css
|
|
136
142
|
- templates/frontend/app/layout.tsx.tt
|
|
143
|
+
- templates/frontend/app/page.module.css
|
|
137
144
|
- templates/frontend/app/page.tsx.tt
|
|
138
145
|
- templates/frontend/app/providers.tsx
|
|
139
146
|
- templates/frontend/lib/gemstack/client.ts
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
# Production-only settings. Defaults here: JSON logs, no exception details in
|
|
4
|
-
# responses, eager loading, HSTS on HTTPS requests.
|
|
5
|
-
GemStack.configure do |config|
|
|
6
|
-
# config.logger.level = :info
|
|
7
|
-
# config.http.cors.origins = ["https://app.example.com"] # only if the frontend is on another domain
|
|
8
|
-
end
|