diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 00000000..859041bd --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,1123 @@ +--- +version: '3' +includes: + ansible: + taskfile: ./.config/taskfiles/ansible/Taskfile.yml + optional: true + ansible:ansibler: + taskfile: ./.config/taskfiles/ansible/Taskfile-ansibler.yml + optional: true + ansible:playbook: + taskfile: ./.config/taskfiles/ansible/Taskfile-playbook.yml + optional: true + ansible:populate: + taskfile: ./.config/taskfiles/ansible/Taskfile-populate.yml + optional: true + ansible:test: + taskfile: ./.config/taskfiles/ansible/Taskfile-test.yml + optional: true + app: + taskfile: ./.config/taskfiles/app/Taskfile.yml + optional: true + app:virtualbox: + taskfile: ./.config/taskfiles/app/Taskfile-virtualbox.yml + optional: true + boilerplate: + taskfile: ./.config/taskfiles/boilerplate/Taskfile.yml + optional: true + boilerplate:populate: + taskfile: ./.config/taskfiles/boilerplate/Taskfile-populate.yml + optional: true + boilerplate:prompt: + taskfile: ./.config/taskfiles/boilerplate/Taskfile-prompt.yml + optional: true + ci: + taskfile: ./.config/taskfiles/ci/Taskfile.yml + optional: true + ci:github: + taskfile: ./.config/taskfiles/ci/Taskfile-github.yml + optional: true + cloud: + taskfile: ./.config/taskfiles/cloud/Taskfile.yml + optional: true + cloud:heroku: + taskfile: ./.config/taskfiles/cloud/Taskfile-heroku.yml + optional: true + cloud:s3: + taskfile: ./.config/taskfiles/cloud/Taskfile-s3.yml + optional: true + common: + taskfile: ./.config/taskfiles/common/Taskfile.yml + optional: true + common:code: + taskfile: ./.config/taskfiles/common/Taskfile-code.yml + optional: true + common:start: + taskfile: ./.config/taskfiles/common/Taskfile-start.yml + optional: true + common:update: + taskfile: ./.config/taskfiles/common/Taskfile-update.yml + optional: true + common:util: + taskfile: ./.config/taskfiles/common/Taskfile-util.yml + optional: true + docker: + taskfile: ./.config/taskfiles/docker/Taskfile.yml + optional: true + docker:build: + taskfile: ./.config/taskfiles/docker/Taskfile-build.yml + optional: true + docker:test: + taskfile: ./.config/taskfiles/docker/Taskfile-test.yml + optional: true + docker:update: + taskfile: ./.config/taskfiles/docker/Taskfile-update.yml + optional: true + dotfiles: + taskfile: ./.config/taskfiles/dotfiles/Taskfile.yml + optional: true + fix: + taskfile: ./.config/taskfiles/fix/Taskfile.yml + optional: true + git: + taskfile: ./.config/taskfiles/git/Taskfile.yml + optional: true + git:bug: + taskfile: ./.config/taskfiles/git/Taskfile-bug.yml + optional: true + git:github: + taskfile: ./.config/taskfiles/git/Taskfile-github.yml + optional: true + git:gitlab: + taskfile: ./.config/taskfiles/git/Taskfile-gitlab.yml + optional: true + git:gitomatic: + taskfile: ./.config/taskfiles/git/Taskfile-gitomatic.yml + optional: true + git:hook: + taskfile: ./.config/taskfiles/git/Taskfile-hook.yml + optional: true + git:issues: + taskfile: ./.config/taskfiles/git/Taskfile-issues.yml + optional: true + go: + taskfile: ./.config/taskfiles/go/Taskfile.yml + optional: true + go:goreleaser: + taskfile: ./.config/taskfiles/go/Taskfile-goreleaser.yml + optional: true + go:test: + taskfile: ./.config/taskfiles/go/Taskfile-test.yml + optional: true + image: + taskfile: ./.config/taskfiles/image/Taskfile.yml + optional: true + install: + taskfile: ./.config/taskfiles/install/Taskfile.yml + optional: true + install:ansible: + taskfile: ./.config/taskfiles/install/Taskfile-ansible.yml + optional: true + install:apt: + taskfile: ./.config/taskfiles/install/Taskfile-apt.yml + optional: true + install:gh: + taskfile: ./.config/taskfiles/install/Taskfile-gh.yml + optional: true + install:github: + taskfile: ./.config/taskfiles/install/Taskfile-github.yml + optional: true + install:go: + taskfile: ./.config/taskfiles/install/Taskfile-go.yml + optional: true + install:npm: + taskfile: ./.config/taskfiles/install/Taskfile-npm.yml + optional: true + install:pipx: + taskfile: ./.config/taskfiles/install/Taskfile-pipx.yml + optional: true + install:python: + taskfile: ./.config/taskfiles/install/Taskfile-python.yml + optional: true + install:qubes: + taskfile: ./.config/taskfiles/install/Taskfile-qubes.yml + optional: true + install:requirements: + taskfile: ./.config/taskfiles/install/Taskfile-requirements.yml + optional: true + install:rust: + taskfile: ./.config/taskfiles/install/Taskfile-rust.yml + optional: true + install:service: + taskfile: ./.config/taskfiles/install/Taskfile-service.yml + optional: true + install:software: + taskfile: ./.config/taskfiles/install/Taskfile-software.yml + optional: true + install:tap: + taskfile: ./.config/taskfiles/install/Taskfile-tap.yml + optional: true + install:ventoy: + taskfile: ./.config/taskfiles/install/Taskfile-ventoy.yml + optional: true + lint: + taskfile: ./.config/taskfiles/lint/Taskfile.yml + optional: true + lint:codeclimate: + taskfile: ./.config/taskfiles/lint/Taskfile-codeclimate.yml + optional: true + lint:esprint: + taskfile: ./.config/taskfiles/lint/Taskfile-esprint.yml + optional: true + lint:markdown: + taskfile: ./.config/taskfiles/lint/Taskfile-markdown.yml + optional: true + lint:prose: + taskfile: ./.config/taskfiles/lint/Taskfile-prose.yml + optional: true + log: + optional: true + taskfile: ./.config/taskfiles/log/Taskfile.yml + nest: + taskfile: ./.config/taskfiles/nest/Taskfile.yml + optional: true + npm: + taskfile: ./.config/taskfiles/npm/Taskfile.yml + optional: true + npm:bundle: + taskfile: ./.config/taskfiles/npm/Taskfile-bundle.yml + optional: true + npm:cov: + taskfile: ./.config/taskfiles/npm/Taskfile-cov.yml + optional: true + npm:doc: + taskfile: ./.config/taskfiles/npm/Taskfile-doc.yml + optional: true + packer: + taskfile: ./.config/taskfiles/packer/Taskfile.yml + optional: true + packer:build: + taskfile: ./.config/taskfiles/packer/Taskfile-build.yml + optional: true + packer:update: + taskfile: ./.config/taskfiles/packer/Taskfile-update.yml + optional: true + publish: + taskfile: ./.config/taskfiles/publish/Taskfile.yml + optional: true + publish:android: + taskfile: ./.config/taskfiles/publish/Taskfile-android.yml + optional: true + publish:brew: + taskfile: ./.config/taskfiles/publish/Taskfile-brew.yml + optional: true + publish:chrome: + taskfile: ./.config/taskfiles/publish/Taskfile-chrome.yml + optional: true + publish:firefox: + taskfile: ./.config/taskfiles/publish/Taskfile-firefox.yml + optional: true + publish:ios: + taskfile: ./.config/taskfiles/publish/Taskfile-ios.yml + optional: true + publish:menubar: + taskfile: ./.config/taskfiles/publish/Taskfile-menubar.yml + optional: true + publish:opera: + taskfile: ./.config/taskfiles/publish/Taskfile-opera.yml + optional: true + publish:snap: + taskfile: ./.config/taskfiles/publish/Taskfile-snap.yml + optional: true + python: + taskfile: ./.config/taskfiles/python/Taskfile.yml + optional: true + python:test: + taskfile: ./.config/taskfiles/python/Taskfile-test.yml + optional: true + release: + taskfile: ./.config/taskfiles/release/Taskfile.yml + optional: true + security: + taskfile: ./.config/taskfiles/security/Taskfile.yml + optional: true + security:disk: + taskfile: ./.config/taskfiles/security/Taskfile-disk.yml + optional: true + security:gpg: + taskfile: ./.config/taskfiles/security/Taskfile-gpg.yml + optional: true + security:ssh: + taskfile: ./.config/taskfiles/security/Taskfile-ssh.yml + optional: true + security:yubikey: + taskfile: ./.config/taskfiles/security/Taskfile-yubikey.yml + optional: true + symlink: + taskfile: ./.config/taskfiles/symlink/Taskfile.yml + optional: true + ui: + taskfile: ./.config/taskfiles/ui/Taskfile.yml + optional: true + update: + taskfile: ./.config/taskfiles/update/Taskfile.yml + optional: true + upstream: + taskfile: ./.config/taskfiles/upstream/Taskfile.yml + optional: true + upstream:common: + taskfile: ./.config/taskfiles/upstream/Taskfile-common.yml + optional: true + upstream:commondocs: + taskfile: ./.config/taskfiles/upstream/Taskfile-commondocs.yml + optional: true + upstream:docs: + taskfile: ./.config/taskfiles/upstream/Taskfile-docs.yml + optional: true + upstream:project: + taskfile: ./.config/taskfiles/upstream/Taskfile-project.yml + optional: true + upstream:shared: + taskfile: ./.config/taskfiles/upstream/Taskfile-shared.yml + optional: true + vagrant: + taskfile: ./.config/taskfiles/vagrant/Taskfile.yml + optional: true + vagrant:qubes: + taskfile: ./.config/taskfiles/vagrant/Taskfile-qubes.yml + optional: true + vscode: + taskfile: ./.config/taskfiles/vscode/Taskfile.yml + optional: true + web: + taskfile: ./.config/taskfiles/web/Taskfile.yml + optional: true + web:cloudflare: + taskfile: ./.config/taskfiles/web/Taskfile-cloudflare.yml + optional: true + web:ionic: + taskfile: ./.config/taskfiles/web/Taskfile-ionic.yml + optional: true + web:nx: + taskfile: ./.config/taskfiles/web/Taskfile-nx.yml + optional: true + web:profile: + taskfile: ./.config/taskfiles/web/Taskfile-profile.yml + optional: true + cloud:cloudflare: + taskfile: ./.config/taskfiles/cloud/Taskfile-cloudflare.yml + optional: true + cloud:dyno: + taskfile: ./.config/taskfiles/cloud/Taskfile-dyno.yml + optional: true +output: interleaved +vars: + DOCKERHUB_PROFILE: + sh: | + if [ -f .config/variables.json ] && type jq &> /dev/null; then + echo "$(jq -r '.profile.dockerhub' .config/variables.json)" + else + echo 'megabytelabs' + fi + DOCKERHUB_USER: + sh: | + if [ -f .config/variables.json ] && type jq &> /dev/null; then + echo "$(jq -r '.profile.dockerHubUser' .config/variables.json)" + else + echo 'ProfessorManhattan' + fi + ESLINT_FIX_RECURSIVE: 'true' + ESLINT_FORMATTER: stylish + ESLINT_FORMATTER_OPTIONS: git-log gitlab pretty summary + GALAXY_AUTHOR: ProfessorManhattan + GALAXY_COMPANY: Megabyte Labs + GALAXY_NAMESPACE: + sh: | + if [ -f meta/main.yml ]; then + grep namespace < meta/main.yml | sed 's/.*namespace: \\(.*\\)$/\\1/g' + else + if [ -f .config/variables.json ] && type jq &> /dev/null; then + echo "$(jq -r '.profile.galaxy' .config/variables.json)" + else + echo 'ProfessorManhattan' + fi + fi + GALAXY_ROLE_NAME: + sh: "if [ -f meta/main.yml ]; then grep role_name < meta/main.yml | sed 's/.*role_name: \\(.*\\)$/\\1/g'; fi" + GITHUB_ORG: + sh: | + if [ -f package.json ] && type jq &> /dev/null && [ "$(jq -r '.blueprint.repository.github' package.json)" != 'null' ]; then + echo "$(jq -r '.blueprint.repository.github' package.json | sed 's/https:\/\/github.com\///' | sed 's/\/.*$//')" + elif [ -f .config/variables.json ] && type jq &> /dev/null; then + echo "$(jq -r '.profile.githubOrg' .config/variables.json)" + else + echo 'megabyte-labs' + fi + GITHUB_USER: + sh: | + if [ -f .config/variables.json ] && type jq &> /dev/null; then + echo "$(jq -r '.profile.github' .config/variables.json)" + else + echo 'ProfessorManhattan' + fi + GROUP_EXEC_ASYNC: 'false' + # yamllint disable rule:line-length + IGNORE_FOLDERS: >- + -path './.autodoc/*' -o -path './.cache/*' -o -path './.common*' -o -path './.config/*' -o -path './.git/*' -o -path './.modules/*' -o -path './.npm/*' -o -path './.pnpm-store/*' -o -path './.shared/*' -o -path './.task/*' -o -path './.venv/*' -o -path './.vscode/*' -o -path './build/*' -o -path './dist/*' -o -path './node_modules/*' -o -path './roles/*' -o -name pnpm-lock.yaml -o -name package-lock.json -o -name poetry.lock -o -name '.variables.json' -o -name '.git' + INIT_SCRIPT: https://gitlab.com/megabyte-labs/gitlab-ci/-/raw/master/scripts/update-init.sh + LOG_FIX: + sh: chmod +x .config/log + MODEL_TASKFILE: https://gitlab.com/megabyte-labs/common/shared/-/raw/master/Taskfile.yml + NPM_KEEP_UPDATED: '' + NPM_PROGRAM: pnpm + PROJECT_TYPE: + sh: | + if type jq &> /dev/null && [ -f package.json ]; then VER="$(jq -r '.blueprint.group' package.json)"; if [ "$VER" == 'null' ]; then TYPE="$GROUP_TYPE"; else TYPE="$VER"; fi; else TYPE="$GROUP_TYPE"; fi + if type jq &> /dev/null && [ -f package.json ]; then VER="$(jq -r '.blueprint.subgroup' package.json)"; if [ "$VER" == 'null' ]; then SUBTYPE="$REPOSITORY_TYPE"; else SUBTYPE="$VER"; fi; else SUBTYPE="$REPOSITORY_TYPE"; fi + if [ "$TYPE" == 'common' ] && [ "$SUBTYPE" == 'shared' ]; then + echo 'shared' + elif [ "$TYPE" == 'common' ]; then + echo 'common' + elif [ "$TYPE" == 'documentation' ] && [ "$SUBTYPE" == 'shared' ]; then + echo 'commondocs' + elif [ "$TYPE" == 'documentation' ]; then + echo 'docs' + else + echo 'project' + fi + # yamllint enable rule:line-length + PYTHON_HANDLE: + sh: | + if type poetry &> /dev/null; then + echo 'poetry run ' + else + echo '' + fi + PYTHON_VIRTUALENV: true + REPOSITORY_SUBTYPE: + sh: if type jq &> /dev/null && [ -f package.json ]; then VER="$(jq -r .blueprint.subgroup package.json)"; if [ "$VER" == null ]; then echo "$REPOSITORY_TYPE"; else echo "$VER"; fi; else echo "$REPOSITORY_TYPE"; fi + REPOSITORY_TYPE: + sh: if type jq &> /dev/null && [ -f package.json ]; then VER="$(jq -r .blueprint.group package.json)"; if [ "$VER" == null ]; then echo "$GROUP_TYPE"; else echo "$VER"; fi; else echo "$GROUP_TYPE"; fi + SEMANTIC_CONFIG: semantic-release-config + TIMEZONE: America/New_York + includes: + common:start: ./.config/taskfiles/common/Taskfile-start.yml +env: + GOPATH: + sh: | + if [ -z "$GOPATH" ]; then + echo "$HOME/.local/go" + else + echo "$GOPATH" + fi + OSTYPE: + sh: | + {{if (eq OS "linux")}}LINUX_FLAVOR='linux-gnu' + LIBC=$(ldd /bin/ls | grep 'musl' | head -1 | cut -d ' ' -f1) + if [ -n "$LIBC" ]; then LINUX_FLAVOR='linux-musl'; fi{{end}} + echo "{{if (eq OS "linux")}}$LINUX_FLAVOR{{else}}darwin{{end}}" + PATH: + sh: | + PATH="$PATH:$HOME/.local/bin:$HOME/.poetry/bin:$HOME/.gem/bin:$HOME/.asdf/bin" + PATH="$PATH:$HOME/.local/go/bin:$HOME/go/bin" + PATH="$PATH:/home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin" + PATH="$PATH:$PWD/bin" + PATH="$PATH:$HOME/.volta/bin" + echo "$PATH" + RECORD_INSTALL: + sh: if [ -n "$CI" ]; then echo 'true'; fi + SENTRY_DSN: + sh: | + if type jq &> /dev/null; then + DSN="$(jq -r '.blueprint.sentryDSN' package.json)" + if [ "$DSN" != 'null' ]; then echo "$DSN"; fi + fi + VOLTA_HOME: + sh: echo "$HOME/.volta" +profile: | + if [[ "$OSTYPE" == 'linux-gnu'* ]] || [[ "$OSTYPE" == 'linux-musl'* ]]; then + if [ -f /home/linuxbrew/.linuxbrew/bin/brew ] && ! type brew > /dev/null; then + eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)" + fi + fi + if [[ "$OSTYPE" == 'darwin'* ]] && type brew > /dev/null; then + BREW_PREFIX="$(brew --prefix)" + PATH="$PATH:$BREW_PREFIX/opt/coreutils/libexec/gnubin" + PATH="$PATH:$BREW_PREFIX/opt/findutils/libexec/gnubin" + PATH="$PATH:$BREW_PREFIX/opt/gnu-sed/libexec/gnubin" + PATH="$PATH:$BREW_PREFIX/opt/grep/libexec/gnubin" + PATH="$PATH:$BREW_PREFIX/opt/gnu-tar/libexec/gnubin" + PATH="$PATH:$BREW_PREFIX/opt/gawk/libexec/gnubin" + fi + if [ -f .venv/bin/activate ]; then + . .venv/bin/activate + fi +tasks: + build: + deps: + - install:software:jq + - install:software:node + desc: Build the project using the build script defined in `package.json` + summary: | + # Build the Project + + Use this command to build the project. It executes the command defined + in `package.json` under the `.scripts.build` key. + vars: + NONINTERACTIVE_MISSING_BUILD_CMD: There must be a build command under `.scripts.build` in `package.json`! + log: + error: Encountered error while running `npm run build` + start: Running `npm run build` + success: Successfully ran `npm run build` + cmds: + - | + if [ "$(jq '.scripts.build' package.json)" != 'null' ]; then + if type npm &> /dev/null; then + npm run build + else + task {{.REPOSITORY_TYPE}}:build + fi + else + [[ $- == *i* ]] && task prepare || (.config/log error '{{.NONINTERACTIVE_MISSING_BUILD_CMD}}' && exit 1) + fi + clean: + desc: Removes optional folders that are cached during various tasks + summary: | + # Clean Project / Remove Optional Directories and Files + + This task will remove all the unnecessary files that are downloaded, generated, and + cached during various build steps. This task is used by the `reset` task + which will re-generate the project from scratch. Ideally, this task and the reset task + should never be necessary. The `start` task should be used instead. + vars: + CLEAN_TARGETS: .autodoc .cache .task .venv node_modules tsconfig.tsbuildinfo venv .variables.json + cmds: + - task: common:clean + vars: + CLEAN_TARGETS: '{{.CLEAN_TARGETS}}' + commit: + desc: Lint staged files, report spelling errors, and open a _required_ commit dialoge + summary: | + # Commit Code + + This task will perform linting and auto-fixing on the files that have been staged in + git (i.e. the files that were added with `git add --all`). It will then report possible + spelling errors that you may choose to fix. Then, it opens a _required_ interactive commit + questionnaire that will help you make better commits that are compatible with software + that generates the CHANGELOG.md. + + It is very important that you use this task to commit rather than the conventional approach + using `git commit -m`. However, if you really need to, you can add the flag `--no-verify` + to your regular `git commit -m` command to bypass the pre-commit hook. + cmds: + - task: common:commit + commit:all: + deps: + - install:software:git + desc: Add all the untracked changes and commit the code + summary: | + # Commit All Untracked Code + + By default, this task is an alias for: + + ``` + git add --all + git commit + ``` + + If you pass a commit message as a CLI argument, then the pre-commit + logic will be bypassed. By bypassing the hooks, the commit message will be + excluded from the automatic CHANGELOG.md entry generated during commits. + Passing the commit message as a CLI argument can be done like so: + + ``` + task commit -- "Added new files that do not need to show up in CHANGELOG.md" + ``` + log: + error: Error while running commit logic + start: Adding all changes and committing + success: Successfully commit changes + cmds: + - | + {{if .CLI_ARGS}} + task --list > /dev/null || (echo "ERROR: Invalid Taskfiles!" && exit 1) + git add --all + HUSKY=0 git commit -m "{{.CLI_ARGS}}" --no-verify + {{else}} + task --list > /dev/null || (echo "ERROR: Invalid Taskfiles!" && exit 1) + git add --all + git commit + {{end}} + commit:quick: + deps: + - ci:commit:config + cmds: + - | + task --list > /dev/null || (echo "ERROR: Invalid Taskfiles!" && exit 1) + git add --all + - "HUSKY=0 git commit -m '\U0001F527 chore(tweak)": quick minor update' --no-verify + - git push origin master + devcontainer: + deps: + - install:npm:devcontainer + - install:software:docker + donothing: 'true' + fix: + desc: Run code auto-fixers / auto-formatters + summary: | + # Auto-Fix / Auto-Format Code + + This task will automatically apply lint fixes across the whole project using + auto-fixers. Although unlikely, it is possible that auto-fixing can introduce + an error so the auto-fixes still have to be validated. + cmds: + - task: fix:all + fresh: + summary: Initialize a new project with only the Taskfile.yml present + cmds: + - curl -sSL https://gitlab.com/megabyte-labs/common/shared/-/raw/master/common/start.sh > start.sh + - curl -sSL https://gitlab.com/megabyte-labs/common/shared/-/blob/master/common/.gitignore > .gitignore + - curl -sSL https://gitlab.com/megabyte-labs/common/shared/-/blob/master/common/.editorconfig > .editorconfig + - curl -sSL https://gitlab.com/megabyte-labs/common/shared/-/blob/master/package.json > package.json + - curl -sSL https://gitlab.com/megabyte-labs/common/shared/-/blob/master/.gitlab-ci.yml > .gitlab-ci.yml + - TMP="$(mktemp)" && jq -r 'del(.blueprint)' package.json > "$TMP" && mv "$TMP" package.json + - bash start.sh + - task: prepare + get:links: + deps: + - install:software:jq + desc: Log useful links such as the current project's git repository link + vars: + GITHUB_URL: + sh: jq -r '.blueprint.repository.github' package.json + GITLAB_URL: + sh: jq -r '.blueprint.repository.gitlab' package.json + cmds: + - .config/log info 'GitHub -----> `{{.GITHUB_URL}}`' + - .config/log info 'GitLab -----> `{{.GITLAB_URL}}`' + group:exec: + desc: Execute group commands on any GitLab group (including repositories in sub-groups) + summary: | + # Run Commands on Multiple GitLab Repositories + + This command requires that the `GITLAB_TOKEN` environment variable be set. With the + token defined, you can use this task to run scripts on repositories that fall under + any given group or sub-group. By default, it runs the script(s) on all the + repositories in the specified group and sub-groups. + + You can use this task to perform adhoc bulk changes to large numbers of + repositories that are hosted on GitLab. + + **Example usage:** + `task group:exec -- group/subgroup ---- 'bash <(curl https://myscript.com)' + + In the example above, the bash command will be run in the root of each repository. + Be sure to wrap the command in quotes or you might observe some odd behavior. + cmds: + - task: git:gitlab:group:exec + init: + deps: + - install:software:jq + - install:software:yq + desc: Runs the initialization script + summary: | + # Initialize the Project + + This command will mostly not be needed for pre-existing projects. This task: + + 1. Migrates legacy projects + 2. Fixes common errors + 3. Ensures the project is initialized + 4. Prompts for missing metadata + 5. Runs the `start` task + + If you are facing difficulties, you can try running this task to reset the project, + perform common fixes, and run the whole start up routine. + + ## Using a Custom Initialization Script + + You can control the initialization script that this task runs by specifying + `UPDATE_INIT_SCRIPT` as an environment variable. If it is not specified, then this + task runs the [default script]({{.INIT_SCRIPT}}). + log: + error: Failed to run initialization script + start: Initializing project by running initialization script + success: Successfully bootstrapped project by running initialization script + cmds: + - git init + - task: repair + - task: prepare + jumpusb: + desc: Creates a JumpUSB (https://jumpusb.com) + cmds: + - task: install:ventoy + lint: + desc: Lints the project using all linters + summary: | + # Lint the Project Using Most of the Linters + + This task will do a project-wide linting using all of the linters that commonly + report errors. You can use this task to spot linting errors prior to running + the git pre-commit hooks. + + **Example usage:** + `task lint` + cmds: + - task: lint:all + livereload: + deps: + - install:npm:nodemon + desc: Start the project with live-reloading (i.e. watch mode) + summary: | + # LiveReload the Project + + Using LiveReload, you can automatically run the project any time you make + changes to the project. The LiveReload feature uses [nodemon](https://github.com/remy/nodemon) + with the configuration stored in `.config/nodemon.json`. + + Whenever you make a change to the project, `task project:livereload` will run. You can + define the command that runs every time you make a change in the `Taskfile-project.yml` + `livereload` section. This file is kept intact even across upstream propagation updates, + unlike the regular `Taskfile.yml`. + + **Example usage:** + `task livereload` + + The default configuration (in `.config/nodemon.json`) is generic and makes assumptions + about the file types to watch based on what type project it is. If you would like to + use a different configuration, you can pass in the location of the configuration like so: + + **Example with custom configuration:** + `task livereload -- nodemon.custom.json` + + The task program also provides a `--watch` feature. However, it relies on the `sources` attribute + being defined so it might not work in all cases. + + **Example using `task --watch`:** + `task --watch mytask` + log: + start: Starting `nodemon` live-reload session + cmds: + - | + if [ ! -f Taskfile-project.yml ]; then + .config/log error '`Taskfile-project.yml` must exist and have a `livereload` task to use with `nodemon`' && exit 1 + fi + - nodemon --config {{if .CLI_ARGS}}{{.CLI_ARGS}}{{else}}.config/nodemon.json{{end}} + new:project: + desc: Create a new project + cmds: + - task: prepare + preload: + desc: Set up your workstation in advance by installing commonly used programs + summary: | + # Speed Up Future Development by Preloading Common System Applications + + Just about every development task requires some sort of globally installed + application. TypeScript requires Node.js to be installed. Ansible requires + a handful of CLIs (and Python). This task will check for missing applications + and install them globally. If you do not run this task, the routines will + assume you want to keep the footprint small and use disposable virtual + environments whenever possible (i.e. `{{.NPX_PACKAGE}}` in the case of Node.js, + `poetry` in the case of Python, etc.). + + ## Reboot Requirements + + Some software that is installed, like VirtualBox and Docker, require reboots so + it is recommended that you reboot after running this task. + + ## Passwordless Installation + + This installation method and all the methods we use will only prompt for a sudo + password when absolutely necessary. If you already have common development tools + it is possible that you will not even have to enter a sudo password. This is + accomplished by installing to `$HOME/.local/bin` whenever a traditionally system + level binary is installed. However, if you are missing something like git, then + chances are you will be asked for a sudo password. In this case, we recommend + you adopt the Megabyte Labs philosophy and inspect the code before running it. + log: + error: Encountered error while preloading system with common dependencies (REBOOT RECOMMENDED) + start: Preloading system with common dependencies + success: Successfully preloaded system with common dependencies (REBOOT RECOMMENDED) + cmds: + - task: install:software:common + - task: install:brewfile + - task: install:extras + - task: install:go:bundle + - task: install:rust:bundle + - task: install:github:bundle + - task: install:npm:bundle + - task: install:pipx:bundle + - task: install:python:requirements + - task: install:modules:local + prepare: + desc: Prepares the project for the normal start command + summary: | + # Prepare the Project + + This task ensures the project has all the required metadata. If a project + is missing required metadata then this task will interactively prompt the user + for the missing data. It also performs miscellaneous tasks that are sometimes + required by the `start` task. + log: + error: Encountered error while preparing the project + start: Preparing the project + success: Successfully prepared the project + cmds: + - task: boilerplate:check:package + - task: boilerplate:clean + publish: + desc: Publish a semantic release via `semantic-release` + summary: | + # Publish the Project + + Using this task, you can publish a project to all its targets. The targets + depend on the project type. Most projects will include: + + 1. Updating version numbers in `package.json`, `pyproject.toml`, etc. + 2. Adding a new git tag at the version and publishing it with updated assets + 3. Publishing repository-specific items like NPM packages, PyPi packages, etc. + 4. Publishing artifacts to GitLab Releases and GitHub Releases + 5. Updating the `docs/CHANGELOG.md` + + ## Packages Used + + This task runs `semantic-release` using the `semantic-release-config` package. The package + includes release instructions for: + + 1. Python packages + 2. NPM packages + 3. Dockerfile projects + 4. Go projects + 5. Ansible roles + + ## Formats + + It does its best to ship projects out in as many formats (that make sense) as possible. + For instance, an NPM CLI gets published as: + + 1. NPM package + 2. Binaries (including deb and rpm) available on GitHub/GitLab releases + 3. Brew formulae + 4. Snapcraft + 5. Probably more implemented after writing this + + ## Python Package Notes + + If `setup.cfg` is present, then the project is assumed to be a legacy Python + project that needs to run in environment with access to the `python-semantic` + extras defined in `pyproject.toml`. + log: + error: Error running `semantic-release` via `task publish` + start: Running `semantic-release` via `task publish` + success: Successfully ran `semantic-release` via `task publish` + cmds: + - task: publish:semantic-release + publish:force: + desc: Force a `semantic-release` even if there are no new eligible commits + summary: | + # Force a Release with Empty Commit + + Trigger any kind of version bump / semantic release by passing + `PATCH UPDATE`, `MINOR UPDATE`, or `MAJOR UPDATE` in via CLI arguments. + This task will commit an empty commit message that will trigger the + corresponding version bump type. + + **Example of patch update:** + `task publish:force -- 'PATCH UPDATE'` + + **Example of minor update:** + `task publish:force -- 'MINOR UPDATE'` + + **Example of major update:** + `task publish:force -- 'MAJOR UPDATE'` + vars: + UPDATE_LEVEL: '{{if .CLI_ARGS}}{{.CLI_ARGS}}{{else}}PATCH UPDATE{{end}}' + log: + error: Error creating update via `task publish:force` + start: Publishing `semantic-release` update via `task publish:force` + success: Successfully published update via `task publish:force` + cmds: + - "HUSKY=0 git commit -a --allow-empty -m '\U0001F528 chore(bump): Forced semantic-release {{.UPDATE_LEVEL}}' -n\n" + - task: publish + pull:upstream: + desc: Pull from upstream repositories + summary: | + # Pull Changes from Upstream Repositories + + This task will do a `git pull` on each repository defined in `.blueprint.upstreamRemotes`. + The variable should be an array of objects where each object has the `remote`, `url`, and `branch` keys. + + **Sample value of `.blueprint.upstreamRemotes`:** + `[{ "remote": "ionic", "url": "git@github.com:ionic-team/ionic-docs.git", "branch": "main" }]` + log: + error: Encountered error while running `git pull` on each of the `upstreamRemotes` + start: Running `git pull` on each of the `upstreamRemotes` + success: Successfully pulled from `upstreamRemotes` + cmds: + - task: common:update:upstream:remotes:pull + repair: + cmds: + - task: common:repair + - | + TMP="$(mktemp)" + if [ -n "$UPDATE_INIT_SCRIPT" ]; then + curl -sSL "$UPDATE_INIT_SCRIPT" > "$TMP" && bash "$TMP" && rm "$TMP" + else + curl -sSL {{.INIT_SCRIPT}} > "$TMP" && bash "$TMP" && rm "$TMP" + fi + reset: + desc: Resets the project by removing all caches and then re-generating templated files + summary: | + # Reset Project and Re-Generate Assets + + This task is intended to be used when there appear to be cache related issues. + It will first clean the project by removing common local libraries like `node_modules/` + and the `.cache/` folder, to name a couple. Then, without acquiring any upstream + updates, it re-generates any templated files. + cmds: + - task: common:reset + reset:force: + desc: 'Aggressively reset the project (**WARNING** This will wipe uncommitted work)' + summary: | + # Aggressively Reset Project + + This task will completely wipe the project by: + + 1. Resetting to the HEAD + 2. Removing all files that are not included in source control (i.e. `node_modules/` etc.) + 3. Pulling the latest changes from `master` + + This task is essentially the equivalent of deleteing the project and re-cloning it. + prompt: + type: confirm + message: Are you sure you want to forcefully reset the project? + answer: + cmds: + - task: common:reset:force + scripts: + interactive: true + deps: + - install:npm:ntl + desc: Run and view descriptions for `npm scripts` via an interactive dialog + summary: | + # Interactively Select Scripts Defined in package.json + + Our projects use `run` (a fork of task) to handle most of the task running. + However, in some projects there may be scripts defined in package.json. This + task is a convenience method that will present a list of scripts alongside + their descriptions that you can interactively launch. + log: + error: Error running `NTL_RUNNER={{.NPM_PROGRAM}} ntl` + start: Running `NTL_RUNNER={{.NPM_PROGRAM}} ntl` + cmds: + - NTL_RUNNER={{.NPM_PROGRAM}} ntl + services: + desc: Update elements of the repository that require API access + summary: | + # Update Elements of the Repository that Require API Access + + This task will ensure that the git repositories and other services related to the + project are updated with the proper configurations. It requires that certain + API keys be set. Generally, only project owners will use this task. + cmds: + - task: common:update:services + status: + - '[ -n "$GITLAB_CI" ] && [ "$REPOSITORY_UPDATE" != "true" ]' + shell: + desc: Start a terminal session using Docker with any Linux operating system + compile: | + '.tasks.common.shell = $this' common/Taskfile.yml + summary: | + # Start a Docker Terminal Session + + Use Docker to run commands on nearly any operating system. The operating + systems are all stock distros with systemd added. + + The selected environment will mount the current working directory to the + Docker container. + + **Example opening an interactive prompt:** + `task shell` + + **Example of directly shelling into a container:** + `task shell -- ubuntu-21.04` + + ## Available operating systems (that you can use with the example above): + + * archlinux + * centos-7 + * centos-8 + * debian-9 + * debian-10 + * fedora-33 + * fedora-34 + * ubuntu-18.04 + * ubuntu-20.04 + * ubuntu-21.04 + cmds: + - task: common:shell + ssh-keys: + deps: + - cloud:heroku:ssh-keys + - git:github:ssh-keys + - git:gitlab:ssh-keys + start: + desc: Start the project by installing / updating dependencies, repairing issues, and opening a tutorial + summary: | + # Start the Project + + This task does everything that running `bash start.sh` does plus a little bit more: + + 1. Installs Homebrew if it is not already installed + 2. Ensures Bodega is installed and up-to-date + 3. Ensures common system dependencies and runtimes + 4. Installs commonly used CLI tools + 5. Ensures local dependencies such as `node_modules` are installed + 6. Attempts to repair the project if it detects any issues + + It basically sets up all the tools you need, in advance so that problems can be detected as early + as possible. In some cases, this command will open documentation or a tutorial to read while dependencies are being + installed. + log: + error: Yikes! Error starting the project! + start: Starting the project.. + success: Project started! + cmds: + - task: upstream:project + synchronize: + desc: Set up the project and refresh it with the latest changes + summary: | + # Start the Project + + This task will scaffold the project with the latest upstream changes + and ensure your development environment has all the dependencies installed. + + This command should be used whenever the files that are automatically generated + need to be updated. It also serves as an entry point for new developers who + are getting started with the project. + + **Example usage:** + `task start` + log: + error: Error encountered while starting up the project + start: Synchronizing project with upstream file changes and bootstrapping + success: Successfully synchronized the project with upstream file changes and also bootstrapped the project + cmds: + - task: upstream:{{.PROJECT_TYPE}} + tag:deps: + desc: Inject a new command in the `Taskfile.yml` that includes all tasks matching a given tag as deps + summary: | + # Create Task with Deps Based on Tag + + Use this task to inject a new task in the `Taskfile.yml` that includes all the tasks + that are tagged with a certain tag as deps. The tag must be supplied as a CLI argument. + + Say you have a task that looks like this: + + ``` + my-task: + tags: + - docker + cmds: + - docker volume ls + ``` + + Then, by running `task tag:deps -- docker`, you will get the following task injected into the + `Taskfile.yml` file: + + ``` + deps:docker: + deps: + - my-task + ``` + cmds: + - task: common:util:task:tag:deps + template: + deps: + - install:npm:liquidjs + summary: | + # Render a Template + + This task is provided to serve as an alias for developing new templated files. + cmds: + - | + function handlebars() { + FILE="$1" + TMP="$(mktemp)" + .config/log info 'Generating `'"${FILE//.liquid}"'` from `'"$FILE"'`' + hbs --data .variables.json --helper ./.config/hbs.cjs "$FILE" --stdout > "$TMP" + mv "$TMP" "${FILE//.liquid}" + rm "$FILE" + } + handlebars '{{.CLI_ARGS}}' + preconditions: + - sh: test -f .variables.json + msg: This task requires that you have already spun up the project by running `task start` + test: + deps: + - install:software:jq + - install:software:node + interactive: true + desc: Open an interactive dialog to select and run a Molecule test + summary: | + # Test the Project + + This task calls `npm run test` which functions differently based on the + definition made in `package.json` under the `.scripts.test` key. + vars: + NONINTERACTIVE_MISSING_TEST_CMD: There must be a `.scripts.test` definition in `package.json`! + cmds: + - | + if [ "$(jq '.scripts.test' package.json)" != 'null' ]; then + npm run test + else + [[ $- == *i* ]] && task prepare || (.config/log error '{{.NONINTERACTIVE_MISSING_TEST_CMD}}' && exit 1) + fi + update: + desc: Fully update the repository + summary: | + # Update the Repository / Project + + This task will: + + 1. Ensure Homebrew is installed + 2. Ensure runtimes like Node.js, Go, Python, and Poetry are installed + 3. Ensure the `PATH` environment variable is properly configured + 4. Configure the environment if it is in a `$CI` environment + 5. Run a repair script that is used to ensure older repositories are compatible with the Taskfile scripts + 6. Prepare the repository by automatically setting up certain parts of the project + 7. Run a full update on the repository with `task start` + 8. Automatically commit changes + 9. Update GitLab/GitHub repository settings if the `GITHUB_TOKEN` / `GITLAB_TOKEN` variables are set + + Along the way, if any dependencies are missing they will be installed as well just like with all of the + tasks included in these Taskfiles. + log: + error: Failed to update the repository + start: Updating the repository.. + success: Finished updating the repository + cmds: + - task: common:start + env: + UPDATE_PROJECT: "true" + yubikey: + desc: Create an OpenGPG-enabled YubiKey + summary: | + # Create an OpenGPG-enabled YubiKey SmartCard + + This task will walk you through creating an OpenGPG-enabled + YubiKey. You can then use it to manage your GPG keys and SSH keys. + It will prompt you for things like your pin and automatically + set up the YubiKey. + + The interactive process simplifies this very popular guide on + how to set up a YubiKey like this: + + [Guide on Using YubiKey for OpenGPG and SSH keys](https://github.com/drduh/YubiKey-Guide) + + We do still recommend that you skim through the guide. + cmds: + - | + export GNUPGHOME="$HOME/.gnupgyubi" + export MASTER_KEY="$(LC_ALL=C tr -dc '[:upper:]' < /dev/urandom | fold -w 30 | head -n1)" + task security:yubikey:prepare diff --git a/package.json b/package.json new file mode 100644 index 00000000..57cab3ed --- /dev/null +++ b/package.json @@ -0,0 +1,219 @@ +{ + "private": false, + "name": "@tainted/hiawatha", + "version": "0.0.1", + "description": "Scripts, files, and assets used to build a JumpUSB (https://jumpusb.com)", + "license": "MIT", + "author": "Brian Zalewski (https://megabyte.space)", + "homepage": "https://megabyte.space", + "repository": { + "type": "git", + "url": "git+https://github.com/megabyte-labs/jumpusb.git" + }, + "bugs": { + "email": "help@megabyte.space", + "url": "https://gitlab.com/megabyte-labs/jumpusb/-/issues" + }, + "type": "commonjs", + "main": "dist/main.js", + "files": [ + "dist", + "lib" + ], + "scripts": { + "build": "bash start.sh && task npm:build:tsconfig", + "bump": "npm run build && npm version patch --no-scripts --no-commit-hooks --no-git-tag-version --force && SKIP_PREPUB=true npm publish", + "commit": "bash start.sh && task commit", + "fix": "bash start.sh && task fix", + "help": "bash start.sh && task --menu", + "lint": "bash start.sh && task lint", + "preload": "bash start.sh && task preload", + "prepare": "bash start.sh && (test -f Taskfile.yml && task npm:prepare) || true", + "release": "bash start.sh && task publish:semantic-release", + "repair": "bash <(curl -sS https://install.doctor/repair)", + "start": "test -z $SKIP_NPM_START && bash start.sh || true", + "test": "bash start.sh && task donothing", + "unpack": "bash start.sh && task npm:bundle:unpack", + "update": "bash start.sh && task update", + "vscode": "bash start.sh && task vscode" + }, + "config": { + "commitizen": { + "path": "node_modules/git-cz-emoji" + } + }, + "dependencies": { + "tslib": "latest" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + }, + "optionalDependencies": {}, + "devDependencies": { + "@types/node": "^16.11.52", + "esbuild": "^0.12.29", + "esbuild-node-externals": "^1.4.1", + "eslint-config-strict-mode": "latest", + "git-cz-emoji": "latest", + "jest-preset-ts": "latest", + "prettier": "^2.7.1", + "prettier-config-sexy-mode": "latest", + "semantic-release-config": "latest", + "typescript": "^4.7.4", + "@commitlint/config-conventional": "latest", + "handlebars-helpers": "latest" + }, + "keywords": [ + "common", + "configurations", + "files", + "gitlab-ci", + "mblabs", + "megabytelabs", + "miscellaneous", + "npm", + "other", + "package", + "professormanhattan", + "shared", + "supporting", + "taskfiles", + "washingtondc" + ], + "engines": { + "node": ">=14.18.0" + }, + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/", + "tag": "edge" + }, + "blueprint": { + "description": "A glorious combination of application settings, theme files, and a performant cross-platform, desktop-oriented software installer.", + "group": "npm", + "name": "Hiawatha Dotfiles", + "overview": "Hiawatha Dotfiles is a glorious combination of application settings, theme files, and a performant yet flexible software installer written with [ZX](https://github.com/google/zx). The installer supports almost any operating system, just checkout the [software.yml file](https://gitlab.com/megabyte-labs/hiawatha-dotfiles/-/blob/master/software.yml). It uses [Chezmoi](https://github.com/twpayne/chezmoi) to apply file changes in an interactive way. It is not your typical Chezmoi project - it is built around the philosophy that you should be able to bash all your computers to bits with a hammer and then resurrect them the next day ✝️️ by storing stateful data to an S3 bucket and automating desktop configuration as much as possible.", + "repository": { + "github": "https://github.com/megabyte-labs/hiawatha-dotfiles", + "gitlab": "https://gitlab.com/megabyte-labs/hiawatha-dotfiles" + }, + "slug": "hiawatha-dotfiles", + "subgroup": "misc", + "title": "Hiawatha Dotfiles - The Spirit of GitHub" + }, + "changelog": { + "displayTypes": [ + "feat", + "fix", + "perf", + "refactor", + "revert" + ], + "showAuthor": true + }, + "commitlint": { + "extends": [], + "helpUrl": "https://megabyte.space/docs/contributing/commits" + }, + "eslintConfig": { + "extends": "eslint-config-strict-mode" + }, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/megabytelabs" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/ProfessorManhattan" + } + ], + "jest": { + "preset": "jest-preset-ts", + "reporters": [ + "default", + [ + "jest-html-reporters", + { + "filename": "index.html", + "logoImgPath": "./.config/assets/logo-jest-report.png", + "openReport": true, + "pageTitle": "Code Coverage Report", + "publicPath": "./coverage" + } + ] + ] + }, + "jscpd": { + "gitignore": true, + "threshold": 0.1 + }, + "nodemonConfig": { + "exec": "tsc -p . && node --inspect-brk dist/main", + "ext": "js,jsx,json,ts,tsx,yml", + "ignore": [ + "src/**/*.spec.ts" + ], + "watch": [ + ".env", + "src" + ] + }, + "ntl": { + "descriptions": { + "build": "Builds the project using the build command specified under scripts in package.json", + "bump": "Used to quickly build, increase the package.json version, and publish the package", + "commit": "If unsure, use this task to commit your code so that it adheres to our commit rules", + "fix": "Run auto-fixing with all available auto-fixers", + "help": "Display the Bodega Taskfile.yml interactive help menu", + "lint": "Lint the project with all available linters", + "preload": "Preload the operating system with commonly used libraries and tools", + "prepare": "A hook triggered by running npm install that runs logic such as installing pre-commit hooks", + "release": "Publish the software to all supported channels using semantic-release", + "repair": "Fixes basic issues in projects that are having trouble running basic commands like 'task start'", + "start": "Entrypoint for new developers that installs requirements and then runs 'task start'", + "test": "Runs the appropriate test logic by running the test command defined under scripts in package.json", + "unpack": "Populates production node_modules from compressed copies saved in the .modules/ folder", + "update": "Update and refresh the repository with upstream changes and templated parts" + } + }, + "pnpm": { + "allowedVersions": { + "@typescript-eslint/eslint-plugin": "5", + "eslint": "8", + "typescript": "4" + }, + "neverBuiltDependencies": [ + "core-js", + "core-js-pure", + "highlight.js" + ], + "peerDependencyRules": { + "ignoreMissing": [ + "eslint", + "prettier", + "puppeteer" + ] + } + }, + "prettier": "prettier-config-sexy-mode", + "release": { + "branches": [ + "main", + "master", + "next", + { + "name": "beta", + "prerelease": true + } + ], + "extends": "semantic-release-config" + }, + "volta": { + "node": "18.4.0", + "yarn": "1.22.19" + } + } + \ No newline at end of file diff --git a/start.sh b/start.sh new file mode 100644 index 00000000..aaea9371 --- /dev/null +++ b/start.sh @@ -0,0 +1,717 @@ +#!/usr/bin/env bash + +# @file .config/scripts/start.sh +# @brief Ensures Task is installed and up-to-date and then runs `task start` +# @description +# This script will ensure [Task](https://github.com/go-task/task) is up-to-date +# and then run the `start` task which is generally a good entrypoint for any repository +# that is using the Megabyte Labs templating/taskfile system. The `start` task will +# ensure that the latest upstream changes are retrieved, that the project is +# properly generated with them, and that all the development dependencies are installed. +# Documentation on Taskfile.yml syntax can be found [here](https://taskfile.dev/). + +set -eo pipefail + +# @description Initialize variables +DELAYED_CI_SYNC="" +ENSURED_TASKFILES="" +export HOMEBREW_NO_INSTALL_CLEANUP=true + +# @description Ensure permissions in CI environments +if [ -n "$CI" ]; then + if type sudo &> /dev/null; then + sudo chown -R "$(whoami):$(whoami)" . + fi +fi + +# @description Ensure .config/log is present +if [ ! -f .config/log ]; then + mkdir -p .config + if type curl &> /dev/null; then + curl -sSL https://gitlab.com/megabyte-labs/common/shared/-/raw/master/common/.config/log > .config/log + fi +fi + +# @description Ensure .config/log is executable +chmod +x .config/log + +# @description Acquire unique ID for this script +if [ -z "$CI" ]; then + if type md5sum &> /dev/null; then + FILE_HASH="$(md5sum "$0" | sed 's/\s.*$//')" + else + FILE_HASH="$(date -r "$0" +%s)" + fi +else + FILE_HASH="none" +fi + +# @description Caches values from commands +# +# @example +# cache brew --prefix golang +# +# @arg The command to run +function cache() { + local DIR="${CACHE_DIR:-.cache}" + if ! test -d "$DIR"; then + mkdir -p "$DIR" + fi + local FN="$DIR/${LINENO}-${FILE_HASH}" + if ! test -f "$FN" ; then + "$@" > "$FN" + fi + cat "$FN" +} + +# @description Formats log statements +# +# @example +# format 'Message to be formatted' +# +# @arg $1 string The message to be formatted +function format() { + # shellcheck disable=SC2001,SC2016 + ANSI_STR="$(echo "$1" | sed 's/^\([^`]*\)`\([^`]*\)`/\1\\e[100;1m \2 \\e[0;39m/')" + if [[ $ANSI_STR == *'`'*'`'* ]]; then + ANSI_STR="$(format "$ANSI_STR")" + fi + echo -e "$ANSI_STR" +} + +# @description Proxy function for handling logs in this script +# +# @example +# logger warn "Warning message" +# +# @arg $1 string The type of log message (can be info, warn, success, or error) +# @arg $2 string The message to log +function logger() { + if [ -f .config/log ]; then + .config/log "$1" "$2" + else + if [ "$1" == 'error' ]; then + echo -e "\e[1;41m ERROR \e[0m $(format "$2")\e[0;39m" + elif [ "$1" == 'info' ]; then + echo -e "\e[1;46m INFO \e[0m $(format "$2")\e[0;39m" + elif [ "$1" == 'success' ]; then + echo -e "\e[1;42m SUCCESS \e[0m $(format "$2")\e[0;39m" + elif [ "$1" == 'warn' ]; then + echo -e "\e[1;43m WARNING \e[0m $(format "$2")\e[0;39m" + else + echo "$2" + fi + fi +} + +# @description Helper function for ensurePackageInstalled for Alpine installations +function ensureAlpinePackageInstalled() { + if type sudo &> /dev/null && [ "$CAN_USE_SUDO" != 'false' ]; then + sudo apk --no-cache add "$1" + else + apk --no-cache add "$1" + fi +} + +# @description Helper function for ensurePackageInstalled for ArchLinux installations +function ensureArchPackageInstalled() { + if type sudo &> /dev/null && [ "$CAN_USE_SUDO" != 'false' ]; then + sudo pacman update + sudo pacman -S "$1" + else + pacman update + pacman -S "$1" + fi +} + +# @description Helper function for ensurePackageInstalled for Debian installations +function ensureDebianPackageInstalled() { + if type sudo &> /dev/null && [ "$CAN_USE_SUDO" != 'false' ]; then + sudo apt-get update + sudo apt-get install -y "$1" + else + apt-get update + apt-get install -y "$1" + fi +} + +# @description Helper function for ensurePackageInstalled for RedHat installations +function ensureRedHatPackageInstalled() { + if type sudo &> /dev/null && [ "$CAN_USE_SUDO" != 'false' ]; then + if type dnf &> /dev/null; then + sudo dnf install -y "$1" + else + sudo yum install -y "$1" + fi + else + if type dnf &> /dev/null; then + dnf install -y "$1" + else + yum install -y "$1" + fi + fi +} + +# @description Installs package when user is root on Linux +# +# @example +# ensureRootPackageInstalled "sudo" +# +# @arg $1 string The name of the package that must be present +# +# @exitcode 0 The package was successfully installed +# @exitcode 1+ If there was an error, the package needs to be installed manually, or if the OS is unsupported +function ensureRootPackageInstalled() { + export CAN_USE_SUDO='false' + if ! type "$1" &> /dev/null; then + if [[ "$OSTYPE" == 'linux'* ]]; then + if [ -f "/etc/redhat-release" ]; then + ensureRedHatPackageInstalled "$1" + elif [ -f "/etc/debian_version" ]; then + ensureDebianPackageInstalled "$1" + elif [ -f "/etc/arch-release" ]; then + ensureArchPackageInstalled "$1" + elif [ -f "/etc/alpine-release" ]; then + ensureAlpinePackageInstalled "$1" + fi + fi + fi +} + +# @description If the user is running this script as root, then create a new user named +# megabyte and restart the script with that user. This is required because Homebrew +# can only be invoked by non-root users. +if [ -z "$NO_INSTALL_HOMEBREW" ] && [ "$USER" == "root" ] && [ -z "$INIT_CWD" ] && type useradd &> /dev/null; then + # shellcheck disable=SC2016 + logger info 'Running as root - creating seperate user named `megabyte` to run script with' + echo "megabyte ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers + useradd -m -s "$(which bash)" --gecos "" --disabled-login -c "Megabyte Labs" megabyte > /dev/null || ROOT_EXIT_CODE=$? + if [ -n "$ROOT_EXIT_CODE" ]; then + # shellcheck disable=SC2016 + logger info 'User `megabyte` already exists' + fi + ensureRootPackageInstalled "sudo" + # shellcheck disable=SC2016 + logger info 'Reloading the script with the `megabyte` user' + exec su megabyte "$0" -- "$@" +fi + +# @description Ensures ~/.local/bin is in the PATH variable on *nix machines and +# exits with an error on unsupported OS types +# +# @example +# ensureLocalPath +# +# @set PATH string The updated PATH with a reference to ~/.local/bin +# +# @noarg +# +# @exitcode 0 If the PATH was appropriately updated or did not need updating +# @exitcode 1+ If the OS is unsupported +function ensureLocalPath() { + if [[ "$OSTYPE" == 'darwin'* ]] || [[ "$OSTYPE" == 'linux'* ]]; then + # shellcheck disable=SC2016 + PATH_STRING='export PATH="$HOME/.local/bin:$PATH"' + mkdir -p "$HOME/.local/bin" + if ! grep "$PATH_STRING" < "$HOME/.profile" > /dev/null; then + echo -e "${PATH_STRING}\n" >> "$HOME/.profile" + logger info "Updated the PATH variable to include ~/.local/bin in $HOME/.profile" + fi + elif [[ "$OSTYPE" == 'cygwin' ]] || [[ "$OSTYPE" == 'msys' ]] || [[ "$OSTYPE" == 'win32' ]]; then + logger error "Windows is not directly supported. Use WSL or Docker." && exit 1 + elif [[ "$OSTYPE" == 'freebsd'* ]]; then + logger error "FreeBSD support not added yet" && exit 1 + else + logger warn "System type not recognized" + fi +} + +# @description Ensures given package is installed on a system. +# +# @example +# ensurePackageInstalled "curl" +# +# @arg $1 string The name of the package that must be present +# +# @exitcode 0 The package(s) were successfully installed +# @exitcode 1+ If there was an error, the package needs to be installed manually, or if the OS is unsupported +function ensurePackageInstalled() { + export CAN_USE_SUDO='true' + if ! type "$1" &> /dev/null; then + if [[ "$OSTYPE" == 'darwin'* ]]; then + brew install "$1" + elif [[ "$OSTYPE" == 'linux'* ]]; then + if [ -f "/etc/redhat-release" ]; then + ensureRedHatPackageInstalled "$1" + elif [ -f "/etc/debian_version" ]; then + ensureDebianPackageInstalled "$1" + elif [ -f "/etc/arch-release" ]; then + ensureArchPackageInstalled "$1" + elif [ -f "/etc/alpine-release" ]; then + ensureAlpinePackageInstalled "$1" + elif type dnf &> /dev/null || type yum &> /dev/null; then + ensureRedHatPackageInstalled "$1" + elif type apt-get &> /dev/null; then + ensureDebianPackageInstalled "$1" + elif type pacman &> /dev/null; then + ensureArchPackageInstalled "$1" + elif type apk &> /dev/null; then + ensureAlpinePackageInstalled "$1" + else + logger error "$1 is missing. Please install $1 to continue." && exit 1 + fi + elif [[ "$OSTYPE" == 'cygwin' ]] || [[ "$OSTYPE" == 'msys' ]] || [[ "$OSTYPE" == 'win32' ]]; then + logger error "Windows is not directly supported. Use WSL or Docker." && exit 1 + elif [[ "$OSTYPE" == 'freebsd'* ]]; then + logger error "FreeBSD support not added yet" && exit 1 + else + logger error "System type not recognized" + fi + fi +} + +# @description Ensures the latest version of Task is installed to `/usr/local/bin` (or `~/.local/bin`, as +# a fallback. +# +# @example +# ensureTaskInstalled +# +# @noarg +# +# @exitcode 0 If the package is already present and up-to-date or if it was installed/updated +# @exitcode 1+ If the OS is unsupported or if there was an error either installing the package or setting the PATH +function ensureTaskInstalled() { + # @description Release API URL used to get the latest release's version + TASK_RELEASE_API="https://api.github.com/repos/go-task/task/releases/latest" + if ! type task &> /dev/null; then + if [[ "$OSTYPE" == 'darwin'* ]] || [[ "$OSTYPE" == 'linux-gnu'* ]] || [[ "$OSTYPE" == 'linux-musl' ]]; then + installTask + elif [[ "$OSTYPE" == 'cygwin' ]] || [[ "$OSTYPE" == 'msys' ]] || [[ "$OSTYPE" == 'win32' ]]; then + logger error "Windows is not directly supported. Use WSL or Docker." && exit 1 + elif [[ "$OSTYPE" == 'freebsd'* ]]; then + logger error "FreeBSD support not added yet" && exit 1 + else + logger error "System type not recognized. You must install task manually." && exit 1 + fi + else + mkdir -p "$HOME/.cache/megabyte/start.sh" + if [ -f "$HOME/.cache/megabyte/start.sh/bodega-update-check" ]; then + TASK_UPDATE_TIME="$(cat "$HOME/.cache/megabyte/start.sh/bodega-update-check")" + else + TASK_UPDATE_TIME="$(date +%s)" + echo "$TASK_UPDATE_TIME" > "$HOME/.cache/megabyte/start.sh/bodega-update-check" + fi + # shellcheck disable=SC2004 + TIME_DIFF="$(($(date +%s) - $TASK_UPDATE_TIME))" + # Only run if it has been at least 15 minutes since last attempt + if [ "$TIME_DIFF" -gt 900 ] || [ "$TIME_DIFF" -lt 5 ]; then + date +%s > "$HOME/.cache/megabyte/start.sh/bodega-update-check" + logger info "Checking for latest version of Task" + CURRENT_VERSION="$(task --version | cut -d' ' -f3 | cut -c 2-)" + LATEST_VERSION="$(curl -s "$TASK_RELEASE_API" | grep tag_name | cut -c 17- | sed 's/\",//')" + if printf '%s\n%s\n' "$LATEST_VERSION" "$CURRENT_VERSION" | sort -V -c &> /dev/null; then + logger info "Task is already up-to-date" + else + logger info "A new version of Task is available (version $LATEST_VERSION)" + logger info "The current version of Task installed is $CURRENT_VERSION" + if ! type task &> /dev/null; then + logger info "Task is not available in the PATH" + installTask + else + if rm -f "$(which task)"; then + logger info "Removing task was successfully done without sudo" + installTask + elif sudo rm -f "$(which task)"; then + logger info "Removing task was successfully done with sudo" + installTask + else + logger warn "Unable to remove previous version of Task" + fi + fi + fi + fi + fi +} + +# @description Helper function for ensureTaskInstalled that performs the installation of Task. +# +# @see ensureTaskInstalled +# +# @example +# installTask +# +# @noarg +# +# @exitcode 0 If Task installs/updates properly +# @exitcode 1+ If the installation fails +function installTask() { + # @description Release URL to use when downloading [Task](https://github.com/go-task/task) + TASK_RELEASE_URL="https://github.com/go-task/task/releases/latest" + CHECKSUM_DESTINATION=/tmp/megabytelabs/task_checksums.txt + CHECKSUMS_URL="$TASK_RELEASE_URL/download/task_checksums.txt" + DOWNLOAD_DESTINATION=/tmp/megabytelabs/task.tar.gz + TMP_DIR=/tmp/megabytelabs + logger info "Checking if install target is macOS or Linux" + if [[ "$OSTYPE" == 'darwin'* ]]; then + DOWNLOAD_URL="$TASK_RELEASE_URL/download/task_darwin_amd64.tar.gz" + else + DOWNLOAD_URL="$TASK_RELEASE_URL/download/task_linux_amd64.tar.gz" + fi + logger "Creating folder for Task download" + mkdir -p "$(dirname "$DOWNLOAD_DESTINATION")" + logger info "Downloading latest version of Task" + curl -sSL "$DOWNLOAD_URL" -o "$DOWNLOAD_DESTINATION" + curl -sSL "$CHECKSUMS_URL" -o "$CHECKSUM_DESTINATION" + DOWNLOAD_BASENAME="$(basename "$DOWNLOAD_URL")" + DOWNLOAD_SHA256="$(grep "$DOWNLOAD_BASENAME" < "$CHECKSUM_DESTINATION" | cut -d ' ' -f 1)" + sha256 "$DOWNLOAD_DESTINATION" "$DOWNLOAD_SHA256" > /dev/null + logger success "Validated checksum" + mkdir -p "$TMP_DIR/task" + tar -xzf "$DOWNLOAD_DESTINATION" -C "$TMP_DIR/task" > /dev/null + if type task &> /dev/null && [ -w "$(which task)" ]; then + TARGET_BIN_DIR="." + TARGET_DEST="$(which task)" + else + if [ "$USER" == "root" ] || (type sudo &> /dev/null && sudo -n true); then + TARGET_BIN_DIR='/usr/local/bin' + else + TARGET_BIN_DIR="$HOME/.local/bin" + fi + TARGET_DEST="$TARGET_BIN_DIR/task" + fi + if [ "$USER" == "root" ]; then + mkdir -p "$TARGET_BIN_DIR" + mv "$TMP_DIR/task/task" "$TARGET_DEST" + elif type sudo &> /dev/null && sudo -n true; then + sudo mkdir -p "$TARGET_BIN_DIR" + sudo mv "$TMP_DIR/task/task" "$TARGET_DEST" + else + mkdir -p "$TARGET_BIN_DIR" + mv "$TMP_DIR/task/task" "$TARGET_DEST" + fi + logger success 'Installed Task to `'"$TARGET_DEST"'`' + rm "$CHECKSUM_DESTINATION" + rm "$DOWNLOAD_DESTINATION" +} + +# @description Verifies the SHA256 checksum of a file +# +# @example +# sha256 myfile.tar.gz 5b30f9c042553141791ec753d2f96786c60a4968fd809f75bb0e8db6c6b4529b +# +# @arg $1 string Path to the file +# @arg $2 string The SHA256 signature +# +# @exitcode 0 The checksum is valid or the system is unrecognized +# @exitcode 1+ The OS is unsupported or if the checksum is invalid +function sha256() { + if [[ "$OSTYPE" == 'darwin'* ]]; then + if type brew &> /dev/null && ! type sha256sum &> /dev/null; then + brew install coreutils + else + logger warn "Brew is not installed - this may cause issues" + fi + if type brew &> /dev/null; then + PATH="$(brew --prefix)/opt/coreutils/libexec/gnubin:$PATH" + fi + if type sha256sum &> /dev/null; then + echo "$2 $1" | sha256sum -c + else + logger warn "Checksum validation is being skipped for $1 because the sha256sum program is not available" + fi + elif [[ "$OSTYPE" == 'linux-gnu'* ]]; then + if ! type shasum &> /dev/null; then + logger warn "Checksum validation is being skipped for $1 because the shasum program is not installed" + else + echo "$2 $1" | shasum -s -a 256 -c + fi + elif [[ "$OSTYPE" == 'linux-musl' ]]; then + if ! type sha256sum &> /dev/null; then + logger warn "Checksum validation is being skipped for $1 because the sha256sum program is not available" + else + echo "$2 $1" | sha256sum -c + fi + elif [[ "$OSTYPE" == 'cygwin' ]] || [[ "$OSTYPE" == 'msys' ]] || [[ "$OSTYPE" == 'win32' ]]; then + logger error "Windows is not directly supported. Use WSL or Docker." && exit 1 + elif [[ "$OSTYPE" == 'freebsd'* ]]; then + logger error "FreeBSD support not added yet" && exit 1 + else + logger warn "System type not recognized. Skipping checksum validation." + fi +} + +# @description Ensures the Taskfile.yml is accessible +# +# @example +# ensureTaskfiles +function ensureTaskfiles() { + if [ -z "$ENSURED_TASKFILES" ]; then + # shellcheck disable=SC2030 + task donothing || BOOTSTRAP_EXIT_CODE=$? + mkdir -p "$HOME/.cache/megabyte/start.sh" + if [ -f "$HOME/.cache/megabyte/start.sh/ensure-taskfiles" ]; then + TASK_UPDATE_TIME="$(cat "$HOME/.cache/megabyte/start.sh/ensure-taskfiles")" + else + TASK_UPDATE_TIME="$(date +%s)" + echo "$TASK_UPDATE_TIME" > "$HOME/.cache/megabyte/start.sh/ensure-taskfiles" + fi + # shellcheck disable=SC2004 + TIME_DIFF="$(($(date +%s) - $TASK_UPDATE_TIME))" + # Only run if it has been at least 60 minutes since last attempt + if [ -n "$BOOTSTRAP_EXIT_CODE" ] || [ "$TIME_DIFF" -gt 3600 ] || [ "$TIME_DIFF" -lt 5 ] || [ -n "$FORCE_TASKFILE_UPDATE" ]; then + logger info 'Grabbing latest Taskfiles by downloading shared-master.tar.gz' + # shellcheck disable=SC2031 + date +%s > "$HOME/.cache/megabyte/start.sh/ensure-taskfiles" + ENSURED_TASKFILES="true" + if [ -d common/.config/taskfiles ]; then + if [[ "$OSTYPE" == 'darwin'* ]]; then + cp -rf common/.config/taskfiles/ .config/taskfiles + else + cp -rT common/.config/taskfiles/ .config/taskfiles + fi + else + mkdir -p .config/taskfiles + curl -sSL https://gitlab.com/megabyte-labs/common/shared/-/archive/master/shared-master.tar.gz > shared-master.tar.gz + tar -xzf shared-master.tar.gz > /dev/null + rm shared-master.tar.gz + rm -rf .config/taskfiles + mv shared-master/common/.config/taskfiles .config/taskfiles + mv shared-master/common/.editorconfig .editorconfig + mv shared-master/common/.gitignore .gitignore + rm -rf shared-master + fi + fi + if [ -n "$BOOTSTRAP_EXIT_CODE" ] && ! task donothing; then + # task donothing still does not work so issue must be with main Taskfile.yml + # shellcheck disable=SC2016 + logger warn 'Something is wrong with the `Taskfile.yml` - grabbing main `Taskfile.yml`' + git checkout HEAD~1 -- Taskfile.yml + if ! task donothing; then + logger error 'Error appears to be with main Taskfile.yml' + else + logger warn 'Error appears to be with one of the included Taskfiles' + logger info 'Removing and cloning Taskfile library from upstream repository' + rm -rf .config/taskfiles + FORCE_TASKFILE_UPDATE=true ensureTaskfiles + if task donothing; then + logger warn 'The issue was remedied by cloning the latest Taskfile includes' + fi + fi + fi + fi +} + +# @description Ensures basic files like package.json and Taskfile.yml are present +# +# @example +# ensureProjectBootstrapped +function ensureProjectBootstrapped() { + if [ ! -f start.sh ] || [ ! -f package.json ] || [ ! -f Taskfile.yml ]; then + if [ ! -f start.sh ]; then + curl -sSL https://gitlab.com/megabyte-labs/common/shared/-/raw/master/common/start.sh > start.sh + fi + if [ ! -f package.json ]; then + curl -sSL https://gitlab.com/megabyte-labs/common/shared/-/raw/master/package.json > package.json + fi + if [ ! -f Taskfile.yml ]; then + curl -sSL https://gitlab.com/megabyte-labs/common/shared/-/raw/master/Taskfile.yml > Taskfile.yml + fi + ensureTaskfiles + task new:project + fi +} + +##### Main Logic ##### + +if [ ! -f "$HOME/.profile" ]; then + touch "$HOME/.profile" +fi + +# @description Ensure git hosts are all in ~/.ssh/known_hosts +mkdir -p ~/.ssh +chmod 700 ~/.ssh +if [ ! -f ~/.ssh/known_hosts ]; then + touch ~/.ssh/known_hosts + chmod 600 ~/.ssh/known_hosts +fi +if ! grep -q "^gitlab.com " ~/.ssh/known_hosts; then + ssh-keyscan gitlab.com >> ~/.ssh/known_hosts 2>/dev/null +fi +if ! grep -q "^github.com " ~/.ssh/known_hosts; then + ssh-keyscan github.com >> ~/.ssh/known_hosts 2>/dev/null +fi +if ! grep -q "^bitbucket.org " ~/.ssh/known_hosts; then + ssh-keyscan bitbucket.org >> ~/.ssh/known_hosts 2>/dev/null +fi + +# @description Ensures ~/.local/bin is in PATH +ensureLocalPath + +# @description Ensures base dependencies are installed +if [[ "$OSTYPE" == 'darwin'* ]]; then + if ! type curl &> /dev/null && type brew &> /dev/null; then + brew install curl + fi + if ! type git &> /dev/null; then + # shellcheck disable=SC2016 + logger info 'Git is not present. A password may be required to run `sudo xcode-select --install`' + sudo xcode-select --install + fi +elif [[ "$OSTYPE" == 'linux-gnu'* ]] || [[ "$OSTYPE" == 'linux-musl'* ]]; then + if ! type curl &> /dev/null || ! type git &> /dev/null || ! type gzip &> /dev/null || ! type sudo &> /dev/null || ! type jq &> /dev/null; then + ensurePackageInstalled "curl" + ensurePackageInstalled "file" + ensurePackageInstalled "git" + ensurePackageInstalled "gzip" + ensurePackageInstalled "sudo" + ensurePackageInstalled "jq" + fi +fi + +# @description Ensures Homebrew, Poetry, and Volta are installed +if [ -z "$NO_INSTALL_HOMEBREW" ]; then + if [[ "$OSTYPE" == 'darwin'* ]] || [[ "$OSTYPE" == 'linux-gnu'* ]] || [[ "$OSTYPE" == 'linux-musl'* ]]; then + if [ -z "$INIT_CWD" ]; then + if ! type brew &> /dev/null; then + if type sudo &> /dev/null && sudo -n true; then + echo | /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + else + logger warn "Homebrew is not installed. The script will attempt to install Homebrew and you might be prompted for your password." + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + fi + fi + if ! (grep "/bin/brew shellenv" < "$HOME/.profile" &> /dev/null) && [[ "$OSTYPE" != 'darwin'* ]]; then + # shellcheck disable=SC2016 + logger info 'Adding linuxbrew source command to `~/.profile`' + # shellcheck disable=SC2016 + echo 'eval "$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)"' >> "$HOME/.profile" + fi + if [ -f "$HOME/.profile" ]; then + # shellcheck disable=SC1091 + . "$HOME/.profile" &> /dev/null || true + fi + if ! type poetry &> /dev/null; then + # shellcheck disable=SC2016 + brew install poetry || logger info 'There may have been an issue installing `poetry` with `brew`' + fi + if ! type jq &> /dev/null; then + # shellcheck disable=SC2016 + brew install jq || logger info 'There may have been an issue installiny `jq` with `brew`' + fi + if ! type yq &> /dev/null; then + # shellcheck disable=SC2016 + brew install yq || logger info 'There may have been an issue installing `yq` with `brew`' + fi + if ! type volta &> /dev/null || ! type node &> /dev/null; then + # shellcheck disable=SC2016 + curl https://get.volta.sh | bash + # shellcheck disable=SC1091 + . "$HOME/.profile" &> /dev/null || true + volta setup + volta install node + fi + fi + fi +fi + +# @description Second attempt to install yq if snap is on system but the Homebrew install was skipped +if ! type yq &> /dev/null && type snap &> /dev/null; then + if type sudo &> /dev/null; then + sudo snap install yq + else + snap install yq + fi +fi + +# @description Attempts to pull the latest changes if the folder is a git repository. +if [ -d .git ] && type git &> /dev/null; then + if [ -n "$GROUP_ACCESS_TOKEN" ] && [ -n "$GITLAB_CI_EMAIL" ] && [ -n "$GITLAB_CI_NAME" ] && [ -n "$GITLAB_CI" ]; then + git remote set-url origin "https://root:$GROUP_ACCESS_TOKEN@$CI_SERVER_HOST/$CI_PROJECT_PATH.git" + git config user.email "$GITLAB_CI_EMAIL" + git config user.name "$GITLAB_CI_NAME" + fi + mkdir -p .cache/start.sh + if [ -f .cache/start.sh/git-pull-time ]; then + GIT_PULL_TIME="$(cat .cache/start.sh/git-pull-time)" + else + GIT_PULL_TIME=$(date +%s) + echo "$GIT_PULL_TIME" > .cache/start.sh/git-pull-time + fi + # shellcheck disable=SC2004 + TIME_DIFF="$(($(date +%s) - $GIT_PULL_TIME))" + # Only run if it has been at least 15 minutes since last attempt + if [ "$TIME_DIFF" -gt 900 ] || [ "$TIME_DIFF" -lt 5 ]; then + date +%s > .cache/start.sh/git-pull-time + git fetch origin + GIT_POS="$(git rev-parse --abbrev-ref HEAD)" + logger info 'Current branch is `'"$GIT_POS"'`' + if [ "$GIT_POS" == 'synchronize' ] || [ "$CI_COMMIT_REF_NAME" == 'synchronize' ]; then + git reset --hard origin/master + git push --force origin synchronize || FORCE_SYNC_ERR=$? + if [ -n "$FORCE_SYNC_ERR" ] && type task &> /dev/null; then + NO_GITLAB_SYNCHRONIZE=true task ci:synchronize || CI_SYNC_TASK_ISSUE=$? + if [ -n "$CI_SYNC_TASK_ISSUE" ]; then + ensureTaskfiles + NO_GITLAB_SYNCHRONIZE=true task ci:synchronize + fi + else + DELAYED_CI_SYNC=true + fi + elif [ "$GIT_POS" == 'HEAD' ]; then + if [ -n "$GITLAB_CI" ]; then + printenv + fi + fi + git pull --force origin master --ff-only || true + ROOT_DIR="$PWD" + if ls .modules/*/ > /dev/null 2>&1; then + for SUBMODULE_PATH in .modules/*/; do + cd "$SUBMODULE_PATH" + DEFAULT_BRANCH=$(git remote show origin | grep 'HEAD branch' | cut -d' ' -f5) + git reset --hard HEAD + git checkout "$DEFAULT_BRANCH" + git pull origin "$DEFAULT_BRANCH" --ff-only || true + cd "$ROOT_DIR" + done + # shellcheck disable=SC2016 + logger success 'Ensured submodules in the `.modules` folder are pointing to the master branch' + fi + fi +fi + +# @description Ensures Task is installed and properly configured +ensureTaskInstalled + +# @description Ensures Taskfiles are up-to-date +logger info 'Ensuring Taskfile.yml files are all in good standing' +ensureTaskfiles + +# @description Try synchronizing again (in case Task was not available yet) +if [ "$DELAYED_CI_SYNC" == 'true' ]; then + logger info 'Attempting to synchronize CI..' + task ci:synchronize +fi + +# @description Run the start logic, if appropriate +if [ -z "$CI" ] && [ -z "$START" ] && [ -z "$INIT_CWD" ]; then + if ! type pipx &> /dev/null; then + task install:software:pipx + fi + logger info "Sourcing profile located in $HOME/.profile" + # shellcheck disable=SC1091 + . "$HOME/.profile" &> /dev/null || true + ensureProjectBootstrapped + if task donothing &> /dev/null; then + task -vvv start + else + FORCE_TASKFILE_UPDATE=true ensureTaskfiles + if task donothing &> /dev/null; then + task -vvv start + else + # shellcheck disable=SC2016 + logger warn 'Something appears to be wrong with the main `Taskfile.yml` - resetting to shared common version' + rm Taskfile.yml + ensureProjectBootstrapped + fi + fi +fi