brut 0.5.0 → 0.8.0
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/.gitignore +4 -0
- data/CHANGELOG.md +7 -0
- data/Dockerfile.dx +19 -0
- data/Gemfile.lock +1 -1
- data/README.md +19 -0
- data/assets/YouTubeThumb.pxd +0 -0
- data/bin/build +86 -0
- data/bin/ci +36 -0
- data/bin/docs +39 -9
- data/bin/publish +61 -0
- data/bin/setup +6 -0
- data/brut-css/bin/build +19 -0
- data/brut-css/bin/ci +19 -0
- data/brut-css/bin/docs +19 -0
- data/brut-css/bin/publish +21 -0
- data/brut-css/bin/setup +1 -0
- data/brut-css/package-lock.json +2 -2
- data/brut-css/package.json +1 -1
- data/brut-js/bin/build +15 -6
- data/brut-js/bin/docs +25 -0
- data/brut-js/bin/publish +21 -0
- data/brut-js/bin/setup +1 -0
- data/brut-js/dx +1 -0
- data/brut-js/package-lock.json +2 -2
- data/brut-js/package.json +1 -1
- data/brut.gemspec +2 -2
- data/brutrb.com/bin/setup +1 -0
- data/brutrb.com/getting-started.md +3 -0
- data/brutrb.com/overview.md +6 -0
- data/brutrb.com/tutorial.md +7 -3
- data/docs/404.html +2 -2
- data/docs/adrs.html +3 -3
- data/docs/ai.html +3 -3
- data/docs/assets/{app.D6BuVHo9.js → app.DyQLb4Ot.js} +1 -1
- data/docs/assets/chunks/@localSearchIndexroot.CmtZyrFA.js +1 -0
- data/docs/assets/chunks/{VPLocalSearchBox.BpvHMbx6.js → VPLocalSearchBox.T1iA-eJx.js} +1 -1
- data/docs/assets/chunks/{theme.wlAOvi2f.js → theme.ChwsbWjK.js} +2 -2
- data/docs/assets/{components.md.iLiv2E9X.js → components.md.DHh-NwKs.js} +3 -3
- data/docs/assets/{configuration.md.DmuAdsli.js → configuration.md.D8Wz3oJU.js} +1 -1
- data/docs/assets/{forms.md.D8aa_qI-.js → forms.md.BRE85eju.js} +1 -1
- data/docs/assets/{getting-started.md.DLplsDUd.js → getting-started.md.2ioiTe-B.js} +6 -3
- data/docs/assets/{getting-started.md.DLplsDUd.lean.js → getting-started.md.2ioiTe-B.lean.js} +1 -1
- data/docs/assets/overview.md.DlKiRRG_.js +1 -0
- data/docs/assets/overview.md.DlKiRRG_.lean.js +1 -0
- data/docs/assets/tutorial.md.BIb7XT6j.js +1 -0
- data/docs/assets/tutorial.md.BIb7XT6j.lean.js +1 -0
- data/docs/assets.html +3 -3
- data/docs/brut-js.html +3 -3
- data/docs/business-logic.html +3 -3
- data/docs/cli.html +3 -3
- data/docs/components.html +7 -7
- data/docs/configuration.html +5 -5
- data/docs/css.html +3 -3
- data/docs/custom-element-tests.html +3 -3
- data/docs/database-access.html +3 -3
- data/docs/database-schema.html +3 -3
- data/docs/deployment.html +3 -3
- data/docs/dev-environment.html +3 -3
- data/docs/dir-structure.html +3 -3
- data/docs/doc-conventions.html +3 -3
- data/docs/end-to-end-tests.html +3 -3
- data/docs/features.html +3 -3
- data/docs/flash-and-session.html +3 -3
- data/docs/form-constraints.html +3 -3
- data/docs/forms.html +5 -5
- data/docs/getting-started.html +9 -6
- data/docs/handlers.html +3 -3
- data/docs/hashmap.json +1 -1
- data/docs/hooks.html +3 -3
- data/docs/i18n.html +3 -3
- data/docs/index.html +3 -3
- data/docs/instrumentation.html +3 -3
- data/docs/javascript.html +3 -3
- data/docs/jobs.html +3 -3
- data/docs/keyword-injection.html +3 -3
- data/docs/layouts.html +3 -3
- data/docs/lsp.html +3 -3
- data/docs/markdown-examples.html +3 -3
- data/docs/middleware.html +3 -3
- data/docs/overview.html +5 -5
- data/docs/pages.html +3 -3
- data/docs/recipes/alternate-layouts.html +3 -3
- data/docs/recipes/authentication.html +3 -3
- data/docs/recipes/blank-layouts.html +3 -3
- data/docs/recipes/custom-flash.html +3 -3
- data/docs/recipes/indexed-forms.html +3 -3
- data/docs/recipes/migrations.html +3 -3
- data/docs/recipes/text-field-component.html +3 -3
- data/docs/roadmap.html +3 -3
- data/docs/routes.html +3 -3
- data/docs/security.html +3 -3
- data/docs/seed-data.html +3 -3
- data/docs/space-time-continuum.html +3 -3
- data/docs/tutorial.html +5 -5
- data/docs/unit-tests.html +3 -3
- data/docs/why.html +3 -3
- data/lib/brut/framework/mcp.rb +1 -1
- data/lib/brut/front_end/components/form_tag.rb +2 -2
- data/lib/brut/version.rb +1 -1
- data/mkbrut/.gitignore +16 -0
- data/mkbrut/CODE_OF_CONDUCT.txt +100 -0
- data/mkbrut/Gemfile +3 -0
- data/mkbrut/Gemfile.lock +19 -0
- data/mkbrut/LICENSE.txt +370 -0
- data/mkbrut/README.md +145 -0
- data/mkbrut/Rakefile +2 -0
- data/mkbrut/bin/build +36 -0
- data/mkbrut/bin/ci +19 -0
- data/mkbrut/bin/docs +19 -0
- data/mkbrut/bin/publish +129 -0
- data/mkbrut/bin/rake +16 -0
- data/mkbrut/bin/setup +30 -0
- data/mkbrut/brut-welcome.png +0 -0
- data/mkbrut/deploy/.dockerignore +2 -0
- data/mkbrut/deploy/Dockerfile +25 -0
- data/mkbrut/exe/mkbrut +5 -0
- data/mkbrut/lib/mkbrut/app.rb +79 -0
- data/mkbrut/lib/mkbrut/app_id.rb +8 -0
- data/mkbrut/lib/mkbrut/app_name.rb +29 -0
- data/mkbrut/lib/mkbrut/app_options.rb +36 -0
- data/mkbrut/lib/mkbrut/base.rb +57 -0
- data/mkbrut/lib/mkbrut/cli.rb +107 -0
- data/mkbrut/lib/mkbrut/erb_binding_delegate.rb +20 -0
- data/mkbrut/lib/mkbrut/internet_identifier.rb +32 -0
- data/mkbrut/lib/mkbrut/invalid_identifier.rb +4 -0
- data/mkbrut/lib/mkbrut/ops/add_css_import.rb +42 -0
- data/mkbrut/lib/mkbrut/ops/add_i18n_message.rb +74 -0
- data/mkbrut/lib/mkbrut/ops/add_method.rb +48 -0
- data/mkbrut/lib/mkbrut/ops/append_to_file.rb +20 -0
- data/mkbrut/lib/mkbrut/ops/base_op.rb +21 -0
- data/mkbrut/lib/mkbrut/ops/copy_file.rb +12 -0
- data/mkbrut/lib/mkbrut/ops/insert_code_in_method.rb +58 -0
- data/mkbrut/lib/mkbrut/ops/insert_route.rb +52 -0
- data/mkbrut/lib/mkbrut/ops/mkdir.rb +13 -0
- data/mkbrut/lib/mkbrut/ops/prism_parsing_op.rb +70 -0
- data/mkbrut/lib/mkbrut/ops/render_template.rb +26 -0
- data/mkbrut/lib/mkbrut/ops/skip_file.rb +10 -0
- data/mkbrut/lib/mkbrut/ops.rb +16 -0
- data/mkbrut/lib/mkbrut/organization.rb +5 -0
- data/mkbrut/lib/mkbrut/prefix.rb +26 -0
- data/mkbrut/lib/mkbrut/prefixed_io.rb +16 -0
- data/mkbrut/lib/mkbrut/segments/bare_bones.rb +185 -0
- data/mkbrut/lib/mkbrut/segments/demo.rb +121 -0
- data/mkbrut/lib/mkbrut/segments/heroku.rb +30 -0
- data/mkbrut/lib/mkbrut/segments/sidekiq.rb +3 -0
- data/mkbrut/lib/mkbrut/segments.rb +8 -0
- data/mkbrut/lib/mkbrut/version.rb +3 -0
- data/mkbrut/lib/mkbrut/versions.rb +13 -0
- data/mkbrut/lib/mkbrut.rb +18 -0
- data/mkbrut/mkbrut.gemspec +32 -0
- data/mkbrut/templates/Base/.dockerignore +25 -0
- data/mkbrut/templates/Base/.env.development.erb +60 -0
- data/mkbrut/templates/Base/.env.test.erb +8 -0
- data/mkbrut/templates/Base/.gitignore +31 -0
- data/mkbrut/templates/Base/.projections.json +59 -0
- data/mkbrut/templates/Base/Dockerfile.dx +205 -0
- data/mkbrut/templates/Base/Gemfile.erb +53 -0
- data/mkbrut/templates/Base/Procfile.development +5 -0
- data/mkbrut/templates/Base/Procfile.test +1 -0
- data/mkbrut/templates/Base/README.md +4 -0
- data/mkbrut/templates/Base/README.md.erb +40 -0
- data/mkbrut/templates/Base/app/bootstrap.rb +61 -0
- data/mkbrut/templates/Base/app/config/i18n/en/1_defaults.rb +128 -0
- data/mkbrut/templates/Base/app/config/i18n/en/2_app.rb +24 -0
- data/mkbrut/templates/Base/app/public/static/manifest.json.erb +33 -0
- data/mkbrut/templates/Base/app/src/app.rb.erb +37 -0
- data/mkbrut/templates/Base/app/src/back_end/data_models/app_data_model.rb +5 -0
- data/mkbrut/templates/Base/app/src/back_end/data_models/db.rb +19 -0
- data/mkbrut/templates/Base/app/src/back_end/data_models/migrations/20240101130000_citext.rb +6 -0
- data/mkbrut/templates/Base/app/src/back_end/data_models/seed/seed_data.rb +9 -0
- data/mkbrut/templates/Base/app/src/front_end/components/app_component.rb +8 -0
- data/mkbrut/templates/Base/app/src/front_end/components/custom_element_registration.rb.erb +7 -0
- data/mkbrut/templates/Base/app/src/front_end/css/index.css +2 -0
- data/mkbrut/templates/Base/app/src/front_end/css/svgs.css +12 -0
- data/mkbrut/templates/Base/app/src/front_end/forms/app_form.rb +4 -0
- data/mkbrut/templates/Base/app/src/front_end/handlers/app_handler.rb +4 -0
- data/mkbrut/templates/Base/app/src/front_end/images/LogoPylon.png +0 -0
- data/mkbrut/templates/Base/app/src/front_end/images/LogoTransit.png +0 -0
- data/mkbrut/templates/Base/app/src/front_end/images/apple-touch-icon-120x120.png +0 -0
- data/mkbrut/templates/Base/app/src/front_end/images/apple-touch-icon-152x152.png +0 -0
- data/mkbrut/templates/Base/app/src/front_end/images/apple-touch-icon-167x167.png +0 -0
- data/mkbrut/templates/Base/app/src/front_end/images/apple-touch-icon-180x180.png +0 -0
- data/mkbrut/templates/Base/app/src/front_end/images/favicon.ico +0 -0
- data/mkbrut/templates/Base/app/src/front_end/images/icon.png +0 -0
- data/mkbrut/templates/Base/app/src/front_end/images/mkicons.sh +6 -0
- data/mkbrut/templates/Base/app/src/front_end/js/index.js +6 -0
- data/mkbrut/templates/Base/app/src/front_end/layouts/default_layout.rb.erb +73 -0
- data/mkbrut/templates/Base/app/src/front_end/pages/app_page.rb +11 -0
- data/mkbrut/templates/Base/app/src/front_end/pages/home_page.rb +62 -0
- data/mkbrut/templates/Base/app/src/front_end/support/app_session.rb +6 -0
- data/mkbrut/templates/Base/app/src/front_end/svgs/README.md +5 -0
- data/mkbrut/templates/Base/app/src/front_end/svgs/comment-button.svg +59 -0
- data/mkbrut/templates/Base/bin/README.md.erb +5 -0
- data/mkbrut/templates/Base/bin/build-assets +7 -0
- data/mkbrut/templates/Base/bin/ci +39 -0
- data/mkbrut/templates/Base/bin/console +31 -0
- data/mkbrut/templates/Base/bin/db +9 -0
- data/mkbrut/templates/Base/bin/dbconsole +51 -0
- data/mkbrut/templates/Base/bin/dev +25 -0
- data/mkbrut/templates/Base/bin/release +26 -0
- data/mkbrut/templates/Base/bin/run +86 -0
- data/mkbrut/templates/Base/bin/scaffold +9 -0
- data/mkbrut/templates/Base/bin/setup +256 -0
- data/mkbrut/templates/Base/bin/startup-message +65 -0
- data/mkbrut/templates/Base/bin/test +9 -0
- data/mkbrut/templates/Base/bin/test-server +29 -0
- data/mkbrut/templates/Base/bin/watch-and-build-assets +37 -0
- data/mkbrut/templates/Base/config.ru +16 -0
- data/mkbrut/templates/Base/docker-compose.dx.yml +92 -0
- data/mkbrut/templates/Base/dx/README.md +28 -0
- data/mkbrut/templates/Base/dx/bash_customizations +12 -0
- data/mkbrut/templates/Base/dx/bash_customizations.local +8 -0
- data/mkbrut/templates/Base/dx/build +107 -0
- data/mkbrut/templates/Base/dx/docker-compose.env.erb +25 -0
- data/mkbrut/templates/Base/dx/dx.sh.lib +137 -0
- data/mkbrut/templates/Base/dx/exec +68 -0
- data/mkbrut/templates/Base/dx/prune +19 -0
- data/mkbrut/templates/Base/dx/show-help-in-app-container-then-wait.sh +38 -0
- data/mkbrut/templates/Base/dx/start +30 -0
- data/mkbrut/templates/Base/dx/stop +23 -0
- data/mkbrut/templates/Base/package.json.erb +37 -0
- data/mkbrut/templates/Base/puma.config.rb +53 -0
- data/mkbrut/templates/Base/specs/e2e/home_page.spec.rb.erb +23 -0
- data/mkbrut/templates/Base/specs/front_end/js/SpecHelper.js +24 -0
- data/mkbrut/templates/Base/specs/front_end/pages/home_page.spec.rb +22 -0
- data/mkbrut/templates/Base/specs/lint_factories.spec.rb +7 -0
- data/mkbrut/templates/Base/specs/spec_helper.rb +78 -0
- data/mkbrut/templates/Base/specs/support.rb +2 -0
- data/mkbrut/templates/segments/BareBones/app/src/front_end/handlers/trigger_exception_handler.rb +24 -0
- data/mkbrut/templates/segments/BareBones/app/src/front_end/js/Example.js.erb +49 -0
- data/mkbrut/templates/segments/BareBones/specs/front_end/handlers/trigger_exception_handler.spec.rb +41 -0
- data/mkbrut/templates/segments/BareBones/specs/front_end/js/Example.spec.js.erb +38 -0
- data/mkbrut/templates/segments/Demo/app/src/back_end/data_models/db/guestbook_message.rb +3 -0
- data/mkbrut/templates/segments/Demo/app/src/back_end/data_models/migrations/20250628194124_guestbook.rb +14 -0
- data/mkbrut/templates/segments/Demo/app/src/front_end/components/flash_component.rb +36 -0
- data/mkbrut/templates/segments/Demo/app/src/front_end/css/constraint-violations.css +18 -0
- data/mkbrut/templates/segments/Demo/app/src/front_end/css/fonts.css +19 -0
- data/mkbrut/templates/segments/Demo/app/src/front_end/fonts/monaspace-xenon.ttf +0 -0
- data/mkbrut/templates/segments/Demo/app/src/front_end/forms/guestbook_message_form.rb +4 -0
- data/mkbrut/templates/segments/Demo/app/src/front_end/handlers/guestbook_message_handler.rb +64 -0
- data/mkbrut/templates/segments/Demo/app/src/front_end/pages/guestbook_page/message_component.rb +41 -0
- data/mkbrut/templates/segments/Demo/app/src/front_end/pages/guestbook_page.rb +43 -0
- data/mkbrut/templates/segments/Demo/app/src/front_end/pages/new_guestbook_message_page.rb +64 -0
- data/mkbrut/templates/segments/Demo/specs/back_end/data_models/db/guestbook_message.spec.rb +5 -0
- data/mkbrut/templates/segments/Demo/specs/e2e/guest_message.spec.rb +54 -0
- data/mkbrut/templates/segments/Demo/specs/factories/db/guestbook_message.factory.rb +7 -0
- data/mkbrut/templates/segments/Demo/specs/front_end/components/flash_component.spec.rb +5 -0
- data/mkbrut/templates/segments/Demo/specs/front_end/handlers/guestbook_message_handler.spec.rb +122 -0
- data/mkbrut/templates/segments/Demo/specs/front_end/pages/guestbook_page/message_component.spec.rb +5 -0
- data/mkbrut/templates/segments/Demo/specs/front_end/pages/guestbook_page.spec.rb +52 -0
- data/mkbrut/templates/segments/Demo/specs/front_end/pages/new_guestbook_message_page.spec.rb +5 -0
- data/mkbrut/templates/segments/Heroku/bin/deploy +11 -0
- data/mkbrut/templates/segments/Heroku/deploy/Dockerfile +125 -0
- data/mkbrut/templates/segments/Heroku/deploy/docker-entrypoint +15 -0
- data/mkbrut/templates/segments/Heroku/deploy/heroku_config.rb +26 -0
- metadata +185 -21
- data/docs/assets/chunks/@localSearchIndexroot.COP2Bcmp.js +0 -1
- data/docs/assets/overview.md.iMnwLO4x.js +0 -1
- data/docs/assets/overview.md.iMnwLO4x.lean.js +0 -1
- data/docs/assets/tutorial.md.BYXj4cOu.js +0 -1
- data/docs/assets/tutorial.md.BYXj4cOu.lean.js +0 -1
- /data/docs/assets/{components.md.iLiv2E9X.lean.js → components.md.DHh-NwKs.lean.js} +0 -0
- /data/docs/assets/{configuration.md.DmuAdsli.lean.js → configuration.md.D8Wz3oJU.lean.js} +0 -0
- /data/docs/assets/{forms.md.D8aa_qI-.lean.js → forms.md.BRE85eju.lean.js} +0 -0
data/mkbrut/bin/publish
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
#!/usr/bin/env bash
|
2
|
+
|
3
|
+
set -e
|
4
|
+
SCRIPT_DIR=$( cd -- "$( dirname -- "${0}" )" > /dev/null 2>&1 && pwd )
|
5
|
+
ROOT_DIR=$(realpath "$SCRIPT_DIR/..")
|
6
|
+
|
7
|
+
main() {
|
8
|
+
detect_gem_version
|
9
|
+
detect_docker_platform
|
10
|
+
release_gem
|
11
|
+
build_and_maybe_push
|
12
|
+
}
|
13
|
+
|
14
|
+
log() {
|
15
|
+
echo "[ mkbrut/bin/publish ] ${*}"
|
16
|
+
}
|
17
|
+
|
18
|
+
usage() {
|
19
|
+
echo "Usage: $0"
|
20
|
+
echo
|
21
|
+
echo " Publish Ruby Gem and Docker image"
|
22
|
+
echo
|
23
|
+
}
|
24
|
+
|
25
|
+
release_gem() {
|
26
|
+
log "Releasing Gem"
|
27
|
+
rake release
|
28
|
+
wait_for_rubygems
|
29
|
+
}
|
30
|
+
|
31
|
+
wait_for_rubygems() {
|
32
|
+
timeout=30
|
33
|
+
interval=2
|
34
|
+
elapsed=0
|
35
|
+
|
36
|
+
log "Waiting ${timeout} seconds for mkbrut $GEM_VERSION to be available on RubyGems"
|
37
|
+
while (( elapsed < timeout )); do
|
38
|
+
if gem list -r "mkbrut" -a | grep -q "$GEM_VERSION"; then
|
39
|
+
log "mkbrut $GEM_VERSION is available on RubyGems"
|
40
|
+
return 0
|
41
|
+
fi
|
42
|
+
sleep $interval
|
43
|
+
(( elapsed += interval ))
|
44
|
+
done
|
45
|
+
|
46
|
+
log "mkbrut $GEM_VERSION is not available on RubyGems after $timeout seconds"
|
47
|
+
log "Something may have gone wrong. Or, it could just be taking a while"
|
48
|
+
log "You can re-run this with -G to bypassing pushing to RubyGems"
|
49
|
+
echo
|
50
|
+
log " bin/deploy -G"
|
51
|
+
exit 1
|
52
|
+
}
|
53
|
+
|
54
|
+
setup_docker_buildx() {
|
55
|
+
local builder_name=mkbrut-builder
|
56
|
+
|
57
|
+
if ! docker buildx ls | grep -q "^$builder_name"; then
|
58
|
+
log "Creating Docker buildx builder: $builder_name"
|
59
|
+
docker buildx create --name "$builder_name" --use
|
60
|
+
docker buildx inspect "$builder_name" --bootstrap
|
61
|
+
else
|
62
|
+
log "Docker buildx builder $builder_name already exists"
|
63
|
+
docker buildx use "$builder_name"
|
64
|
+
fi
|
65
|
+
}
|
66
|
+
|
67
|
+
detect_gem_version() {
|
68
|
+
|
69
|
+
GEM_VERSION=$( rake -T | grep "rake install " | sed 's/^.*mkbrut-//g' | sed 's/\.gem.*$//g')
|
70
|
+
if [[ "$GEM_VERSION" =~ ^[0-9]+\.[0-9]\.[0-9]+$ ]]; then
|
71
|
+
log "Releasing version ${GEM_VERSION}"
|
72
|
+
else
|
73
|
+
log "Version '${GEM_VERSION}' does not look like a version: X.Y.Z"
|
74
|
+
exit 1
|
75
|
+
fi
|
76
|
+
}
|
77
|
+
|
78
|
+
detect_docker_platform() {
|
79
|
+
|
80
|
+
KERNEL_NAME_FOR_ARCHITECTURE=$(docker info --format '{{.Architecture}}')
|
81
|
+
OSTYPE=$(docker info --format '{{.OSType}}')
|
82
|
+
|
83
|
+
case "$KERNEL_NAME_FOR_ARCHITECTURE" in
|
84
|
+
"aarch64")
|
85
|
+
DOCKER_NAME_FOR_ARCHITECTURE="arm64"
|
86
|
+
;;
|
87
|
+
"x86_64")
|
88
|
+
DOCKER_NAME_FOR_ARCHITECTURE="amd64"
|
89
|
+
;;
|
90
|
+
*)
|
91
|
+
log "Docker reported architecture is $KERNEL_NAME_FOR_ARCHITECTURE - hoping this works"
|
92
|
+
;;
|
93
|
+
esac
|
94
|
+
HOST_PLATFORM=${OSTYPE}/${DOCKER_NAME_FOR_ARCHITECTURE}
|
95
|
+
}
|
96
|
+
|
97
|
+
build_and_maybe_push() {
|
98
|
+
|
99
|
+
# See bin/setup for another use of this page
|
100
|
+
local docker_credentials_source="${SCRIPT_DIR}/../dx/credentials/dockerhub.credentials"
|
101
|
+
setup_docker_buildx
|
102
|
+
|
103
|
+
. "${docker_credentials_source}"
|
104
|
+
log "Loging into DockerHub with credentials from ${docker_credentials_source}"
|
105
|
+
docker login --username "${DOCKERHUB_USERNAME}" --password "${DOCKERHUB_PAT}"
|
106
|
+
|
107
|
+
DOCKER_BUILD_PUSH_OR_LOAD=--push
|
108
|
+
DOCKER_PLATFORMS="linux/amd64,linux/arm64"
|
109
|
+
|
110
|
+
log "Building Docker image and pushing to DockerHub"
|
111
|
+
|
112
|
+
docker buildx build \
|
113
|
+
--platform ${DOCKER_PLATFORMS} \
|
114
|
+
${DOCKER_BUILD_PUSH_OR_LOAD} \
|
115
|
+
--no-cache \
|
116
|
+
--build-arg brut_version="${GEM_VERSION}" \
|
117
|
+
--tag thirdtank/mkbrut:"${GEM_VERSION}" \
|
118
|
+
--tag thirdtank/mkbrut:latest \
|
119
|
+
deploy
|
120
|
+
}
|
121
|
+
|
122
|
+
for arg in "${@}"; do
|
123
|
+
if [ "${arg}" = "-h" ] || [ "${arg}" = "--help" ] || [ "${arg}" = "help" ]; then
|
124
|
+
usage
|
125
|
+
exit 0
|
126
|
+
fi
|
127
|
+
done
|
128
|
+
|
129
|
+
main
|
data/mkbrut/bin/rake
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rake' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
|
12
|
+
|
13
|
+
require "rubygems"
|
14
|
+
require "bundler/setup"
|
15
|
+
|
16
|
+
load Gem.bin_path("rake", "rake")
|
data/mkbrut/bin/setup
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require_relative "../../bin/bin_kit"
|
4
|
+
require "optparse"
|
5
|
+
require "pathname"
|
6
|
+
require "fileutils"
|
7
|
+
|
8
|
+
def setup(update_gems:)
|
9
|
+
if update_gems
|
10
|
+
log "Updating gems"
|
11
|
+
system! "bundle update"
|
12
|
+
else
|
13
|
+
log "Installing gems"
|
14
|
+
# Only do bundle install if the much-faster
|
15
|
+
# bundle check indicates we need to
|
16
|
+
system! "bundle check --no-color || bundle install --no-color --quiet"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
options = {
|
22
|
+
update_gems: false,
|
23
|
+
}
|
24
|
+
OptionParser.new do |opts|
|
25
|
+
opts.on("--update-gems","Update gems get the latest versions consistent with Gemfile / gemspec.") do
|
26
|
+
options[:update_gems] = true
|
27
|
+
end
|
28
|
+
end.parse!
|
29
|
+
|
30
|
+
setup(**options)
|
Binary file
|
@@ -0,0 +1,25 @@
|
|
1
|
+
FROM ruby:3.4
|
2
|
+
|
3
|
+
SHELL [ "/bin/bash", "-o", "pipefail", "-c" ]
|
4
|
+
|
5
|
+
ENV DEBIAN_FRONTEND=noninteractive
|
6
|
+
RUN apt-get -y update
|
7
|
+
|
8
|
+
# Update RubyGems and Bundler
|
9
|
+
RUN echo "gem: --no-document" >> ~/.gemrc && \
|
10
|
+
gem update --system
|
11
|
+
|
12
|
+
ARG brut_version
|
13
|
+
RUN gem install --version ${brut_version} mkbrut
|
14
|
+
|
15
|
+
ARG user_uid=10001
|
16
|
+
ARG user_gid=10002
|
17
|
+
|
18
|
+
# Create the user's group ID if it does not exist
|
19
|
+
RUN getent group ${user_gid} || groupadd --gid ${user_gid} appgroup
|
20
|
+
RUN useradd --uid ${user_uid} --gid ${user_gid} --create-home --home-dir /home/appuser appuser
|
21
|
+
|
22
|
+
# NOT including the group here as that will place the user's environment
|
23
|
+
# ONLY in that group and not in all the groups in which they are a part.
|
24
|
+
USER appuser
|
25
|
+
|
data/mkbrut/exe/mkbrut
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
require "erb"
|
3
|
+
require "ostruct"
|
4
|
+
|
5
|
+
module MKBrut
|
6
|
+
class App
|
7
|
+
def initialize(current_dir:, app_options:, out:, err:)
|
8
|
+
@out = out
|
9
|
+
@app_options = app_options
|
10
|
+
|
11
|
+
@out.puts "Creating app with these options:\n"
|
12
|
+
@out.puts "App name: #{app_options.app_name}"
|
13
|
+
@out.puts "App ID: #{app_options.app_id}"
|
14
|
+
@out.puts "Prefix: #{app_options.prefix}"
|
15
|
+
@out.puts "Organization: #{app_options.organization}"
|
16
|
+
@out.puts "Include demo? #{app_options.demo}\n"
|
17
|
+
|
18
|
+
if app_options.dry_run?
|
19
|
+
@out.puts "Dry Run"
|
20
|
+
MKBrut::Ops::BaseOp.dry_run = true
|
21
|
+
end
|
22
|
+
|
23
|
+
templates_dir = Pathname(
|
24
|
+
Gem::Specification.find_by_name("mkbrut").gem_dir
|
25
|
+
) / "templates"
|
26
|
+
|
27
|
+
@base = MKBrut::Base.new(
|
28
|
+
app_options:,
|
29
|
+
current_dir:,
|
30
|
+
templates_dir:
|
31
|
+
)
|
32
|
+
@segments = [
|
33
|
+
MKBrut::Segments::BareBones.new(
|
34
|
+
app_options:,
|
35
|
+
current_dir:,
|
36
|
+
templates_dir:,
|
37
|
+
)
|
38
|
+
]
|
39
|
+
if app_options.demo?
|
40
|
+
@segments << MKBrut::Segments::Demo.new(
|
41
|
+
app_options:,
|
42
|
+
current_dir:,
|
43
|
+
templates_dir:
|
44
|
+
)
|
45
|
+
end
|
46
|
+
app_options.segments.each do |segment|
|
47
|
+
case segment
|
48
|
+
when "heroku"
|
49
|
+
@segments << MKBrut::Segments::Heroku.new(
|
50
|
+
app_options:,
|
51
|
+
current_dir:,
|
52
|
+
templates_dir:
|
53
|
+
)
|
54
|
+
else
|
55
|
+
raise "Segment #{segment} is not supported"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def create!
|
61
|
+
@out.puts "Creating Base app"
|
62
|
+
@base.create!
|
63
|
+
@segments.each do |segment|
|
64
|
+
@out.puts "Creating segment: #{segment.class.friendly_name}"
|
65
|
+
segment.add!
|
66
|
+
end
|
67
|
+
@out.puts "#{@app_options.app_name} was created\n\n"
|
68
|
+
@out.puts "Time to get building:"
|
69
|
+
@out.puts "1. cd #{@app_options.app_name}"
|
70
|
+
@out.puts "2. dx/build"
|
71
|
+
@out.puts "3. dx/start"
|
72
|
+
@out.puts "4. [ in another terminal ] dx/exec bash"
|
73
|
+
@out.puts "5. [ inside the Docker container ] bin/setup"
|
74
|
+
@out.puts "6. [ inside the Docker container ] bin/dev"
|
75
|
+
@out.puts "7. Visit http://localhost:6502 in your browser"
|
76
|
+
@out.puts "8. [ inside the Docker container ] bin/setup help # to see more commands"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module MKBrut
|
2
|
+
class AppName
|
3
|
+
def initialize(value)
|
4
|
+
identifier = value.to_s
|
5
|
+
if identifier.empty?
|
6
|
+
raise MKBrut::InvalidIdentifier, "app-name is required"
|
7
|
+
end
|
8
|
+
|
9
|
+
if identifier.length > 63
|
10
|
+
raise MKBrut::InvalidIdentifier, "app-name cannot be longer than 63 characters"
|
11
|
+
end
|
12
|
+
|
13
|
+
if identifier.start_with?("-") || identifier.end_with?("-")
|
14
|
+
raise MKBrut::InvalidIdentifier, "app-name cannot start or end with a hyphen"
|
15
|
+
end
|
16
|
+
|
17
|
+
if identifier.match?(/[^a-zA-Z\-_]/)
|
18
|
+
raise MKBrut::InvalidIdentifier, "app-name can only contain letters, hyphens, and underscores"
|
19
|
+
end
|
20
|
+
if identifier.match?(/__/) || identifier.match?(/--/)
|
21
|
+
raise MKBrut::InvalidIdentifier, "app-name can not have repeating underscores or hyphens"
|
22
|
+
end
|
23
|
+
@identifier = identifier.to_s.gsub(/_/,"-")
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s = @identifier
|
27
|
+
alias to_str to_s
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class MKBrut::AppOptions
|
2
|
+
attr_reader :app_name, :app_id, :prefix, :organization, :demo, :versions, :segments
|
3
|
+
|
4
|
+
def initialize(
|
5
|
+
app_name:,
|
6
|
+
app_id: nil,
|
7
|
+
prefix: nil,
|
8
|
+
dry_run: nil,
|
9
|
+
organization: nil,
|
10
|
+
demo: false,
|
11
|
+
versions: nil,
|
12
|
+
**rest
|
13
|
+
)
|
14
|
+
if app_name.nil?
|
15
|
+
raise ArgumentError, "app_name is required"
|
16
|
+
end
|
17
|
+
|
18
|
+
@app_name = app_name
|
19
|
+
@app_id = app_id || MKBrut::AppId.from_app_name(@app_name)
|
20
|
+
@prefix = prefix || MKBrut::Prefix.from_app_id(@app_id)
|
21
|
+
@organization = organization || @app_id
|
22
|
+
@dry_run = !!dry_run
|
23
|
+
@demo = !!demo
|
24
|
+
@versions = versions
|
25
|
+
@segments = rest.map { |key,value|
|
26
|
+
if key =~ /^segment-(.+)$/ && value
|
27
|
+
$1
|
28
|
+
else
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
}.compact
|
32
|
+
end
|
33
|
+
|
34
|
+
def dry_run? = @dry_run
|
35
|
+
def demo? = @demo
|
36
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require "pathname"
|
2
|
+
require "securerandom"
|
3
|
+
# Constructs the base of any Brut app.
|
4
|
+
class MKBrut::Base
|
5
|
+
include MKBrut
|
6
|
+
|
7
|
+
class ErbBinding < MKBrut::ErbBindingDelegate
|
8
|
+
def session_secret = SecureRandom.hex(64)
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(app_options:, current_dir:, templates_dir:)
|
12
|
+
@project_root = current_dir / app_options.app_name
|
13
|
+
@templates_dir = templates_dir / "Base"
|
14
|
+
@erb_binding = ErbBinding.new(app_options)
|
15
|
+
end
|
16
|
+
|
17
|
+
def create!
|
18
|
+
if @project_root.exist?
|
19
|
+
raise "Project root #{@project_root} already exists"
|
20
|
+
end
|
21
|
+
operations = [ Ops::Mkdir.new(@project_root) ] +
|
22
|
+
copy_files(@templates_dir, @project_root)
|
23
|
+
|
24
|
+
operations.each do |operation|
|
25
|
+
operation.call
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def filenames_to_always_skip = [ "README.md", "mkicons.sh" ]
|
32
|
+
|
33
|
+
def copy_files(source_dir, destination_root)
|
34
|
+
operations = []
|
35
|
+
Dir.glob("#{source_dir}/*", flags: File::FNM_DOTMATCH).each do |template_file|
|
36
|
+
template_file = Pathname(template_file)
|
37
|
+
if [ ".", ".." ].include?(template_file.basename.to_s)
|
38
|
+
next
|
39
|
+
end
|
40
|
+
if template_file.directory?
|
41
|
+
operations << Ops::Mkdir.new(destination_root / template_file.basename)
|
42
|
+
operations += copy_files(template_file, destination_root / template_file.basename)
|
43
|
+
elsif template_file.extname == ".erb"
|
44
|
+
operations << Ops::RenderTemplate.new(
|
45
|
+
template_file,
|
46
|
+
destination_root:,
|
47
|
+
erb_binding: @erb_binding
|
48
|
+
)
|
49
|
+
elsif filenames_to_always_skip.include?(template_file.basename.to_s)
|
50
|
+
operations << Ops::SkipFile.new(template_file)
|
51
|
+
else
|
52
|
+
operations << Ops::CopyFile.new(template_file, destination_root:)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
operations
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require "optparse"
|
2
|
+
require "pathname"
|
3
|
+
|
4
|
+
module MKBrut
|
5
|
+
class CLI
|
6
|
+
def initialize(args:, out: $stdout, err: $stderr)
|
7
|
+
@args = args
|
8
|
+
@out = out
|
9
|
+
@err = err
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
|
14
|
+
app_options = parse_options(@args, MKBrut::Versions.new)
|
15
|
+
new_app = MKBrut::App.new(
|
16
|
+
current_dir: Pathname.pwd.expand_path,
|
17
|
+
app_options:,
|
18
|
+
out: PrefixedIO.new(@out, "mkbrut"),
|
19
|
+
err: @err
|
20
|
+
)
|
21
|
+
new_app.create!
|
22
|
+
0
|
23
|
+
rescue => e
|
24
|
+
@err.puts "Error: #{e.message}"
|
25
|
+
if ENV["BRUT_CLI_RAISE_ON_ERROR"] == "true"
|
26
|
+
raise
|
27
|
+
end
|
28
|
+
1
|
29
|
+
end
|
30
|
+
|
31
|
+
def show_help(versions)
|
32
|
+
@out.puts @option_parser
|
33
|
+
@out.puts
|
34
|
+
@out.puts "ARGUMENTS"
|
35
|
+
@out.puts
|
36
|
+
@out.puts " app-name - name for your app, which will be the folder where your app's files are created"
|
37
|
+
@out.puts
|
38
|
+
@out.puts "ENVIRONMENT VARIABLES"
|
39
|
+
@out.puts
|
40
|
+
@out.puts " BRUT_CLI_RAISE_ON_ERROR - if set to 'true', any error will raise an exception instead of printing to stderr"
|
41
|
+
@out.puts
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
|
47
|
+
def parse_options(args, versions)
|
48
|
+
options = {}
|
49
|
+
@option_parser = OptionParser.new do |opts|
|
50
|
+
opts.accept(MKBrut::Prefix) do |prefix|
|
51
|
+
MKBrut::Prefix.new(prefix)
|
52
|
+
end
|
53
|
+
opts.accept(MKBrut::AppId) do |prefix|
|
54
|
+
MKBrut::AppId.new(prefix)
|
55
|
+
end
|
56
|
+
opts.accept(MKBrut::Organization) do |prefix|
|
57
|
+
MKBrut::Organization.new(prefix)
|
58
|
+
end
|
59
|
+
opts.banner = [
|
60
|
+
"Usage: mkbrut [options] app-name",
|
61
|
+
"",
|
62
|
+
" Creates a new Brut-powered app",
|
63
|
+
"",
|
64
|
+
"VERSION",
|
65
|
+
"",
|
66
|
+
" #{MKBrut::VERSION}",
|
67
|
+
"",
|
68
|
+
"OPTIONS",
|
69
|
+
"",
|
70
|
+
].join("\n")
|
71
|
+
|
72
|
+
opts.on("-a", "--app-id=ID", MKBrut::AppId,
|
73
|
+
"App identifier, which must be able to be used as a hostname or other Internet identifier. Derived from your app name, if omitted")
|
74
|
+
|
75
|
+
opts.on("-o", "--organization=ORG",MKBrut::Organization,
|
76
|
+
"Organization name, e.g. what you'd use for GitHub. Defaults to the app-id value")
|
77
|
+
|
78
|
+
opts.on("-e", "--prefix=PREFIX", MKBrut::Prefix,
|
79
|
+
"Two-character prefix for external IDs and autonomous custom elements. Derived from your app-id, if omitted.")
|
80
|
+
|
81
|
+
opts.on("--dry-run", "Only show what would happen, don't actually do anything")
|
82
|
+
opts.on("--[no-]demo", "Include, or not, additional files that demonstrate Brut's features (default is true for now")
|
83
|
+
{
|
84
|
+
"sidekiq" => "Use Sidekiq for background jobs",
|
85
|
+
"heroku" => "Use Heroku for container-based deployment",
|
86
|
+
}.each do |segment,description|
|
87
|
+
opts.on("--segment-#{segment}", description)
|
88
|
+
end
|
89
|
+
opts.on("-h", "--help", "Show this help message") do
|
90
|
+
show_help(versions)
|
91
|
+
exit
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
@option_parser.parse!(args, into: options)
|
96
|
+
if !options.key?(:demo)
|
97
|
+
options[:demo] = true
|
98
|
+
end
|
99
|
+
|
100
|
+
options[:app_name] = MKBrut::AppName.new(args.first)
|
101
|
+
options[:app_id] = options[:'app-id']
|
102
|
+
options[:dry_run] = !!options[:'dry-run']
|
103
|
+
MKBrut::AppOptions.new(**options.merge(versions:))
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# This exists because ERB can't working with a SimpleDelegator or
|
2
|
+
# Delegate.
|
3
|
+
class MKBrut::ErbBindingDelegate
|
4
|
+
def initialize(app_options)
|
5
|
+
@app_options = app_options
|
6
|
+
end
|
7
|
+
|
8
|
+
# Not using Delegate because it won't work with ERB binding
|
9
|
+
def method_missing(syn,*args,&block)
|
10
|
+
if args.empty? && @app_options.respond_to?(syn)
|
11
|
+
@app_options.send(syn)
|
12
|
+
else
|
13
|
+
super
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def respond_to_missing?(syn,include_all)
|
18
|
+
@app_options.respond_to?(syn)
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module MKBrut
|
2
|
+
class InternetIdentifier
|
3
|
+
def initialize(name, value)
|
4
|
+
@name = name
|
5
|
+
@identifier = value.to_s
|
6
|
+
validate_identifier
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_s = @identifier
|
10
|
+
alias to_str to_s
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def validate_identifier
|
15
|
+
if @identifier.empty?
|
16
|
+
raise MKBrut::InvalidIdentifier, "#{@name} cannot be empty"
|
17
|
+
end
|
18
|
+
|
19
|
+
if @identifier.length > 63
|
20
|
+
raise MKBrut::InvalidIdentifier, "#{@name} cannot be longer than 63 characters"
|
21
|
+
end
|
22
|
+
|
23
|
+
if @identifier.start_with?("-") || @identifier.end_with?("-")
|
24
|
+
raise MKBrut::InvalidIdentifier, "#{@name} cannot start or end with a hyphen"
|
25
|
+
end
|
26
|
+
|
27
|
+
if @identifier.match?(/[^a-zA-Z0-9-]/)
|
28
|
+
raise MKBrut::InvalidIdentifier, "#{@name} can only contain letters, numbers, and hyphens"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class MKBrut::Ops::AddCSSImport < MKBrut::Ops::BaseOp
|
2
|
+
def initialize(project_root:, import:)
|
3
|
+
@file = project_root / "app" / "src" / "front_end" / "css" / "index.css"
|
4
|
+
@import = import
|
5
|
+
end
|
6
|
+
|
7
|
+
def call
|
8
|
+
if dry_run?
|
9
|
+
puts "Would add import '#{@import}'; to '#{@file}'"
|
10
|
+
return
|
11
|
+
end
|
12
|
+
|
13
|
+
contents = File.read(@file).split(/\n/)
|
14
|
+
|
15
|
+
inserted_import = false
|
16
|
+
previous_line_was_import = false
|
17
|
+
new_contents = []
|
18
|
+
contents.each do |line|
|
19
|
+
if line =~ /^\s*@import\s+["']/
|
20
|
+
previous_line_was_import = true
|
21
|
+
new_contents << line
|
22
|
+
else
|
23
|
+
if previous_line_was_import && !inserted_import
|
24
|
+
new_contents << "@import '#{@import}';"
|
25
|
+
inserted_import = true
|
26
|
+
end
|
27
|
+
previous_line_was_import = false
|
28
|
+
new_contents << line
|
29
|
+
end
|
30
|
+
end
|
31
|
+
if !inserted_import && previous_line_was_import
|
32
|
+
new_contents << "@import \"#{@import}\";"
|
33
|
+
inserted_import = true
|
34
|
+
end
|
35
|
+
if !inserted_import
|
36
|
+
raise "Did not find any other @imports in '#{@file}' - was expecting at least one to exist"
|
37
|
+
end
|
38
|
+
File.open(@file, "w") do |file|
|
39
|
+
file.puts new_contents.join("\n")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|