squared 0.7

Version Compatibility

Date squared Min Latest LTS
2024-12-07 0.1.0 2.4.0 3.3.6
2025-01-07 0.2.0 3.4.0
2025-02-07 0.3.0 3.4.1
2025-03-06 0.4.0 3.4.2 *
2025-06-16 0.5.0 2.5.0 3.4.4 *
---------- ----- ----- ----- ---
2025-08-23 0.5.5 3.4.5
2025-10-31 0.6.0 3.4.7 *
2026-01-07 0.7.0 2.5.0 4.0.0 *

The range chart indicates the latest Ruby tested against at the time of release.

Installation

gem install squared

Example - Rakefile

Projects from any accessible folder can be added relative to the parent directory or absolutely. Missing projects will simply be excluded from the task runner.

require "squared"

require "squared/workspace"
require "squared/workspace/project/node"    # Optional (when using ref: :node)
require "squared/workspace/project/python"  #
require "squared/workspace/project/ruby"    #
require "squared/workspace/project/docker"  #
# OR
require "squared/app"                       # All workspace related modules

# NODE_ENV  = production

# pathname  = /workspaces/pathname
# optparse  = /workspaces/optparse
# log       = /workspaces/logger
# emc       = /workspaces/e-mc
# pir       = /workspaces/pi-r
# pir2      = /workspaces/pi-r2
# squared   = /workspaces/squared
# cli       = /workspaces/squared/publish/sqd-cli
# sqd-serve = /workspaces/squared/publish/sqd-serve
# sqd       = /workspaces/squared/sqd

# Load external Project classes (optional)
Workspace::Application.load_ref("/home/user/.gem/ruby/4.0.0/gems/squared-0.7.0/lib/squared/workspace/project") # ref = :node => node.rb (absolute)
Workspace::Application.load_ref("lib/squared/workspace/project", gem: "squared")                               # bundle env (relative)

Workspace::Application
  .new(Dir.pwd, main: "squared", exception: Logger::ERROR) # Dir.pwd? (main? is implicitly basename) | treat warnings as errors
  .banner("group", "project", styles: ["yellow", "black"], border: "bold") # name | project | path | ref | group? | parent? | version?
  .run("rake install", ref: :ruby)
  .depend(false, group: "default")
  .clean("rake clean", group: "default")
  .clean(["build/"], group: "app")
  .log({ file: "tmp/%Y-%m-%d.log", level: "debug" }, group: "app")
  .add("pathname", run: "rake compile", copy: "rake install", test: "rake test", group: "default", env: { # Ruby (with C extensions)
    "CFLAGS" => "-fPIC -O1"
  })
  .add("optparse", doc: "rake rdoc", gemspec: "optparse.gemspec", group: "default") # Uses bundler/gem_tasks (without C extensions)
  .add("logger", copy: { from: "lib", glob: "**/*.rb", into: "/home/user/.local/gem/ruby/4.0.0/gems/logger-1.7.0" }, clean: ["tmp/"]) do # autodetect: true | "rvm" | "rbenv" | "asdf" | "bundler"
    self.gemdir = "/home/user/.local/gem/ruby/4.0.0/gems/logger-1.7.0"       # Default for "into"
  end
  .add("e-mc", "emc", copy: { from: "publish", scope: "@e-mc", also: [:pir, "squared-express/"] }, ref: :node) # Node
  .add("pi-r", "pir", copy: { from: "publish", scope: "@pi-r" }, clean: ["publish/**/*.js", "tmp/"]) # Trailing slash required for directories
  .add("pi-r2", "pir2", copy: { from: :npm, also: %i[squared express], files: ["LICENSE", ["README.md.ruby", "README.md"]] }) # Uses dist files from NPM package spec
  .add("squared", init: "pnpm", script: ["build:stage1", "build:stage2"], group: "app") do # Use pnpm/yarn/berry for depend + Copy target (main)
    # Repo (global)
    as(:run, "build:dev", "dev")                                             # npm run build:dev -> npm run dev
    as(:run, { "build:dev": "dev", "build:prod": "prod" })

    add("publish/sqd-cli", "cli", exclude: [:git])                           # rake cli:build
    add("publish/sqd-serve")                                                 # rake sqd-serve:build
    add("publish/sqd-admin", group: "sqd", exclude: [:base])
    # OR
    with(exclude: [:base]) { add("publish/*", "packages") }                  # rake packages:sqd-serve:build
    # OR
    add(["publish/sqd-cli", "publish/sqd-serve"], true, exclude: [:base])    # rake squared:sqd-serve:build

    # Git
    revbuild(include: %w[src/ framework/ types/])                            # Synchronous is recommended
  end
  .add("squared/sqd", exclude: :git, pass: [:node, "checkout", "bump"]) do   # Skip initialize(:node) + squared:checkout:* + squared:bump:*
    apply :script, "build:sqd"                                               # Override detection
    apply :depend, false                                                     #
    apply :clean, ["build/sqd/"]                                             # variable_set (alias)
  end
  .with(:docker, only: ["build", "compose"], hide: [:windows?]) do           # When true hide entire group (formerly pass 0.6.0)
    .add("squared", "docker", file: "Dockerfile", context: ".", tag: "latest", registry: "localhost:5000", username: "squared", # compose publish => { oci: "oci://docker.io" }
                              args: "--ssh=default",
                              secrets: ["id=github,env=GITHUB_TOKEN"],
                              mounts: ["src=.,dst=/project,ro,bind-propagation=rshared"]) do # Docker
      series(:clean) do                                                                      # run | depend | doc | lint | test | copy | clean
        File.read(basepath("docker-bake.hcl"))
            .scan(/\btags\s+=\s+\["([^"]+)"\]/)
            .each { |val| image(:rm, tag: val.first) }
      end
    end
  end
  .pass("pull", group: "default") { test? || doc? } # rake pathname:pull | rake optparse:pull
  .style("banner", 255.255) # 256 colors (fg | fg.bg | -0.bg)
  .build(default: "build", parallel: ["pull", "fetch", "rebase", "archive", "clean", /^outdated:/], pass: ["publish"]) do |workspace|
    workspace
      .enable_aixterm
      .style({
        banner: ["bright_cyan", "bold", "bright_black!"],
        border: "bright_white"
      })
  end

