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.
@@ -0,0 +1,635 @@
1
+ # template.sh — sourceable template creation function
2
+ # Usage:
3
+ # source template.sh
4
+ # template [layer-name]
5
+ #
6
+ # Creates a new template layer in the current app.
7
+
8
+ ###############################################################################
9
+ # template.sh — promote the CURRENT APP into a NEW TEMPLATE REPO that the owner can commit files into, and publish
10
+ #
11
+ # This script creates a NEW hidden layer folder (e.g. .MyApp2 or .sk2)
12
+ # and initializes a template git repo for it, WITHOUT disturbing:
13
+ # - the app repo (.git)
14
+ # - any existing layers (e.g. .geet)
15
+ #
16
+ # Think of this as:
17
+ # “I built something useful, and I think that SOME but not all of my code is re-usable.
18
+ # I want to publish some of my code for other's to use (or to re-use myself)...
19
+ # But I don't want to spend weeks refactoring to split apart the reusable code from the implementation specific code
20
+ # In fact, it may not even be possible”
21
+ #
22
+ # -----------------------------------------------------------------------------
23
+ # What this script does:
24
+ #
25
+ # 1) Sets up the new layer
26
+ # MyApp/
27
+ # .git <- this is your app's git dir which tracks EVERYTHING, not just template repo code, but including template repo code
28
+ # .mytemplate/ <<<<- THIS is what we are setting up
29
+ # dot-git/ <- this is the .git of the template repo, just in an odd spot with an odd name
30
+ # git.sh <- base git command for the template's repo
31
+ # geet.sh <- calls geet but specifies which template we are in
32
+ # .geetinclude <- to allow adding files to the template's repo
33
+ # .geetexclude <- this is the .gitignore used by git.sh
34
+ # README.md <- just helps explain stuff to you and your users
35
+ # ... <- the rest of your source code for both the app and the template, interleaved
36
+ # .gitignore <- your app's .gitignore, not to be confused with .mytemplate/.geetexclude, this file mus also exclude **/dot-git/
37
+ # README.md <- your app's README, not to be confused with the template's readme. this leads to some complication for developers working on both an app and a template... they have to pull a switcheroo....
38
+ #
39
+ # 2) Initialize a NEW template git repo and commits some files to it
40
+ # .mytemplate/dot-git/
41
+ #
42
+ # IMPORTANT:
43
+ # - This does have to temporarily touch .git (the app's git dir) to move it out of the way, during the init, but it puts it back immediately, unscathed
44
+ # - dot-git/ should NEVER be committed to any git repo
45
+ #
46
+ ###############################################################################
47
+
48
+ template() {
49
+ debug "creating new template layer, APP_NAME=$APP_NAME"
50
+
51
+ ###############################################################################
52
+ # ARGUMENT PARSING & VALIDATION
53
+ ###############################################################################
54
+
55
+ # Required argument: explicit name for the new template layer
56
+ # Example:
57
+ # $GEET_ALIAS template sk2 -> creates .sk2
58
+ #
59
+ RAW_NAME="${1:-}"
60
+
61
+ # Show help if requested
62
+ if [[ "$RAW_NAME" == "help" || "$RAW_NAME" == "-h" || "$RAW_NAME" == "--help" ]]; then
63
+ cat <<EOF
64
+ $GEET_ALIAS template — promote the CURRENT APP into a NEW TEMPLATE REPO
65
+
66
+ This script creates a NEW hidden layer folder (e.g., .MyApp2 or .sk2)
67
+ and initializes a template git repo for it, WITHOUT disturbing:
68
+ - the app repo (.git)
69
+ - any existing layers (e.g., .geet)
70
+
71
+ Think of this as:
72
+ "I built something useful, and I think that SOME but not all of my code is re-usable.
73
+ I want to publish some of my code for others to use (or to re-use myself)...
74
+ But I don't want to spend weeks refactoring to split apart the reusable code from
75
+ the implementation specific code. In fact, it may not even be possible"
76
+
77
+ Usage:
78
+ $GEET_ALIAS template <name> [description]
79
+
80
+ Examples:
81
+ $GEET_ALIAS template mytemplate
82
+ $GEET_ALIAS template mytemplate "A React Native base project"
83
+ $GEET_ALIAS template sk2 "Starter kit v2 with TypeScript"
84
+
85
+ Requirements:
86
+ - <name> must be non-empty and different from the app name
87
+ - <name> cannot contain spaces
88
+
89
+ What this creates:
90
+ - $DD_APP_NAME/$DD_TEMPLATE_NAME/ (new layer directory)
91
+ - $DD_APP_NAME/$DD_TEMPLATE_NAME/dot-git/ (template's git repository)
92
+ - $DD_APP_NAME/$DD_TEMPLATE_NAME/geet-git.sh (git wrapper for template repo)
93
+ - $DD_APP_NAME/$DD_TEMPLATE_NAME/geet.sh (geet wrapper for template)
94
+ - $DD_APP_NAME/$DD_TEMPLATE_NAME/.geetinclude (whitelist of files to include)
95
+ - $DD_APP_NAME/$DD_TEMPLATE_NAME/.geetexclude (compiled excludes)
96
+ - $DD_APP_NAME/$DD_TEMPLATE_NAME/README.md (template documentation)
97
+ EOF
98
+ return 0
99
+ fi
100
+
101
+ # Detect GH_USER lazily (only when needed for template creation)
102
+ if [[ "$GH_USER" == "$DEFAULT_GH_USER" ]] || [[ -z "$GH_USER" ]]; then
103
+ get_gh_user
104
+ fi
105
+ TEMPLATE_GH_USER=GH_USER
106
+ debug "GH_USER=$GH_USER"
107
+ # Validation: must be non-empty
108
+ if [[ -z "$RAW_NAME" ]]; then
109
+ die "template requires a name argument (e.g., '$GEET_ALIAS template mytemplate')"
110
+ fi
111
+
112
+ # Normalize: remove leading dot if present
113
+ LAYER_NAME="${RAW_NAME#.}"
114
+
115
+ # Validation: cannot be empty after normalization
116
+ if [[ -z "$LAYER_NAME" ]]; then
117
+ die "template name cannot be empty or just a dot"
118
+ fi
119
+
120
+ # Validation: cannot contain spaces
121
+ if [[ "$LAYER_NAME" =~ [[:space:]] ]]; then
122
+ die "template name cannot contain spaces: '$LAYER_NAME'"
123
+ fi
124
+
125
+ # Validation: must be different from app name
126
+ if [[ "$LAYER_NAME" == "$APP_NAME" ]]; then
127
+ die "template name must be different from app name: '$APP_NAME'"
128
+ fi
129
+
130
+ NEW_LAYER_DIR="$APP_DIR/.${LAYER_NAME}"
131
+ TEMPLATE_DIR="$NEW_LAYER_DIR"
132
+
133
+ # Optional description argument
134
+ NEW_TEMPLATE_DESC="${2:-}"
135
+
136
+
137
+
138
+
139
+ debug "new template layer will be created at: $NEW_LAYER_DIR"
140
+
141
+
142
+
143
+ ###############################################################################
144
+ # SAFETY CHECKS
145
+ ###############################################################################
146
+
147
+ # Check if we have a git repo in current directory
148
+ if [[ ! -d "$APP_DIR/.git" ]]; then
149
+ log "no git repo found at $APP_DIR/.git"
150
+ log "initializing new git repo..."
151
+ git -C "$APP_DIR" init >/dev/null
152
+ fi
153
+
154
+ # Idempotency check - if layer already exists, exit cleanly
155
+ if [[ -e "$NEW_LAYER_DIR" ]]; then
156
+ log "layer already exists: $NEW_LAYER_DIR"
157
+ log "leaving existing layer undisturbed"
158
+ return 0
159
+ fi
160
+
161
+ # We expect the base layer (TEMPLATE_DIR) to have the required files
162
+ if [[ ! -f "$GEET_LIB/git.sh" || ! -f "$GEET_LIB/init.sh" ]]; then
163
+ die "source files missing (expected at $GEET_LIB/)"
164
+ fi
165
+
166
+ ###############################################################################
167
+ # CREATE NEW LAYER STRUCTURE
168
+ ###############################################################################
169
+
170
+ log "creating new template layer: .$LAYER_NAME"
171
+
172
+ # make empty dirs
173
+ mkdir -p "$NEW_LAYER_DIR"
174
+
175
+ # append the layer name into the hierarchy
176
+ # Copy from the base template's .geethier if it exists
177
+ if [[ -f "$TEMPLATE_DIR/.geethier" ]]; then
178
+ cp "$TEMPLATE_DIR/.geethier" "$NEW_LAYER_DIR/.geethier"
179
+ else
180
+ touch "$NEW_LAYER_DIR/.geethier"
181
+ fi
182
+ echo "$LAYER_NAME" >> "$NEW_LAYER_DIR/.geethier"
183
+ debug "made" "$NEW_LAYER_DIR/.geethier"
184
+
185
+
186
+ cat > "$NEW_LAYER_DIR/README.md" <<EOFREADME
187
+ # Welcome to the "$LAYER_NAME" template!
188
+
189
+ > ${NEW_TEMPLATE_DESC:-a template to build from}
190
+
191
+ This template was created with [geet](https://github.com/modularizer/geet),
192
+ a CLI git wrapper which acts as an alternative to git submodules,
193
+ allowing publishing a template which controls files which are interspersed in the same working directory as your project.
194
+
195
+ ## QUICKSTART
196
+ \`\`\`bash
197
+ npm install -g geet-geet
198
+ geet install $GH_USER/$LAYER_NAME.git $DD_APP_NAME
199
+ \`\`\`
200
+
201
+ ### Operations
202
+ ### 1. Pull template updates
203
+ \`\`\`bash
204
+ geet pull
205
+ \`\`\`
206
+
207
+ ### 2. Soft-detach a file or many files
208
+ > "If I diverge from the template, let me"
209
+
210
+ This applies keep-ours merges to only accept ff-only updates to specific files, and configures a precommit hook to unstage these files if you accidentally stage your updates.
211
+ \`\`\`bash
212
+ geet slide app/index.tsx
213
+ \`\`\`
214
+
215
+ ### 3. Hard-detach
216
+ > "I am absolutely changing this file and diverging from the template"
217
+
218
+ Done via --skip-worktree
219
+
220
+ \`\`\`bash
221
+ geet detach app/index.tsx
222
+ \`\`\`
223
+
224
+ ### 4. Check what is tracked by the template
225
+ \`\`\`bash
226
+ geet tree
227
+ \`\`\`
228
+
229
+ ### Everything else
230
+ Just use \`git\` for commands related to your app and \`geet\` for any git commands related to the template repo
231
+
232
+ ---
233
+
234
+ ## Things to know:
235
+ 1. Typically, template files get double-tracked
236
+ - They get pulled into your working directory and tracked by YOU
237
+ - They ALSO are tracked by the remote template repo
238
+ - If and when you wish, you can pull updates from the template repo into your project and add and commit the files into your repo
239
+ - If you are a developer/contributor of the template repo, you can optionally push code back to the template repo using a different git command
240
+ 2. \`$GEET_ALIAS\` is the suggested entrypoint for all your pull/push git-like commands. It protects you and adds some features. More on that later.
241
+ 3. You can either operate your template on an **include basis (recommended)** or and exclude basis.
242
+ - You probably know about standard \`.gitignore\` files, but in this case since we have all the app code stuff can be a bit different.
243
+ - [.$LAYER_NAME/.geetinclude](.$LAYER_NAME/.geetinclude) is a whitelist that gets parsed into [.$LAYER_NAME/.geetexclude](.$LAYER_NAME/.geetexclude) which is acting as the template's \`.gitignore\`
244
+ - Let's say your actual full app is 80% of the code and the generic stuff you are turning into a template is only 20% of the code, it might be best to exclude everything to avoid committing implementation-specific code to the template repo, then add some generic files and folders back in, to allow commiting them to the template. This is when you would use .geetinclude for the convenience
245
+ - Alternatively, if your primary goal is to develop a template, and 80% of your code is reusable, but then you just have 20% of "sample" code that you don't want included, maybe just overwrite .geetexclude file entierly, **but leave \*\*/dot-git/ excluded**
246
+ - read the comments in .geetexclude for more info
247
+ - use `$GEET_ALIAS tree` to see what is currently included in the template repo
248
+ 4. geet supports many layered templating, so if you want to extend a template and publish as a new template it is definitly possible! See .geehier to see how many levels this one has
249
+
250
+ If you're the owner of this template, feel free to overwrite or add to this README to tell users about what your project does. It's all your's from here.
251
+
252
+
253
+ EOFREADME
254
+ debug "wrote" "$NEW_LAYER_DIR/README.md"
255
+
256
+
257
+ cat > "$NEW_LAYER_DIR/parent.gitignore" <<EOFPGI
258
+ # This is a sample of your APP's gitignore, (NOT the template repo's gitignore)
259
+ # You can extend or overwrite it, but it NEEDS to ignore the following two things
260
+ **/dot-git/
261
+ **/untracked-template-config.env
262
+ EOFPGI
263
+ debug "wrote" "$NEW_LAYER_DIR/parent.gitignore"
264
+ # Helper functions to create .env files
265
+ #write_global_config() {
266
+ # local target="$GEET_LIB/../config.env"
267
+ # # Only create if it doesn't exist (don't overwrite user preferences)
268
+ # if [[ -f "$target" ]]; then
269
+ # debug "global config already exists at $target, skipping"
270
+ # return 0
271
+ # fi
272
+ #
273
+ # cat > "$target" <<'EOF'
274
+ ## Geet Global User Preferences
275
+ ## This file contains global settings that apply to all geet operations
276
+ ## Edit these values to customize your geet experience
277
+ #
278
+ #SHOW_LEVEL=true
279
+ #COLOR_MODE=light
280
+ #COLOR_SCOPE=line
281
+ #EOF
282
+ # log "created global config at $target"
283
+ #}
284
+ extract_flag --topics TEMPLATE_TOPICS
285
+ extract_flag --homepage TEMPLATE_HOMEPAGE
286
+ write_geet_template_env() {
287
+ local target="$NEW_LAYER_DIR/template-config.env"
288
+ cat > "$target" <<EOF
289
+ # ______________________________________________________________________________________________________________________
290
+ # Basics
291
+ # ______________________________________________________________________________________________________________________
292
+ # What is your project?
293
+ TEMPLATE_NAME=$LAYER_NAME
294
+ TEMPLATE_DESC="$NEW_TEMPLATE_DESC"
295
+ TEMPLATE_TOPICS="geet,template,$TEMPLATE_TOPICS"
296
+ TEMPLATE_HOMEPAGE="$TEMPLATE_HOMEPAGE"
297
+
298
+ # ______________________________________________________________________________________________________________________
299
+ # REPO LOCATION
300
+ # ______________________________________________________________________________________________________________________
301
+ # Where is the repo? (this should get autopopulated when you run '$GEET_ALIAS template', but you can fix it here)
302
+ TEMPLATE_GH_USER=$GH_USER
303
+ TEMPLATE_GH_NAME=$LAYER_NAME
304
+ TEMPLATE_GH_URL=https://github.com/$GH_USER/$LAYER_NAME
305
+ TEMPLATE_GH_SSH=git@github.com:$GH_USER/$LAYER_NAME.git
306
+ TEMPLATE_GH_HTTPS=https://github.com/$GH_USER/$LAYER_NAME.git
307
+
308
+ # ______________________________________________________________________________________________________________________
309
+ # Configuring Help Text
310
+ # ______________________________________________________________________________________________________________________
311
+ # Is there an alias to your /path/to/.$TEMPLATE_NAME/geet.sh?
312
+ # maybe you added to package.json or something like that
313
+ # update it here to update the CLI docs and help text
314
+ GEET_ALIAS="${GEET_ALIAS}"
315
+
316
+ # also use in help text thorughout the CLI
317
+ DD_APP_NAME=MyApp
318
+ DD_TEMPLATE_NAME=$LAYER_NAME
319
+
320
+
321
+ # ______________________________________________________________________________________________________________________
322
+ # Configuring Precommit
323
+ # ______________________________________________________________________________________________________________________
324
+ PREVENT_COMMIT_FILE_PATTERNS=".*secret.*"
325
+
326
+ # Prevent committing content matching these patterns (pipe-delimited regex)
327
+ PREVENT_COMMIT_CONTENT_PATTERNS="API_KEY=|SECRET_KEY=|password:\\s*[\"'].*[\"']|TODO.*remove.*template|CUSTOMER_ID=|stripe_live_key"
328
+ EOF
329
+ debug "wrote $target"
330
+ }
331
+
332
+
333
+ # Create global config if it doesn't exist
334
+ #write_global_config
335
+
336
+ # Create template .env configuration files
337
+ write_geet_template_env
338
+ log "created template .env configuration files"
339
+
340
+ # Create or copy .geetinclude from base template
341
+ if [[ -f "$TEMPLATE_DIR/.geetinclude" ]]; then
342
+ log "copying .geetinclude template from $TEMPLATE_DIR/.geetinclude"
343
+ cp "$TEMPLATE_DIR/.geetinclude" "$NEW_LAYER_DIR/.geetinclude"
344
+ else
345
+ cat > "$NEW_LAYER_DIR/.geetinclude" <<'EOFGEETINCLUDE'
346
+ # Add your include stuff here, you can call '$GEET_ALIAS sync' to sync it to the .geetexclude if you wish, but it will also auto-sync on every geet command
347
+ EOFGEETINCLUDE
348
+ fi
349
+
350
+ # Create initial .geetexclude with base rules and markers for compiled includes
351
+ cat > "$NEW_LAYER_DIR/.geetexclude" <<EOFGEETEXCLUDE
352
+ #-----------------------------------------------------------------------------------------------------------------------
353
+ # FAQ SECTION (docs)
354
+ #-----------------------------------------------------------------------------------------------------------------------
355
+ # Q: Can I fully overwrite this file?
356
+ # A: YES BUT: you MUST ensure **/dot-git/ gets ignored/excluded
357
+
358
+ # Q: How to sync from my .$LAYER_NAME/.geetinclude?
359
+ # A: run \`$GEET_ALIAS sync\` or \`.$LAYER_NAME/bin/git-sync.sh\`
360
+
361
+ #-----------------------------------------------------------------------------------------------------------------------
362
+ # DEFAULT INCLUDE SECTION (optional)
363
+ # this section excludes everything, then adds back in some tools
364
+ #-----------------------------------------------------------------------------------------------------------------------
365
+ *
366
+ !*/
367
+ !.$LAYER_NAME/geet.sh
368
+ !.$LAYER_NAME/.geethier
369
+ !.$LAYER_NAME/.geetinclude
370
+ !.$LAYER_NAME/.geetexclude
371
+ !.$LAYER_NAME/template-config.env
372
+ !.$LAYER_NAME/parent.gitignore
373
+ !.$LAYER_NAME/geet-git.sh
374
+ !.$LAYER_NAME/README.md
375
+ !.$LAYER_NAME/pre-commit/*
376
+
377
+ #-----------------------------------------------------------------------------------------------------------------------
378
+ # AUTOGENERATED INCLUDE SECTION (optional)
379
+ # now add back in contents from .geetinclude, just flipped
380
+ #-----------------------------------------------------------------------------------------------------------------------
381
+ # GEETINCLUDESTART
382
+
383
+ # Whoops! either .$LAYER_NAME/.geetinclude is empty or .$LAYER_NAME/.geetinclude hasn't been synced
384
+
385
+ # GEETINCLUDEEND
386
+
387
+ #-----------------------------------------------------------------------------------------------------------------------
388
+ # MANUAL EXCLUDE SECTION
389
+ # treat this part as your standard .gitignore, if you want to operate on an exclude basis vs an include basis
390
+ # typically either add to this section OR use .geetinclude, not both
391
+ # technically you could use both this section and your .geetinclude, but why?
392
+ #-----------------------------------------------------------------------------------------------------------------------
393
+
394
+
395
+ #-----------------------------------------------------------------------------------------------------------------------
396
+ # MANDATORY EXCLUDE SECTION (required)
397
+ # we must never ever commit these files/folders
398
+ #-----------------------------------------------------------------------------------------------------------------------
399
+ **/dot-git/
400
+ **/untracked-template-config.env
401
+ EOFGEETEXCLUDE
402
+
403
+
404
+ ###############################################################################
405
+ # MAKE A GIT WRAPPER
406
+ ###############################################################################
407
+ cat > "$NEW_LAYER_DIR/geet-git.sh" <<EOFGIT
408
+ #!/usr/bin/env bash
409
+
410
+ THIS_FILE="\${BASH_SOURCE[0]}" # e.g. .$LAYER_NAME/geet-git.sh
411
+ THIS_DIR="\$(cd -- "\$(dirname -- "\$THIS_FILE")" && pwd)" # e.g. .$LAYER_NAME
412
+ PARENT_DIR="\$(dirname "\$THIS_DIR")" # e.g. # e.g. $PATH_TO/$APP_NAME
413
+
414
+ # this file behaves like git, but always specifies our correct git directory, working tree, and gitignore
415
+ # e.g. exec git --git-dir=".$LAYER_NAME/dot-git" --work-tree="." -c "core.excludesFile=.$LAYER_NAME/.geetexclude" "\$@"
416
+ exec git --git-dir="\$THIS_DIR/dot-git" --work-tree="\$PARENT_DIR" -c "core.excludesFile=\$THIS_DIR/.geetexclude" "\$@"
417
+ EOFGIT
418
+ chmod +x "$NEW_LAYER_DIR/geet-git.sh"
419
+ GEET_GIT="$NEW_LAYER_DIR/geet-git.sh"
420
+ log "created geet.sh wrapper (ensures excludesFile is always applied)"
421
+
422
+ ###############################################################################
423
+ # MAKE A GEET WRAPPER
424
+ ###############################################################################
425
+ cat > "$NEW_LAYER_DIR/geet.sh" <<EOFGEET
426
+ #!/usr/bin/env bash
427
+ # this file behaves like geet, but always specifies our correct template directory, so it can be called from anywhere
428
+ THIS_FILE="\${BASH_SOURCE[0]}" # e.g. .$LAYER_NAME/geet.sh
429
+ THIS_DIR="\$(cd -- "\$(dirname -- "\$THIS_FILE")" && pwd)" # e.g. .$LAYER_NAME
430
+
431
+ # now call geet, but tell it the absolute path of the template folder
432
+ # e.g. exec geet --geet-dir ".$LAYER_NAME" "\$@"
433
+ exec geet --geet-dir "\$THIS_DIR" "\$@"
434
+ EOFGEET
435
+ chmod +x "$NEW_LAYER_DIR/geet.sh"
436
+ log "created geet.sh wrapper (ensures geet sees the correct template dir)"
437
+
438
+ mkdir -p "$NEW_LAYER_DIR/pre-commit"
439
+ cat > "$NEW_LAYER_DIR/pre-commit/README.md" <<EOFGEET
440
+ # We support pre-commit hooks!
441
+ * Simply add .sh files to ${NEW_LAYER_DIR}/pre-commit/
442
+ * make certain they are executable (\`chmod +x $NEW_LAYER_DIR/pre-commit/*.sh\`)
443
+ * During pre-commit we will iterate through each and \`source\` each one.
444
+ * It's up to you which hooks you track with git or ignore, in general probably best to commit them
445
+ EOFGEET
446
+ chmod +x "$NEW_LAYER_DIR/pre-commit/README.md"
447
+
448
+ debug "added files"
449
+ ###############################################################################
450
+ # INITIALIZE TEMPLATE GIT REPO FOR THE NEW LAYER
451
+ ###############################################################################
452
+ NEW_DOTGIT="$NEW_LAYER_DIR/dot-git"
453
+ debug "NEW_DOTGIT=$NEW_DOTGIT"
454
+
455
+ if [ -d "$APP_DIR/.git" ]; then
456
+ log "temporarily moving $APP_DIR/.git to $APP_DIR/not-git"
457
+ mv "$APP_DIR/.git" "$APP_DIR/not-git"
458
+ fi
459
+
460
+ log "initializing template git repo for $LAYER_NAME using 'git init --separate-git-dir=$NEW_DOTGIT $APP_DIR'"
461
+ git init --separate-git-dir="$NEW_DOTGIT" "$APP_DIR"
462
+
463
+ log "removing the pointer file that git leaves behind when --separate-git-dir is specified"
464
+ rm "$APP_DIR/.git"
465
+
466
+ if [ -d "$APP_DIR/not-git" ]; then
467
+ log "restoring our original git dir from $APP_DIR/not-git back to $APP_DIR/.git"
468
+ mv "$APP_DIR/not-git" "$APP_DIR/.git"
469
+ fi
470
+
471
+ # log "don't worry, that file-shuffle was kinda ugly but it was a one-time thing, we don't need to do on every command"
472
+ # log "instead, in the future we will use something like 'git --git-dir=<somefolder> --work-tree=<somefolder> -c core.exludesFile=<somefile>'"
473
+
474
+
475
+ ###############################################################################
476
+ # COMPILE WHITELIST AND CREATE INITIAL COMMIT
477
+ ###############################################################################
478
+
479
+ # We run the NEW layer's geet.sh, not the base layer's.
480
+ # This ensures:
481
+ # - .geetinclude is compiled into .geetexclude
482
+ # - commands are scoped correctly to the new layer
483
+ #
484
+ # First, compile excludes by calling status (idempotent).
485
+ source "$GEET_LIB/sync.sh"
486
+ sync
487
+ geet_git add ".$LAYER_NAME/geet.sh"
488
+ geet_git add ".$LAYER_NAME/.geethier"
489
+ geet_git add ".$LAYER_NAME/.geetinclude"
490
+ geet_git add ".$LAYER_NAME/.geetexclude"
491
+ geet_git add ".$LAYER_NAME/template-config.env"
492
+ geet_git add ".$LAYER_NAME/geet-git.sh"
493
+ geet_git add ".$LAYER_NAME/pre-commit/README.md"
494
+
495
+
496
+ ###############################################################################
497
+ # SETUP README PROMOTION
498
+ ###############################################################################
499
+ # Promote .mytemplate/README.md to README.md so it shows on GitHub
500
+ # Uses merge=keep-ours to prevent conflicts after README files diverge
501
+ geet_git add ".$LAYER_NAME/README.md"
502
+
503
+ log "setting up README.md promotion (see docs/AUTO_PROMOTE.md)"
504
+
505
+ # Set up keep-ours merge driver (prevents conflicts)
506
+ git --git-dir="$NEW_DOTGIT" config merge.keep-ours.name "Always keep working tree version"
507
+ git --git-dir="$NEW_DOTGIT" config merge.keep-ours.driver "true"
508
+
509
+ # Get hash of README.md content
510
+ readme_hash=$(git --git-dir="$NEW_DOTGIT" hash-object -w "$NEW_LAYER_DIR/README.md")
511
+
512
+ # Stage README at promoted location (root)
513
+ git --git-dir="$NEW_DOTGIT" update-index --add --cacheinfo 100644 "$readme_hash" "README.md"
514
+
515
+ # Configure merge strategy for promoted README
516
+ mkdir -p "$NEW_DOTGIT/info"
517
+ echo "README.md merge=keep-ours" >> "$NEW_DOTGIT/info/attributes"
518
+
519
+ ###############################################################################
520
+ # SETUP PARENT GITIGNORE PROMOTION
521
+ ###############################################################################
522
+ # Promote .mytemplate/README.md to README.md so it shows on GitHub
523
+ # Uses merge=keep-ours to prevent conflicts after README files diverge
524
+ geet_git add ".$LAYER_NAME/parent.gitignore"
525
+
526
+ log "setting up parent.gitignore promotion (see docs/AUTO_PROMOTE.md)"
527
+ # Get hash of README.md content
528
+ pgi_hash=$(git --git-dir="$NEW_DOTGIT" hash-object -w "$NEW_LAYER_DIR/parent.gitignore")
529
+
530
+ # Stage file at promoted location (root)
531
+ git --git-dir="$NEW_DOTGIT" update-index --add --cacheinfo 100644 "$pgi_hash" ".gitignore"
532
+
533
+ # Configure merge strategy for promoted file
534
+ mkdir -p "$NEW_DOTGIT/info"
535
+ echo ".gitignore merge=keep-ours" >> "$NEW_DOTGIT/info/attributes"
536
+
537
+
538
+ debug "added files"
539
+ ###############################################################################
540
+ # SETUP PRE-COMMIT HOOKS
541
+ ###############################################################################
542
+ # Create pre-commit hook to auto-promote README on future commits
543
+
544
+ log "creating pre-commit hook for auto-promotion"
545
+
546
+ mkdir -p "$NEW_DOTGIT/hooks"
547
+ cp "$GEET_LIB/pre-commit/hook.sh" "$NEW_DOTGIT/hooks/pre-commit"
548
+ chmod +x "$NEW_DOTGIT/hooks/pre-commit"
549
+ log "pre-commit hook created:"
550
+ log " • Auto-promotes README.md to root"
551
+ log " • Checks for app-specific patterns (configure in template-config.env)"
552
+
553
+ # Commit the initial promotion
554
+
555
+
556
+ log "README.md will appear at root on GitHub"
557
+ log "future edits to .$LAYER_NAME/README.md auto-promote to README.md"
558
+
559
+ geet_git commit -m "Initial commit" || true
560
+ ###############################################################################
561
+ # SETUP CUSTOM ALIAS (package.json if present)
562
+ ###############################################################################
563
+
564
+ PACKAGE_JSON="$APP_DIR/package.json"
565
+ if [[ -f "$PACKAGE_JSON" ]]; then
566
+ # Check if jq is available for safe JSON manipulation
567
+ if command -v jq >/dev/null 2>&1; then
568
+ log "adding '$LAYER_NAME' script to package.json"
569
+
570
+ # Add script using jq
571
+ tmp_json=$(mktemp)
572
+ jq --arg name "$LAYER_NAME" --arg path ".$LAYER_NAME/geet.sh" \
573
+ '.scripts[$name] = $path' \
574
+ "$PACKAGE_JSON" > "$tmp_json"
575
+ mv "$tmp_json" "$PACKAGE_JSON"
576
+
577
+ log "you can now run: npm run $LAYER_NAME <command>"
578
+ else
579
+ log "tip: install jq to auto-add npm scripts"
580
+ log "or manually add to package.json:"
581
+ log " \"scripts\": { \"$LAYER_NAME\": \".$LAYER_NAME/geet.sh\" }"
582
+ fi
583
+ fi
584
+
585
+
586
+ # update the parents gitignore
587
+ debug "checking " "$APP_DIR/.gitignore"
588
+ touch "$APP_DIR/.gitignore"
589
+ grep -qxF "**/dot-git/" "$APP_DIR/.gitignore" || echo "**/dot-git/" >> "$APP_DIR/.gitignore"
590
+ grep -qxF "**/untracked-template-config.env" "$APP_DIR/.gitignore" || echo "**/untracked-template-config.env" >> "$APP_DIR/.gitignore"
591
+
592
+
593
+ ###############################################################################
594
+ # FINAL OUTPUT
595
+ ###############################################################################
596
+ log "NICE! we think it worked!"
597
+ log "a template repo was created which uses $APP_DIR as its working directory, but its .git lives at $NEW_DOTGIT"
598
+ log "\`$GEET_ALIAS\` commands will call \`git\` for the template repo, while \`git\` will continue to mean your working repo"
599
+ log "you can modify the template's .gitignore using $NEW_LAYER_DIR/.geetinclude or $NEW_LAYER_DIR/.geetexclude"
600
+ log "you can modify some tracked settings at $NEW_LAYER_DIR/.templateconfig.env or untracked ones at $NEW_LAYER_DIR/untracked-template-config.env"
601
+ log "you can customize your template's README at $NEW_LAYER_DIR/README.md and we will sync it to the right location to show up in project root on GitHub and clones"
602
+
603
+ local PUB_APP=""
604
+ local PRI_APP=""
605
+ local INT_APP=""
606
+
607
+ has_flag --public PUB_APP
608
+ has_flag --private PRI_APP
609
+ has_flag --internal INT_APP
610
+
611
+ # Determine publish visibility
612
+ local publish_visibility="none"
613
+ if [[ -n "$PUB_APP" ]]; then
614
+ publish_visibility="public"
615
+ elif [[ -n "$PRI_APP" ]]; then
616
+ publish_visibility="private"
617
+ elif [[ -n "$INT_APP" ]]; then
618
+ publish_visibility="internal"
619
+ fi
620
+
621
+ if [[ "$publish_visibility" != "none" ]];then
622
+ source "$GEET_LIB/ghcli.sh"
623
+ publish_template "--$publish_visibility"
624
+ exit 0
625
+ fi
626
+
627
+
628
+
629
+ log
630
+ log "next steps:"
631
+ log " 1) stage files: \`$GEET_ALIAS include \"app/index.tsx\" \"app/_layout.tsx\"\` # we use include instead of add because it will modify the .geetinclude to allow the file in"
632
+ log " 2) commit: \`$GEET_ALIAS commit -m \"Add the basic template\""\`
633
+ log " 5) publish to github: \`$GEET_ALIAS publish template\` # (or $GEET_ALIAS publish template --private)"
634
+
635
+ } # end of template()