Set up the Galaxy frontend's stack (#433)

* Setup webpacker for typescript

* Typescript is working

* Add documentation to set up the Galaxy project and start contributing to it

* Add Storybook

* Add Jest

* Remove tuistenv from the .gitignore

* Run rubocop
This commit is contained in:
Pedro Piñera Buendía 2019-07-03 13:41:08 +02:00 committed by GitHub
parent 5657230a8f
commit f584917534
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 22037 additions and 403 deletions

View File

@ -144,6 +144,11 @@ jobs:
bundle exec rails db:schema:load bundle exec rails db:schema:load
bundle exec rake test bundle exec rake test
- run:
name: Javascript tests
command: |
yarn test
galaxy-deploy: galaxy-deploy:
docker: docker:
- image: buildpack-deps:trusty - image: buildpack-deps:trusty

1
.gitignore vendored
View File

@ -98,7 +98,6 @@ Carthage/Checkouts
*.profraw *.profraw
tuist.zip tuist.zip
build/ build/
tuistenv
TODO TODO
TODO.md TODO.md
mkmf.log mkmf.log

View File

@ -15,7 +15,7 @@
}, },
"editor.defaultFormatter": "esbenp.prettier-vscode", "editor.defaultFormatter": "esbenp.prettier-vscode",
"files.exclude": { "files.exclude": {
"**/.git*": true, "**/.git": true,
"**/node_modules": true, "**/node_modules": true,
"**/.DS_Store": true "**/.DS_Store": true
} }

View File

@ -0,0 +1,5 @@
import Foundation
import TuistEnvKit
var registry = CommandRegistry()
registry.run()

3
docs/.gitignore vendored
View File

@ -1,2 +1,3 @@
node_modules node_modules
.docz .docz
yarn-error.log

View File