# default   = /workspaces/ruby/*
# pathname  = /workspaces/ruby/pathname
# optparse  = /workspaces/ruby/optparse
# logger    = /workspaces/ruby/logger
# android   = /workspaces/android-docs
# chrome    = /workspaces/chrome-docs

# ...symbol?, string, ...string?, ...symbol?
#    extend     name       args     project

Node = Workspace::Project::Node                                      # tsc
Node.options("build:dev", "target=es2022", project: "tsconfig.json") # :node (ref)
Node.options("build:dev", "outDir=tmp", :squared)                    # squared (project name)

Ruby = Workspace::Project::Ruby                                      # ruby | gem | rake | bundle | irb | rdbg | rbs | rubocop | pry
Ruby.options("lint", "rubocop", opts: ["gemfile=Gemfile"])           # :ruby
Ruby.options("lint", "--parallel", :squared)

Python = Workspace::Project::Python                                  # pip
Python.options("build", opts: ["r=requirements.txt"])                # :python
Python.options(:build, "build:dev", opts: ["no-deps"])               # inherits "build" as "build:dev"

Workspace::Application
  .new(ENV["SQUARED_HOME"], prefix: "rb", common: false)             # Local styles
  .group("ruby", "default", run: "rake build", copy: "rake install", clean: "rake clean", rubygems: 3, ref: :ruby, override: { # legacy: gem 3.6 | bundler 2.7 => ["3", "2.7"]
    pathname: {
      run: "rake compile"                                            # rake rb:pathname:build
    }
  })
  .add("squared") do
    apply :run, proc {                                               # rake rb:squared:build
      tsc(with: scriptname || "build:dev", verbose: true)            # Repo (global)
      # OR
      tsc("target=es2022", "outDir=tmp", project: "tsconfig.json")   # 1
      tsc("clean=true", "./sqd/tsconfig.json", build: true)          # 2
      run("npm run build:dev", { "RUBYOPT" => "-W2" }, from: :run)   # 3

      # Parallel
      [
        Thread.new { tsc(sync: false) },
        Thread.new { tsc(sync: false) },
        Thread.new { run(sync: false) }
      ]
      .each(&:join)
    }
    apply :lint, proc {
      bundle("exec", "-A --cache=true", with: "lint", verbose: true) # bundle exec --gemfile="/squared/Gemfile" rubocop --parallel -A --cache=true
    }
  end
  .with(:python, editable: false) do                                         # ref=Symbol | group=String
    banner([:name, ": ", :version], "path")                                  # chrome-docs: 0.1.0 | /workspaces/chrome-docs
    doc("make html")                                                         # rake rb:doc:python
    run(false)                                                               # rake rb:build:python (disabled)
    exclude(%i[base git])                                                    # Project::Git.ref (superclass)
    add("android-docs", "android", venv: [".venv", "virtualenv", "--clean"]) # directory,virtualenv?,options (python -m virtualenv) | virtualenv.ini
    add("chrome-docs", "chrome", venv: ".venv") do                           # rake rb:chrome:doc (python -m venv)
      apply :run, proc {
        pip("wheel", with: "build:dev")                                      # pip wheel -r "/chrome-docs/requirements.txt" --no-deps
      }
    end
  end
  .style("inline", "bold")
  .stage("init:project") { puts self.path }                                  # modify attributes before building tasks (init | begin | project:begin/end | populate | extensions | series | finalize | end)
  .build

