diff --git a/package-lock.json b/package-lock.json index 2ff730a4..58192ed8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -815,6 +815,19 @@ "node": ">=6" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2733,6 +2746,20 @@ "node": ">=4" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -2787,6 +2814,51 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -2964,13 +3036,15 @@ } }, "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -3028,6 +3102,30 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-pkg-repo": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/get-pkg-repo/-/get-pkg-repo-4.2.1.tgz", @@ -3125,6 +3223,19 @@ "node": ">=10" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", @@ -3481,6 +3592,18 @@ "which": "bin/which" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -3532,6 +3655,33 @@ "node": ">=4" } }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -4592,6 +4742,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/meow": { "version": "8.1.2", "resolved": "https://registry.npmjs.org/meow/-/meow-8.1.2.tgz", @@ -6795,6 +6954,7 @@ "zz-colors": "_zz_colors.sh", "zz-context": "_zz_context.sh", "zz-dispatch": "_zz_dispatch.sh", + "zz-dist": "_zz_dist.sh", "zz-edit": "_zz_edit.sh", "zz-input": "_zz_input.sh", "zz-json": "_zz_json.sh", diff --git a/src/common-utils/README.md b/src/common-utils/README.md index 602d308c..690808d6 100644 --- a/src/common-utils/README.md +++ b/src/common-utils/README.md @@ -68,6 +68,67 @@ In addition to the specified utilities, some additional local utilities are also - `zz_log`: A utility to log messages with color - `zz_args`: A utility to parse command line arguments and display associated help messages in one go. +### Distribute zz_\* Utilities + +The `zz_dist` utility allows you to copy all `zz_*` utilities from the devcontainer-installed location to a target directory. This is useful for maintaining a local copy of utilities in your project. + +#### Usage + +```bash +zz_dist [options] +``` + +#### Options + +| Option | Description | +| ---------- | ----------------------------------------------------------------------------------- | +| `-t ` | Target directory (required unless specified in config). | +| `-s ` | Source directory (default: `/usr/local/share/common-utils`). | +| `-q` | Quiet mode: exit silently (code 0) if no target found instead of generating error. | + +#### Configuration + +The target directory **must** be specified using one of these methods (checked in order): + +1. **Command-line option**: Use the `-t` parameter to explicitly specify the target directory. + +2. **`.zz_dist` file**: Create a `.zz_dist` file in your project root with the target directory path on the first line. + +```bash +echo "./scripts" > .zz_dist +``` + +3. **`package.json`**: Add a `config.zz_dist` entry to your `package.json`: + +```json +{ + "config": { + "zz_dist": "./scripts" + } +} +``` + +If no target is found and `-q` (quiet mode) is not specified, the script will exit with an error. The target directory must exist before running the script. + +#### Example + +```bash +# Copy using config file +echo "./scripts" > .zz_dist +mkdir -p ./scripts +zz_dist + +# Copy to specific directory (must exist) +mkdir -p ./my-utils +zz_dist -t ./my-utils + +# Copy from custom source to existing directory +zz_dist -s /custom/path -t ./scripts + +# Quiet mode - no error if target not configured +zz_dist -q +``` + ### Validate JSON The `validate-json` utility allows you to validate JSON files against a JSON schema. It supports the following features: diff --git a/src/common-utils/_zz_dist.sh b/src/common-utils/_zz_dist.sh new file mode 100755 index 00000000..72225e78 --- /dev/null +++ b/src/common-utils/_zz_dist.sh @@ -0,0 +1,111 @@ +#!/bin/sh + +set -e + +# Source colors script +. zz_colors + +# Parse arguments and display help if needed +eval $( + zz_args "Distribute zz_* utilities to target directory" $0 "$@" <<-help + t target target Target directory (required unless specified in config) + s source source Source directory (default: /usr/local/share/common-utils) + q - quiet Quiet mode: exit 0 if no target found instead of error + help +) + +# Function to read target from config +get_target_from_config() { + local config_target="" + + # Check for .zz_dist file in project root + if [ -f ".zz_dist" ]; then + config_target=$(cat .zz_dist | head -n 1 | tr -d '\r\n ') + if [ -n "$config_target" ]; then + zz_log i "Found target in {U .zz_dist}: {B $config_target}" + echo "$config_target" + return 0 + fi + fi + + # Check for config.zz_dist in package.json using zz_json + if [ -f "package.json" ]; then + config_target=$(zz_json package.json 2>/dev/null | jq -r '.config.zz_dist // empty' 2>/dev/null) + if [ -n "$config_target" ]; then + zz_log i "Found target in {U package.json}: {B $config_target}" + echo "$config_target" + return 0 + fi + fi +} + +# Determine the target directory +if [ -z "$target" ]; then + # Try to get from config (first .zz_dist file, then package.json) + target=$(get_target_from_config); + if [ -z "$target" ] && [ -n "$quiet" ]; then + # Quiet mode: exit 0 if no target found + zz_log w "No target directory specified. Exiting quietly." + exit 0 + elif [ -z "$target" ]; then + # No target found: error out + zz_log e "No target directory specified. Use -t option, create .zz_dist file, or add config.zz_dist in package.json" + exit 1 + fi +fi + +# Resolve target to absolute path +target=$(readlink -f "$target") + +# Verify target directory exists +if [ ! -d "$target" ]; then + zz_log e "Target directory {U $target} does not exist" + exit 1 +fi + +zz_log s "Resolved target directory to {U $target}" + +# Set default source directory +if [ -z "$source" ]; then + # Try common installation locations + if [ -d "/usr/local/share/common-utils" ]; then + source="/usr/local/share/common-utils" + elif [ -d "/usr/local/bin" ]; then + source="/usr/local/bin" + else + zz_log e "No source directory found. Expected /usr/local/share/common-utils or /usr/local/bin" + exit 1 + fi +fi + +zz_log i "Distributing zz_* utilities" +zz_log - "From: {U $source}" +zz_log - "To: {U $target}" + + +# Copy zz_* files from source to target +for file in $(ls -1 $source/*zz_*); do + + zz_log - "Processing file: {U $file}" + + # _zz_*.sh files are copied as zz_* files without leading underscore and .sh extension + if [ -f "$file" ] || [ -L "$file" ]; then + basefile=$(basename "$file") + if echo "$basefile" | grep -q '^_zz_.*\.sh$'; then + targetfile=$target/$(echo "$basefile" | sed -e 's/^_//;s/\.sh$//') + else + targetfile=$target/$basefile + fi + + # Copy file if it exists and is executable + if [ -x "$file" ]; then + cp -u "$file" "$targetfile" + chmod +x "$targetfile" + zz_log s "Copied {U $basefile} to {U $targetfile}" + else + zz_log w "Skipping non-executable file {U $basefile}" + fi + fi +done + +zz_log s "Successfully distributed utilities to {U $target}" diff --git a/src/common-utils/package.json b/src/common-utils/package.json index d2fb08f3..ab72896f 100644 --- a/src/common-utils/package.json +++ b/src/common-utils/package.json @@ -20,6 +20,7 @@ "zz-colors": "./_zz_colors.sh", "zz-context": "./_zz_context.sh", "zz-dispatch": "./_zz_dispatch.sh", + "zz-dist": "./_zz_dist.sh", "zz-edit": "./_zz_edit.sh", "zz-input": "./_zz_input.sh", "zz-json": "./_zz_json.sh",