@ -0,0 +1,43 @@
---
name: Galaxy
menu: Contributors
---
# Galaxy
Galaxy is a [Rails](https://rubyonrails.org/) application that exposes a [GraphQL API](https://graphql.org/learn/) that is consumed by a [React](https://reactjs.org/) frontend that is bundled and served by Rails using [Webpacker](https://github.com/rails/webpacker).
## Set up for development
- Git clone the repository: `git clone git@github.com:tuist/tuist.git`.
- Install [Postgress](https://www.postgresql.org/download/macosx/).
- Choose the galaxy directory: `cd galaxy`.
- Install Bundler dependencies: `bundle install`.
- Install NPM dependencies: `yarn install`.
- Run: `rails start`.
## Running tests
- Rails unit tests: `rails test`.
- Frontend tests: `yarn test`.
## Storybook
The project has [Storybook](https://storybook.js.org/) configured, a Javascript tool to create a catalogue for the project components. The catalogue entries are called stories, and they are defined in the directory `stories/`.
To run the catalogue, just run the command `yarn storybook` in your terminal. That'll transpile the catalogue and open the browser with it.
## Useful commands
- `rails db:drop`: Deletes the database.
- `rails db:create`: Creates the database.
- `rails db:migrate`: Migrates the database structure.
## Useful resources
- [Rails](https://rubyonrails.org/)
- [Styled components](https://www.styled-components.com/)
- [Relay](https://relay.dev/)
- [GraphQL](https://graphql.org/learn/)
- [React](https://reactjs.org/)
- [Webpacker](https://github.com/rails/webpacker)

View File

@ -1,4 +1,4 @@
import doczPluginNetlify from "docz-plugin-netlify"; import doczPluginNetlify from 'docz-plugin-netlify'
export default { export default {
title: 'Tuist Documentation', title: 'Tuist Documentation',
@ -27,15 +27,15 @@ export default {
width: 100, width: 100,
}, },
styles: { styles: {
"body": ` body: `
font-family: -apple-system, system-ui, "Helvetica Neue", Helvetica, Arial, Verdana, sans-serif; font-family: -apple-system, system-ui, "Helvetica Neue", Helvetica, Arial, Verdana, sans-serif;
`, `,
code: ` code: `
font-size: 13px; font-size: 13px;
font-family: Menlo, Consolas, Monaco, "Courier New", monospace, serif; font-family: Menlo, Consolas, Monaco, "Courier New", monospace, serif;
background-color: white; background-color: white;
` `,
} },
}, },
public: './public', public: './public',
htmlContext: { htmlContext: {
@ -66,6 +66,7 @@ export default {
name: 'Contributors', name: 'Contributors',
menu: [ menu: [
'Getting started', 'Getting started',
'Galaxy',
'Code of conduct', 'Code of conduct',
'Changelog guidelines', 'Changelog guidelines',
'Core team', 'Core team',

View File

@ -1,7 +1,5 @@
{ {
"devDependencies": { "devDependencies": {},
"react-hot-loader": "^4.9.0"
},
"scripts": { "scripts": {
"docz:dev": "docz dev", "docz:dev": "docz dev",
"docz:build": "docz build" "docz:build": "docz build"

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,9 @@
import { configure } from '@storybook/react'
// automatically import all files ending in *.stories.tsx
console.log('yolo')
function loadStories() {
require('../stories')
}
configure(loadStories, module)

View File

@ -0,0 +1,27 @@
{
"compilerOptions": {
"outDir": "build/lib",
"module": "commonjs",
"target": "es5",
"lib": ["es5", "es6", "es7", "es2017", "dom"],
"sourceMap": true,
"allowJs": false,
"jsx": "react",
"moduleResolution": "node",
"rootDirs": ["../frontend", "../stories"],
"baseUrl": "src",
"forceConsistentCasingInFileNames": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noImplicitAny": true,
"strictNullChecks": true,
"suppressImplicitAnyIndexErrors": true,
"noUnusedLocals": true,
"declaration": true,
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"include": ["../frontend/**/*"],
"exclude": ["../node_modules", "../build", "../public", "scripts"]
}

View File

@ -0,0 +1,20 @@
const path = require('path')
module.exports = ({ config }) => {
config.module.rules.push({
test: /\.(ts|tsx)$/,
use: [
{
loader: require.resolve('awesome-typescript-loader'),
options: {
configFileName: path.join(__dirname, 'tsconfig.json'),
},
},
{
loader: require.resolve('react-docgen-typescript-loader'),
},
],
})
config.resolve.extensions.push('.ts', '.tsx')
return config
}

View File

@ -5,64 +5,35 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby('2.6.3') ruby('2.6.3')
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem('rails', '~> 5.2.3') gem('rails', '~> 5.2.3')
# Use postgresql as the database for Active Record
gem('pg', '>= 0.18', '< 2.0') gem('pg', '>= 0.18', '< 2.0')
# Use Puma as the app server
gem('puma', '~> 4.0') gem('puma', '~> 4.0')
# Use SCSS for stylesheets
gem('sass-rails', '~> 5.0') gem('sass-rails', '~> 5.0')
# Use Uglifier as compressor for JavaScript assets
gem('uglifier', '>= 1.3.0') gem('uglifier', '>= 1.3.0')
# Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker
gem('webpacker') gem('webpacker')
# See https://github.com/rails/execjs#readme for more supported runtimes gem("react-rails", "~> 2.5")
# gem 'mini_racer', platforms: :ruby
# Use CoffeeScript for .coffee assets and views
gem('coffee-rails', '~> 5.0') gem('coffee-rails', '~> 5.0')
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
gem('turbolinks', '~> 5') gem('turbolinks', '~> 5')
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
gem('jbuilder', '~> 2.5') gem('jbuilder', '~> 2.5')
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 4.0'
# Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7'
# Use ActiveStorage variant
# gem 'mini_magick', '~> 4.8'
# Use Capistrano for deployment
# gem 'capistrano-rails', group: :development
# Reduces boot times through caching; required in config/boot.rb
gem('bootsnap', '>= 1.1.0', require: false) gem('bootsnap', '>= 1.1.0', require: false)
gem('tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby])
gem("sentry-raven")
gem("sidekiq", "~> 5.2")
group :development, :test do group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
end end
group :development do group :development do
# Access an interactive console on exception pages or by calling 'console' anywhere in the code.
gem 'web-console', '>= 3.3.0' gem 'web-console', '>= 3.3.0'
gem 'listen', '>= 3.0.5', '< 3.2' gem 'listen', '>= 3.0.5', '< 3.2'
# Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
gem 'spring' gem 'spring'
gem 'spring-watcher-listen', '~> 2.0.0' gem 'spring-watcher-listen', '~> 2.0.0'
gem "foreman", "~> 0.64.0"
end end
group :test do group :test do
# Adds support for Capybara system testing and selenium driver
gem 'capybara', '>= 2.15' gem 'capybara', '>= 2.15'
gem 'selenium-webdriver' gem 'selenium-webdriver'
# Easy installation and use of chromedriver to run system tests with Chrome
gem 'chromedriver-helper' gem 'chromedriver-helper'
end end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem('tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby])
gem("komponent", "~> 2.2")
gem "sentry-raven"

View File

@ -47,6 +47,10 @@ GEM
archive-zip (0.12.0) archive-zip (0.12.0)
io-like (~> 0.3.0) io-like (~> 0.3.0)
arel (9.0.0) arel (9.0.0)
babel-source (5.8.35)
babel-transpiler (0.7.0)
babel-source (>= 4.0, < 6)
execjs (~> 2.0)
bindex (0.7.0) bindex (0.7.0)
bootsnap (1.4.4) bootsnap (1.4.4)
msgpack (~> 1.0) msgpack (~> 1.0)
@ -73,12 +77,17 @@ GEM
execjs execjs
coffee-script-source (1.12.2) coffee-script-source (1.12.2)
concurrent-ruby (1.1.5) concurrent-ruby (1.1.5)
connection_pool (2.2.2)
crass (1.0.4) crass (1.0.4)
dotenv (0.7.0)
erubi (1.8.0) erubi (1.8.0)
execjs (2.7.0) execjs (2.7.0)
faraday (0.15.4) faraday (0.15.4)
multipart-post (>= 1.2, < 3) multipart-post (>= 1.2, < 3)
ffi (1.11.1) ffi (1.11.1)
foreman (0.64.0)
dotenv (~> 0.7.0)
thor (>= 0.13.6)
globalid (0.4.2) globalid (0.4.2)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
i18n (1.6.0) i18n (1.6.0)
@ -86,11 +95,6 @@ GEM
io-like (0.3.0) io-like (0.3.0)
jbuilder (2.9.1) jbuilder (2.9.1)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
komponent (2.2.0)
actionview (>= 4.2)
activesupport (>= 4.2)
railties (>= 4.2)
webpacker (>= 3.0.0)
listen (3.1.5) listen (3.1.5)
rb-fsevent (~> 0.9, >= 0.9.4) rb-fsevent (~> 0.9, >= 0.9.4)
rb-inotify (~> 0.9, >= 0.9.7) rb-inotify (~> 0.9, >= 0.9.7)
@ -117,6 +121,8 @@ GEM
puma (4.0.0) puma (4.0.0)
nio4r (~> 2.0) nio4r (~> 2.0)
rack (2.0.7) rack (2.0.7)
rack-protection (2.0.5)
rack
rack-proxy (0.6.5) rack-proxy (0.6.5)
rack rack
rack-test (1.1.0) rack-test (1.1.0)
@ -149,6 +155,13 @@ GEM
rb-fsevent (0.10.3) rb-fsevent (0.10.3)
rb-inotify (0.10.0) rb-inotify (0.10.0)
ffi (~> 1.0) ffi (~> 1.0)
react-rails (2.5.0)
babel-transpiler (>= 0.7.0)
connection_pool
execjs
railties (>= 3.2)
tilt
redis (4.1.2)
regexp_parser (1.5.1) regexp_parser (1.5.1)
ruby_dep (1.5.0) ruby_dep (1.5.0)
rubyzip (1.2.3) rubyzip (1.2.3)
@ -168,6 +181,11 @@ GEM
rubyzip (~> 1.2, >= 1.2.2) rubyzip (~> 1.2, >= 1.2.2)
sentry-raven (2.9.0) sentry-raven (2.9.0)
faraday (>= 0.7.6, < 1.0) faraday (>= 0.7.6, < 1.0)
sidekiq (5.2.7)
connection_pool (~> 2.2, >= 2.2.2)
rack (>= 1.5.0)
rack-protection (>= 1.5.0)
redis (>= 3.3.5, < 5)
spring (2.1.0) spring (2.1.0)
spring-watcher-listen (2.0.1) spring-watcher-listen (2.0.1)
listen (>= 2.7, < 4.0) listen (>= 2.7, < 4.0)
@ -213,15 +231,17 @@ DEPENDENCIES
capybara (>= 2.15) capybara (>= 2.15)
chromedriver-helper chromedriver-helper
coffee-rails (~> 5.0) coffee-rails (~> 5.0)
foreman (~> 0.64.0)
jbuilder (~> 2.5) jbuilder (~> 2.5)
komponent (~> 2.2)
listen (>= 3.0.5, < 3.2) listen (>= 3.0.5, < 3.2)
pg (>= 0.18, < 2.0) pg (>= 0.18, < 2.0)
puma (~> 4.0) puma (~> 4.0)
rails (~> 5.2.3) rails (~> 5.2.3)
react-rails (~> 2.5)
sass-rails (~> 5.0) sass-rails (~> 5.0)
selenium-webdriver selenium-webdriver
sentry-raven sentry-raven
sidekiq (~> 5.2)
spring spring
spring-watcher-listen (~> 2.0.0) spring-watcher-listen (~> 2.0.0)
turbolinks (~> 5) turbolinks (~> 5)

View File

@ -5,4 +5,8 @@
require_relative 'config/application' require_relative 'config/application'
task :start do
system("bundle exec foreman start -f config/Procfile.dev") || abort
end
Rails.application.load_tasks Rails.application.load_tasks

View File

@ -1,7 +0,0 @@
# frozen_string_literal: true
class GraphController < ApplicationController
def show
render("pages/graph")
end
end

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
class PagesController < ApplicationController
def index
render(component: "pages/home")
end
end

View File

@ -17,9 +17,6 @@
</head> </head>
<body> <body>
<div class="container-fluid"> <%= yield %>
<%= component("navbar") %>
<%= yield %>
</div>
</body> </body>
</html> </html>

View File

@ -1 +0,0 @@
<h1>this is a test</h1>

View File

View File

@ -1,4 +1,5 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
# frozen_string_literal: true
ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development"
ENV["NODE_ENV"] ||= "development" ENV["NODE_ENV"] ||= "development"

View File

@ -1,4 +1,5 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
# frozen_string_literal: true
ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development" ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development"
ENV["NODE_ENV"] ||= "development" ENV["NODE_ENV"] ||= "development"

View File

@ -0,0 +1,2 @@
web: bundle exec rails s
webpack: ../bin/webpack-dev-server

View File

@ -7,17 +7,17 @@
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
Rails.application.config.content_security_policy do |policy| Rails.application.config.content_security_policy do |policy|
policy.connect_src :self, :https, "http://localhost:3035", "ws://localhost:3035" if Rails.env.development? policy.connect_src(:self, :https, "http://localhost:3035", "ws://localhost:3035") if Rails.env.development?
# policy.default_src :self, :https # policy.default_src :self, :https
# policy.font_src :self, :https, :data # policy.font_src :self, :https, :data
# policy.img_src :self, :https, :data # policy.img_src :self, :https, :data
# policy.object_src :none # policy.object_src :none
# policy.script_src :self, :https # policy.script_src :self, :https
# policy.style_src :self, :https # policy.style_src :self, :https
# # Specify URI for violation reports # # Specify URI for violation reports
# # policy.report_uri "/csp-violation-report-endpoint" # # policy.report_uri "/csp-violation-report-endpoint"
end end
# If you are using UJS then enable automatic nonce generation # If you are using UJS then enable automatic nonce generation

View File

@ -1,3 +1,5 @@
# frozen_string_literal: true
if ENV.key?("SENTRY_DSN") if ENV.key?("SENTRY_DSN")
Raven.configure do |config| Raven.configure do |config|
config.dsn = ENV["SENTRY_DSN"] config.dsn = ENV["SENTRY_DSN"]

View File

@ -1,6 +1,5 @@
# frozen_string_literal: true # frozen_string_literal: true
Rails.application.routes.draw do Rails.application.routes.draw do
# For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html root to: 'pages#index'
get "graph", to: "graph#show"
end end

View File

@ -1,14 +1,7 @@
const { environment } = require('@rails/webpacker') const { environment } = require('@rails/webpacker')
const typescript = require('./loaders/typescript')
const webpack = require('webpack') const webpack = require('webpack')
environment.plugins.append( environment.loaders.prepend('typescript', typescript)
'Provide',
new webpack.ProvidePlugin({
$: 'jquery',
jQuery: 'jquery',
Popper: ['popper.js', 'default'],
})
)
module.exports = environment module.exports = environment

View File

@ -0,0 +1,11 @@
const PnpWebpackPlugin = require('pnp-webpack-plugin')
module.exports = {
test: /\.(ts|tsx)?(\.erb)?$/,
use: [
{
loader: 'ts-loader',
options: PnpWebpackPlugin.tsLoaderOptions()
}
]
}

View File

@ -34,6 +34,8 @@ default: &default
- .woff2 - .woff2
extensions: extensions:
- .tsx
- .ts
- .mjs - .mjs
- .js - .js
- .sass - .sass

View File

@ -1,2 +0,0 @@
import "components/button/button";
import "components/navbar/navbar";

View File

@ -1,29 +0,0 @@
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<a class="navbar-brand" href="#">Tuist</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNavDropdown" aria-controls="navbarNavDropdown" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNavDropdown">
<ul class="navbar-nav">
<li class="nav-item active">
<a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Features</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Pricing</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" id="navbarDropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Dropdown link
</a>
<div class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
<a class="dropdown-item" href="#">Action</a>
<a class="dropdown-item" href="#">Another action</a>
<a class="dropdown-item" href="#">Something else here</a>
</div>
</li>
</ul>
</div>
</nav>

View File

@ -1 +0,0 @@
.navbar {}

View File

@ -1 +0,0 @@
import "./navbar.css";

View File

@ -1,5 +0,0 @@
# frozen_string_literal: true
module NavbarComponent
extend ComponentHelper
end

View File

@ -0,0 +1,5 @@
describe('this is just a sample test', () => {
test('2 should be equal to 2', () => {
expect(2).toBe(2)
})
})

View File

@ -0,0 +1,9 @@
import * as React from 'react'
class HomePage extends React.Component {
render() {
return <>Hello world. How are you??</>
}
}
export default HomePage

View File

@ -1,3 +0,0 @@
/* eslint no-console:0 */
import 'bootstrap/dist/js/bootstrap'
import 'components'

View File

@ -1 +1,3 @@
@import '~bootstrap/scss/bootstrap'; .ola {
color: red;
}

View File

@ -0,0 +1,6 @@
/* eslint no-console:0 */
// Support component names relative to this directory:
var componentRequireContext = require.context('components', true)
var ReactRailsUJS = require('react_ujs')
ReactRailsUJS.useContext(componentRequireContext)

View File

@ -0,0 +1,5 @@
// By default, this pack is loaded for server-side rendering.
// It must expose react_ujs as `ReactRailsUJS` and prepare a require context.
var componentRequireContext = require.context("components", true);
var ReactRailsUJS = require("react_ujs");
ReactRailsUJS.useContext(componentRequireContext);

5
galaxy/jest.config.js Normal file
View File

@ -0,0 +1,5 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testRegex: 'frontend/.*\\.(test|spec)\\.[jt]sx?$',
}

13000
galaxy/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,30 @@
{ {
"name": "galaxy", "name": "galaxy",
"private": true, "private": true,
"scripts": {
"storybook": "start-storybook",
"test": "jest"
},
"dependencies": { "dependencies": {
"@rails/webpacker": "^4.0.7", "@rails/webpacker": "^4.0.7",
"bootstrap": "4.0.0-beta", "@types/react": "^16.8.22",
"jquery": "^3.4.1", "@types/react-dom": "^16.8.4",
"popper.js": "^1.15.0" "@types/webpack-env": "^1.13.9",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react_ujs": "^2.5.0",
"ts-loader": "^6.0.4",
"typescript": "^3.5.2"
}, },
"devDependencies": { "devDependencies": {
"@storybook/addon-info": "^5.1.9",
"@storybook/react": "^5.1.9",
"@types/jest": "^24.0.15",
"@types/storybook__react": "^4.0.2",
"awesome-typescript-loader": "^5.2.1",
"jest": "^24.8.0",
"react-docgen-typescript-loader": "^3.1.0",
"ts-jest": "^24.0.2",
"webpack-dev-server": "^3.7.2" "webpack-dev-server": "^3.7.2"
} }
} }

7
galaxy/stories/index.tsx Normal file
View File

@ -0,0 +1,7 @@
import * as React from 'react'
import { storiesOf } from '@storybook/react'
import HomePage from '../frontend/components/pages/home'
const stories = storiesOf('Components', module)
stories.add('Home', () => <HomePage />, { info: { inline: true } })

19
galaxy/tsconfig.json Normal file
View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"declaration": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": ["es6", "dom"],
"module": "es6",
"moduleResolution": "node",
"jsx": "react",
"baseUrl": ".",
"paths": {
"*": ["node_modules/*", "app/javascript/*"]
},
"sourceMap": true,
"target": "es5"
},
"exclude": ["**/*.spec.ts", "node_modules", "vendor", "public"],
"compileOnSave": false
}

File diff suppressed because it is too large Load Diff

View File

@ -14,26 +14,26 @@ TUIST_API_URL = "https://api.github.com/repos/#{TUIST_REPOSITORY}"
TUIST_RELEASES_URL = "#{TUIST_API_URL}/releases/latest" TUIST_RELEASES_URL = "#{TUIST_API_URL}/releases/latest"
module Tty module Tty
module_function extend self
def blue def blue
bold 34 bold(34)
end end
def red def red
bold 31 bold(31)
end end
def reset def reset
escape 0 escape(0)
end end
def bold(code = 39) def bold(code = 39)
escape "1;#{code}" escape("1;#{code}")
end end
def underline def underline
escape '4;39' escape('4;39')
end end
def escape(code) def escape(code)
@ -45,7 +45,7 @@ class Array
def shell_s def shell_s
cp = dup cp = dup
first = cp.shift first = cp.shift
cp.map { |arg| arg.gsub ' ', '\\ ' }.unshift(first).join(' ') cp.map { |arg| arg.gsub(' ', '\\ ') }.unshift(first).join(' ')
end end
end end
@ -58,24 +58,24 @@ def warn(warning)
end end
def system(*args) def system(*args)
abort "Failed during: #{args.shell_s}" unless Kernel.system(*args) abort("Failed during: #{args.shell_s}") unless Kernel.system(*args)
end end
def sudo(*args) def sudo(*args)
args.unshift('-A') unless ENV['SUDO_ASKPASS'].nil? args.unshift('-A') unless ENV['SUDO_ASKPASS'].nil?
ohai '/usr/bin/sudo', *args ohai('/usr/bin/sudo', *args)
system '/usr/bin/sudo', *args system('/usr/bin/sudo', *args)
end end
def getc def getc
system '/bin/stty raw -echo' system('/bin/stty raw -echo')
if STDIN.respond_to?(:getbyte) if STDIN.respond_to?(:getbyte)
STDIN.getbyte STDIN.getbyte
else else
STDIN.getc STDIN.getc
end end
ensure ensure
system '/bin/stty -raw echo' system('/bin/stty -raw echo')
end end
def wait_for_user def wait_for_user
@ -104,11 +104,11 @@ def force_curl?
end end
def macos_version def macos_version
@macos_version ||= Version.new(`/usr/bin/sw_vers -productVersion`.chomp[/10\.\d+/]) @macos_version ||= Version.new(%x(/usr/bin/sw_vers -productVersion).chomp[/10\.\d+/])
end end
def command_line_tools_path def command_line_tools_path
path = (`/usr/bin/xcode-select --print-path`).strip path = %x(/usr/bin/xcode-select --print-path).strip
path.empty? ? "/Library/Developer/CommandLineTools" : path path.empty? ? "/Library/Developer/CommandLineTools" : path
end end
@ -120,41 +120,41 @@ def should_install_command_line_tools?
end end
# Invalidate sudo timestamp before exiting (if it wasn't active before). # Invalidate sudo timestamp before exiting (if it wasn't active before).
Kernel.system '/usr/bin/sudo -n -v 2>/dev/null' Kernel.system('/usr/bin/sudo -n -v 2>/dev/null')
at_exit { Kernel.system '/usr/bin/sudo', '-k' } unless $CHILD_STATUS.success? at_exit { Kernel.system('/usr/bin/sudo', '-k') } unless $CHILD_STATUS.success?
# The block form of Dir.chdir fails later if Dir.CWD doesn't exist which I # The block form of Dir.chdir fails later if Dir.CWD doesn't exist which I
# guess is fair enough. Also sudo prints a warning message for no good reason # guess is fair enough. Also sudo prints a warning message for no good reason
Dir.chdir '/usr' Dir.chdir('/usr')
####################################################################### Script ####################################################################### Script
abort 'Tuist is not available for Linux platforms' if RUBY_PLATFORM.to_s.downcase.include?('linux') abort('Tuist is not available for Linux platforms') if RUBY_PLATFORM.to_s.downcase.include?('linux')
abort "Don't run this as root!" if Process.uid.zero? abort("Don't run this as root!") if Process.uid.zero?
ohai 'Installing Tuist' ohai('Installing Tuist')
if should_install_command_line_tools? if should_install_command_line_tools?
ohai 'Searching online for the Command Line Tools' ohai('Searching online for the Command Line Tools')
# This temporary file prompts the 'softwareupdate' utility to list the Command Line Tools # This temporary file prompts the 'softwareupdate' utility to list the Command Line Tools
clt_placeholder = '/tmp/.com.apple.dt.CommandLineTools.installondemand.in-progress' clt_placeholder = '/tmp/.com.apple.dt.CommandLineTools.installondemand.in-progress'
sudo '/usr/bin/touch', clt_placeholder sudo('/usr/bin/touch', clt_placeholder)
clt_label = `/usr/sbin/softwareupdate -l | grep -B 1 -E "Command Line (Developer|Tools)" | awk -F"*" '/^ +\\*/ {print $2}' | sed 's/^ *//' | tail -n1`.chomp clt_label = %x(/usr/sbin/softwareupdate -l | grep -B 1 -E "Command Line (Developer|Tools)" | awk -F"*" '/^ +\\*/ {print $2}' | sed 's/^ *//' | tail -n1).chomp
ohai "Installing #{clt_label}" ohai("Installing #{clt_label}")
sudo '/usr/sbin/softwareupdate', '-i', clt_label sudo('/usr/sbin/softwareupdate', '-i', clt_label)
sudo '/bin/rm', '-f', clt_placeholder sudo('/bin/rm', '-f', clt_placeholder)
sudo '/usr/bin/xcode-select', '--switch', command_line_tools_path sudo('/usr/bin/xcode-select', '--switch', command_line_tools_path)
end end
# Headless install may have failed, so fallback to original 'xcode-select' method # Headless install may have failed, so fallback to original 'xcode-select' method
if should_install_command_line_tools? && STDIN.tty? if should_install_command_line_tools? && STDIN.tty?
ohai 'Installing the Command Line Tools (expect a GUI popup):' ohai('Installing the Command Line Tools (expect a GUI popup):')
sudo '/usr/bin/xcode-select', '--install' sudo('/usr/bin/xcode-select', '--install')
puts 'Press any key when the installation has completed.' puts 'Press any key when the installation has completed.'
getc getc
sudo '/usr/bin/xcode-select', '--switch', command_line_tools_path sudo('/usr/bin/xcode-select', '--switch', command_line_tools_path)
end end
abort <<~EOABORT if `/usr/bin/xcrun clang 2>&1` =~ /license/ && !$CHILD_STATUS.success? abort(<<~EOABORT) if %x(/usr/bin/xcrun clang 2>&1) =~ /license/ && !$CHILD_STATUS.success?
You have not agreed to the Xcode license. You have not agreed to the Xcode license.
Before running the installer again please agree to the license by opening Before running the installer again please agree to the license by opening
Xcode.app or running: Xcode.app or running:
@ -172,16 +172,16 @@ begin
uncompressed_path = File.join(tmp_dir, 'tuistenv') uncompressed_path = File.join(tmp_dir, 'tuistenv')
installation_path = File.join('/usr', 'local', 'bin', 'tuist') installation_path = File.join('/usr', 'local', 'bin', 'tuist')
system 'curl', '-LSs', '--output', download_path, download_url system('curl', '-LSs', '--output', download_path, download_url)
system "unzip -o #{download_path} -d #{uncompressed_path} > /dev/null" system("unzip -o #{download_path} -d #{uncompressed_path} > /dev/null")
system 'mv', File.join(uncompressed_path, 'tuistenv'), installation_path system('mv', File.join(uncompressed_path, 'tuistenv'), installation_path)
system 'chmod', '+x', installation_path system('chmod', '+x', installation_path)
ensure ensure
# remove the directory. # remove the directory.
FileUtils.remove_entry tmp_dir FileUtils.remove_entry(tmp_dir)
end end
ohai 'Tuist installed 🎉' ohai('Tuist installed 🎉')
puts '- Run `tuist --help` to get started' puts '- Run `tuist --help` to get started'
puts '- Further documentation: ' puts '- Further documentation: '

View File

@ -9,22 +9,22 @@ require 'pathname'
@failed = false @failed = false
module Tty module Tty
module_function extend self
def blue def blue
bold 34 bold(34)
end end
def red def red
bold 31 bold(31)
end end
def reset def reset
escape 0 escape(0)
end end
def bold(code = 39) def bold(code = 39)
escape "1;#{code}" escape("1;#{code}")
end end
def escape(code) def escape(code)
@ -36,7 +36,7 @@ class Array
def shell_s def shell_s
cp = dup cp = dup
first = cp.shift first = cp.shift
cp.map { |arg| arg.gsub ' ', '\\ ' }.unshift(first).join(' ') cp.map { |arg| arg.gsub(' ', '\\ ') }.unshift(first).join(' ')
end end
end end
@ -71,12 +71,12 @@ end
def system(*args) def system(*args)
return if Kernel.system(*args) return if Kernel.system(*args)
warn "Failed during: #{args.shell_s}" warn("Failed during: #{args.shell_s}")
@failed = true @failed = true
end end
ohai 'Uninstalling Tuist' ohai('Uninstalling Tuist')
system 'rm', '-rf', '/usr/local/bin/tuist' system('rm', '-rf', '/usr/local/bin/tuist')
system 'rm', '-rf', File.join(Dir.home, '.tuist') system('rm', '-rf', File.join(Dir.home, '.tuist'))
ohai 'Tuist uninstalled' ohai('Tuist uninstalled')