NOTE: The use of "ref" (class name) is only necessary when initializing an empty directory (e.g. rake repo:init).

Archive

# HEADERS='{"Authorization":"Bearer RANDOM-TOKEN"}' | hash/json
# ZIP_DEPTH=0                                       | default=1
# TAR_DEPTH=0                                       | TAR_DEPTH_SQUARED
# UNPACK_FORCE=1                                    | Remove target directory

Workspace::Application
  .new(main: "squared")
  .with(:python) do
    add("android-docs", "android", archive: "https://github.com/anpham6/android-docs/archive/refs/tags/v0.3.0.zip")
    add("chrome-docs", "chrome", archive: {
      uri: "https://github.com/anpham6/chrome-docs/archive/refs/tags/v0.5.0.tar.gz",  # URI.open (required)
      digest: "e3d55d2004d4770dd663254c9272dc3baad0d57a5bd14ca767de6546cdf14680",     # SHA1 | SHA256 | SHA384 | SHA512 | MD5
      digest: "rmd160:47b7790a511eed675fec1a3e742845fef058799b",                      # RMD160
      ext: "tar.gz",                                                                  # zip | tar | tar.gz | tgz | tar.xz | txz | 7z
      depth: 1,                                                                       # nested directories (e.g. --strip-components)
      headers: {                                                                      # URI.open
        "Authorization" => "Bearer RANDOM-TOKEN"
      }
    })
  end
  .add("squared", release: "https://github.com/anpham6/squared/archive/refs/tags/??") # squared:unpack:zip[v5.4.0,/tmp/squared]
end

Clone

The task is only active when the project directory is empty or does not exist.

Workspace::Application
  .new(main: "squared")
  .git(
    "emc": "https://github.com/anpham6/e-mc",                   # rake emc:clone
    "pir": {                                                    # rake pir:clone
      uri: "https://github.com/anpham6/pi-r",                   #
      options: {                                                #
        "origin": "github",                                     # --origin=github
        "recurse-submodules": false,                            # --no-recurse-submodules
        "shallow-exclude": ["v0.0.1", "v0.0.2"]                 # --shallow-exclude=v0.0.1 --shallow-exclude=v0.0.2
      }
    }
  )
  .git("squared", "/path/to/squared", options: { local: true }) # Relative paths resolve from workspace root
  .git(
    {
      emc: { uri: "e-mc", options: { "depth": 2 } },            # https://github.com/anpham6/e-mc
      pir: "pi-r"                                               # Maps task alias to repository folder
    },
    base: "https://github.com/anpham6",                         # Required
    repo: ["squared", "android-docs", "chrome-docs"],           # https://github.com/anpham6/squared
    options: {                                                  # Only "repo"
      "depth": 1,
      "quiet": true
    }
  )
  .with(:node) do                                               # rake clone:node
    add("e-mc", "emc") do                                       # https://github.com/anpham6/e-mc
      revbuild(before: %w[status squared:status],               # emc:status + squared:status (always)
               after: "refresh")                                # emc:refresh (changed)
    end                                                         #
    add("pi-r", "pir")                                          # https://github.com/anpham6/pi-r
    add("squared")                                              # https://github.com/anpham6/squared
  end
  .with(:python) do                                             # rake clone:python
    add("android-docs") { revbuild(pass: false) }               # Do not update status on a failed run
    add("chrome-docs") do
      revbuild(include: "source/", exclude: ["source/conf.py"]) # Limit files being watched
    end
  end
  .git("https://github.com/anpham6", ["emc", "pir"])            # Targets any defined project
  .git("https://github.com/anpham6", cache: true)               # Uses already defined root projects + revbuild
  .revbuild                                                     # Enables task revbuild (squared.revb)
  .revbuild(file: "../build.json")                              # $ROOT/build.json
  .build(parallel: ["clone"])                                   # rake clone + rake clone:sync

