geet-geet 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/git.sh ADDED
@@ -0,0 +1,55 @@
1
+ # git.sh — template git operations
2
+ # Usage:
3
+ # source git.sh
4
+ # (exposed functions are used via geet.sh command dispatcher)
5
+ #
6
+ # Provides git wrapper functions for template repo operations
7
+
8
+
9
+
10
+ # Helper: sync .geetinclude to .geetexclude
11
+ sync_exclude() {
12
+ source "$GEET_LIB/sync.sh"
13
+ sync
14
+ }
15
+
16
+ # Helper: check if template git repo exists
17
+ need_dotgit() {
18
+ [[ -d "$DOTGIT" && -f "$DOTGIT/HEAD" ]] || die "missing $DOTGIT (run: $GEET_ALIAS init)"
19
+ }
20
+
21
+ # Helper: block dangerous git commands
22
+ block_footguns() {
23
+ case "${1-}" in
24
+ clean|reset|checkout|restore|rm)
25
+ brave_guard "git $1" "git $1 can be destructive and mess with your app's working directory"
26
+ ;;
27
+ esac
28
+ }
29
+
30
+ ###############################################################################
31
+ # CLONE - Simple git clone without init
32
+ ###############################################################################
33
+ # Just clones a repository without running any post-install logic
34
+ call_cmd() {
35
+ if [[ -z "$TEMPLATE_DIR" ]]; then
36
+ critical "We could not find your template repo anywhere in this project!"
37
+ warn "Are you sure you are somewhere inside a project which has a template repo?"
38
+ warn "The template repo is a hidden folder at the root of your working directory which contains a .geethier file inside it"
39
+ warn "To debug our search, run \`$GEET_ALIAS status --verbose --filter LOCATE\`"
40
+ warn "If you think we made a mistake, review the code at $GEET_LIB/digest-and-locate.sh detect_template_dir_from_cwd"
41
+ exit 1
42
+ fi
43
+ need_dotgit
44
+ sync_exclude
45
+ block_footguns "$@"
46
+ if [[ "$1" == "push" ]] && ! [[ "$(geet_git remote -v)" ]]; then
47
+ log "It looks like you have no remote setup yet. Would you like to publish on github?"
48
+ log "Just run \`$GEET_ALIAS pub\` to publish as a public repo or \`$GEET_ALIAS publish --private\`"
49
+ exit 1
50
+ fi
51
+ geet_git "$@"
52
+ # if [[ "$1" == "status" ]]; then
53
+ # log "Don't Panic! it is expected to see README.md and .gitignore show up as deleted, don't worry. read docs/AUTO_PROMOTE.md to understand why"
54
+ # fi
55
+ }
package/lib/help.sh ADDED
@@ -0,0 +1,53 @@
1
+ # help.sh — display overview of all geet commands
2
+ # Usage:
3
+ # source help.sh
4
+ # help
5
+
6
+ help() {
7
+
8
+ # digest-and-locate.sh provides: GEET_ALIAS, TEMPLATE_NAME, die, log
9
+
10
+ cat <<EOF
11
+ $GEET_ALIAS — Git-based template layering system
12
+
13
+ Usage: $GEET_ALIAS <command> [args...]
14
+
15
+ TEMPLATE MANAGEMENT:
16
+ template <name> [desc] Create a new template layer from current app
17
+ init Initialize a freshly-cloned template repo as your app
18
+ install <repo> Clone a template repo and initialize it (clone + init)
19
+ clone <repo> Clone a git repository (standard git clone)
20
+
21
+ FILE MANAGEMENT:
22
+ tree [list|tracked|all] Show what files the template includes
23
+ split <dest> [mode] Export template files to external folder
24
+ sync Compile .geetinclude whitelist into .geetexclude
25
+ detach "Detach" a file, folder, or glob to always use "keep-ours" to resolve merge conflicts, essentially allowing you to overwrite a file's template
26
+ retach undo a detach command
27
+ detached list files that have been detached
28
+
29
+ OPERATIONS:
30
+ session run [opts] -- cmd Run command in isolated template snapshot
31
+ publish [opts] Publish template to GitHub (auto-detects repo name)
32
+ gh <subcommand> [...] GitHub CLI integration (pr, issue, etc.)
33
+ doctor Run health checks on your geet setup
34
+
35
+ GIT ACCESS:
36
+ git <command> [...] Direct git access to template repo
37
+ <git-command> [...] Any git command (auto-forwarded to template repo)
38
+
39
+ Get help on any command:
40
+ $GEET_ALIAS <command> --help
41
+
42
+ Examples:
43
+ $GEET_ALIAS template my-stack "A modern web stack"
44
+ $GEET_ALIAS install https://github.com/user/template-repo
45
+ $GEET_ALIAS tree list
46
+ $GEET_ALIAS publish --public
47
+ $GEET_ALIAS status
48
+ $GEET_ALIAS commit -m "Update template"
49
+
50
+ Current layer: ${TEMPLATE_NAME:-none}
51
+ EOF
52
+
53
+ } # end of help()
package/lib/ignored.sh ADDED
@@ -0,0 +1,4 @@
1
+ is_ignored(){
2
+ r="$(geet_git check-ignore -q -- "$1" && echo ignored || echo included)"
3
+ printf '%s' "$r"
4
+ }
package/lib/include.sh ADDED
@@ -0,0 +1,54 @@
1
+ # git.sh — template git operations
2
+ # Usage:
3
+ # source git.sh
4
+ # (exposed functions are used via geet.sh command dispatcher)
5
+ #
6
+ # Provides git wrapper functions for template repo operations
7
+
8
+
9
+
10
+ # Helper: sync .geetinclude to .geetexclude
11
+ sync_exclude() {
12
+ source "$GEET_LIB/sync.sh"
13
+ sync
14
+ }
15
+
16
+ # Helper: check if template git repo exists
17
+ need_dotgit() {
18
+ [[ -d "$DOTGIT" && -f "$DOTGIT/HEAD" ]] || die "missing $DOTGIT (run: $GEET_ALIAS init)"
19
+ }
20
+
21
+
22
+ ###############################################################################
23
+ # CLONE - Simple git clone without init
24
+ ###############################################################################
25
+ # Just clones a repository without running any post-install logic
26
+ include() {
27
+ if [[ -z "$TEMPLATE_DIR" ]]; then
28
+ critical "We could not find your template repo anywhere in this project!"
29
+ warn "Are you sure you are somewhere inside a project which has a template repo?"
30
+ warn "The template repo is a hidden folder at the root of your working directory which contains a .geethier file inside it"
31
+ warn "To debug our search, run \`$GEET_ALIAS status --verbose --filter LOCATE\`"
32
+ warn "If you think we made a mistake, review the code at $GEET_LIB/digest-and-locate.sh detect_template_dir_from_cwd"
33
+ exit 1
34
+ fi
35
+ need_dotgit
36
+ sync_exclude
37
+ # first, modify .geetinclude
38
+ source "$GEET_LIB/ignored.sh"
39
+ for arg in "$@"; do
40
+ status="$(is_ignored $arg)"
41
+ debug "status of $arg is \"$status\""
42
+ if [[ "$status" == "ignored" ]]; then
43
+ echo "$arg" >> "$TEMPLATE_DIR/.geetinclude"
44
+ sync_exclude
45
+ status="$(is_ignored $arg)"
46
+ if [[ "$status" == "ignored" ]]; then
47
+ die "failed to unignore $arg"
48
+ fi
49
+ fi
50
+ geet_git add "$arg"
51
+ done
52
+
53
+
54
+ }
package/lib/init.sh ADDED
@@ -0,0 +1,234 @@
1
+ # init.sh — layer initializer / bootstrapper (IDEMPOTENT)
2
+ # Usage:
3
+ # source init.sh
4
+ # init [args...]
5
+ #
6
+ # Turns a freshly-cloned TEMPLATE REPO into a normal APP REPO,
7
+ # while preserving the template repo as a "layer view" in dot-git/
8
+
9
+ init() {
10
+
11
+ ###############################################################################
12
+ # HELP
13
+ ###############################################################################
14
+ if [[ "${1:-}" == "help" || "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
15
+ cat <<EOF
16
+ $GEET_ALIAS init — initialize a freshly-cloned template repo as your app
17
+
18
+ This command turns a freshly-cloned TEMPLATE REPO into a normal APP REPO,
19
+ while preserving the template repo as a "layer view" in dot-git/
20
+
21
+ What it does:
22
+ 1. Moves .git → .$APP_NAME/dot-git (or whatever the layer is called)
23
+ This "captures" the cloned template repo's git database
24
+ 2. Creates a brand new app repo at .git
25
+ Your new app starts with a clean slate
26
+ 3. Syncs whitelist rules (.geetinclude → .geetexclude)
27
+ 4. Ensures .gitignore excludes **/dot-git/
28
+ 5. Optionally runs post-init hook if present
29
+
30
+ Usage:
31
+ $GEET_ALIAS init [post-init-args...]
32
+
33
+ Note: Usually called automatically by '$GEET_ALIAS install'
34
+
35
+ Arguments:
36
+ post-init-args Optional arguments passed to the post-init hook
37
+ (if .geet/post-init.sh exists and is executable)
38
+
39
+ Key Idea (two repos, one working tree):
40
+ - App repo: $DD_APP_NAME/.git (your normal development)
41
+ - Template layer: $DD_APP_NAME/.geet/dot-git (tracks whitelisted files only)
42
+
43
+ Both repos share the same working tree ($DD_APP_NAME/), but have separate gitdirs.
44
+
45
+ Idempotency:
46
+ - If already initialized, prints status and exits successfully
47
+ - Safe to run multiple times
48
+
49
+ Environment Variables:
50
+ GEET_RUN_POST_INIT Set to 0 to skip post-init hook (default: 1)
51
+
52
+ Examples:
53
+ cd MyNewApp && $GEET_ALIAS init
54
+ cd MyNewApp && $GEET_ALIAS init --app-name "My Cool App"
55
+ GEET_RUN_POST_INIT=0 $GEET_ALIAS init # Skip post-init hook
56
+ EOF
57
+ return 0
58
+ fi
59
+
60
+
61
+ has_flag --skip-post SKIP_POST_INIT
62
+
63
+ ###############################################################################
64
+ # SETUP
65
+ ###############################################################################
66
+ # digest-and-locate.sh provides: GEET_LIB, APP_DIR, TEMPLATE_DIR, DOTGIT,
67
+ # TEMPLATE_GEET_CMD, die, log, debug
68
+
69
+ # App repo git directory (normal repo)
70
+ APP_GIT="$APP_DIR/.git"
71
+
72
+ ###############################################################################
73
+ # PRECONDITIONS
74
+ ###############################################################################
75
+
76
+ # Sanity check: git.sh should exist
77
+ if [[ ! -f "$GEET_LIB/git.sh" ]]; then
78
+ log "warning: missing $GEET_LIB/git.sh (will skip post-init exclude sync)"
79
+ fi
80
+
81
+ ###############################################################################
82
+ # IDEMPOTENCY CHECK
83
+ ###############################################################################
84
+
85
+ # If the layer dot-git already exists, we've already initialized this layer
86
+ # for this working tree. Do not attempt to re-run conversion.
87
+ if [[ -d "$DOTGIT" && -f "$DOTGIT/HEAD" ]]; then
88
+ log "already initialized"
89
+ log "layer gitdir: $DOTGIT"
90
+ log "app gitdir: $APP_GIT"
91
+ return 0
92
+ fi
93
+
94
+ ###############################################################################
95
+ # MAIN CONVERSION LOGIC
96
+ ###############################################################################
97
+
98
+ # The expected "fresh clone" state for THIS layer is:
99
+ # - APP_GIT exists (because you just cloned a template repo)
100
+ # - DOTGIT does not exist yet
101
+ #
102
+ # Example:
103
+ # - user clones $GEET_ALIAS template to MyApp2
104
+ # - MyApp2/.git exists
105
+ # - MyApp2/.geet/dot-git does not
106
+ #
107
+ # Another example:
108
+ # - user clones sk2 template to MyApp3
109
+ # - MyApp3/.git exists
110
+ # - MyApp3/.sk2/dot-git does not
111
+ #
112
+ # If APP_GIT doesn't exist, we do NOT know what to do (maybe user didn't clone).
113
+ if [[ ! -d "$APP_GIT" ]]; then
114
+ die "expected $APP_GIT to exist (did you run this in a freshly cloned template repo?)"
115
+ fi
116
+
117
+ # Create layer directory if missing (should exist because this script is inside it)
118
+ log "making $TEMPLATE_DIR"
119
+ mkdir -p "$TEMPLATE_DIR"
120
+
121
+ log "moving $APP_GIT to $DOTGIT"
122
+ mv "$APP_GIT" "$DOTGIT"
123
+
124
+
125
+ ###############################################################################
126
+ # CREATE NEW APP REPO
127
+ ###############################################################################
128
+
129
+ # After the move:
130
+ # - APP_DIR/.git no longer exists
131
+ # - DOTGIT contains the template git database
132
+ #
133
+ # Now we create a fresh app repo at APP_DIR/.git
134
+ log "initializing new app repo at $APP_GIT"
135
+ git -C "$APP_DIR" init >/dev/null
136
+
137
+ ###############################################################################
138
+ # ENSURE APP .geetexclude IGNORES dot-git/
139
+ ###############################################################################
140
+
141
+ # Critical safety: dot-git/ contains git internals and must NEVER be committed
142
+ # to the app repo. Ensure it's in .gitignore.
143
+ APP_GITIGNORE="$APP_DIR/.gitignore"
144
+ DOTGIT_PATTERN="**/dot-git/"
145
+ UNTRACKED_PATTERN="**/untracked-template-config.env"
146
+ if [[ -f "$APP_GITIGNORE" ]]; then
147
+ # Check if any form of dot-git ignore already exists
148
+ if ! grep -Eq '(^|[[:space:]])((\*\*/)?dot-git/|\.geet/dot-git/)([[:space:]]|$)' "$APP_GITIGNORE"; then
149
+ log "adding $DOTGIT_PATTERN to app .gitignore"
150
+ echo "$DOTGIT_PATTERN" >> "$APP_GITIGNORE"
151
+ else
152
+ log "app .gitignore already ignores dot-git/"
153
+ fi
154
+ if ! grep -Eq 'untracked-template-config.env' "$APP_GITIGNORE"; then
155
+ log "adding $UNTRACKED_PATTERN to app .gitignore"
156
+ echo "$UNTRACKED_PATTERN" >> "$APP_GITIGNORE"
157
+ else
158
+ log "app .gitignore already ignores untracked/"
159
+ fi
160
+ else
161
+ log "creating app .gitignore with $DOTGIT_PATTERN"
162
+ echo "$DOTGIT_PATTERN" > "$APP_GITIGNORE"
163
+ fi
164
+
165
+
166
+ git add .
167
+ git commit -m "initial commit"
168
+ ###############################################################################
169
+ # FINAL OUTPUT
170
+ ###############################################################################
171
+ log "done"
172
+ log "layer initialized:"
173
+ log " layer: $TEMPLATE_NAME"
174
+ log " worktree: $APP_DIR"
175
+ log " layer gitdir: $DOTGIT"
176
+ log " app gitdir: $APP_GIT"
177
+ log
178
+ log "next steps:"
179
+ log " - develop normally with: git ..."
180
+ log " - update this layer with: $GEET_ALIAS pull"
181
+ log " - see included files with: $GEET_ALIAS tree"
182
+
183
+
184
+ if ! [[ "$SKIP_POST_INIT" ]]; then
185
+ ###############################################################################
186
+ # OPTIONAL POST-INIT HOOK
187
+ ###############################################################################
188
+ # Some templates want to do a one-time setup step after init, e.g.:
189
+ # - create .env from .env.sample
190
+ # - rename package name / bundle id
191
+ # - install dependencies
192
+ # - print “next steps” instructions
193
+ #
194
+ # geet itself should not become a framework-specific scaffold tool, so we keep
195
+ # this mechanism very small and template-owned:
196
+ #
197
+ # - If <layer>/post-init.sh exists AND is executable, we run it.
198
+ # - Otherwise, we do nothing.
199
+ #
200
+ # Security note:
201
+ # - This is code from the template you just cloned.
202
+ # - Running it is equivalent to "running a script from the internet".
203
+ # - Keep it simple and obvious, and consider requiring an env var gate later.
204
+ POST_INIT_SH="$TEMPLATE_DIR/post-init.sh"
205
+
206
+ if [[ -f "$POST_INIT_SH" ]]; then
207
+ if [[ ! -x "$POST_INIT_SH" ]]; then
208
+ die "found post-init hook but it is not executable: $POST_INIT_SH (run: chmod +x $POST_INIT_SH)"
209
+ fi
210
+
211
+ log "running post-init hook:"
212
+ log " $POST_INIT_SH"
213
+
214
+ # Provide some context to the hook.
215
+ # The hook can use these to make decisions without re-discovering paths.
216
+ export GEET_LAYER_DIR="$TEMPLATE_DIR"
217
+ export GEET_LAYER_NAME="$TEMPLATE_NAME"
218
+ export GEET_ROOT="$APP_DIR"
219
+ export GEET_DOTGIT="$DOTGIT"
220
+
221
+ # Run from the project root so relative paths behave naturally.
222
+ # Pass through any arguments that were provided to init.sh (from install command).
223
+ (
224
+ cd "$APP_DIR"
225
+ "$POST_INIT_SH" "$@"
226
+ )
227
+
228
+ log "post-init hook complete"
229
+ log "enjoy developing!"
230
+ fi
231
+
232
+ fi
233
+
234
+ } # end of init()
package/lib/install.sh ADDED
@@ -0,0 +1,166 @@
1
+ # install.sh — clone and init
2
+ # Usage:
3
+ # source install.sh
4
+ # (exposed functions are used via geet.sh command dispatcher)
5
+
6
+
7
+ ###############################################################################
8
+ # INSTALL - Clone a template repo and run init
9
+ ###############################################################################
10
+ # This is a convenience command for first-time users:
11
+ #
12
+ # $GEET_ALIAS install <repo> [dir] [--recurse-submodules]
13
+ #
14
+ # It:
15
+ # 1) runs `git clone`
16
+ # 2) cd's into the directory
17
+ # 3) runs this layer's init.sh to convert the clone into an app + layer
18
+ #
19
+ install() {
20
+ local INIT_SH="$GEET_LIB/init.sh"
21
+ [[ -f "$INIT_SH" ]] || die "missing init script: $INIT_SH"
22
+
23
+ # Check for help first
24
+ if [[ "${1:-}" == "help" || "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
25
+ cat <<EOF
26
+ Usage:
27
+ $GEET_ALIAS install <repo> <dir> [options] [-- post-init-args...]
28
+
29
+ Options:
30
+ --recurse-submodules, -r Clone with submodules
31
+ --pub, --public Publish app as public GitHub repo after install
32
+ --pri, --private Publish app as private GitHub repo after install
33
+ --int, --internal Publish app as internal GitHub repo after install
34
+
35
+ Examples:
36
+ $GEET_ALIAS install git@github.com:$GH_USER/template.git MyApp
37
+ $GEET_ALIAS install mytemplate MyApp --pub
38
+ $GEET_ALIAS install modularizer/geet MyGeetApp --private
39
+ $GEET_ALIAS install --recurse-submodules git@github.com:$GH_USER/template.git MyApp -- --app-name "My Cool App"
40
+
41
+ Notes:
42
+ - <dir> is required and must be different from the template repo name
43
+ - After cloning, this runs init automatically
44
+ - Arguments after '--' are passed to the post-init hook (if present)
45
+ - Publish flags create a NEW GitHub repo for the app (not the template)
46
+ EOF
47
+ return 0
48
+ fi
49
+
50
+ # Extract flags using has_flag
51
+ local RECURSE=""
52
+ local PUB_APP=""
53
+ local PRI_APP=""
54
+ local INT_APP=""
55
+
56
+ has_flag --recurse-submodules RECURSE
57
+ has_flag --public PUB_APP
58
+ has_flag --private PRI_APP
59
+ has_flag --internal INT_APP
60
+
61
+ # Determine publish visibility
62
+ local publish_visibility=""
63
+ if [[ -n "$PUB_APP" ]]; then
64
+ publish_visibility="public"
65
+ elif [[ -n "$PRI_APP" ]]; then
66
+ publish_visibility="private"
67
+ elif [[ -n "$INT_APP" ]]; then
68
+ publish_visibility="internal"
69
+ fi
70
+
71
+ # Separate positional args from post-init args
72
+ local -a args=()
73
+ local -a post_init_args=()
74
+ local parsing_post_init=0
75
+
76
+ for arg in "${GEET_ARGS[@]}"; do
77
+ if [[ "$arg" == "--" ]]; then
78
+ parsing_post_init=1
79
+ continue
80
+ fi
81
+
82
+ if [[ "$parsing_post_init" -eq 1 ]]; then
83
+ post_init_args+=("$arg")
84
+ else
85
+ args+=("$arg")
86
+ fi
87
+ done
88
+
89
+ [[ ${#args[@]} -ge 2 ]] || die "install requires both <repo> and <dir> arguments"
90
+
91
+ local repo="${args[0]}"
92
+ local dir="${args[1]}"
93
+
94
+ # repo supports 5 possibilities
95
+ # OPTION 1: resolves to a path on your own computer which already exists
96
+ # OPTION 2: startswith http, treat as a git remote. add ".git" to the end if it isn't there already
97
+ # OPTION 3: startswith git@, treat as a git ssh remote. add ".git" to the end if it isn't there already
98
+ # OPTION 4: in format or username/name, e.g. "modularizer/geet" or "modularizer/geet/", convert to "https://github.com/modularizer/geet.git"
99
+ # OPTION 5: just a repo name, e.g. "mytemplate", use get_gh_user then convert to "$GH_USER/mytemplate"
100
+
101
+ if [[ -e "$repo" ]]; then
102
+ # OPTION 1: local path exists, use as-is
103
+ :
104
+ elif [[ "$repo" == http* ]]; then
105
+ # OPTION 2: HTTP URL
106
+ if [[ "$repo" != *.git ]]; then
107
+ repo="${repo}.git"
108
+ fi
109
+ elif [[ "$repo" == git@* ]]; then
110
+ # OPTION 3: SSH URL
111
+ if [[ "$repo" != *.git ]]; then
112
+ repo="${repo}.git"
113
+ fi
114
+ elif [[ "$repo" =~ ^[a-zA-Z0-9_-]+/[a-zA-Z0-9_.-]+/?$ ]]; then
115
+ # OPTION 4: username/repo format
116
+ repo="${repo%/}" # remove trailing slash if present
117
+ repo="git@github.com:${repo}.git"
118
+ elif [[ "$repo" =~ ^[a-zA-Z0-9_.-]+$ ]]; then
119
+ # OPTION 5: just a repo name
120
+ get_gh_user
121
+ repo="git@github.com:${GH_USER}/${repo}.git"
122
+ fi
123
+
124
+ # Extract repo name for validation
125
+ local repo_name="$(basename "$repo")"
126
+ repo_name="${repo_name%.git}"
127
+
128
+ # Ensure dir is different from repo name to avoid confusion
129
+ # (template repo vs new app directory)
130
+ if [[ "$dir" == "$repo_name" ]]; then
131
+ die "dir must be different from repo name ('$repo_name'). The dir is your NEW app, not the template."
132
+ fi
133
+
134
+ local -a clone_args=()
135
+ if [[ -n "$RECURSE" ]]; then
136
+ clone_args+=(--recurse-submodules)
137
+ fi
138
+
139
+ log "cloning template repo:"
140
+ log " repo: $repo"
141
+ log " dir: $dir"
142
+ git clone "${clone_args[@]}" "$repo" "$dir"
143
+ log "calling \`cd \"$dir\"\`"
144
+ cd "$dir" || exit
145
+ log "pwd: $(pwd)"
146
+
147
+ GEET_CMD="${BASH_SOURCE[-1]}"
148
+ log "geet_cmd=$GEET_CMD"
149
+
150
+ log "running init in cloned directory"
151
+ (
152
+ "$GEET_CMD" init "${post_init_args[@]}"
153
+ )
154
+ log "pwd: $(pwd)"
155
+ log "publish_visibility=$publish_visibility"
156
+
157
+ # Publish the app as a GitHub repo if requested
158
+ if [[ -n "$publish_visibility" ]]; then
159
+ log "Calling \`$GEET_CMD publish app\`"
160
+ (
161
+ "$GEET_CMD" publish app "--${publish_visibility}"
162
+ )
163
+ fi
164
+
165
+ log "install complete"
166
+ }