Build

Chain

There has to be at least one project which uses the step attribute. Other placement attributes are ignored.

NOTE: Projects can only reference non-global namespaced tasks. (e.g. with ":")

Workspace::Application
  .new
  .with(:node) do
    add("e-mc", "emc") do
      chain "all", "clean", step: 1                                        # Required
      chain "all", "build", step: 2
    end
    add("pi-r", "pir") do
      chain "all", "build", after: "emc:build"                             # step: 3
    end
    add("pi-r2", "pir2") do
      chain "all", "build", before: "squared"                              # step: 3
    end
    add("squared-express", "express") do
      chain "all", "clean", with: "emc"                                    # step: 1
      chain "all", "build", with: "pir"                                    # step: 3
    end
    add("squared") do
      revbuild(include: %w[src/ framework/ types/])                        # Git revision build command (optional)
      chain "all", "revbuild", after: "express:build"                      # step: 4
      chain "publish", "bump:patch", "publish:latest", step: 0, sync: true # rake publish
    end
  end
  .with(:python) do
    doc("make html")
    add("android-docs", serve: "./build/html") do                          # Uses built-in http.server module (ruby: webrick)
      chain "all", "doc", with: "squared", after: "squared"                # step: 4
    end
    add("chrome-docs", serve: { root: "./build/html", port: 8000 }) do     # root | bind | port | opts[]
      chain "all", "doc", with: "squared", before: "squared:revbuild"      # Same
    end
  end
  .chain("all", "status", with: "squared", after: "android-docs")          # Global tasks (e.g. without ":")
  .build
rake all                # all[1-3-4]
rake all:print

Threaded is the default when there are two or more tasks. Using with and either before or after will create a synchronous group.

  • Step 1: emc:clean + express:clean (thread)
  • Step 2: emc:build (sync)
  • Step 3: pir:build + express:build + pir2:build (thread)
  • Step 4: chrome-docs:doc + squared:revbuild + android-docs:doc + status (sync)

Graph

Workspace::Application
  .new(main: "squared")
  .graph(["depend"], ref: :git)                                                # Optional
  .with(:python) do
    doc(windows? ? ".\make.bat html" : "make html")                            # rake android-docs:doc | rake doc:python
    add("android-docs", "android", venv: "/home/user/.venv")                   # rake android-docs:depend
    add("chrome-docs", "chrome", graph: "android", venv: %w[.venv --clear]) do # /workspaces/chrome-docs/.venv
      apply :dependindex, 1                                                    # Use Poetry for dependencies (optional)
    end
  end
  .with(:node) do
    graph(["build", "copy"], on: {                                             # Overrides "git"
      first: proc { puts "1" },
      last: proc { puts "2" }
    })
    script("build:dev")                                                        # npm run build:dev
    # OR
    run([nil, "build:dev", { "PATH" => "~/.bin" }, "--workspace", "--silent"]) # PATH="~/.bin" npm run build:dev --workspace -- --silent
    # OR
    run({                                                                      # Same
      script: "build:dev",                                                     #
      env: { "PATH" => "~/.bin" },                                             #
      opts: "--workspace",                                                     #
      args: "--silent"                                                         #
    })

    add("e-mc", "emc") do
      first("build", "emc:clean", "emc:depend")                                # rake emc:clean && rake emc:depend && rake emc:build && echo "123"
      last("build", out: "123") { |out: nil| puts out }                        #
      error("build") { |err: nil| log.debug err }                              #
    end
    add("pi-r", "pir", graph: "emc", first: {
      build: proc { puts self.name }                                           # puts "pir"
    })
    add("squared-express", "express", graph: "pir")
    add("squared", graph: ["chrome", "express"]) do
      first("git:ls-files") { puts "1" }                                       # skipped
      first("git:ls-files", override: true) { puts "2" }                       # puts "2"
      last("git:ls-files") { puts workspace.root }                             # puts "/workspaces" (does not run when error is raised)
      error("git:ls-files") { |err| err.is_a?(TypeError) }                     # return true to suppress error
end
  end
  .with(:ruby) do
    run("gem build")                                                           # gem build
    # OR
    run("gem build", on: { first: -> { p "2" }, last: -> { p "4" } }) do       # run | depend | graph | clean | doc | lint | test
      p "1"
    end
    # OR
    run(on: { first: -> { p "pass" }, last: -> { p "pass" } }) do
      p "1"
    end
    # OR
    run(["gem build", "--force", { "RUBY_VERSION" => "4.0.0" }])               # RUBY_VERSION="4.0.0" gem build --force
    # OR
    run({                                                                      #
      command: "gem build",                                                    # RUBY_VERSION="4.0.0" gem build --silent --force
      opts: "--force",                                                         # composable
      env: { "PATH" => "~/.bin" },                                             #
      args: { silent: true }                                                   #
    })
    # OR
    run(["gem pristine", ["gem build", "gem cleanup"], nil, "--debug"])        # gem pristine --debug && gem build --debug && gem cleanup --debug
    #
    # All commands are either Array or Hash
    #
    run([                                                                      # PATH="~/.bin" GEM_HOME="~/.gems/ruby-4.0.0" (merged)
      ["gem pristine", "--all", { "PATH" => "~/.bin" }, "--silent"],           # gem pristine --silent --all
      ["gem build", { strict: true }, { "GEM_HOME" => "~/.gems/ruby-4.0.0" }]  # gem build --strict
    ])                                                                         #
    # OR
    run([                                                                      # Same
      {                                                                        #
        env: { "PATH" => "~/.bin" },                                           #
        command: "gem pristine",                                               #
        opts: "--all", args: "--silent"                                        #
      },                                                                       #
      {                                                                        #
        env: { "GEM_HOME" => "~/.gems/ruby-4.0.0" },                           #
        command: "gem build",                                                  #
        opts: { strict: true }                                                 #
      }                                                                        #
    ])

    add("pathname", test: ["rake test", { jobs: ENV["RAKE_JOBS"] }])           # rake test --jobs 4
    add("fileutils", graph: "pathname")
    add("optparse", run: "gem build", env: { "PATH" => "~/.bin" }, opts: "-v") # PATH="~/.bin" gem build -v
    add("rake", graph: ["fileutils", "optparse"])
    banner(command: false)                                                     # Always hide banner
  end
  .build
rake pir:graph                       # emc + pir
rake express:graph                   # emc + pir + express
rake chrome:graph                    # android + chrome
rake graph:python                    # same
rake squared:graph                   # android + chrome + emc + pir + express + squared
rake graph:node                      # same
rake rake:graph                      # pathname + fileutils + optparse + rake
rake graph:ruby                      # same
rake graph                           # graph:node + graph:ruby

rake squared:graph:run[express,pir]  # emc + pir + express + squared
rake squared:graph:run[node,-emc]    # pir + express + squared

Tasks

Workspace::Application
  .new(prefix: "rb")
  .batch(:ruby, :node, {
    stage: [%i[graph test], true],  # stage? (optional)
    reset: %i[stash pull]           # reset? (required)
  })
  .rename("depend", "install")
  .add("squared", timeout: { ruby: 10 }, ref: :ruby) do                         # Overrides ruby=5,8
    scope("nested:version") { puts self.version }                               # rake rb:squared:nested:version
  end
  .add("chrome-docs", timeout: 30, ref: :python)                                # Does not override ruby/python
  .timeout({ ruby: 5, python: 5 })                                              # global (seconds/fraction)
  .timeout({ ruby: 8, gem: 5, gem_update: 20, bundle_install: 30 }, ref: :ruby) # Overrides global (group | ref)
  .build

Some global tasks and local git commands do not support using process shell timeout.

Usage

rake -T                          # List tasks
rake                             # rake status (usually "build")

# GIT_OPTIONS=rebase
rake pull                        # All except "default" + "app"
rake pull:ruby                   # pathname + optparse + logger
rake pull:default                # pathname + optparse
rake pull:app                    # squared
rake pull:node                   # emc + pir + squared

rake build                       # All except "android"
rake doc                         # optparse + android
rake depend                      # All except "default"

rake build:ruby                  # rake compile + rake install + rake install

rake clean                       # All except "default" + "app"
rake clean:ruby                  # rake clean + rake clean + ["tmp/"]
rake clean:default               # rake clean + rake clean + skip
rake clean:app                   # none + skip + ["build/"]
rake clean:node                  # none + ["publish/**/*.js", "tmp/"] + ["build/"]

rake squared:run[#]              # List scripts (node)
rake squared:rake[#]             # List tasks (ruby)
rake build:app                   # squared + cli + sqd-serve
rake squared:build:workspace     # cli + sqd-serve
rake pull:sqd                    # sqd-admin
rake squared:pull:workspace      # sqd-serve + sqd-admin
rake squared:outdated:workspace  # cli + sqd-serve + sqd-admin

Methods

Task:

  • run
  • script
  • depend
  • archive
  • graph
  • doc
  • lint
  • test
  • clean

Non-task:

  • log
  • exclude

Styles

  • banner
  • border
  • header
  • active
  • inline
  • subject
  • border
  • warn
  • caution
  • current
  • latest
  • extra
  • major
  • red
  • yellow
  • green

Git

Most project classes will inherit from Git which enables these tasks:

Task Git Command
branch branch create track delete move copy list current
checkout checkout commit branch track detach path
commit commit add all amend amend-orig fixup
diff diff head branch files view between contain
fetch fetch origin remote all
files ls-files cached modified deleted others
git add blame clean grep mv revert rm sparse-checkout status
log log view between contain
merge merge commit no-commit send
pull pull origin remote all
range-diff range-diff view between contain
rebase rebase branch onto send
refs ls-remote --refs heads tags remote
reset reset commit index patch mode undo
restore restore source staged worktree
rev rev commit branch build output
sparse-checkout sparse-checkout add reapply list clean disable
show show format oneline textconv
stash stash push pop apply branch drop clear list all staged worktree
submodule submodule status update branch url sync
switch switch branch create detach
tag tag add sign delete list

You can disable all of them at once using the exclude property.

Workspace::Application
  .new
  .add("squared", exclude: :git)
  .build(exclude: ["autostash", "rebase"])

You can disable one or more of them using the pass property as a string.

Workspace::Application
  .new
  .add("squared", pass: ["pull"], ref: :node)
  .pass("pull", ref: :node) { read_packagemanager(:private) }

Commit Hash

Commands which use commit hashes are parsed using a ":" prefix as to not be confused for an option.

rake squared:log:view[:af012345]                   # git log af012345
rake squared:log:view[H1,HEAD^5,all,lib,./H12345]  # git log --all @~1 @^5 -- "lib" "H12345"

Environment

Path

All project binary programs can have their executable path set to a non-global alias.

Common::PATH.update({
  git: "/usr/bin/git",                              # PATH_GIT=/usr/bin/git
  tar: "/opt/archivers/tar",                        # PATH_TAR=/opt/archivers/tar
  unzip: "/opt/archivers/unzip",
  gem: "~/.rvm/gems/ruby-4.0.0/bin/gem",
  bundle: "~/.rvm/gems/ruby-4.0.0/bin/bundle",
  rake: "~/.rvm/gems/ruby-4.0.0/bin/rake",
  npm: "/opt/node/v22.0.0/bin/npm",
  python: "#{ENV["PYTHONPATH"]}/bin/python"
})

Build

Workspace::Application
  .new
  .add("squared", run: "gcc a.c -o a.o", opts: { __debug__: { g: true, O2: true, c: nil }, c: true, j: 4 }) # gcc a.c -o a.o -c -j4
BUILD_TYPE                   # global
${PROG}_COLOR=0              # --no-color (e.g. GIT_COLOR)

# :env                                         :run           :args :opts :type
# LD_LIBRARY_PATH="path/to/lib" CFLAGS="-Wall" gcc a.c -o a.o       -g    -O2
BUILD_${NAME}                # gcc a.c -o a.o
BUILD_${NAME}_OPTS           # -g
BUILD_${NAME}_ENV            # {"LD_LIBRARY_PATH":"path/to/lib","CFLAGS":"-Wall"} (hash/json)
BUILD_${NAME}_TYPE           # debug

# :env                                       :script   :opts                                  :args
# NODE_ENV="production" NO_COLOR="1" npm run build:dev --loglevel=error --workspaces=false -- --quiet
BUILD_${NAME}                # build:dev
BUILD_${NAME}_OPTS           # --loglevel=error --workspaces=false
BUILD_${NAME}_ENV            # {"NODE_ENV":"production","NO_COLOR":"1"} (hash/json)
BUILD_${NAME}_DEV            # pattern,0,1 (:dev)
BUILD_${NAME}_PROD           # pattern,0,1 (:prod)
${REF}_${NAME}_OPTS          # --quiet (e.g. NODE_SQUARED_OPTS)

BUILD_${NAME}=0              # skip project
BUILD_${NAME}_VERSION=0.1.0  # publish + detection

BANNER=0                     # hide banner
BANNER_${NAME}=0             #

STRICT=0                     # bypass error checking CLI commands
STRICT_${NAME}=0             #

VERBOSE=0                    # console output level
VERBOSE_${NAME}=0            # 0,1,2,n

REVBUILD_FORCE=1             # Rebuild all targets (GIT_FORCE)
REVBUILD_FORCE_${NAME}=1     # Rebuild project

PREREQS_${NAME}=build,copy   # Class method name to invoke
PREREQS_${REF}=depend        # e.g. Node

Graph

GRAPH_${NAME}                # depend,build => squared:depend + squared:build
GRAPH_${NAME}_PASS           # -emc,pir,express => pir + express

Logger

These global options also can target the project suffix ${NAME}. (e.g. LOG_FILE_EMC)

LOG_FILE                     # %Y-%m-%d.log
# OR
LOG_AUTO                     # year,y,month,m,day,d,1
# Optional
LOG_DIR                      # exist?
LOG_LEVEL                    # See gem "logger"

Git

GIT_OPTIONS=q,strategy=ort   # all
GIT_OPTIONS_${NAME}=v,ff     # project only
GIT_AUTOSTASH=1              # rebase (all)
GIT_AUTOSTASH_${NAME}=0      # rebase (project only)
GIT_REFLOG=1                 # list all commits
GIT_COUNT=50                 # list display limit
Command Flag ENV
branch create TRACK=0,1,s F
branch move copy F
branch delete COUNT=n
branch global SYNC
checkout branch DETACH TRACK=s COUNT=n
checkout detach REFLOG=1
checkout track COUNT=n
checkout global path HEAD=s PATHSPEC=s
checkout * F
clone * DEPTH=n ORIGIN=s BRANCH=s REVISION=s BARE=1 LOCAL=0,1
SINGLE_BRANCH=0,1 NO_CHECKOUT=1 NO_TAGS=1 QUIET=1
commit * UPSTREAM=s DRY_RUN EDIT=0 M
diff head branch INDEX=n
diff * PATHSPEC=s
fetch -remote ALL
fetch remote REFSPEC=s
fetch * F
git rm PATHSPEC=s
log * PATHSPEC=s
pull remote REFSPEC=s
pull -remote REBASE=0,1 ALL
pull all FF_ONLY=0
pull * AUTOSTASH F
range-diff * PATHSPEC=s
rebase branch HEAD=s
rebase onto INTERACTIVE I HEAD=s
reset mode (mixed) N REFRESH=0
reset index PATHSPEC=s
reset commit COUNT=n REFLOG=1
reset -commit HEAD=s
restore * PATHSPEC=s
revbuild global UNTRACKED_FILES=s IGNORE_SUBMODULES=s IGNORED=s (status)
stash push PATHSPEC=s
stash global ALL=0,1 KEEP_INDEX=0,1 INCLUDE_UNTRACKED=0,1 STAGED=0,1 M
status global BRANCH LONG IGNORE_SUBMODULES=s,0-3 PATHSPEC=s
submodule -branch -url R
switch detach REFLOG=1
switch -detach HEAD=s
switch * F
tag add SIGN FORCE HEAD=s M
tag sign F
tag delete COUNT=n
rev commit branch HEAD=s

Docker

DOCKER_OPTIONS=q,no-cache                # all
DOCKER_OPTIONS_${NAME}=v,no-cache=false  # project only (override)
DOCKER_TAG=latest                        # all
DOCKER_TAG_${NAME}=v0.1.0                # project only (override)
DOCKER_ALL=1                             # list every image/container
DOCKER_Y=1                               # confirm all

BUILD_SQUARED_OPTS="NODE_TAG=24 RUBY_VERSION=4.0.0" DOCKER_SQUARED_OPTS="--no-cache --label=v1" rake squared:build
docker build --no-cache --label=v1 --build-arg="NODE_TAG=24" --build-arg="RUBY_VERSION=4.0.0" .
Command Flag ENV
buildx build context TAG=s
buildx bake SERVICE=s
compose build TARGET=s
compose run VERSION=s
compose publish TAG=s REGISTRY=s
container commit REGISTRY=s PLATFORM=s DISABLE_CONTENT_TRUST=0,1
container -run -create -exec ALL=1 FILTER=s
-update -commit
image rm Y=1
image push TAG=s REGISTRY=s
image -push ALL=1
image -push -pull FILTER=s
network * ALL=1 FILTER=s

asdf

Command Options Arguments
set u home p
exec command,args*
current
Workspace::Application
  .new
  .add("squared", asdf: "ruby", ref: :node)   # Detects ruby instead of nodejs
  .add("squared-ruby", "squared", ref: :ruby) # Uses asdf program alias "ruby"
# ~/.bashrc (legacy)

export ASDF_DIR=/opt/asdf-vm

. $ASDF_DIR/asdf.sh

Repo

mkdir -p ~/.bin
PATH="${HOME}/.bin:${PATH}"
curl https://storage.googleapis.com/git-repo-downloads/repo > ~/.bin/repo
chmod a+rx ~/.bin/repo
require "squared/workspace"
require "squared/workspace/repo"

# REPO_ROOT = /workspaces                            |
# REPO_HOME = /workspaces/squared                    | Dir.pwd
# rake      = /workspaces/squared/Rakefile           | main?
#
# OR
#
# REPO_ROOT = /workspaces                            | Dir.pwd
# rake      = /workspaces/Rakefile                   |
# REPO_HOME = /workspaces/squared                    | main: "squared"

Workspace::Application
  .new(main: "squared")
  .repo("https://github.com/anpham6/squared-repo", "nightly", script: ["build:dev", "prod"], install: "#{ENV["HOME"]}/.bin", # install: first only
                                                              ref: %i[node -docker])                                         # exclude: -docker
  .repo("https://github.com/anpham6/squared-repo", name: "doc", doc: true, test: true, ref: :python)
  .add("squared", script: ["build:stage1", "build:stage2"])
  .with(:python) do
    add "android-docs"
    add "chrome-docs"
  end
  .build
REPO_ROOT=/workspace/node rake repo:init

REPO_ROOT=/workspace/python rake repo:doc:init

These global options also can target the application main suffix ${NAME}. (e.g. REPO_ROOT_SQUARED)

REPO_ROOT            # parent dir
REPO_HOME            # project dir (main)
REPO_OPTIONS         # appended to application command
REPO_BUILD           # script,run (e.g. build:dev | build:dev,make install | make)
REPO_SERIES          # depend,build:parallel,doc:detect,lint (task order)
REPO_GROUP           # string
REPO_REF             # e.g. ruby,node
REPO_DEV             # pattern,0,1
REPO_PROD            # pattern,0,1
REPO_WARN            # 0,1
REPO_SYNC            # 0,1
REPO_GIT             # manifest repository
REPO_MANIFEST        # e.g. latest,nightly,prod
REPO_GROUPS          # e.g. base,prod,docs
REPO_STAGE           # 0,1|sync,2|depend,4|build,8|copy,16|lint,512|dev
REPO_SUBMODULES      # 0,1
REPO_Y               # 0,1 (bypass interactive prompt)
REPO_TIMEOUT         # confirm dialog (seconds)

Other similarly cloned Repo repositories can be managed remotely by setting the REPO_ROOT environment variable to the location.

Project

Features can be enabled through ENV when calling global tasks such as through CI/CD automation (e.g. Repo).

Ruby

  • Prefix: BUNDLE/GEM/RBS
Command Flag ENV
depend - BINSTUBS=s JOBS=n
depend update VERSION=major
outdated - U
gem outdated DOCUMENT=0,1 USER_INSTALL=0,1
rbs prototype Y=0,1

Node

  • Prefix: NPM/PNPM/YARN
Command Flag ENV
depend - FORCE CI IGNORE_SCRIPTS
outdated - U
publish - OTP=s TAG=s ACCESS=0,1,s DRY_RUN Y
depend package * PACAKGE_LOCK
npm pnpm depend package CPU=s OS=s LIBC=s
npm package SAVE IGNORE_SCRIPTS STRICT_PEER_DEPS
pnpm depend PUBLIC_HOIST_PATTERN=s APPROVE_BUILDS
pnpm depend:add ALLOW_BUILD=s
yarn depend package IGNORE_ENGINES
yarn depend:add W

Python

  • Prefix: PIP/POETRY
Command Flag ENV
global * CACHE_DIR=0,s PROXY=s PYTHON=s COLOR=0
depend - E
outdated - U
venv exec INTERACTIVE=0
poetry * PROJECT=s
poetry depend NO_ROOT

LICENSE

BSD 3-Clause