Compare commits
57 Commits
1.0.0-rc.4
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 52cb73be47 | |||
|
|
3e689ed0b2 | ||
| 5d7068ae1d | |||
|
|
201700f27b | ||
| 540efeac89 | |||
| 0ff81cccbd | |||
| 501f89d3a0 | |||
| ba92e6c212 | |||
| 7bed32b60a | |||
|
9652714455
|
|||
|
|
b33a02991c | ||
|
|
fc393caa5b | ||
|
|
b6dfa4b787 | ||
|
|
c4bb1ae3f1 | ||
| 3a64fee42e | |||
|
e5d2604edc
|
|||
| 19a5bafeec | |||
|
|
e01d929793 | ||
| 02f2c9a887 | |||
|
|
ebe2a6be99 | ||
| 53e20b43d3 | |||
|
e85a83c2c6
|
|||
| be432b6f79 | |||
|
|
bd5963d454 | ||
| 8465487155 | |||
| 3f30bedcd3 | |||
| df2caadb70 | |||
|
ee0c0171a5
|
|||
|
|
2705ab40d0 | ||
|
|
f9f55d3a72 | ||
| 3243639e3c | |||
|
cb43e511c2
|
|||
|
0f01a1f154
|
|||
| 949cc0b5b5 | |||
|
|
42260f46b5 | ||
| 1c25833e69 | |||
|
a6a4950947
|
|||
|
69c730f463
|
|||
| bd680be110 | |||
|
898d05bfa5
|
|||
| df8d313896 | |||
|
fb95f72e03
|
|||
|
be51ec4831
|
|||
| 55eba06cb2 | |||
|
4ce2eb82e6
|
|||
| b6e1c794d2 | |||
|
a51a957530
|
|||
|
cd659d7b4f
|
|||
|
|
868e67ad4b
|
||
|
58f310bb8c
|
|||
|
2e5101650f
|
|||
|
04d807c388
|
|||
|
7ab8829442
|
|||
|
9452466843
|
|||
|
71ad62dd80
|
|||
|
ed36d3fe5b
|
|||
|
084cba10d5
|
22
.claude/settings.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(cat:*)",
|
||||||
|
"Bash(git add:*)",
|
||||||
|
"Bash(git commit:*)",
|
||||||
|
"Bash(git diff:*)",
|
||||||
|
"Bash(ls:*)",
|
||||||
|
"Bash(npm install:*)",
|
||||||
|
"Bash(npm run astro:*)",
|
||||||
|
"Bash(npm run build:*)",
|
||||||
|
"Bash(npm run check:*)",
|
||||||
|
"Bash(npm run dev:*)",
|
||||||
|
"Bash(npm run format:*)",
|
||||||
|
"Bash(npm run format:check:*)",
|
||||||
|
"Bash(npm run lint:*)",
|
||||||
|
"Bash(npm run preview:*)",
|
||||||
|
"Bash(npm run update-specs:*)",
|
||||||
|
"mcp__deepwiki__ask_question"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
24
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: github-actions
|
||||||
|
directory: /
|
||||||
|
schedule:
|
||||||
|
interval: monthly
|
||||||
|
- package-ecosystem: npm
|
||||||
|
directory: /
|
||||||
|
schedule:
|
||||||
|
interval: monthly
|
||||||
|
groups:
|
||||||
|
npm-development:
|
||||||
|
dependency-type: development
|
||||||
|
update-types:
|
||||||
|
- major
|
||||||
|
- minor
|
||||||
|
- patch
|
||||||
|
npm-production:
|
||||||
|
dependency-type: production
|
||||||
|
update-types:
|
||||||
|
- major
|
||||||
|
- minor
|
||||||
|
- patch
|
||||||
47
.github/workflows/update-specs.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
name: Update Specs
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update-specs:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Generate GitHub App Token
|
||||||
|
id: app-token
|
||||||
|
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.BOT_APP_ID }}
|
||||||
|
private-key: ${{ secrets.BOT_PRIVATE_KEY }}
|
||||||
|
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
|
with:
|
||||||
|
token: ${{ steps.app-token.outputs.token }}
|
||||||
|
|
||||||
|
- name: Setup Node.js
|
||||||
|
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
||||||
|
with:
|
||||||
|
node-version-file: ".node-version"
|
||||||
|
cache: "npm"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Update specs
|
||||||
|
run: npm run update-specs
|
||||||
|
|
||||||
|
- name: Create Pull Request
|
||||||
|
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0
|
||||||
|
with:
|
||||||
|
token: ${{ steps.app-token.outputs.token }}
|
||||||
|
commit-message: "chore: update spec versions"
|
||||||
|
title: "chore: update spec versions"
|
||||||
|
body: |
|
||||||
|
Automated update of spec versions from upstream repository.
|
||||||
|
|
||||||
|
This PR was created by the update-specs workflow.
|
||||||
|
branch: update-specs
|
||||||
|
delete-branch: true
|
||||||
|
add-paths: |
|
||||||
|
src/content/spec/*
|
||||||
21
.gitignore
vendored
@@ -1,5 +1,26 @@
|
|||||||
|
# Astro
|
||||||
|
dist/
|
||||||
|
.astro/
|
||||||
|
|
||||||
|
# Claude
|
||||||
|
.claude/settings.local.json
|
||||||
|
|
||||||
|
# Node
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Jekyll (legacy, can be removed after full migration)
|
||||||
_site
|
_site
|
||||||
.asset-cache
|
.asset-cache
|
||||||
.sass-cache
|
.sass-cache
|
||||||
.jekyll-metadata
|
.jekyll-metadata
|
||||||
|
.jekyll-cache
|
||||||
docs/assets/.sprockets-manifest-*.json
|
docs/assets/.sprockets-manifest-*.json
|
||||||
|
|
||||||
|
# Editor
|
||||||
|
.DS_Store
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
|
||||||
|
# Environment
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
|||||||
2
.mise.toml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[settings]
|
||||||
|
idiomatic_version_file_enable_tools = ["node"]
|
||||||
1
.node-version
Normal file
@@ -0,0 +1 @@
|
|||||||
|
24
|
||||||
5
.prettierignore
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
.astro/
|
||||||
|
dist/
|
||||||
|
node_modules/
|
||||||
|
package-lock.json
|
||||||
|
src/content/spec/
|
||||||
4
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"recommendations": ["astro-build.astro-vscode"],
|
||||||
|
"unwantedRecommendations": []
|
||||||
|
}
|
||||||
11
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"command": "npm run dev",
|
||||||
|
"name": "dev server",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "node-terminal"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
9
404.html
@@ -1,9 +0,0 @@
|
|||||||
---
|
|
||||||
title: 404 Page Not Found
|
|
||||||
sitemap: false
|
|
||||||
---
|
|
||||||
<div class="header">
|
|
||||||
<h1>404</h1>
|
|
||||||
<p><strong>Page not found :(</strong></p>
|
|
||||||
<p>The requested page could not be found.</p>
|
|
||||||
</div>
|
|
||||||
92
CLAUDE.md
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with
|
||||||
|
code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
This is the source for commonflow.org, an Astro-based static site that documents
|
||||||
|
the Git Common-Flow specification. Common-Flow is a git workflow specification
|
||||||
|
that combines GitHub Flow with versioned releases.
|
||||||
|
|
||||||
|
## Build Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install tooling (node) via mise
|
||||||
|
mise install
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
npm install
|
||||||
|
|
||||||
|
# Development server
|
||||||
|
npm run dev
|
||||||
|
|
||||||
|
# Build site (outputs to dist/ directory)
|
||||||
|
npm run build
|
||||||
|
|
||||||
|
# Preview built site
|
||||||
|
npm run preview
|
||||||
|
|
||||||
|
# Type checking
|
||||||
|
npm run check
|
||||||
|
|
||||||
|
# Linting
|
||||||
|
npm run lint
|
||||||
|
|
||||||
|
# Formatting
|
||||||
|
npm run format
|
||||||
|
npm run format:check
|
||||||
|
|
||||||
|
# Update specs from upstream (fetches from github.com/jimeh/common-flow)
|
||||||
|
npm run update-specs
|
||||||
|
```
|
||||||
|
|
||||||
|
The site is built to `dist/` and deployed to Cloudflare Workers.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
- **Astro 5.x** static site generator
|
||||||
|
- **Tailwind CSS 4.x** for styling with dark mode support
|
||||||
|
- **astro-icon** with Heroicons, Simple Icons, and custom local SVGs (`src/icons/`)
|
||||||
|
- **Content Collections** for spec markdown files
|
||||||
|
- **TypeScript** throughout
|
||||||
|
- **Node.js** as JavaScript runtime (managed via mise)
|
||||||
|
|
||||||
|
### Key Files
|
||||||
|
|
||||||
|
- `src/config.ts` - Site configuration (metadata, update settings)
|
||||||
|
- `src/content.config.ts` - Astro content collection definition
|
||||||
|
- `src/styles/global.css` - Global Tailwind styles
|
||||||
|
- `src/layouts/BaseLayout.astro` - Base layout with head, meta tags, theme scripts
|
||||||
|
- `src/layouts/SpecLayout.astro` - Spec page layout composing all sections
|
||||||
|
- `src/components/` - UI components:
|
||||||
|
- `Header.astro` - Site header with navigation
|
||||||
|
- `Footer.astro` - Site footer
|
||||||
|
- `Hero.astro` - Landing page hero section
|
||||||
|
- `AboutSection.astro` - About Common-Flow section
|
||||||
|
- `FAQSection.astro` - FAQ section
|
||||||
|
- `SectionHeader.astro` - Reusable section header (title + subtitle)
|
||||||
|
- `SpecSection.astro` - Spec section with terminology and specification
|
||||||
|
- `SpecSidebar.astro` - Spec page table of contents sidebar
|
||||||
|
- `TocLink.astro` - Reusable TOC link component
|
||||||
|
- `ThemeToggle.astro` - Dark/light mode toggle
|
||||||
|
- `VersionSelector.astro` - Spec version dropdown
|
||||||
|
- `src/scripts/` - Client-side TypeScript:
|
||||||
|
- `activeSectionTracker.ts` - Scroll-based active section tracking
|
||||||
|
- `clauseHighlight.ts` - Clause highlight on anchor navigation
|
||||||
|
- `src/pages/index.astro` - Landing page
|
||||||
|
- `src/pages/404.astro` - 404 error page
|
||||||
|
- `src/pages/spec/[version].astro` - Dynamic route for spec versions
|
||||||
|
- `src/pages/spec/[version]/raw.astro` - Raw markdown spec page
|
||||||
|
- `src/utils/` - Utility functions:
|
||||||
|
- `parseSpecContent.ts` - Markdown parsing utilities
|
||||||
|
- `versions.ts` - Version info helper (derives current version from specs)
|
||||||
|
- `src/content/spec/*.md` - Versioned spec documents
|
||||||
|
- `public/spec/*.svg` - SVG diagrams for each version
|
||||||
|
- `scripts/update-specs.ts` - Fetches specs from GitHub
|
||||||
|
- `wrangler.jsonc` - Cloudflare Workers deployment config
|
||||||
|
|
||||||
|
### Updating Spec Versions
|
||||||
|
|
||||||
|
1. Run `npm run update-specs` to fetch specs from GitHub
|
||||||
|
2. Run `npm run build` to rebuild the site
|
||||||
21
Gemfile
@@ -1,21 +0,0 @@
|
|||||||
source 'https://rubygems.org'
|
|
||||||
|
|
||||||
gem 'jekyll', '3.5.0'
|
|
||||||
|
|
||||||
group :development do
|
|
||||||
gem 'rake'
|
|
||||||
gem 'rubocop'
|
|
||||||
end
|
|
||||||
|
|
||||||
# If you have any plugins, put them here!
|
|
||||||
group :jekyll_plugins do
|
|
||||||
gem 'jekyll-assets'
|
|
||||||
gem 'jekyll-pants'
|
|
||||||
gem 'jekyll-seo-tag'
|
|
||||||
gem 'jekyll-sitemap'
|
|
||||||
gem 'jekyll-tidy'
|
|
||||||
gem 'uglifier' # required by 'jekyll-assets' for JS compression
|
|
||||||
end
|
|
||||||
|
|
||||||
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
|
|
||||||
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
|
|
||||||
102
Gemfile.lock
@@ -1,102 +0,0 @@
|
|||||||
GEM
|
|
||||||
remote: https://rubygems.org/
|
|
||||||
specs:
|
|
||||||
addressable (2.5.1)
|
|
||||||
public_suffix (~> 2.0, >= 2.0.2)
|
|
||||||
ast (2.3.0)
|
|
||||||
colorator (1.1.0)
|
|
||||||
concurrent-ruby (1.0.5)
|
|
||||||
execjs (2.7.0)
|
|
||||||
extras (0.3.0)
|
|
||||||
forwardable-extended (~> 2.5)
|
|
||||||
fastimage (2.1.0)
|
|
||||||
ffi (1.9.18)
|
|
||||||
forwardable-extended (2.6.0)
|
|
||||||
htmlbeautifier (1.3.1)
|
|
||||||
htmlcompressor (0.3.1)
|
|
||||||
jekyll (3.5.0)
|
|
||||||
addressable (~> 2.4)
|
|
||||||
colorator (~> 1.0)
|
|
||||||
jekyll-sass-converter (~> 1.0)
|
|
||||||
jekyll-watch (~> 1.1)
|
|
||||||
kramdown (~> 1.3)
|
|
||||||
liquid (~> 4.0)
|
|
||||||
mercenary (~> 0.3.3)
|
|
||||||
pathutil (~> 0.9)
|
|
||||||
rouge (~> 1.7)
|
|
||||||
safe_yaml (~> 1.0)
|
|
||||||
jekyll-assets (2.3.2)
|
|
||||||
concurrent-ruby (~> 1.0)
|
|
||||||
extras (~> 0.2)
|
|
||||||
fastimage (~> 2.0, >= 1.8)
|
|
||||||
jekyll (~> 3.1, >= 3.0)
|
|
||||||
pathutil (>= 0.8)
|
|
||||||
rack (~> 1.6)
|
|
||||||
sprockets (~> 3.3, < 3.8)
|
|
||||||
jekyll-pants (0.2.1)
|
|
||||||
rubypants
|
|
||||||
jekyll-sass-converter (1.5.0)
|
|
||||||
sass (~> 3.4)
|
|
||||||
jekyll-seo-tag (2.2.3)
|
|
||||||
jekyll (~> 3.3)
|
|
||||||
jekyll-sitemap (1.0.0)
|
|
||||||
jekyll (~> 3.3)
|
|
||||||
jekyll-tidy (0.2.2)
|
|
||||||
htmlbeautifier
|
|
||||||
htmlcompressor
|
|
||||||
jekyll
|
|
||||||
jekyll-watch (1.5.0)
|
|
||||||
listen (~> 3.0, < 3.1)
|
|
||||||
kramdown (1.14.0)
|
|
||||||
liquid (4.0.0)
|
|
||||||
listen (3.0.8)
|
|
||||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
|
||||||
rb-inotify (~> 0.9, >= 0.9.7)
|
|
||||||
mercenary (0.3.6)
|
|
||||||
parser (2.4.0.0)
|
|
||||||
ast (~> 2.2)
|
|
||||||
pathutil (0.14.0)
|
|
||||||
forwardable-extended (~> 2.6)
|
|
||||||
powerpack (0.1.1)
|
|
||||||
public_suffix (2.0.5)
|
|
||||||
rack (1.6.8)
|
|
||||||
rainbow (2.2.1)
|
|
||||||
rake (12.0.0)
|
|
||||||
rb-fsevent (0.10.2)
|
|
||||||
rb-inotify (0.9.10)
|
|
||||||
ffi (>= 0.5.0, < 2)
|
|
||||||
rouge (1.11.1)
|
|
||||||
rubocop (0.47.1)
|
|
||||||
parser (>= 2.3.3.1, < 3.0)
|
|
||||||
powerpack (~> 0.1)
|
|
||||||
rainbow (>= 1.99.1, < 3.0)
|
|
||||||
ruby-progressbar (~> 1.7)
|
|
||||||
unicode-display_width (~> 1.0, >= 1.0.1)
|
|
||||||
ruby-progressbar (1.8.1)
|
|
||||||
rubypants (0.6.0)
|
|
||||||
safe_yaml (1.0.4)
|
|
||||||
sass (3.4.25)
|
|
||||||
sprockets (3.7.1)
|
|
||||||
concurrent-ruby (~> 1.0)
|
|
||||||
rack (> 1, < 3)
|
|
||||||
uglifier (3.2.0)
|
|
||||||
execjs (>= 0.3.0, < 3)
|
|
||||||
unicode-display_width (1.3.0)
|
|
||||||
|
|
||||||
PLATFORMS
|
|
||||||
ruby
|
|
||||||
|
|
||||||
DEPENDENCIES
|
|
||||||
jekyll (= 3.5.0)
|
|
||||||
jekyll-assets
|
|
||||||
jekyll-pants
|
|
||||||
jekyll-seo-tag
|
|
||||||
jekyll-sitemap
|
|
||||||
jekyll-tidy
|
|
||||||
rake
|
|
||||||
rubocop
|
|
||||||
tzinfo-data
|
|
||||||
uglifier
|
|
||||||
|
|
||||||
BUNDLED WITH
|
|
||||||
1.14.6
|
|
||||||
85
Rakefile
@@ -1,85 +0,0 @@
|
|||||||
require 'open-uri'
|
|
||||||
require 'yaml'
|
|
||||||
|
|
||||||
desc 'Build site into docs directory'
|
|
||||||
task :build do
|
|
||||||
jekyll_build
|
|
||||||
end
|
|
||||||
|
|
||||||
desc 'Update index.md and spec folder based on versions in _config.yml'
|
|
||||||
task :update do
|
|
||||||
config = YAML.load_file('_config.yml')
|
|
||||||
current_version = config['current_version']
|
|
||||||
versions = config['versions']
|
|
||||||
|
|
||||||
remove_all_specs(config['update'])
|
|
||||||
|
|
||||||
puts ''
|
|
||||||
puts 'Fetching configured spec versions:'
|
|
||||||
versions.each do |version|
|
|
||||||
spec = fetch_spec(version, config['update'])
|
|
||||||
|
|
||||||
if current_version == version
|
|
||||||
write_file('index.md', spec[:body], " (#{version})")
|
|
||||||
end
|
|
||||||
|
|
||||||
filename = File.join(config['update']['output_dir'], version)
|
|
||||||
write_file("#{filename}.md", spec[:body])
|
|
||||||
write_file("#{filename}.svg", spec[:diagram]) if spec[:diagram]
|
|
||||||
end
|
|
||||||
|
|
||||||
jekyll_build
|
|
||||||
end
|
|
||||||
|
|
||||||
def jekyll_build
|
|
||||||
puts 'Rebuilding output into docs directory...'
|
|
||||||
exec 'jekyll build --destination docs && touch docs/.nojekyll'
|
|
||||||
end
|
|
||||||
|
|
||||||
def write_file(file, content, comment = nil)
|
|
||||||
puts " - #{file}#{comment}"
|
|
||||||
File.write(file, content)
|
|
||||||
end
|
|
||||||
|
|
||||||
def fetch_spec(version, config)
|
|
||||||
document = get(build_file_url('document', version, config))
|
|
||||||
diagram = get(build_file_url('diagram', version, config))
|
|
||||||
|
|
||||||
if diagram
|
|
||||||
img_tag = config['img_tpl'].gsub('{{file}}', "#{version}.svg")
|
|
||||||
document.gsub!(/\A(.*\n=+\n)/, "\\1\n#{img_tag}\n")
|
|
||||||
end
|
|
||||||
|
|
||||||
title = document.split("\n", 2).first
|
|
||||||
body = config['body_tpl'].gsub('{{content}}', document)
|
|
||||||
.gsub('{{title}}', title)
|
|
||||||
.gsub('{{version}}', version)
|
|
||||||
|
|
||||||
{
|
|
||||||
version: version,
|
|
||||||
title: title,
|
|
||||||
body: body,
|
|
||||||
diagram: diagram
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def build_file_url(file, version, config)
|
|
||||||
config['url_tpl']
|
|
||||||
.gsub('{{version}}', version)
|
|
||||||
.gsub('{{file}}', config['files'][file])
|
|
||||||
end
|
|
||||||
|
|
||||||
def get(url)
|
|
||||||
URI.parse(url).read
|
|
||||||
rescue OpenURI::HTTPError
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
def remove_all_specs(config)
|
|
||||||
puts ''
|
|
||||||
puts 'Removing existing spec files:'
|
|
||||||
Dir["#{config['output_dir']}/*"].each do |file|
|
|
||||||
puts " #{file.gsub(File.dirname(__FILE__), '')}"
|
|
||||||
File.delete(file)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
html {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: 'Open Sans', Helvetica, Arial, sans-serif;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 1.5;
|
|
||||||
color: #1a1a1a;
|
|
||||||
background-color: #fdfdfd;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1, h2, h3, h4, h5, h6 {
|
|
||||||
font-family: 'Open Sans Condensed', Helvetica, Arial, sans-serif;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 2.5em;
|
|
||||||
line-height: 1.2;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol ol, ul ol {
|
|
||||||
list-style-type: lower-roman;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul ul ol, ul ol ol, ol ul ol, ol ol ol {
|
|
||||||
list-style-type: lower-alpha;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
margin-top: 80px;
|
|
||||||
|
|
||||||
a {
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
background-color: rgba(27,31,35,0.05);
|
|
||||||
border-radius: 3px;
|
|
||||||
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
|
|
||||||
font-size: 90%;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0.2em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#menu {
|
|
||||||
.pure-menu-label {
|
|
||||||
color: #999;
|
|
||||||
border: none;
|
|
||||||
padding: 0.6em 0 0.6em 0.6em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.links {
|
|
||||||
font-size: 50px;
|
|
||||||
position: absolute;
|
|
||||||
bottom: 10px;
|
|
||||||
left: 0px;
|
|
||||||
right: 0px;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: #555;
|
|
||||||
padding: 0;
|
|
||||||
position: relative;
|
|
||||||
text-decoration: none;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: #777;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,248 +0,0 @@
|
|||||||
body {
|
|
||||||
color: #777;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pure-img-responsive {
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Add transition to containers so they can push in and out.
|
|
||||||
*/
|
|
||||||
#layout,
|
|
||||||
#menu,
|
|
||||||
.menu-link {
|
|
||||||
-webkit-transition: all 0.2s ease-out;
|
|
||||||
-moz-transition: all 0.2s ease-out;
|
|
||||||
-ms-transition: all 0.2s ease-out;
|
|
||||||
-o-transition: all 0.2s ease-out;
|
|
||||||
transition: all 0.2s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
This is the parent `<div>` that contains the menu and the content area.
|
|
||||||
*/
|
|
||||||
#layout {
|
|
||||||
position: relative;
|
|
||||||
left: 0;
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
#layout.active #menu {
|
|
||||||
left: 150px;
|
|
||||||
width: 150px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#layout.active .menu-link {
|
|
||||||
left: 150px;
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
The content `<div>` is where all your content goes.
|
|
||||||
*/
|
|
||||||
.content {
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0 2em;
|
|
||||||
max-width: 800px;
|
|
||||||
margin-bottom: 50px;
|
|
||||||
line-height: 1.6em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
margin: 0;
|
|
||||||
color: #333;
|
|
||||||
text-align: center;
|
|
||||||
padding: 2.5em 2em 0;
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
}
|
|
||||||
.header h1 {
|
|
||||||
margin: 0.2em 0;
|
|
||||||
font-size: 3em;
|
|
||||||
font-weight: 300;
|
|
||||||
}
|
|
||||||
.header h2 {
|
|
||||||
font-weight: 300;
|
|
||||||
color: #ccc;
|
|
||||||
padding: 0;
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-subhead {
|
|
||||||
margin: 50px 0 20px 0;
|
|
||||||
font-weight: 300;
|
|
||||||
color: #888;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
The `#menu` `<div>` is the parent `<div>` that contains the `.pure-menu` that
|
|
||||||
appears on the left side of the page.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#menu {
|
|
||||||
margin-left: -150px; /* "#menu" width */
|
|
||||||
width: 150px;
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
|
||||||
z-index: 1000; /* so the menu or its navicon stays above all content */
|
|
||||||
background: #191818;
|
|
||||||
overflow-y: auto;
|
|
||||||
-webkit-overflow-scrolling: touch;
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
All anchors inside the menu should be styled like this.
|
|
||||||
*/
|
|
||||||
#menu a {
|
|
||||||
color: #999;
|
|
||||||
border: none;
|
|
||||||
padding: 0.6em 0 0.6em 0.6em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Remove all background/borders, since we are applying them to #menu.
|
|
||||||
*/
|
|
||||||
#menu .pure-menu,
|
|
||||||
#menu .pure-menu ul {
|
|
||||||
border: none;
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Add that light border to separate items into groups.
|
|
||||||
*/
|
|
||||||
#menu .pure-menu ul,
|
|
||||||
#menu .pure-menu .menu-item-divided {
|
|
||||||
border-top: 1px solid #333;
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
Change color of the anchor links on hover/focus.
|
|
||||||
*/
|
|
||||||
#menu .pure-menu li a:hover,
|
|
||||||
#menu .pure-menu li a:focus {
|
|
||||||
background: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
This styles the selected menu item `<li>`.
|
|
||||||
*/
|
|
||||||
#menu .pure-menu-selected,
|
|
||||||
#menu .pure-menu-heading {
|
|
||||||
background: #1f8dd6;
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
This styles a link within a selected menu item `<li>`.
|
|
||||||
*/
|
|
||||||
#menu .pure-menu-selected a {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
This styles the menu heading.
|
|
||||||
*/
|
|
||||||
#menu .pure-menu-heading {
|
|
||||||
font-size: 110%;
|
|
||||||
color: #fff;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- Dynamic Button For Responsive Menu -------------------------------------*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
The button to open/close the Menu is custom-made and not part of Pure. Here's
|
|
||||||
how it works:
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
`.menu-link` represents the responsive menu toggle that shows/hides on
|
|
||||||
small screens.
|
|
||||||
*/
|
|
||||||
.menu-link {
|
|
||||||
position: fixed;
|
|
||||||
display: block; /* show this only on small screens */
|
|
||||||
top: 0;
|
|
||||||
left: 0; /* "#menu width" */
|
|
||||||
background: #000;
|
|
||||||
background: rgba(0,0,0,0.7);
|
|
||||||
font-size: 10px; /* change this value to increase/decrease button size */
|
|
||||||
z-index: 10;
|
|
||||||
width: 2em;
|
|
||||||
height: auto;
|
|
||||||
padding: 2.1em 1.6em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-link:hover,
|
|
||||||
.menu-link:focus {
|
|
||||||
background: #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-link span {
|
|
||||||
position: relative;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-link span,
|
|
||||||
.menu-link span:before,
|
|
||||||
.menu-link span:after {
|
|
||||||
background-color: #fff;
|
|
||||||
width: 100%;
|
|
||||||
height: 0.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-link span:before,
|
|
||||||
.menu-link span:after {
|
|
||||||
position: absolute;
|
|
||||||
margin-top: -0.6em;
|
|
||||||
content: " ";
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-link span:after {
|
|
||||||
margin-top: 0.6em;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* -- Responsive Styles (Media Queries) ------------------------------------- */
|
|
||||||
|
|
||||||
/*
|
|
||||||
Hides the menu at `48em`, but modify this based on your app's needs.
|
|
||||||
*/
|
|
||||||
@media (min-width: 48em) {
|
|
||||||
|
|
||||||
.header,
|
|
||||||
.content {
|
|
||||||
padding-left: 2em;
|
|
||||||
padding-right: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#layout {
|
|
||||||
padding-left: 150px; /* left col width "#menu" */
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
#menu {
|
|
||||||
left: 150px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-link {
|
|
||||||
position: fixed;
|
|
||||||
left: 150px;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#layout.active .menu-link {
|
|
||||||
left: 150px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 48em) {
|
|
||||||
/* Only apply this when the window is small. Otherwise, the following
|
|
||||||
case results in extra padding on the left:
|
|
||||||
* Make the window small.
|
|
||||||
* Tap the menu to trigger the active state.
|
|
||||||
* Make the window large again.
|
|
||||||
*/
|
|
||||||
#layout.active {
|
|
||||||
position: relative;
|
|
||||||
left: 150px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
@import "side-menu";
|
|
||||||
@import "base";
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
// = require ui
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
(function (window, document) {
|
|
||||||
var layout = document.getElementById('layout');
|
|
||||||
var menu = document.getElementById('menu');
|
|
||||||
var menuLink = document.getElementById('menuLink');
|
|
||||||
var content = document.getElementById('main');
|
|
||||||
|
|
||||||
function toggleClass (element, className) {
|
|
||||||
var classes = element.className.split(/\s+/);
|
|
||||||
var length = classes.length;
|
|
||||||
var i = 0;
|
|
||||||
|
|
||||||
for (; i < length; i++) {
|
|
||||||
if (classes[i] === className) {
|
|
||||||
classes.splice(i, 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// The className is not found
|
|
||||||
if (length === classes.length) {
|
|
||||||
classes.push(className);
|
|
||||||
}
|
|
||||||
|
|
||||||
element.className = classes.join(' ');
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleAll (e) {
|
|
||||||
var active = 'active';
|
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
toggleClass(layout, active);
|
|
||||||
toggleClass(menu, active);
|
|
||||||
toggleClass(menuLink, active);
|
|
||||||
}
|
|
||||||
|
|
||||||
menuLink.onclick = function (e) {
|
|
||||||
toggleAll(e);
|
|
||||||
};
|
|
||||||
|
|
||||||
content.onclick = function (e) {
|
|
||||||
if (menu.className.indexOf('active') !== -1) {
|
|
||||||
toggleAll(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}(this, this.document));
|
|
||||||
58
_config.yml
@@ -1,58 +0,0 @@
|
|||||||
title: Git Common Flow
|
|
||||||
description: >
|
|
||||||
An attempt to gather a sensible selection of the most common usage patterns of
|
|
||||||
git into a single and concise specification.
|
|
||||||
author: Jim Myhrberg
|
|
||||||
hostname: commonflow.org
|
|
||||||
url: https://commonflow.org
|
|
||||||
|
|
||||||
current_version: 1.0.0-rc.4
|
|
||||||
versions:
|
|
||||||
- 1.0.0-rc.4
|
|
||||||
- 1.0.0-rc.3
|
|
||||||
- 1.0.0-rc.2
|
|
||||||
- 1.0.0-rc.1
|
|
||||||
|
|
||||||
exclude:
|
|
||||||
- Gemfile
|
|
||||||
- Gemfile.lock
|
|
||||||
- Rakefile
|
|
||||||
- README.md
|
|
||||||
|
|
||||||
update:
|
|
||||||
body_tpl: |
|
|
||||||
---
|
|
||||||
title: {{title}}
|
|
||||||
version: {{version}}
|
|
||||||
---
|
|
||||||
{{content}}
|
|
||||||
url_tpl: "https://raw.githubusercontent.com/jimeh/common-flow/{{version}}/{{file}}"
|
|
||||||
img_tpl: "<img src=\"/spec/{{file}}\" width=\"100%\" />"
|
|
||||||
output_dir: "spec"
|
|
||||||
files:
|
|
||||||
document: common-flow.md
|
|
||||||
diagram: common-flow.svg
|
|
||||||
|
|
||||||
plugins:
|
|
||||||
- jekyll-assets
|
|
||||||
- jekyll-pants
|
|
||||||
- jekyll-sitemap
|
|
||||||
- jekyll-seo-tag
|
|
||||||
- jekyll-tidy
|
|
||||||
|
|
||||||
defaults:
|
|
||||||
-
|
|
||||||
scope:
|
|
||||||
path: ""
|
|
||||||
values:
|
|
||||||
layout: "default"
|
|
||||||
|
|
||||||
assets:
|
|
||||||
digest: true
|
|
||||||
compress:
|
|
||||||
css: true
|
|
||||||
js: true
|
|
||||||
|
|
||||||
markdown: kramdown
|
|
||||||
kramdown:
|
|
||||||
input: Pantsdown # disable smart quotes typographic symbols
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link href='https://fonts.googleapis.com/css?family=Open+Sans+Condensed:700,300|Open+Sans:400italic,700italic,400,700' rel='stylesheet' type='text/css'>
|
|
||||||
<link rel="stylesheet" href="https://unpkg.com/purecss@1.0.0/build/pure-min.css" integrity="sha384-nn4HPE8lTHyVtfCBi5yW9d20FjT8BJwUXyWZT9InLYax14RDjBj46LmSztkmNP9w" crossorigin="anonymous">
|
|
||||||
<link rel="stylesheet" href="https:////maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
|
|
||||||
{% css main %}
|
|
||||||
{% seo %}
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="layout">
|
|
||||||
<a href="#menu" id="menuLink" class="menu-link">
|
|
||||||
<span></span>
|
|
||||||
</a>
|
|
||||||
<div id="menu">
|
|
||||||
<div class="pure-menu">
|
|
||||||
<ul class="pure-menu-list">
|
|
||||||
<li class="pure-menu-item">
|
|
||||||
<div class="pure-menu-label">Versions:</div>
|
|
||||||
</li>
|
|
||||||
{% for version in site.versions %}
|
|
||||||
{% assign expected = "spec/" | append: version | append: ".md" %}
|
|
||||||
{% assign found = site.pages | where: "path", expected | first %}
|
|
||||||
{% assign selected = "" %}
|
|
||||||
{% if version == page.version %}
|
|
||||||
{% assign selected = " pure-menu-selected" %}
|
|
||||||
{% endif %}
|
|
||||||
{% if found %}
|
|
||||||
<li class="pure-menu-item version-{{ version }}{{ selected }}">
|
|
||||||
<a href="{{ found.url }}" class="pure-menu-link">{{ version }}</a>
|
|
||||||
</li>
|
|
||||||
{% endif %}
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="links">
|
|
||||||
<a href="https://github.com/jimeh/common-flow">
|
|
||||||
<i class="fa fa-github" aria-hidden="true"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="main">
|
|
||||||
<div class="content">
|
|
||||||
{{ content }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% js main %}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
38
astro.config.mjs
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { defineConfig, fontProviders } from "astro/config";
|
||||||
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
|
import sitemap from "@astrojs/sitemap";
|
||||||
|
import icon from "astro-icon";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
site: "https://commonflow.org",
|
||||||
|
outDir: "./dist",
|
||||||
|
integrations: [sitemap(), icon()],
|
||||||
|
vite: {
|
||||||
|
plugins: [tailwindcss()],
|
||||||
|
},
|
||||||
|
experimental: {
|
||||||
|
fonts: [
|
||||||
|
{
|
||||||
|
provider: fontProviders.fontsource(),
|
||||||
|
name: "Bricolage Grotesque",
|
||||||
|
cssVariable: "--font-bricolage",
|
||||||
|
weights: ["200 800"],
|
||||||
|
fallbacks: ["system-ui", "sans-serif"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provider: fontProviders.fontsource(),
|
||||||
|
name: "DM Sans",
|
||||||
|
cssVariable: "--font-dm-sans",
|
||||||
|
weights: ["100 1000"],
|
||||||
|
fallbacks: ["system-ui", "sans-serif"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provider: fontProviders.fontsource(),
|
||||||
|
name: "JetBrains Mono",
|
||||||
|
cssVariable: "--font-jetbrains",
|
||||||
|
weights: ["100 800"],
|
||||||
|
fallbacks: ["SF Mono", "Consolas", "monospace"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link href='https://fonts.googleapis.com/css?family=Open+Sans+Condensed:700,300|Open+Sans:400italic,700italic,400,700' rel='stylesheet' type='text/css'>
|
|
||||||
<link rel="stylesheet" href="https://unpkg.com/purecss@1.0.0/build/pure-min.css" integrity="sha384-nn4HPE8lTHyVtfCBi5yW9d20FjT8BJwUXyWZT9InLYax14RDjBj46LmSztkmNP9w" crossorigin="anonymous">
|
|
||||||
<link rel="stylesheet" href="https:////maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
|
|
||||||
<link type="text/css" rel="stylesheet" href="/assets/main-16db41e8ef2362fe9967bb0bee92e459cf76f2a846e85c96322243077a88c301.css">
|
|
||||||
<!-- Begin Jekyll SEO tag v2.2.3 -->
|
|
||||||
<title>404 Page Not Found | Git Common Flow</title>
|
|
||||||
<meta property="og:title" content="404 Page Not Found" />
|
|
||||||
<meta name="author" content="Jim Myhrberg" />
|
|
||||||
<meta property="og:locale" content="en_US" />
|
|
||||||
<meta name="description" content="An attempt to gather a sensible selection of the most common usage patterns of git into a single and concise specification." />
|
|
||||||
<meta property="og:description" content="An attempt to gather a sensible selection of the most common usage patterns of git into a single and concise specification." />
|
|
||||||
<link rel="canonical" href="https://commonflow.org/404.html" />
|
|
||||||
<meta property="og:url" content="https://commonflow.org/404.html" />
|
|
||||||
<meta property="og:site_name" content="Git Common Flow" />
|
|
||||||
<script type="application/ld+json">
|
|
||||||
{"@context":"http://schema.org","@type":"WebPage","headline":"404 Page Not Found","author":{"@type":"Person","name":"Jim Myhrberg"},"description":"An attempt to gather a sensible selection of the most common usage patterns of git into a single and concise specification.","url":"https://commonflow.org/404.html"}
|
|
||||||
</script>
|
|
||||||
<!-- End Jekyll SEO tag -->
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="layout">
|
|
||||||
<a href="#menu" id="menuLink" class="menu-link">
|
|
||||||
<span></span>
|
|
||||||
</a>
|
|
||||||
<div id="menu">
|
|
||||||
<div class="pure-menu">
|
|
||||||
<ul class="pure-menu-list">
|
|
||||||
<li class="pure-menu-item">
|
|
||||||
<div class="pure-menu-label">Versions:</div>
|
|
||||||
</li>
|
|
||||||
<li class="pure-menu-item version-1.0.0-rc.4">
|
|
||||||
<a href="/spec/1.0.0-rc.4.html" class="pure-menu-link">1.0.0-rc.4</a>
|
|
||||||
</li>
|
|
||||||
<li class="pure-menu-item version-1.0.0-rc.3">
|
|
||||||
<a href="/spec/1.0.0-rc.3.html" class="pure-menu-link">1.0.0-rc.3</a>
|
|
||||||
</li>
|
|
||||||
<li class="pure-menu-item version-1.0.0-rc.2">
|
|
||||||
<a href="/spec/1.0.0-rc.2.html" class="pure-menu-link">1.0.0-rc.2</a>
|
|
||||||
</li>
|
|
||||||
<li class="pure-menu-item version-1.0.0-rc.1">
|
|
||||||
<a href="/spec/1.0.0-rc.1.html" class="pure-menu-link">1.0.0-rc.1</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="links">
|
|
||||||
<a href="https://github.com/jimeh/common-flow">
|
|
||||||
<i class="fa fa-github" aria-hidden="true"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="main">
|
|
||||||
<div class="content">
|
|
||||||
<div class="header">
|
|
||||||
<h1>404</h1>
|
|
||||||
<p><strong>Page not found :(</strong></p>
|
|
||||||
<p>The requested page could not be found.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script type="text/javascript" src="/assets/main-870855580c69dec57be4c965d0cf8afe78afa6b7b6f6bdb5aff91ac0256c0a1a.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
commonflow.org
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
body{color:#777}.pure-img-responsive{max-width:100%;height:auto}#layout,#menu,.menu-link{-webkit-transition:all 0.2s ease-out;-moz-transition:all 0.2s ease-out;-ms-transition:all 0.2s ease-out;-o-transition:all 0.2s ease-out;transition:all 0.2s ease-out}#layout{position:relative;left:0;padding-left:0}#layout.active #menu{left:150px;width:150px}#layout.active .menu-link{left:150px}.content{margin:0 auto;padding:0 2em;max-width:800px;margin-bottom:50px;line-height:1.6em}.header{margin:0;color:#333;text-align:center;padding:2.5em 2em 0;border-bottom:1px solid #eee}.header h1{margin:0.2em 0;font-size:3em;font-weight:300}.header h2{font-weight:300;color:#ccc;padding:0;margin-top:0}.content-subhead{margin:50px 0 20px 0;font-weight:300;color:#888}#menu{margin-left:-150px;width:150px;position:fixed;top:0;left:0;bottom:0;z-index:1000;background:#191818;overflow-y:auto;-webkit-overflow-scrolling:touch}#menu a{color:#999;border:none;padding:0.6em 0 0.6em 0.6em}#menu .pure-menu,#menu .pure-menu ul{border:none;background:transparent}#menu .pure-menu ul,#menu .pure-menu .menu-item-divided{border-top:1px solid #333}#menu .pure-menu li a:hover,#menu .pure-menu li a:focus{background:#333}#menu .pure-menu-selected,#menu .pure-menu-heading{background:#1f8dd6}#menu .pure-menu-selected a{color:#fff}#menu .pure-menu-heading{font-size:110%;color:#fff;margin:0}.menu-link{position:fixed;display:block;top:0;left:0;background:#000;background:rgba(0,0,0,0.7);font-size:10px;z-index:10;width:2em;height:auto;padding:2.1em 1.6em}.menu-link:hover,.menu-link:focus{background:#000}.menu-link span{position:relative;display:block}.menu-link span,.menu-link span:before,.menu-link span:after{background-color:#fff;width:100%;height:0.2em}.menu-link span:before,.menu-link span:after{position:absolute;margin-top:-0.6em;content:" "}.menu-link span:after{margin-top:0.6em}@media (min-width: 48em){.header,.content{padding-left:2em;padding-right:2em}#layout{padding-left:150px;left:0}#menu{left:150px}.menu-link{position:fixed;left:150px;display:none}#layout.active .menu-link{left:150px}}@media (max-width: 48em){#layout.active{position:relative;left:150px}}html{height:100%}body{font-family:'Open Sans', Helvetica, Arial, sans-serif;font-size:16px;font-weight:400;line-height:1.5;color:#1a1a1a;background-color:#fdfdfd}h1,h2,h3,h4,h5,h6{font-family:'Open Sans Condensed', Helvetica, Arial, sans-serif;font-weight:700;color:#333}h1{font-size:2.5em;line-height:1.2}ol ol,ul ol{list-style-type:lower-roman}ul ul ol,ul ol ol,ol ul ol,ol ol ol{list-style-type:lower-alpha}.content{margin-top:80px}.content a{word-break:break-word}.content code{background-color:rgba(27,31,35,0.05);border-radius:3px;font-family:"SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;font-size:90%;margin:0;padding:0.2em}#menu .pure-menu-label{color:#999;border:none;padding:0.6em 0 0.6em 0.6em}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
body{color:#777}.pure-img-responsive{max-width:100%;height:auto}#layout,#menu,.menu-link{-webkit-transition:all 0.2s ease-out;-moz-transition:all 0.2s ease-out;-ms-transition:all 0.2s ease-out;-o-transition:all 0.2s ease-out;transition:all 0.2s ease-out}#layout{position:relative;left:0;padding-left:0}#layout.active #menu{left:150px;width:150px}#layout.active .menu-link{left:150px}.content{margin:0 auto;padding:0 2em;max-width:800px;margin-bottom:50px;line-height:1.6em}.header{margin:0;color:#333;text-align:center;padding:2.5em 2em 0;border-bottom:1px solid #eee}.header h1{margin:0.2em 0;font-size:3em;font-weight:300}.header h2{font-weight:300;color:#ccc;padding:0;margin-top:0}.content-subhead{margin:50px 0 20px 0;font-weight:300;color:#888}#menu{margin-left:-150px;width:150px;position:fixed;top:0;left:0;bottom:0;z-index:1000;background:#191818;overflow-y:auto;-webkit-overflow-scrolling:touch}#menu a{color:#999;border:none;padding:0.6em 0 0.6em 0.6em}#menu .pure-menu,#menu .pure-menu ul{border:none;background:transparent}#menu .pure-menu ul,#menu .pure-menu .menu-item-divided{border-top:1px solid #333}#menu .pure-menu li a:hover,#menu .pure-menu li a:focus{background:#333}#menu .pure-menu-selected,#menu .pure-menu-heading{background:#1f8dd6}#menu .pure-menu-selected a{color:#fff}#menu .pure-menu-heading{font-size:110%;color:#fff;margin:0}.menu-link{position:fixed;display:block;top:0;left:0;background:#000;background:rgba(0,0,0,0.7);font-size:10px;z-index:10;width:2em;height:auto;padding:2.1em 1.6em}.menu-link:hover,.menu-link:focus{background:#000}.menu-link span{position:relative;display:block}.menu-link span,.menu-link span:before,.menu-link span:after{background-color:#fff;width:100%;height:0.2em}.menu-link span:before,.menu-link span:after{position:absolute;margin-top:-0.6em;content:" "}.menu-link span:after{margin-top:0.6em}@media (min-width: 48em){.header,.content{padding-left:2em;padding-right:2em}#layout{padding-left:150px;left:0}#menu{left:150px}.menu-link{position:fixed;left:150px;display:none}#layout.active .menu-link{left:150px}}@media (max-width: 48em){#layout.active{position:relative;left:150px}}html{height:100%}body{font-family:'Open Sans', Helvetica, Arial, sans-serif;font-size:16px;font-weight:400;line-height:1.5;color:#1a1a1a;background-color:#fdfdfd}h1,h2,h3,h4,h5,h6{font-family:'Open Sans Condensed', Helvetica, Arial, sans-serif;font-weight:700;color:#333}h1{font-size:2.5em;line-height:1.2}ol ol,ul ol{list-style-type:lower-roman}ul ul ol,ul ol ol,ol ul ol,ol ol ol{list-style-type:lower-alpha}.content{margin-top:80px}.content a{word-break:break-word}.content code{background-color:rgba(27,31,35,0.05);border-radius:3px;font-family:"SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;font-size:90%;margin:0;padding:0.2em}#menu .pure-menu-label{color:#999;border:none;padding:0.6em 0 0.6em 0.6em}#menu .links{font-size:50px;position:absolute;bottom:10px;left:0px;right:0px;text-align:center}#menu .links a{color:#555;padding:0;position:relative;text-decoration:none}#menu .links a:hover{color:#777}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
!function(e,n){function t(e,n){for(var t=e.className.split(/\s+/),i=t.length,c=0;c<i;c++)if(t[c]===n){t.splice(c,1);break}i===t.length&&t.push(n),e.className=t.join(" ")}function i(e){var n="active";e.preventDefault(),t(c,n),t(a,n),t(l,n)}var c=n.getElementById("layout"),a=n.getElementById("menu"),l=n.getElementById("menuLink"),m=n.getElementById("main");l.onclick=function(e){i(e)},m.onclick=function(e){-1!==a.className.indexOf("active")&&i(e)}}(0,this.document);
|
|
||||||
403
docs/index.html
@@ -1,403 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link href='https://fonts.googleapis.com/css?family=Open+Sans+Condensed:700,300|Open+Sans:400italic,700italic,400,700' rel='stylesheet' type='text/css'>
|
|
||||||
<link rel="stylesheet" href="https://unpkg.com/purecss@1.0.0/build/pure-min.css" integrity="sha384-nn4HPE8lTHyVtfCBi5yW9d20FjT8BJwUXyWZT9InLYax14RDjBj46LmSztkmNP9w" crossorigin="anonymous">
|
|
||||||
<link rel="stylesheet" href="https:////maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
|
|
||||||
<link type="text/css" rel="stylesheet" href="/assets/main-16db41e8ef2362fe9967bb0bee92e459cf76f2a846e85c96322243077a88c301.css">
|
|
||||||
<!-- Begin Jekyll SEO tag v2.2.3 -->
|
|
||||||
<title>Git Common-Flow 1.0.0-rc.4 | Git Common Flow</title>
|
|
||||||
<meta property="og:title" content="Git Common-Flow 1.0.0-rc.4" />
|
|
||||||
<meta name="author" content="Jim Myhrberg" />
|
|
||||||
<meta property="og:locale" content="en_US" />
|
|
||||||
<meta name="description" content="An attempt to gather a sensible selection of the most common usage patterns of git into a single and concise specification." />
|
|
||||||
<meta property="og:description" content="An attempt to gather a sensible selection of the most common usage patterns of git into a single and concise specification." />
|
|
||||||
<link rel="canonical" href="https://commonflow.org/" />
|
|
||||||
<meta property="og:url" content="https://commonflow.org/" />
|
|
||||||
<meta property="og:site_name" content="Git Common Flow" />
|
|
||||||
<script type="application/ld+json">
|
|
||||||
{"@context":"http://schema.org","@type":"WebSite","name":"Git Common Flow","headline":"Git Common-Flow 1.0.0-rc.4","author":{"@type":"Person","name":"Jim Myhrberg"},"description":"An attempt to gather a sensible selection of the most common usage patterns of git into a single and concise specification.","url":"https://commonflow.org/"}
|
|
||||||
</script>
|
|
||||||
<!-- End Jekyll SEO tag -->
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="layout">
|
|
||||||
<a href="#menu" id="menuLink" class="menu-link">
|
|
||||||
<span></span>
|
|
||||||
</a>
|
|
||||||
<div id="menu">
|
|
||||||
<div class="pure-menu">
|
|
||||||
<ul class="pure-menu-list">
|
|
||||||
<li class="pure-menu-item">
|
|
||||||
<div class="pure-menu-label">Versions:</div>
|
|
||||||
</li>
|
|
||||||
<li class="pure-menu-item version-1.0.0-rc.4 pure-menu-selected">
|
|
||||||
<a href="/spec/1.0.0-rc.4.html" class="pure-menu-link">1.0.0-rc.4</a>
|
|
||||||
</li>
|
|
||||||
<li class="pure-menu-item version-1.0.0-rc.3">
|
|
||||||
<a href="/spec/1.0.0-rc.3.html" class="pure-menu-link">1.0.0-rc.3</a>
|
|
||||||
</li>
|
|
||||||
<li class="pure-menu-item version-1.0.0-rc.2">
|
|
||||||
<a href="/spec/1.0.0-rc.2.html" class="pure-menu-link">1.0.0-rc.2</a>
|
|
||||||
</li>
|
|
||||||
<li class="pure-menu-item version-1.0.0-rc.1">
|
|
||||||
<a href="/spec/1.0.0-rc.1.html" class="pure-menu-link">1.0.0-rc.1</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="links">
|
|
||||||
<a href="https://github.com/jimeh/common-flow">
|
|
||||||
<i class="fa fa-github" aria-hidden="true"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="main">
|
|
||||||
<div class="content">
|
|
||||||
<h1 id="git-common-flow-100-rc4">Git Common-Flow 1.0.0-rc.4</h1>
|
|
||||||
<p><img src="/spec/1.0.0-rc.4.svg" width="100%" /></p>
|
|
||||||
<h2 id="summary">Summary</h2>
|
|
||||||
<p>Common-Flow is an attempt to gather a sensible selection of the most common
|
|
||||||
usage patterns of git into a single and concise specification. It is based on
|
|
||||||
the <a href="http://scottchacon.com/2011/08/31/github-flow.html">original variant</a>
|
|
||||||
of <a href="https://guides.github.com/introduction/flow/">GitHub Flow</a>, while taking
|
|
||||||
into account how a lot of open source projects use git.</p>
|
|
||||||
<p>In short, Common-Flow is essentially GitHub Flow with the addition of versioned
|
|
||||||
releases, optional release branches, and without the requirement to deploy to
|
|
||||||
production all the time.</p>
|
|
||||||
<h2 id="terminology">Terminology</h2>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Master Branch</strong> - Must be named "master", must always have passing tests,
|
|
||||||
and is not guaranteed to always work in production environments.</li>
|
|
||||||
<li><strong>Change Branches</strong> - Any branch that introduces changes like a new feature, a
|
|
||||||
bug fix, etc.</li>
|
|
||||||
<li><strong>Source Branch</strong> - The branch that a change branch was created from. New
|
|
||||||
changes in the source branch should be incorporated into the change branch via
|
|
||||||
rebasing.</li>
|
|
||||||
<li><strong>Merge Target</strong> - A branch that is the intended merge target for a change
|
|
||||||
branch. Typically the merge target branch will be the same as the source
|
|
||||||
branch.</li>
|
|
||||||
<li><strong>Pull Request</strong> - A means of requesting that a change branch is merged in to
|
|
||||||
its merge target, allowing others to review, discuss and approve the changes.</li>
|
|
||||||
<li><strong>Release</strong> - May be considered safe to use in production environments. Is
|
|
||||||
effectively just a git tag named after the version of the release.</li>
|
|
||||||
<li><strong>Release Branches</strong> - Used both for short-term preparations of a release, and
|
|
||||||
also for long-term maintenance of older version.</li>
|
|
||||||
</ul>
|
|
||||||
<h2 id="git-common-flow-specification-common-flow">Git Common-Flow Specification (Common-Flow)</h2>
|
|
||||||
<p>The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD",
|
|
||||||
"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be
|
|
||||||
interpreted as described in <a href="https://tools.ietf.org/html/rfc2119">RFC 2119</a>.</p>
|
|
||||||
<ol>
|
|
||||||
<li>TL;DR
|
|
||||||
<ol>
|
|
||||||
<li>Don't break the master branch.</li>
|
|
||||||
<li>A release is a git tag.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>The Master Branch
|
|
||||||
<ol>
|
|
||||||
<li>A branch named "master" MUST exist and it MUST be referred to as the
|
|
||||||
"master branch".</li>
|
|
||||||
<li>The master branch MUST always be in a non-broken state with its test
|
|
||||||
suite passing.</li>
|
|
||||||
<li>The master branch IS NOT guaranteed to always work in production
|
|
||||||
environments. Despite test suites passing it may at times contain
|
|
||||||
unfinished work. Only releases may be considered safe for production use.</li>
|
|
||||||
<li>The master branch SHOULD always be in a "as near as possibly ready for
|
|
||||||
release/production" state to reduce any friction with creating a new
|
|
||||||
release.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Change Branches
|
|
||||||
<ol>
|
|
||||||
<li>Each change (feature, bugfix, etc.) MUST be performed on separate
|
|
||||||
branches that SHOULD be referred to as "change branches".</li>
|
|
||||||
<li>All change branches MUST have descriptive names.</li>
|
|
||||||
<li>It is RECOMMENDED that you commit often locally, and that you try and
|
|
||||||
keep the commits reasonably structured to avoid a messy and confusing git
|
|
||||||
history.</li>
|
|
||||||
<li>You SHOULD regularly push your work to the same named branch on the
|
|
||||||
remote server.</li>
|
|
||||||
<li>You SHOULD create separate change branches for each distinctly different
|
|
||||||
change. You SHOULD NOT include multiple unrelated changes into a single
|
|
||||||
change branch.</li>
|
|
||||||
<li>When a change branch is created, the branch that it is created from
|
|
||||||
SHOULD be referred to as the "source branch". Each change branch also
|
|
||||||
needs a designated "merge target" branch, typically this will be the same
|
|
||||||
as the source branch.</li>
|
|
||||||
<li>Change branches MUST be regularly updated with any changes from their
|
|
||||||
source branch. This MUST be done by rebasing the change branch on top of
|
|
||||||
the source branch.</li>
|
|
||||||
<li>After updating a change branch from its source branch you MUST push the
|
|
||||||
change branch to the remote server. Due to the nature of rebasing, you
|
|
||||||
will be required to do a force push, and you MUST use the
|
|
||||||
"--force-with-lease" git push option when doing so instead of the regular
|
|
||||||
"--force".</li>
|
|
||||||
<li>If there is a truly valid technical reason to not use rebase when
|
|
||||||
updating change branches, then you can update change branches via merge
|
|
||||||
instead of rebase. The decision to use merge MUST only be taken after all
|
|
||||||
possible options to use rebase have been tried and failed. People not
|
|
||||||
understanding how to use rebase is NOT a valid reason to use merge. If
|
|
||||||
you do decide to use merge instead of rebase, you MUST NOT use a mixture
|
|
||||||
of both methods, pick one and stick to it.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Pull Requests
|
|
||||||
<ol>
|
|
||||||
<li>To merge a change branch into its merge target, you MUST open a "pull
|
|
||||||
request" (or equivalent).</li>
|
|
||||||
<li>The purpose of a pull request is to allow others to review your changes
|
|
||||||
and give feedback. You can then fix any issues, complaints, and more that
|
|
||||||
might arise, and then let people review again.</li>
|
|
||||||
<li>Before creating a pull request, it is RECOMMENDED that you consider the
|
|
||||||
state of your change branch's commit history. If it is messy and
|
|
||||||
confusing, it might be a good idea to rebase your branch with "git rebase
|
|
||||||
-i" to present a cleaner and easier to follow commit history for your
|
|
||||||
reviewers.</li>
|
|
||||||
<li>A pull request MUST only be merged when the change branch is up-to-date
|
|
||||||
with its source branch, the test suite is passing, and you and others are
|
|
||||||
happy with the change. This is especially important if the merge target
|
|
||||||
is the master branch.</li>
|
|
||||||
<li>To get feedback, help, or generally just discuss a change branch with
|
|
||||||
others, the RECOMMENDED way to do so is by creating a pull request and
|
|
||||||
discuss the changes with others there.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Versioning
|
|
||||||
<ol>
|
|
||||||
<li>A "version string" is a typically mostly numeric string that identifies a
|
|
||||||
specific version of a project. The version string itself MUST NOT have a
|
|
||||||
"v" prefix, but the version string can be displayed with a "v" prefix to
|
|
||||||
indicate it is a version that is being referred to.</li>
|
|
||||||
<li>The source of truth for a project's version MUST be a git tag with a name
|
|
||||||
based on the version string. This kind of tag MUST be referred to as a
|
|
||||||
"release tag".</li>
|
|
||||||
<li>It is OPTIONAL, but RECOMMENDED to also keep the version string
|
|
||||||
hard-coded somewhere in the project code-base.</li>
|
|
||||||
<li>If you hard-code the version string into the code-base, it is RECOMMENDED
|
|
||||||
that you do so in a file called "VERSION" located in the root of the
|
|
||||||
project. But be mindful of the conventions of your programming language
|
|
||||||
and community when choosing if, where and how to hard-code the version
|
|
||||||
string.</li>
|
|
||||||
<li>If you are using a "VERSION" file in the root of the project, this file
|
|
||||||
MUST only contain the exact version string, meaning it MUST NOT have a
|
|
||||||
"v" prefix. For example "v2.11.4" is bad, and "2.11.4" is good.</li>
|
|
||||||
<li>It is OPTIONAL, but RECOMMENDED that that the version string follows
|
|
||||||
Semantic Versioning (<a href="http://semver.org/">http://semver.org/</a>).</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Releases
|
|
||||||
<ol>
|
|
||||||
<li>To create a new release, you MUST create a git tag named as the exact
|
|
||||||
version string of the release. This kind of tag MUST be referred to as a
|
|
||||||
"release tag".</li>
|
|
||||||
<li>The release tag name can OPTIONALLY be prefixed with "v". For example the
|
|
||||||
tag name can be either "2.11.4" or "v2.11.4". It is however RECOMMENDED
|
|
||||||
that you do not use a "v" prefix. You MUST NOT use a mixture of "v"
|
|
||||||
prefixed and non-prefixed tags. Pick one form and stick to it.</li>
|
|
||||||
<li>If the version string is hard-coded into the code-base, you MUST create a
|
|
||||||
"version bump" commit which changes the hard-coded version string of the
|
|
||||||
project.</li>
|
|
||||||
<li>When using version bump commits, the release tag MUST be placed on the
|
|
||||||
version bump commit.</li>
|
|
||||||
<li>If you are not using a release branch, then the release tag, and if
|
|
||||||
relevant the version bump commit, MUST be created directly on the master
|
|
||||||
branch.</li>
|
|
||||||
<li>The version bump commit SHOULD have a commit message title of "Bump
|
|
||||||
version to VERSION". For example, if the new version string is "2.11.4",
|
|
||||||
the first line of the commit message SHOULD read: "Bump version to
|
|
||||||
2.11.4"</li>
|
|
||||||
<li>It is RECOMMENDED that release tags are lightweight tags, but you can
|
|
||||||
OPTIONALLY use annotated tags if you want to include changelog
|
|
||||||
information in the release tag itself.</li>
|
|
||||||
<li>If you use annotated release tags, the first line of the annotation
|
|
||||||
SHOULD read "Release VERSION". For example for version "2.11.4" the first
|
|
||||||
line of the tag annotation SHOULD read "Release 2.11.4". The second line
|
|
||||||
MUST be blank, and the changelog MUST start on the third line.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Short-Term Release Branches
|
|
||||||
<ol>
|
|
||||||
<li>Any branch that has a name starting with "release-" SHOULD be referred to
|
|
||||||
as a "release branch".</li>
|
|
||||||
<li>Any release branch which has a name ending with a specific version
|
|
||||||
string, MUST be referred to as a "short-term release branch".</li>
|
|
||||||
<li>Use of short-term release branches are OPTIONAL, and intended to be used
|
|
||||||
to create a specific versioned release.</li>
|
|
||||||
<li>A short-term release branch is RECOMMENDED if there is a lengthy
|
|
||||||
pre-release verification process to avoid a code freeze on the master
|
|
||||||
branch.</li>
|
|
||||||
<li>Short-term release branches MUST have a name of "release-VERSION". For
|
|
||||||
example for version "2.11.4" the release branch name MUST be
|
|
||||||
"release-2.11.4".</li>
|
|
||||||
<li>When using a short-term release branch to create a release, the release
|
|
||||||
tag and if used, version bump commit, MUST be placed directly on the
|
|
||||||
short-term release branch itself.</li>
|
|
||||||
<li>Only very minor changes should be performed on a short-term release
|
|
||||||
branch directly. Any larger changes SHOULD be done in the master branch,
|
|
||||||
and SHOULD be pulled into the release branch by rebasing it on top of the
|
|
||||||
master branch the same way a change branch pulls in updates from its
|
|
||||||
source branch.</li>
|
|
||||||
<li>After a release tag has been created, the release branch MUST be merged
|
|
||||||
back into its source branch and then deleted. Typically the source branch
|
|
||||||
will be the master branch.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Long-term Release Branches
|
|
||||||
<ol>
|
|
||||||
<li>Any release branch which has a name ending with a non-specific version
|
|
||||||
string, MUST be referred to as a "long-term release branch". For example
|
|
||||||
"release-2.11" is a long-term release branch, while "release-2.11.4" is a
|
|
||||||
short-term release branch.</li>
|
|
||||||
<li>Use of long-term release branches are OPTIONAL, and intended for work on
|
|
||||||
versions which are not currently part of the master branch. Typically
|
|
||||||
this is useful when you need to create a new maintenance release for a
|
|
||||||
older version.</li>
|
|
||||||
<li>A long-term release branch MUST have a name with a non-specific version
|
|
||||||
number. For example a long-term release branch for creating new 2.9.x
|
|
||||||
releases MUST be named "release-2.9".</li>
|
|
||||||
<li>Long-term release branches for maintenance releases of older versions
|
|
||||||
MUST be created from the relevant release tag. For example if the master
|
|
||||||
branch is on version 2.11.4 and there is a security fix for all 2.9.x
|
|
||||||
releases, the latest of which is "2.9.7". Create a new branch called
|
|
||||||
"release-2.9" off of the "2.9.7" release tag. The security fix release
|
|
||||||
will then end up being version "2.9.8".</li>
|
|
||||||
<li>To create a new release from a long-term release branch, you MUST follow
|
|
||||||
the same process as a release from the master branch, except the
|
|
||||||
long-term release branch takes the place of the master branch.</li>
|
|
||||||
<li>A long-term release branch should be treated with the same respect as the
|
|
||||||
master branch. It is effectively the master branch for the release series
|
|
||||||
in question. Meaning it MUST always be in a non-broken state, MUST NOT be
|
|
||||||
force pushed to, etc.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Bug Fixes & Rollback
|
|
||||||
<ol>
|
|
||||||
<li>You MUST NOT under any circumstances force push to the master branch or
|
|
||||||
to long-term release branches.</li>
|
|
||||||
<li>If a change branch which has been merged into the master branch is found
|
|
||||||
to have a bug in it, the bug fix work MUST be done as a new separate
|
|
||||||
change branch and MUST follow the same workflow as any other change
|
|
||||||
branch.</li>
|
|
||||||
<li>If a change branch is wrongfully merged into master, or for any other
|
|
||||||
reason the merge must be undone, you MUST undo the merge by reverting the
|
|
||||||
merge commit itself. Effectively creating a new commit that reverses all
|
|
||||||
the relevant changes.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Git Best Practices
|
|
||||||
<ol>
|
|
||||||
<li>All commit messages SHOULD follow the Commit Guidelines and format from
|
|
||||||
the official git
|
|
||||||
documentation:
|
|
||||||
<a href="https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project#_commit_guidelines">https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project#_commit_guidelines</a></li>
|
|
||||||
<li>You SHOULD never blindly commit all changes with "git commit -a". It is
|
|
||||||
RECOMMENDED you use "git add -i" or "git add -p" to add individual
|
|
||||||
changes to the staging area so you are fully aware of what you are
|
|
||||||
committing.</li>
|
|
||||||
<li>You SHOULD always use "--force-with-lease" when doing a force push. The
|
|
||||||
regular "--force" option is dangerous and destructive. More
|
|
||||||
information:
|
|
||||||
<a href="https://developer.atlassian.com/blog/2015/04/force-with-lease/">https://developer.atlassian.com/blog/2015/04/force-with-lease/</a></li>
|
|
||||||
<li>You SHOULD understand and be comfortable with
|
|
||||||
rebasing: <a href="https://git-scm.com/book/en/v2/Git-Branching-Rebasing">https://git-scm.com/book/en/v2/Git-Branching-Rebasing</a></li>
|
|
||||||
<li>It is RECOMMENDED that you always do "git pull --rebase" instead of "git
|
|
||||||
pull" to avoid unnecessary merge commits. You can make this the default
|
|
||||||
behavior of "git pull" with "git config --global pull.rebase true".</li>
|
|
||||||
<li>It is RECOMMENDED that all branches be merged using "git merge --no-ff".
|
|
||||||
This makes sure the reference to the original branch is kept in the
|
|
||||||
commits, allows one to revert a merge by reverting a single merge commit,
|
|
||||||
and creates a merge commit to mark the integration of the branch with
|
|
||||||
master.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
<h2 id="faq">FAQ</h2>
|
|
||||||
<h3 id="why-use-common-flow-instead-of-git-flow-and-how-does-it-differ">Why use Common-Flow instead of Git Flow, and how does it differ?</h3>
|
|
||||||
<p>Common-Flow tries to be a lot less complicated than Git Flow by having fewer
|
|
||||||
types of branches, and simpler rules. Normal day to day development doesn't
|
|
||||||
really change much:</p>
|
|
||||||
<ul>
|
|
||||||
<li>You create change branches instead of feature branches, without the need of a
|
|
||||||
"feature/" or "change/" prefix in the branch name.</li>
|
|
||||||
<li>Change branches are typically created off of and merged back into "master"
|
|
||||||
instead of "develop".</li>
|
|
||||||
<li>Creating a release is done by simply creating a git tag, typically on the
|
|
||||||
master branch.</li>
|
|
||||||
</ul>
|
|
||||||
<p>In detail, the main differences between Git Flow and Common-Flow are:</p>
|
|
||||||
<ul>
|
|
||||||
<li>There is no "develop" branch, there is only a "master" branch which contains
|
|
||||||
the latest work. In Git Flow the master branch effectively ends up just being
|
|
||||||
a pointer to the latest release, despite the fact that Git Flow includes
|
|
||||||
release tags too. In Common-Flow you just look at the tags to find the latest
|
|
||||||
release.</li>
|
|
||||||
<li>There are no "feature" or "hotfix" branches, there's only "change"
|
|
||||||
branches. Any branch that is not master and introduces changes is a change
|
|
||||||
branch. Change branches also don't have a enforced naming convention, they
|
|
||||||
just have to have a "descriptive name". This makes things simpler and allows
|
|
||||||
more flexibility.</li>
|
|
||||||
<li>Release branches are available, but optional. Instead of enforcing the use of
|
|
||||||
release branches like Git Flow, Common-Flow only recommends the use of release
|
|
||||||
branches when it makes things easier. If creating a new release by tagging
|
|
||||||
"master" works for you, great, do that.</li>
|
|
||||||
</ul>
|
|
||||||
<h3 id="why-use-common-flow-instead-of-github-flow-and-how-does-it-differ">Why use Common-Flow instead of GitHub Flow, and how does it differ?</h3>
|
|
||||||
<p>Common-Flow is essentially GitHub Flow with the addition of a "Release" concept
|
|
||||||
that uses tags. It also attempts to define how certain common tasks are done,
|
|
||||||
like updating change/feature branches from their source branches for
|
|
||||||
example. This is to help end arguments about how such things are done.</p>
|
|
||||||
<p>If a deployment/release for you is just getting the latest code in the master
|
|
||||||
branch out, without caring about bumping version numbers or anything, then
|
|
||||||
GitHub Flow is a good fit for you, and you probably don't need the extras of
|
|
||||||
Common-Flow.</p>
|
|
||||||
<p>However if your deployments/releases have specific version numbers, then
|
|
||||||
Common-Flow gives you a simple set of rules of how to create and manage
|
|
||||||
releases, on top of what GitHub Flow already does.</p>
|
|
||||||
<h3 id="what-does-descriptive-name-mean-for-change-branches">What does "descriptive name" mean for change branches?</h3>
|
|
||||||
<p>It means what it sounds like. The name should be descriptive, as in by just
|
|
||||||
reading the name of the branch you should understand what the branch's purpose
|
|
||||||
is and what it does. Here's a few examples:</p>
|
|
||||||
<ul>
|
|
||||||
<li>add-2fa-support</li>
|
|
||||||
<li>fix-login-issue</li>
|
|
||||||
<li>remove-sort-by-middle-name-functionality</li>
|
|
||||||
<li>update-font-awesome</li>
|
|
||||||
<li>change-search-behavior</li>
|
|
||||||
<li>tweak-footer-style</li>
|
|
||||||
</ul>
|
|
||||||
<p>Notice how none of these have any prefixes like "feature/" or "hotfix/", they're
|
|
||||||
not needed when branch names are properly descriptive. However there's nothing
|
|
||||||
to say you can't use such prefixes if you want. That also means that you can add
|
|
||||||
ticket number prefixes if your team/org has that as part of it's process.</p>
|
|
||||||
<h3 id="how-do-we-release-an-emergency-hotfix-when-the-master-branch-is-broken">How do we release an emergency hotfix when the master branch is broken?</h3>
|
|
||||||
<p>This should ideally never happen, however if it does you can do one of the
|
|
||||||
following:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Review why the master branch is broken and revert the changes that caused the
|
|
||||||
issues. Then apply the hotfix and release.</li>
|
|
||||||
<li>Or use a short-term release branch created from the latest release tag instead
|
|
||||||
of the master branch. Apply the hotfix to the release branch, create a release
|
|
||||||
tag on the release branch, and then merge it back into master.</li>
|
|
||||||
</ul>
|
|
||||||
<p>In this situation, it is recommended you try to revert the offending changes
|
|
||||||
that's preventing a new release from master. But if that proves to be a
|
|
||||||
complicated task and you're short on time, a short-term release branch gives you
|
|
||||||
a instant fix to the situation at hand, and let's you resolve the issues with
|
|
||||||
the master branch when you have more time on your hands.</p>
|
|
||||||
<h2 id="about">About</h2>
|
|
||||||
<p>The Git Common-Flow specification is authored
|
|
||||||
by <a href="http://jimeh.me">Jim Myhrberg</a>.</p>
|
|
||||||
<p>If you'd like to leave feedback,
|
|
||||||
please <a href="https://github.com/jimeh/common-flow/issues">open an issue on GitHub</a>.</p>
|
|
||||||
<h2 id="license">License</h2>
|
|
||||||
<p><a href="http://creativecommons.org/licenses/by/3.0/">Creative Commons - CC BY 3.0</a></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script type="text/javascript" src="/assets/main-870855580c69dec57be4c965d0cf8afe78afa6b7b6f6bdb5aff91ac0256c0a1a.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Sitemap: https://commonflow.org/sitemap.xml
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd" xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
||||||
<url>
|
|
||||||
<loc>https://commonflow.org/spec/1.0.0-rc.1.html</loc>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://commonflow.org/spec/1.0.0-rc.2.html</loc>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://commonflow.org/spec/1.0.0-rc.3.html</loc>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://commonflow.org/spec/1.0.0-rc.4.html</loc>
|
|
||||||
</url>
|
|
||||||
<url>
|
|
||||||
<loc>https://commonflow.org/</loc>
|
|
||||||
</url>
|
|
||||||
</urlset>
|
|
||||||
@@ -1,248 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link href='https://fonts.googleapis.com/css?family=Open+Sans+Condensed:700,300|Open+Sans:400italic,700italic,400,700' rel='stylesheet' type='text/css'>
|
|
||||||
<link rel="stylesheet" href="https://unpkg.com/purecss@1.0.0/build/pure-min.css" integrity="sha384-nn4HPE8lTHyVtfCBi5yW9d20FjT8BJwUXyWZT9InLYax14RDjBj46LmSztkmNP9w" crossorigin="anonymous">
|
|
||||||
<link rel="stylesheet" href="https:////maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
|
|
||||||
<link type="text/css" rel="stylesheet" href="/assets/main-16db41e8ef2362fe9967bb0bee92e459cf76f2a846e85c96322243077a88c301.css">
|
|
||||||
<!-- Begin Jekyll SEO tag v2.2.3 -->
|
|
||||||
<title>Git Common-Flow 1.0.0-rc.1 | Git Common Flow</title>
|
|
||||||
<meta property="og:title" content="Git Common-Flow 1.0.0-rc.1" />
|
|
||||||
<meta name="author" content="Jim Myhrberg" />
|
|
||||||
<meta property="og:locale" content="en_US" />
|
|
||||||
<meta name="description" content="An attempt to gather a sensible selection of the most common usage patterns of git into a single and concise specification." />
|
|
||||||
<meta property="og:description" content="An attempt to gather a sensible selection of the most common usage patterns of git into a single and concise specification." />
|
|
||||||
<link rel="canonical" href="https://commonflow.org/spec/1.0.0-rc.1.html" />
|
|
||||||
<meta property="og:url" content="https://commonflow.org/spec/1.0.0-rc.1.html" />
|
|
||||||
<meta property="og:site_name" content="Git Common Flow" />
|
|
||||||
<script type="application/ld+json">
|
|
||||||
{"@context":"http://schema.org","@type":"WebPage","headline":"Git Common-Flow 1.0.0-rc.1","author":{"@type":"Person","name":"Jim Myhrberg"},"description":"An attempt to gather a sensible selection of the most common usage patterns of git into a single and concise specification.","url":"https://commonflow.org/spec/1.0.0-rc.1.html"}
|
|
||||||
</script>
|
|
||||||
<!-- End Jekyll SEO tag -->
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="layout">
|
|
||||||
<a href="#menu" id="menuLink" class="menu-link">
|
|
||||||
<span></span>
|
|
||||||
</a>
|
|
||||||
<div id="menu">
|
|
||||||
<div class="pure-menu">
|
|
||||||
<ul class="pure-menu-list">
|
|
||||||
<li class="pure-menu-item">
|
|
||||||
<div class="pure-menu-label">Versions:</div>
|
|
||||||
</li>
|
|
||||||
<li class="pure-menu-item version-1.0.0-rc.4">
|
|
||||||
<a href="/spec/1.0.0-rc.4.html" class="pure-menu-link">1.0.0-rc.4</a>
|
|
||||||
</li>
|
|
||||||
<li class="pure-menu-item version-1.0.0-rc.3">
|
|
||||||
<a href="/spec/1.0.0-rc.3.html" class="pure-menu-link">1.0.0-rc.3</a>
|
|
||||||
</li>
|
|
||||||
<li class="pure-menu-item version-1.0.0-rc.2">
|
|
||||||
<a href="/spec/1.0.0-rc.2.html" class="pure-menu-link">1.0.0-rc.2</a>
|
|
||||||
</li>
|
|
||||||
<li class="pure-menu-item version-1.0.0-rc.1 pure-menu-selected">
|
|
||||||
<a href="/spec/1.0.0-rc.1.html" class="pure-menu-link">1.0.0-rc.1</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="links">
|
|
||||||
<a href="https://github.com/jimeh/common-flow">
|
|
||||||
<i class="fa fa-github" aria-hidden="true"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="main">
|
|
||||||
<div class="content">
|
|
||||||
<h1 id="git-common-flow-100-rc1">Git Common-Flow 1.0.0-rc.1</h1>
|
|
||||||
<p><img src="/spec/1.0.0-rc.1.svg" width="100%" /></p>
|
|
||||||
<h2 id="summary">Summary</h2>
|
|
||||||
<p>Common-Flow is an attempt to gather a sensible selection of the most common
|
|
||||||
usage patterns of git into a single and concise specification. It is based on
|
|
||||||
the <a href="http://scottchacon.com/2011/08/31/github-flow.html">original variant</a>
|
|
||||||
of <a href="https://guides.github.com/introduction/flow/">GitHub Flow</a>, while taking
|
|
||||||
into account how a lot of open source projects use git.</p>
|
|
||||||
<p>TL;DR: Common-Flow is basically GitHub Flow with the addition of versioned
|
|
||||||
releases, maintenance releases for old versions, and without the requirement to
|
|
||||||
deploy to production all the time.</p>
|
|
||||||
<h2 id="terminology">Terminology</h2>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Master Branch</strong> - Must always have passing tests, is considered bleeding
|
|
||||||
edge, and must be named <code class="highlighter-rouge">master</code>.</li>
|
|
||||||
<li><strong>Change Branches</strong> - Any branch that introduces changes like a new feature, a
|
|
||||||
bug fix, etc.</li>
|
|
||||||
<li><strong>Source Branch</strong> - The branch that a change branch was created from. New
|
|
||||||
changes in the source branch should be incorporated into the change branch via
|
|
||||||
rebasing.</li>
|
|
||||||
<li><strong>Merge Target</strong> - A branch that is the intended merge target for a change
|
|
||||||
branch. Typically the merge target branch will be the same as the source
|
|
||||||
branch.</li>
|
|
||||||
<li><strong>Maintenance Branches</strong> - Used for maintaining old versions and releasing
|
|
||||||
PATCH updates when the master branch has moved on. Should follow a
|
|
||||||
<code class="highlighter-rouge">stable-X.Y</code> naming pattern, where <code class="highlighter-rouge">X</code> is MAJOR version and <code class="highlighter-rouge">Y</code> is MINOR
|
|
||||||
version.</li>
|
|
||||||
<li><strong>Pull Request</strong> - A means of requesting that a change branch is merged in to
|
|
||||||
its merge target, allowing others to review, discuss and approve the changes.</li>
|
|
||||||
<li><strong>Release</strong> - Consists of a version bump commit directly on the master branch,
|
|
||||||
and a git tag named according to the new version string placed on said commit.</li>
|
|
||||||
<li><strong>Maintenance Release</strong> - Just like a regular release, except the version bump
|
|
||||||
commit and release tag are on a maintenance branch instead of the master
|
|
||||||
branch.</li>
|
|
||||||
</ul>
|
|
||||||
<h2 id="git-common-flow-specification-common-flow">Git Common-Flow Specification (Common-Flow)</h2>
|
|
||||||
<p>The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD",
|
|
||||||
"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be
|
|
||||||
interpreted as described in <a href="https://tools.ietf.org/html/rfc2119">RFC 2119</a>.</p>
|
|
||||||
<ol>
|
|
||||||
<li>The Master Branch
|
|
||||||
<ol>
|
|
||||||
<li>A branch named "master" MUST exist and it MUST be referred to as the
|
|
||||||
"master branch".</li>
|
|
||||||
<li>The master branch MUST be considered bleeding edge.</li>
|
|
||||||
<li>The master branch MUST always be in a non-broken state with its test
|
|
||||||
suite passing.</li>
|
|
||||||
<li>The master branch SHOULD always be in a "as near as possible ready for
|
|
||||||
release/production" state to reduce the friction of creating a new
|
|
||||||
release.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Changes
|
|
||||||
<ol>
|
|
||||||
<li>Changes MUST be performed on a separate branch that SHOULD be referred to
|
|
||||||
as a "change branch". All change branches MUST have descriptive names. It
|
|
||||||
is RECOMMENDED that you commit often locally, and you SHOULD regularly
|
|
||||||
push your work to the same named branch on the remote server.</li>
|
|
||||||
<li>When a change branch is created, the branch that it is created from
|
|
||||||
SHOULD be referred to as the "source branch". Each change branch also
|
|
||||||
needs a designated "merge target branch", typically this will be the same
|
|
||||||
as the source branch.</li>
|
|
||||||
<li>Change branches MUST be regularly updated with any changes from their
|
|
||||||
source branch. This MUST be done by rebasing the change branch on top of
|
|
||||||
the source branch. To be clear you MUST NOT merge a source branch into a
|
|
||||||
change branch.</li>
|
|
||||||
<li>After rebasing a change branch on top of its source branch you MUST push
|
|
||||||
the change branch to the remote server. This will require you do a force
|
|
||||||
push, and you SHOULD use the "--force-with-lease" git push option.</li>
|
|
||||||
<li>To merge a change branch into its merge target branch, you MUST open a
|
|
||||||
"pull request" (or equivalent) so others can review and approve your
|
|
||||||
changes.</li>
|
|
||||||
<li>A pull request MUST only be merged when the change branch is up-to-date
|
|
||||||
with its source branch, the test suite is passing, and you and others are
|
|
||||||
happy with the change. This is especially important if the merge target
|
|
||||||
is the master branch.</li>
|
|
||||||
<li>To get feedback, help, or generally just discuss a change branch with
|
|
||||||
others, it is RECOMMENDED you do this by creating a pull request and
|
|
||||||
discuss the changes with others there.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Git Best Practices
|
|
||||||
<ol>
|
|
||||||
<li>All commit messages SHOULD follow the Commit Guidelines and format from
|
|
||||||
the official git
|
|
||||||
documentation:
|
|
||||||
<a href="https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project">https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project</a></li>
|
|
||||||
<li>You SHOULD always use "--force-with-lease" when doing a force push. The
|
|
||||||
plain "--force" option is dangerous and destructive. More
|
|
||||||
information:
|
|
||||||
<a href="https://developer.atlassian.com/blog/2015/04/force-with-lease/">https://developer.atlassian.com/blog/2015/04/force-with-lease/</a></li>
|
|
||||||
<li>You SHOULD understand and be comfortable with
|
|
||||||
rebasing: <a href="https://git-scm.com/book/en/v2/Git-Branching-Rebasing">https://git-scm.com/book/en/v2/Git-Branching-Rebasing</a></li>
|
|
||||||
<li>It is RECOMMENDED that you always do "git pull --rebase" instead of "git
|
|
||||||
pull" to avoid unnecessary merge commits. You can make this the default
|
|
||||||
behavior of "git pull" with "git config --global pull.rebase true".</li>
|
|
||||||
<li>It is RECOMMENDED that all branches be merged using "git merge --no-ff".
|
|
||||||
This makes sure the reference to the original branch is kept in the commits,
|
|
||||||
allows one to revert a merge by reverting a single merge commit, and creates
|
|
||||||
a merge commit to mark the integration of the branch with master.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Versioning
|
|
||||||
<ol>
|
|
||||||
<li>The project MUST have its version hard-coded somewhere in the
|
|
||||||
code-base. It is RECOMMENDED that this is done in a file called "VERSION"
|
|
||||||
located in the root of the project.</li>
|
|
||||||
<li>If you are using a "VERSION" file in the root of the project, this MUST
|
|
||||||
only contain the exact version string.</li>
|
|
||||||
<li>The version string SHOULD follow the Semantic Versioning
|
|
||||||
(<a href="http://semver.org/">http://semver.org/</a>) format. Use of Semantic Versioning is OPTIONAL,
|
|
||||||
but the version string MUST NOT have a "v" prefix. For example "v2.11.4"
|
|
||||||
is bad, and "2.11.4" is good.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Releases
|
|
||||||
<ol>
|
|
||||||
<li>To create a new release, you MUST create a "version bump" commit directly
|
|
||||||
on the master branch which changes the hard-coded version value of the
|
|
||||||
project. The version bump commit MUST have a git tag created on it and
|
|
||||||
named as the exact version string.</li>
|
|
||||||
<li>A version bump commit MUST have a commit message title of "Bump version
|
|
||||||
to VERSION". For example, if the new version string is "2.11.4", the
|
|
||||||
first line of the commit message MUST read: "Bump version to 2.11.4"</li>
|
|
||||||
<li>The release tag on the version bump commit MUST be named exactly the same
|
|
||||||
as the version string. The tag name can OPTIONALLY be prefixed with
|
|
||||||
"v". For example the tag name can be either "2.11.4" or "v2.11.4".</li>
|
|
||||||
<li>It is RECOMMENDED that release tags are lightweight tags, but you can
|
|
||||||
OPTIONALLY use annotated tags if you want to include changelog
|
|
||||||
information in the release tag itself.</li>
|
|
||||||
<li>If you use annotated release tags, the first line of the annotation MUST
|
|
||||||
read "Release VERSION". For example for version "2.11.4" the first line
|
|
||||||
of the tag annotation would read "Release 2.11.4". The second line must
|
|
||||||
be blank, and the changelog MUST start on the third line.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Bug Fixes & Rollback
|
|
||||||
<ol>
|
|
||||||
<li>You MUST NOT under any circumstances force push to the master branch.</li>
|
|
||||||
<li>If a change branch which has been merged in to the master branch is found
|
|
||||||
to have a bug in it, the bug fix work MUST be done as a new separate
|
|
||||||
change branch and MUST follow the same workflow as any other change
|
|
||||||
branch.</li>
|
|
||||||
<li>If a change branch is wrongfully merged in to master, or for any other
|
|
||||||
reason the merge must be undone, you MUST undo the merge by reverting the
|
|
||||||
merge commit itself. Effectively creating a new commit that reverses all
|
|
||||||
the relevant changes.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Maintenance Releases
|
|
||||||
<ol>
|
|
||||||
<li>Any branch that has a name starting with "stable-" SHOULD be referred to
|
|
||||||
as a "maintenance branch".</li>
|
|
||||||
<li>Maintenance branches are used for managing new releases of older
|
|
||||||
versions. Typically this is used to provide security updates for older
|
|
||||||
versions when the master branch has moved on to a point that a new
|
|
||||||
release for the old version cannot be made from the master branch.</li>
|
|
||||||
<li>A "maintenance release" is identical to a regular release, except the
|
|
||||||
version bump commit and the release tag are placed on the maintenance
|
|
||||||
branch instead of on the master branch.</li>
|
|
||||||
<li>A maintenance branch SHOULD follow a "stable-X.Y" naming pattern, where
|
|
||||||
"X" is the MAJOR version and "Y" is the minor version.</li>
|
|
||||||
<li>A maintenance branch MUST be created from the relevant release tag. For
|
|
||||||
example if there is a security fix for all 2.9.x releases, the latest of
|
|
||||||
which is "2.9.7", we create a new branch called "stable-2.9" off of the
|
|
||||||
"2.9.7" release tag. The security fix release will then end up being
|
|
||||||
version "2.9.8".</li>
|
|
||||||
<li>When working on a maintenance release, the relevant maintenance branch
|
|
||||||
MUST be thought of as the master branch for that maintenance work.</li>
|
|
||||||
<li>Changes in a maintenance branch SHOULD typically come from work being
|
|
||||||
done against the master branch. Meaning changes SHOULD only trickle
|
|
||||||
downwards from the master branch. If a change needs to trickle back up
|
|
||||||
into the master branch, that work should have happened against the master
|
|
||||||
branch in the first place.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
<h2 id="about">About</h2>
|
|
||||||
<p>The Git Common-Flow specification is authored
|
|
||||||
by <a href="http://jimeh.me">Jim Myhrberg</a>.</p>
|
|
||||||
<p>If you'd like to leave feedback,
|
|
||||||
please <a href="https://github.com/jimeh/common-flow/issues">open an issue on GitHub</a>.</p>
|
|
||||||
<h2 id="license">License</h2>
|
|
||||||
<p><a href="http://creativecommons.org/licenses/by/3.0/">Creative Commons - CC BY 3.0</a></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script type="text/javascript" src="/assets/main-870855580c69dec57be4c965d0cf8afe78afa6b7b6f6bdb5aff91ac0256c0a1a.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
Before Width: | Height: | Size: 18 KiB |
@@ -1,282 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link href='https://fonts.googleapis.com/css?family=Open+Sans+Condensed:700,300|Open+Sans:400italic,700italic,400,700' rel='stylesheet' type='text/css'>
|
|
||||||
<link rel="stylesheet" href="https://unpkg.com/purecss@1.0.0/build/pure-min.css" integrity="sha384-nn4HPE8lTHyVtfCBi5yW9d20FjT8BJwUXyWZT9InLYax14RDjBj46LmSztkmNP9w" crossorigin="anonymous">
|
|
||||||
<link rel="stylesheet" href="https:////maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
|
|
||||||
<link type="text/css" rel="stylesheet" href="/assets/main-16db41e8ef2362fe9967bb0bee92e459cf76f2a846e85c96322243077a88c301.css">
|
|
||||||
<!-- Begin Jekyll SEO tag v2.2.3 -->
|
|
||||||
<title>Git Common-Flow 1.0.0-rc.2 | Git Common Flow</title>
|
|
||||||
<meta property="og:title" content="Git Common-Flow 1.0.0-rc.2" />
|
|
||||||
<meta name="author" content="Jim Myhrberg" />
|
|
||||||
<meta property="og:locale" content="en_US" />
|
|
||||||
<meta name="description" content="An attempt to gather a sensible selection of the most common usage patterns of git into a single and concise specification." />
|
|
||||||
<meta property="og:description" content="An attempt to gather a sensible selection of the most common usage patterns of git into a single and concise specification." />
|
|
||||||
<link rel="canonical" href="https://commonflow.org/spec/1.0.0-rc.2.html" />
|
|
||||||
<meta property="og:url" content="https://commonflow.org/spec/1.0.0-rc.2.html" />
|
|
||||||
<meta property="og:site_name" content="Git Common Flow" />
|
|
||||||
<script type="application/ld+json">
|
|
||||||
{"@context":"http://schema.org","@type":"WebPage","headline":"Git Common-Flow 1.0.0-rc.2","author":{"@type":"Person","name":"Jim Myhrberg"},"description":"An attempt to gather a sensible selection of the most common usage patterns of git into a single and concise specification.","url":"https://commonflow.org/spec/1.0.0-rc.2.html"}
|
|
||||||
</script>
|
|
||||||
<!-- End Jekyll SEO tag -->
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="layout">
|
|
||||||
<a href="#menu" id="menuLink" class="menu-link">
|
|
||||||
<span></span>
|
|
||||||
</a>
|
|
||||||
<div id="menu">
|
|
||||||
<div class="pure-menu">
|
|
||||||
<ul class="pure-menu-list">
|
|
||||||
<li class="pure-menu-item">
|
|
||||||
<div class="pure-menu-label">Versions:</div>
|
|
||||||
</li>
|
|
||||||
<li class="pure-menu-item version-1.0.0-rc.4">
|
|
||||||
<a href="/spec/1.0.0-rc.4.html" class="pure-menu-link">1.0.0-rc.4</a>
|
|
||||||
</li>
|
|
||||||
<li class="pure-menu-item version-1.0.0-rc.3">
|
|
||||||
<a href="/spec/1.0.0-rc.3.html" class="pure-menu-link">1.0.0-rc.3</a>
|
|
||||||
</li>
|
|
||||||
<li class="pure-menu-item version-1.0.0-rc.2 pure-menu-selected">
|
|
||||||
<a href="/spec/1.0.0-rc.2.html" class="pure-menu-link">1.0.0-rc.2</a>
|
|
||||||
</li>
|
|
||||||
<li class="pure-menu-item version-1.0.0-rc.1">
|
|
||||||
<a href="/spec/1.0.0-rc.1.html" class="pure-menu-link">1.0.0-rc.1</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="links">
|
|
||||||
<a href="https://github.com/jimeh/common-flow">
|
|
||||||
<i class="fa fa-github" aria-hidden="true"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="main">
|
|
||||||
<div class="content">
|
|
||||||
<h1 id="git-common-flow-100-rc2">Git Common-Flow 1.0.0-rc.2</h1>
|
|
||||||
<p><img src="/spec/1.0.0-rc.2.svg" width="100%" /></p>
|
|
||||||
<h2 id="summary">Summary</h2>
|
|
||||||
<p>Common-Flow is an attempt to gather a sensible selection of the most common
|
|
||||||
usage patterns of git into a single and concise specification. It is based on
|
|
||||||
the <a href="http://scottchacon.com/2011/08/31/github-flow.html">original variant</a>
|
|
||||||
of <a href="https://guides.github.com/introduction/flow/">GitHub Flow</a>, while taking
|
|
||||||
into account how a lot of open source projects use git.</p>
|
|
||||||
<p>TL;DR: Common-Flow is basically GitHub Flow with the addition of versioned
|
|
||||||
releases, maintenance releases for old versions, and without the requirement to
|
|
||||||
deploy to production all the time.</p>
|
|
||||||
<h2 id="terminology">Terminology</h2>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Master Branch</strong> - Must always have passing tests, is considered bleeding
|
|
||||||
edge, and must be named <code class="highlighter-rouge">master</code>.</li>
|
|
||||||
<li><strong>Change Branches</strong> - Any branch that introduces changes like a new feature, a
|
|
||||||
bug fix, etc.</li>
|
|
||||||
<li><strong>Source Branch</strong> - The branch that a change branch was created from. New
|
|
||||||
changes in the source branch should be incorporated into the change branch via
|
|
||||||
rebasing.</li>
|
|
||||||
<li><strong>Merge Target</strong> - A branch that is the intended merge target for a change
|
|
||||||
branch. Typically the merge target branch will be the same as the source
|
|
||||||
branch.</li>
|
|
||||||
<li><strong>Pull Request</strong> - A means of requesting that a change branch is merged in to
|
|
||||||
its merge target, allowing others to review, discuss and approve the changes.</li>
|
|
||||||
<li><strong>Release</strong> - Consists of a version bump commit, and a git tag named according
|
|
||||||
to the new version string placed on said commit.</li>
|
|
||||||
<li><strong>Release Branches</strong> - Used both for short-term preparations of a release, and
|
|
||||||
also for long-term maintenance of older version.</li>
|
|
||||||
</ul>
|
|
||||||
<h2 id="git-common-flow-specification-common-flow">Git Common-Flow Specification (Common-Flow)</h2>
|
|
||||||
<p>The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD",
|
|
||||||
"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be
|
|
||||||
interpreted as described in <a href="https://tools.ietf.org/html/rfc2119">RFC 2119</a>.</p>
|
|
||||||
<ol>
|
|
||||||
<li>The Master Branch
|
|
||||||
<ol>
|
|
||||||
<li>A branch named "master" MUST exist and it MUST be referred to as the
|
|
||||||
"master branch".</li>
|
|
||||||
<li>The master branch MUST be considered bleeding edge.</li>
|
|
||||||
<li>The master branch MUST always be in a non-broken state with its test
|
|
||||||
suite passing.</li>
|
|
||||||
<li>The master branch SHOULD always be in a "as near as possibly ready for
|
|
||||||
release/production" state to reduce any friction with creating a new
|
|
||||||
release.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Change Branches
|
|
||||||
<ol>
|
|
||||||
<li>Each change (feature, bugfix, etc.) MUST be performed on separate
|
|
||||||
branches that SHOULD be referred to as "change branches". All change
|
|
||||||
branches MUST have descriptive names. It is RECOMMENDED that you commit
|
|
||||||
often locally, and you SHOULD regularly push your work to the same named
|
|
||||||
branch on the remote server.</li>
|
|
||||||
<li>You MUST create separate change branches for each distinctly different
|
|
||||||
change. You MUST NOT include multiple unrelated changes into a single
|
|
||||||
change branch.</li>
|
|
||||||
<li>When a change branch is created, the branch that it is created from
|
|
||||||
SHOULD be referred to as the "source branch". Each change branch also
|
|
||||||
needs a designated "merge target" branch, typically this will be the same
|
|
||||||
as the source branch.</li>
|
|
||||||
<li>Change branches MUST be regularly updated with any changes from their
|
|
||||||
source branch. This MUST be done by rebasing the change branch on top of
|
|
||||||
the source branch.</li>
|
|
||||||
<li>After rebasing a change branch on top of its source branch you MUST push
|
|
||||||
the change branch to the remote server. This will require you to do a
|
|
||||||
force push, and you SHOULD use the "--force-with-lease" git push option.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Pull Requests
|
|
||||||
<ol>
|
|
||||||
<li>To merge a change branch into its merge target, you MUST open a "pull
|
|
||||||
request" (or equivalent) so others can review and approve your changes.</li>
|
|
||||||
<li>A pull request MUST only be merged when the change branch is up-to-date
|
|
||||||
with its source branch, the test suite is passing, and you and others are
|
|
||||||
happy with the change. This is especially important if the merge target
|
|
||||||
is the master branch.</li>
|
|
||||||
<li>To get feedback, help, or generally just discuss a change branch with
|
|
||||||
others, the RECOMMENDED way to do so is by creating a pull request and
|
|
||||||
discuss the changes with others there.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Versioning
|
|
||||||
<ol>
|
|
||||||
<li>The project MUST have its version hard-coded somewhere in the
|
|
||||||
code-base. It is RECOMMENDED that this is done in a file called "VERSION"
|
|
||||||
located in the root of the project.</li>
|
|
||||||
<li>If you are using a "VERSION" file in the root of the project, this MUST
|
|
||||||
only contain the exact version string.</li>
|
|
||||||
<li>The version string SHOULD follow the Semantic Versioning
|
|
||||||
(<a href="http://semver.org/">http://semver.org/</a>) format. Use of Semantic Versioning is OPTIONAL,
|
|
||||||
but the version string MUST NOT have a "v" prefix. For example "v2.11.4"
|
|
||||||
is bad, and "2.11.4" is good.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Releases
|
|
||||||
<ol>
|
|
||||||
<li>To create a new release, you MUST create a "version bump" commit which
|
|
||||||
changes the hard-coded version string of the project. The version bump
|
|
||||||
commit MUST have a git tag created on it and named as the exact version
|
|
||||||
string.</li>
|
|
||||||
<li>If you are not using a release branch, then the version bump commit MUST
|
|
||||||
be created directly on the master branch.</li>
|
|
||||||
<li>The version bump commit MUST have a commit message title of "Bump version
|
|
||||||
to VERSION". For example, if the new version string is "2.11.4", the
|
|
||||||
first line of the commit message MUST read: "Bump version to 2.11.4"</li>
|
|
||||||
<li>The release tag on the version bump commit MUST be named exactly the same
|
|
||||||
as the version string. The tag name can OPTIONALLY be prefixed with
|
|
||||||
"v". For example the tag name can be either "2.11.4" or "v2.11.4". You
|
|
||||||
MUST not use a mix of "v" prefixed and non-prefixed tags. Pick one form
|
|
||||||
and stick to it.</li>
|
|
||||||
<li>It is RECOMMENDED that release tags are lightweight tags, but you can
|
|
||||||
OPTIONALLY use annotated tags if you want to include changelog
|
|
||||||
information in the release tag itself.</li>
|
|
||||||
<li>If you use annotated release tags, the first line of the annotation MUST
|
|
||||||
read "Release VERSION". For example for version "2.11.4" the first line
|
|
||||||
of the tag annotation would read "Release 2.11.4". The second line must
|
|
||||||
be blank, and the changelog MUST start on the third line.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Release Branches
|
|
||||||
<ol>
|
|
||||||
<li>Any branch that has a name starting with "release-" SHOULD be referred to
|
|
||||||
as a "release branch".</li>
|
|
||||||
<li>Use of release branches is OPTIONAL.</li>
|
|
||||||
<li>Changes in a release branch SHOULD typically come from work being
|
|
||||||
done against the master branch. Meaning changes SHOULD only trickle
|
|
||||||
downwards from the master branch. If a change needs to trickle back up
|
|
||||||
into the master branch, that work should have happened against the master
|
|
||||||
branch in the first place. One exception to this is version bump commits.</li>
|
|
||||||
<li>There are two types of release branches; short-term, and long-term.</li>
|
|
||||||
<li>Short-Term Release Branches
|
|
||||||
<ol>
|
|
||||||
<li>Used for creating a specific versioned release.</li>
|
|
||||||
<li>A short-term release branch is RECOMMENDED if there is a lengthy
|
|
||||||
pre-release verification process to avoid a code freeze on the master
|
|
||||||
branch.</li>
|
|
||||||
<li>MUST have a name of "release-VERSION". For example for version
|
|
||||||
"2.11.4" the release branch name MUST be "release-2.11.4".</li>
|
|
||||||
<li>When using a short-term release branch, the version bump commit and
|
|
||||||
release tag MUST be made directly on the release branch itself.</li>
|
|
||||||
<li>Only very minor changes should be performed on a short-term release
|
|
||||||
branch directly. Any larger changes SHOULD be done in the master
|
|
||||||
branch, and SHOULD be pulled into the release branch by rebasing it
|
|
||||||
on top of the master branch the same way a change branch pulls in
|
|
||||||
updates from its source branch.</li>
|
|
||||||
<li>After the version bump commit and release tag have been created, the
|
|
||||||
release branch MUST be merged back into its source branch and then
|
|
||||||
deleted. Typically the source branch will be the master branch.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Long-Term Release Branches
|
|
||||||
<ol>
|
|
||||||
<li>Used for work on versions which are not currently part of the master
|
|
||||||
branch. Typically this is useful when you need to create a new
|
|
||||||
maintenance release for a older version.</li>
|
|
||||||
<li>The branch name MUST have a non-specific version number. For example
|
|
||||||
a long-term release branch for creating new 2.9.x releases would be
|
|
||||||
named "release-2.9".</li>
|
|
||||||
<li>To create a new release from a long-term release branch, you MUST
|
|
||||||
create a version bump commit and release tag directly on the release
|
|
||||||
branch.</li>
|
|
||||||
<li>A long-term release branch MUST be created from the relevant release
|
|
||||||
tag. For example if the master branch is on version 2.11.4 and there
|
|
||||||
is a security fix for all 2.9.x releases, the latest of which is
|
|
||||||
"2.9.7". Create a new branch called "release-2.9" off of the "2.9.7"
|
|
||||||
release tag. The security fix release will then end up being version
|
|
||||||
"2.9.8".</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Bug Fixes & Rollback
|
|
||||||
<ol>
|
|
||||||
<li>You MUST NOT under any circumstances force push to the master branch.</li>
|
|
||||||
<li>If a change branch which has been merged into the master branch is found
|
|
||||||
to have a bug in it, the bug fix work MUST be done as a new separate
|
|
||||||
change branch and MUST follow the same workflow as any other change
|
|
||||||
branch.</li>
|
|
||||||
<li>If a change branch is wrongfully merged into master, or for any other
|
|
||||||
reason the merge must be undone, you MUST undo the merge by reverting the
|
|
||||||
merge commit itself. Effectively creating a new commit that reverses all
|
|
||||||
the relevant changes.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Git Best Practices
|
|
||||||
<ol>
|
|
||||||
<li>All commit messages SHOULD follow the Commit Guidelines and format from
|
|
||||||
the official git
|
|
||||||
documentation:
|
|
||||||
<a href="https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project">https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project</a></li>
|
|
||||||
<li>You SHOULD never blindly commit all changes with "git commit -a". It is
|
|
||||||
RECOMMENDED you use "git add -i" to add individual changes to the staging
|
|
||||||
area so you are fully aware of what you are committing.</li>
|
|
||||||
<li>You SHOULD always use "--force-with-lease" when doing a force push. The
|
|
||||||
regular "--force" option is dangerous and destructive. More
|
|
||||||
information:
|
|
||||||
<a href="https://developer.atlassian.com/blog/2015/04/force-with-lease/">https://developer.atlassian.com/blog/2015/04/force-with-lease/</a></li>
|
|
||||||
<li>You SHOULD understand and be comfortable with
|
|
||||||
rebasing: <a href="https://git-scm.com/book/en/v2/Git-Branching-Rebasing">https://git-scm.com/book/en/v2/Git-Branching-Rebasing</a></li>
|
|
||||||
<li>It is RECOMMENDED that you always do "git pull --rebase" instead of "git
|
|
||||||
pull" to avoid unnecessary merge commits. You can make this the default
|
|
||||||
behavior of "git pull" with "git config --global pull.rebase true".</li>
|
|
||||||
<li>It is RECOMMENDED that all branches be merged using "git merge --no-ff".
|
|
||||||
This makes sure the reference to the original branch is kept in the
|
|
||||||
commits, allows one to revert a merge by reverting a single merge commit,
|
|
||||||
and creates a merge commit to mark the integration of the branch with
|
|
||||||
master.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
<h2 id="about">About</h2>
|
|
||||||
<p>The Git Common-Flow specification is authored
|
|
||||||
by <a href="http://jimeh.me">Jim Myhrberg</a>.</p>
|
|
||||||
<p>If you'd like to leave feedback,
|
|
||||||
please <a href="https://github.com/jimeh/common-flow/issues">open an issue on GitHub</a>.</p>
|
|
||||||
<h2 id="license">License</h2>
|
|
||||||
<p><a href="http://creativecommons.org/licenses/by/3.0/">Creative Commons - CC BY 3.0</a></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script type="text/javascript" src="/assets/main-870855580c69dec57be4c965d0cf8afe78afa6b7b6f6bdb5aff91ac0256c0a1a.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
Before Width: | Height: | Size: 18 KiB |
@@ -1,291 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link href='https://fonts.googleapis.com/css?family=Open+Sans+Condensed:700,300|Open+Sans:400italic,700italic,400,700' rel='stylesheet' type='text/css'>
|
|
||||||
<link rel="stylesheet" href="https://unpkg.com/purecss@1.0.0/build/pure-min.css" integrity="sha384-nn4HPE8lTHyVtfCBi5yW9d20FjT8BJwUXyWZT9InLYax14RDjBj46LmSztkmNP9w" crossorigin="anonymous">
|
|
||||||
<link rel="stylesheet" href="https:////maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
|
|
||||||
<link type="text/css" rel="stylesheet" href="/assets/main-16db41e8ef2362fe9967bb0bee92e459cf76f2a846e85c96322243077a88c301.css">
|
|
||||||
<!-- Begin Jekyll SEO tag v2.2.3 -->
|
|
||||||
<title>Git Common-Flow 1.0.0-rc.3 | Git Common Flow</title>
|
|
||||||
<meta property="og:title" content="Git Common-Flow 1.0.0-rc.3" />
|
|
||||||
<meta name="author" content="Jim Myhrberg" />
|
|
||||||
<meta property="og:locale" content="en_US" />
|
|
||||||
<meta name="description" content="An attempt to gather a sensible selection of the most common usage patterns of git into a single and concise specification." />
|
|
||||||
<meta property="og:description" content="An attempt to gather a sensible selection of the most common usage patterns of git into a single and concise specification." />
|
|
||||||
<link rel="canonical" href="https://commonflow.org/spec/1.0.0-rc.3.html" />
|
|
||||||
<meta property="og:url" content="https://commonflow.org/spec/1.0.0-rc.3.html" />
|
|
||||||
<meta property="og:site_name" content="Git Common Flow" />
|
|
||||||
<script type="application/ld+json">
|
|
||||||
{"@context":"http://schema.org","@type":"WebPage","headline":"Git Common-Flow 1.0.0-rc.3","author":{"@type":"Person","name":"Jim Myhrberg"},"description":"An attempt to gather a sensible selection of the most common usage patterns of git into a single and concise specification.","url":"https://commonflow.org/spec/1.0.0-rc.3.html"}
|
|
||||||
</script>
|
|
||||||
<!-- End Jekyll SEO tag -->
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="layout">
|
|
||||||
<a href="#menu" id="menuLink" class="menu-link">
|
|
||||||
<span></span>
|
|
||||||
</a>
|
|
||||||
<div id="menu">
|
|
||||||
<div class="pure-menu">
|
|
||||||
<ul class="pure-menu-list">
|
|
||||||
<li class="pure-menu-item">
|
|
||||||
<div class="pure-menu-label">Versions:</div>
|
|
||||||
</li>
|
|
||||||
<li class="pure-menu-item version-1.0.0-rc.4">
|
|
||||||
<a href="/spec/1.0.0-rc.4.html" class="pure-menu-link">1.0.0-rc.4</a>
|
|
||||||
</li>
|
|
||||||
<li class="pure-menu-item version-1.0.0-rc.3 pure-menu-selected">
|
|
||||||
<a href="/spec/1.0.0-rc.3.html" class="pure-menu-link">1.0.0-rc.3</a>
|
|
||||||
</li>
|
|
||||||
<li class="pure-menu-item version-1.0.0-rc.2">
|
|
||||||
<a href="/spec/1.0.0-rc.2.html" class="pure-menu-link">1.0.0-rc.2</a>
|
|
||||||
</li>
|
|
||||||
<li class="pure-menu-item version-1.0.0-rc.1">
|
|
||||||
<a href="/spec/1.0.0-rc.1.html" class="pure-menu-link">1.0.0-rc.1</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="links">
|
|
||||||
<a href="https://github.com/jimeh/common-flow">
|
|
||||||
<i class="fa fa-github" aria-hidden="true"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="main">
|
|
||||||
<div class="content">
|
|
||||||
<h1 id="git-common-flow-100-rc3">Git Common-Flow 1.0.0-rc.3</h1>
|
|
||||||
<p><img src="/spec/1.0.0-rc.3.svg" width="100%" /></p>
|
|
||||||
<h2 id="summary">Summary</h2>
|
|
||||||
<p>Common-Flow is an attempt to gather a sensible selection of the most common
|
|
||||||
usage patterns of git into a single and concise specification. It is based on
|
|
||||||
the <a href="http://scottchacon.com/2011/08/31/github-flow.html">original variant</a>
|
|
||||||
of <a href="https://guides.github.com/introduction/flow/">GitHub Flow</a>, while taking
|
|
||||||
into account how a lot of open source projects use git.</p>
|
|
||||||
<p>In short, Common-Flow is essentially GitHub Flow with the addition of versioned
|
|
||||||
releases, optional release branches, and without the requirement to deploy to
|
|
||||||
production all the time.</p>
|
|
||||||
<h2 id="terminology">Terminology</h2>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Master Branch</strong> - Must be named "master", must always have passing tests,
|
|
||||||
and is not guaranteed to always work in production environments.</li>
|
|
||||||
<li><strong>Change Branches</strong> - Any branch that introduces changes like a new feature, a
|
|
||||||
bug fix, etc.</li>
|
|
||||||
<li><strong>Source Branch</strong> - The branch that a change branch was created from. New
|
|
||||||
changes in the source branch should be incorporated into the change branch via
|
|
||||||
rebasing.</li>
|
|
||||||
<li><strong>Merge Target</strong> - A branch that is the intended merge target for a change
|
|
||||||
branch. Typically the merge target branch will be the same as the source
|
|
||||||
branch.</li>
|
|
||||||
<li><strong>Pull Request</strong> - A means of requesting that a change branch is merged in to
|
|
||||||
its merge target, allowing others to review, discuss and approve the changes.</li>
|
|
||||||
<li><strong>Release</strong> - May be considered safe to use in production
|
|
||||||
environments. Consists of a version bump commit, and a git tag named according
|
|
||||||
to the new version string placed on said commit.</li>
|
|
||||||
<li><strong>Release Branches</strong> - Used both for short-term preparations of a release, and
|
|
||||||
also for long-term maintenance of older version.</li>
|
|
||||||
</ul>
|
|
||||||
<h2 id="git-common-flow-specification-common-flow">Git Common-Flow Specification (Common-Flow)</h2>
|
|
||||||
<p>The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD",
|
|
||||||
"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be
|
|
||||||
interpreted as described in <a href="https://tools.ietf.org/html/rfc2119">RFC 2119</a>.</p>
|
|
||||||
<ol>
|
|
||||||
<li>TL;DR
|
|
||||||
<ol>
|
|
||||||
<li>Don't break the master branch.</li>
|
|
||||||
<li>A release is a git tag.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>The Master Branch
|
|
||||||
<ol>
|
|
||||||
<li>A branch named "master" MUST exist and it MUST be referred to as the
|
|
||||||
"master branch".</li>
|
|
||||||
<li>The master branch MUST always be in a non-broken state with its test
|
|
||||||
suite passing.</li>
|
|
||||||
<li>The master branch IS NOT guaranteed to always work in production
|
|
||||||
environments. Despite test suites passing it may at times contain
|
|
||||||
unfinished work. Only releases may be considered safe for production use.</li>
|
|
||||||
<li>The master branch SHOULD always be in a "as near as possibly ready for
|
|
||||||
release/production" state to reduce any friction with creating a new
|
|
||||||
release.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Change Branches
|
|
||||||
<ol>
|
|
||||||
<li>Each change (feature, bugfix, etc.) MUST be performed on separate
|
|
||||||
branches that SHOULD be referred to as "change branches". All change
|
|
||||||
branches MUST have descriptive names. It is RECOMMENDED that you commit
|
|
||||||
often locally, and you SHOULD regularly push your work to the same named
|
|
||||||
branch on the remote server.</li>
|
|
||||||
<li>You MUST create separate change branches for each distinctly different
|
|
||||||
change. You MUST NOT include multiple unrelated changes into a single
|
|
||||||
change branch.</li>
|
|
||||||
<li>When a change branch is created, the branch that it is created from
|
|
||||||
SHOULD be referred to as the "source branch". Each change branch also
|
|
||||||
needs a designated "merge target" branch, typically this will be the same
|
|
||||||
as the source branch.</li>
|
|
||||||
<li>Change branches MUST be regularly updated with any changes from their
|
|
||||||
source branch. This MUST be done by rebasing the change branch on top of
|
|
||||||
the source branch.</li>
|
|
||||||
<li>After rebasing a change branch on top of its source branch you MUST push
|
|
||||||
the change branch to the remote server. This will require you to do a
|
|
||||||
force push, and you SHOULD use the "--force-with-lease" git push option.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Pull Requests
|
|
||||||
<ol>
|
|
||||||
<li>To merge a change branch into its merge target, you MUST open a "pull
|
|
||||||
request" (or equivalent) so others can review and approve your changes.</li>
|
|
||||||
<li>A pull request MUST only be merged when the change branch is up-to-date
|
|
||||||
with its source branch, the test suite is passing, and you and others are
|
|
||||||
happy with the change. This is especially important if the merge target
|
|
||||||
is the master branch.</li>
|
|
||||||
<li>To get feedback, help, or generally just discuss a change branch with
|
|
||||||
others, the RECOMMENDED way to do so is by creating a pull request and
|
|
||||||
discuss the changes with others there.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Versioning
|
|
||||||
<ol>
|
|
||||||
<li>The project MUST have its version hard-coded somewhere in the
|
|
||||||
code-base. It is RECOMMENDED that this is done in a file called "VERSION"
|
|
||||||
located in the root of the project.</li>
|
|
||||||
<li>If you are using a "VERSION" file in the root of the project, this MUST
|
|
||||||
only contain the exact version string.</li>
|
|
||||||
<li>The version string SHOULD follow the Semantic Versioning
|
|
||||||
(<a href="http://semver.org/">http://semver.org/</a>) format. Use of Semantic Versioning is OPTIONAL,
|
|
||||||
but the version string MUST NOT have a "v" prefix. For example "v2.11.4"
|
|
||||||
is bad, and "2.11.4" is good.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Releases
|
|
||||||
<ol>
|
|
||||||
<li>To create a new release, you MUST create a "version bump" commit which
|
|
||||||
changes the hard-coded version string of the project. The version bump
|
|
||||||
commit MUST have a git tag created on it and named as the exact version
|
|
||||||
string.</li>
|
|
||||||
<li>If you are not using a release branch, then the version bump commit MUST
|
|
||||||
be created directly on the master branch.</li>
|
|
||||||
<li>The version bump commit MUST have a commit message title of "Bump version
|
|
||||||
to VERSION". For example, if the new version string is "2.11.4", the
|
|
||||||
first line of the commit message MUST read: "Bump version to 2.11.4"</li>
|
|
||||||
<li>The release tag on the version bump commit MUST be named exactly the same
|
|
||||||
as the version string. The tag name can OPTIONALLY be prefixed with
|
|
||||||
"v". For example the tag name can be either "2.11.4" or "v2.11.4". You
|
|
||||||
MUST not use a mix of "v" prefixed and non-prefixed tags. Pick one form
|
|
||||||
and stick to it.</li>
|
|
||||||
<li>It is RECOMMENDED that release tags are lightweight tags, but you can
|
|
||||||
OPTIONALLY use annotated tags if you want to include changelog
|
|
||||||
information in the release tag itself.</li>
|
|
||||||
<li>If you use annotated release tags, the first line of the annotation MUST
|
|
||||||
read "Release VERSION". For example for version "2.11.4" the first line
|
|
||||||
of the tag annotation would read "Release 2.11.4". The second line must
|
|
||||||
be blank, and the changelog MUST start on the third line.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Release Branches
|
|
||||||
<ol>
|
|
||||||
<li>Any branch that has a name starting with "release-" SHOULD be referred to
|
|
||||||
as a "release branch".</li>
|
|
||||||
<li>Use of release branches is OPTIONAL.</li>
|
|
||||||
<li>Changes in a release branch SHOULD typically come from work being
|
|
||||||
done against the master branch. Meaning changes SHOULD only trickle
|
|
||||||
downwards from the master branch. If a change needs to trickle back up
|
|
||||||
into the master branch, that work should have happened against the master
|
|
||||||
branch in the first place. One exception to this is version bump commits.</li>
|
|
||||||
<li>There are two types of release branches; short-term, and long-term.</li>
|
|
||||||
<li>Short-Term Release Branches
|
|
||||||
<ol>
|
|
||||||
<li>Used for creating a specific versioned release.</li>
|
|
||||||
<li>A short-term release branch is RECOMMENDED if there is a lengthy
|
|
||||||
pre-release verification process to avoid a code freeze on the master
|
|
||||||
branch.</li>
|
|
||||||
<li>MUST have a name of "release-VERSION". For example for version
|
|
||||||
"2.11.4" the release branch name MUST be "release-2.11.4".</li>
|
|
||||||
<li>When using a short-term release branch, the version bump commit and
|
|
||||||
release tag MUST be made directly on the release branch itself.</li>
|
|
||||||
<li>Only very minor changes should be performed on a short-term release
|
|
||||||
branch directly. Any larger changes SHOULD be done in the master
|
|
||||||
branch, and SHOULD be pulled into the release branch by rebasing it
|
|
||||||
on top of the master branch the same way a change branch pulls in
|
|
||||||
updates from its source branch.</li>
|
|
||||||
<li>After the version bump commit and release tag have been created, the
|
|
||||||
release branch MUST be merged back into its source branch and then
|
|
||||||
deleted. Typically the source branch will be the master branch.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Long-Term Release Branches
|
|
||||||
<ol>
|
|
||||||
<li>Used for work on versions which are not currently part of the master
|
|
||||||
branch. Typically this is useful when you need to create a new
|
|
||||||
maintenance release for a older version.</li>
|
|
||||||
<li>The branch name MUST have a non-specific version number. For example
|
|
||||||
a long-term release branch for creating new 2.9.x releases would be
|
|
||||||
named "release-2.9".</li>
|
|
||||||
<li>To create a new release from a long-term release branch, you MUST
|
|
||||||
create a version bump commit and release tag directly on the release
|
|
||||||
branch.</li>
|
|
||||||
<li>A long-term release branch MUST be created from the relevant release
|
|
||||||
tag. For example if the master branch is on version 2.11.4 and there
|
|
||||||
is a security fix for all 2.9.x releases, the latest of which is
|
|
||||||
"2.9.7". Create a new branch called "release-2.9" off of the "2.9.7"
|
|
||||||
release tag. The security fix release will then end up being version
|
|
||||||
"2.9.8".</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Bug Fixes & Rollback
|
|
||||||
<ol>
|
|
||||||
<li>You MUST NOT under any circumstances force push to the master branch.</li>
|
|
||||||
<li>If a change branch which has been merged into the master branch is found
|
|
||||||
to have a bug in it, the bug fix work MUST be done as a new separate
|
|
||||||
change branch and MUST follow the same workflow as any other change
|
|
||||||
branch.</li>
|
|
||||||
<li>If a change branch is wrongfully merged into master, or for any other
|
|
||||||
reason the merge must be undone, you MUST undo the merge by reverting the
|
|
||||||
merge commit itself. Effectively creating a new commit that reverses all
|
|
||||||
the relevant changes.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Git Best Practices
|
|
||||||
<ol>
|
|
||||||
<li>All commit messages SHOULD follow the Commit Guidelines and format from
|
|
||||||
the official git
|
|
||||||
documentation:
|
|
||||||
<a href="https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project#_commit_guidelines">https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project#_commit_guidelines</a></li>
|
|
||||||
<li>You SHOULD never blindly commit all changes with "git commit -a". It is
|
|
||||||
RECOMMENDED you use "git add -i" to add individual changes to the staging
|
|
||||||
area so you are fully aware of what you are committing.</li>
|
|
||||||
<li>You SHOULD always use "--force-with-lease" when doing a force push. The
|
|
||||||
regular "--force" option is dangerous and destructive. More
|
|
||||||
information:
|
|
||||||
<a href="https://developer.atlassian.com/blog/2015/04/force-with-lease/">https://developer.atlassian.com/blog/2015/04/force-with-lease/</a></li>
|
|
||||||
<li>You SHOULD understand and be comfortable with
|
|
||||||
rebasing: <a href="https://git-scm.com/book/en/v2/Git-Branching-Rebasing">https://git-scm.com/book/en/v2/Git-Branching-Rebasing</a></li>
|
|
||||||
<li>It is RECOMMENDED that you always do "git pull --rebase" instead of "git
|
|
||||||
pull" to avoid unnecessary merge commits. You can make this the default
|
|
||||||
behavior of "git pull" with "git config --global pull.rebase true".</li>
|
|
||||||
<li>It is RECOMMENDED that all branches be merged using "git merge --no-ff".
|
|
||||||
This makes sure the reference to the original branch is kept in the
|
|
||||||
commits, allows one to revert a merge by reverting a single merge commit,
|
|
||||||
and creates a merge commit to mark the integration of the branch with
|
|
||||||
master.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
<h2 id="about">About</h2>
|
|
||||||
<p>The Git Common-Flow specification is authored
|
|
||||||
by <a href="http://jimeh.me">Jim Myhrberg</a>.</p>
|
|
||||||
<p>If you'd like to leave feedback,
|
|
||||||
please <a href="https://github.com/jimeh/common-flow/issues">open an issue on GitHub</a>.</p>
|
|
||||||
<h2 id="license">License</h2>
|
|
||||||
<p><a href="http://creativecommons.org/licenses/by/3.0/">Creative Commons - CC BY 3.0</a></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script type="text/javascript" src="/assets/main-870855580c69dec57be4c965d0cf8afe78afa6b7b6f6bdb5aff91ac0256c0a1a.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
Before Width: | Height: | Size: 18 KiB |
@@ -1,403 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
||||||
<link href='https://fonts.googleapis.com/css?family=Open+Sans+Condensed:700,300|Open+Sans:400italic,700italic,400,700' rel='stylesheet' type='text/css'>
|
|
||||||
<link rel="stylesheet" href="https://unpkg.com/purecss@1.0.0/build/pure-min.css" integrity="sha384-nn4HPE8lTHyVtfCBi5yW9d20FjT8BJwUXyWZT9InLYax14RDjBj46LmSztkmNP9w" crossorigin="anonymous">
|
|
||||||
<link rel="stylesheet" href="https:////maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
|
|
||||||
<link type="text/css" rel="stylesheet" href="/assets/main-16db41e8ef2362fe9967bb0bee92e459cf76f2a846e85c96322243077a88c301.css">
|
|
||||||
<!-- Begin Jekyll SEO tag v2.2.3 -->
|
|
||||||
<title>Git Common-Flow 1.0.0-rc.4 | Git Common Flow</title>
|
|
||||||
<meta property="og:title" content="Git Common-Flow 1.0.0-rc.4" />
|
|
||||||
<meta name="author" content="Jim Myhrberg" />
|
|
||||||
<meta property="og:locale" content="en_US" />
|
|
||||||
<meta name="description" content="An attempt to gather a sensible selection of the most common usage patterns of git into a single and concise specification." />
|
|
||||||
<meta property="og:description" content="An attempt to gather a sensible selection of the most common usage patterns of git into a single and concise specification." />
|
|
||||||
<link rel="canonical" href="https://commonflow.org/spec/1.0.0-rc.4.html" />
|
|
||||||
<meta property="og:url" content="https://commonflow.org/spec/1.0.0-rc.4.html" />
|
|
||||||
<meta property="og:site_name" content="Git Common Flow" />
|
|
||||||
<script type="application/ld+json">
|
|
||||||
{"@context":"http://schema.org","@type":"WebPage","headline":"Git Common-Flow 1.0.0-rc.4","author":{"@type":"Person","name":"Jim Myhrberg"},"description":"An attempt to gather a sensible selection of the most common usage patterns of git into a single and concise specification.","url":"https://commonflow.org/spec/1.0.0-rc.4.html"}
|
|
||||||
</script>
|
|
||||||
<!-- End Jekyll SEO tag -->
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="layout">
|
|
||||||
<a href="#menu" id="menuLink" class="menu-link">
|
|
||||||
<span></span>
|
|
||||||
</a>
|
|
||||||
<div id="menu">
|
|
||||||
<div class="pure-menu">
|
|
||||||
<ul class="pure-menu-list">
|
|
||||||
<li class="pure-menu-item">
|
|
||||||
<div class="pure-menu-label">Versions:</div>
|
|
||||||
</li>
|
|
||||||
<li class="pure-menu-item version-1.0.0-rc.4 pure-menu-selected">
|
|
||||||
<a href="/spec/1.0.0-rc.4.html" class="pure-menu-link">1.0.0-rc.4</a>
|
|
||||||
</li>
|
|
||||||
<li class="pure-menu-item version-1.0.0-rc.3">
|
|
||||||
<a href="/spec/1.0.0-rc.3.html" class="pure-menu-link">1.0.0-rc.3</a>
|
|
||||||
</li>
|
|
||||||
<li class="pure-menu-item version-1.0.0-rc.2">
|
|
||||||
<a href="/spec/1.0.0-rc.2.html" class="pure-menu-link">1.0.0-rc.2</a>
|
|
||||||
</li>
|
|
||||||
<li class="pure-menu-item version-1.0.0-rc.1">
|
|
||||||
<a href="/spec/1.0.0-rc.1.html" class="pure-menu-link">1.0.0-rc.1</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="links">
|
|
||||||
<a href="https://github.com/jimeh/common-flow">
|
|
||||||
<i class="fa fa-github" aria-hidden="true"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="main">
|
|
||||||
<div class="content">
|
|
||||||
<h1 id="git-common-flow-100-rc4">Git Common-Flow 1.0.0-rc.4</h1>
|
|
||||||
<p><img src="/spec/1.0.0-rc.4.svg" width="100%" /></p>
|
|
||||||
<h2 id="summary">Summary</h2>
|
|
||||||
<p>Common-Flow is an attempt to gather a sensible selection of the most common
|
|
||||||
usage patterns of git into a single and concise specification. It is based on
|
|
||||||
the <a href="http://scottchacon.com/2011/08/31/github-flow.html">original variant</a>
|
|
||||||
of <a href="https://guides.github.com/introduction/flow/">GitHub Flow</a>, while taking
|
|
||||||
into account how a lot of open source projects use git.</p>
|
|
||||||
<p>In short, Common-Flow is essentially GitHub Flow with the addition of versioned
|
|
||||||
releases, optional release branches, and without the requirement to deploy to
|
|
||||||
production all the time.</p>
|
|
||||||
<h2 id="terminology">Terminology</h2>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Master Branch</strong> - Must be named "master", must always have passing tests,
|
|
||||||
and is not guaranteed to always work in production environments.</li>
|
|
||||||
<li><strong>Change Branches</strong> - Any branch that introduces changes like a new feature, a
|
|
||||||
bug fix, etc.</li>
|
|
||||||
<li><strong>Source Branch</strong> - The branch that a change branch was created from. New
|
|
||||||
changes in the source branch should be incorporated into the change branch via
|
|
||||||
rebasing.</li>
|
|
||||||
<li><strong>Merge Target</strong> - A branch that is the intended merge target for a change
|
|
||||||
branch. Typically the merge target branch will be the same as the source
|
|
||||||
branch.</li>
|
|
||||||
<li><strong>Pull Request</strong> - A means of requesting that a change branch is merged in to
|
|
||||||
its merge target, allowing others to review, discuss and approve the changes.</li>
|
|
||||||
<li><strong>Release</strong> - May be considered safe to use in production environments. Is
|
|
||||||
effectively just a git tag named after the version of the release.</li>
|
|
||||||
<li><strong>Release Branches</strong> - Used both for short-term preparations of a release, and
|
|
||||||
also for long-term maintenance of older version.</li>
|
|
||||||
</ul>
|
|
||||||
<h2 id="git-common-flow-specification-common-flow">Git Common-Flow Specification (Common-Flow)</h2>
|
|
||||||
<p>The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD",
|
|
||||||
"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be
|
|
||||||
interpreted as described in <a href="https://tools.ietf.org/html/rfc2119">RFC 2119</a>.</p>
|
|
||||||
<ol>
|
|
||||||
<li>TL;DR
|
|
||||||
<ol>
|
|
||||||
<li>Don't break the master branch.</li>
|
|
||||||
<li>A release is a git tag.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>The Master Branch
|
|
||||||
<ol>
|
|
||||||
<li>A branch named "master" MUST exist and it MUST be referred to as the
|
|
||||||
"master branch".</li>
|
|
||||||
<li>The master branch MUST always be in a non-broken state with its test
|
|
||||||
suite passing.</li>
|
|
||||||
<li>The master branch IS NOT guaranteed to always work in production
|
|
||||||
environments. Despite test suites passing it may at times contain
|
|
||||||
unfinished work. Only releases may be considered safe for production use.</li>
|
|
||||||
<li>The master branch SHOULD always be in a "as near as possibly ready for
|
|
||||||
release/production" state to reduce any friction with creating a new
|
|
||||||
release.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Change Branches
|
|
||||||
<ol>
|
|
||||||
<li>Each change (feature, bugfix, etc.) MUST be performed on separate
|
|
||||||
branches that SHOULD be referred to as "change branches".</li>
|
|
||||||
<li>All change branches MUST have descriptive names.</li>
|
|
||||||
<li>It is RECOMMENDED that you commit often locally, and that you try and
|
|
||||||
keep the commits reasonably structured to avoid a messy and confusing git
|
|
||||||
history.</li>
|
|
||||||
<li>You SHOULD regularly push your work to the same named branch on the
|
|
||||||
remote server.</li>
|
|
||||||
<li>You SHOULD create separate change branches for each distinctly different
|
|
||||||
change. You SHOULD NOT include multiple unrelated changes into a single
|
|
||||||
change branch.</li>
|
|
||||||
<li>When a change branch is created, the branch that it is created from
|
|
||||||
SHOULD be referred to as the "source branch". Each change branch also
|
|
||||||
needs a designated "merge target" branch, typically this will be the same
|
|
||||||
as the source branch.</li>
|
|
||||||
<li>Change branches MUST be regularly updated with any changes from their
|
|
||||||
source branch. This MUST be done by rebasing the change branch on top of
|
|
||||||
the source branch.</li>
|
|
||||||
<li>After updating a change branch from its source branch you MUST push the
|
|
||||||
change branch to the remote server. Due to the nature of rebasing, you
|
|
||||||
will be required to do a force push, and you MUST use the
|
|
||||||
"--force-with-lease" git push option when doing so instead of the regular
|
|
||||||
"--force".</li>
|
|
||||||
<li>If there is a truly valid technical reason to not use rebase when
|
|
||||||
updating change branches, then you can update change branches via merge
|
|
||||||
instead of rebase. The decision to use merge MUST only be taken after all
|
|
||||||
possible options to use rebase have been tried and failed. People not
|
|
||||||
understanding how to use rebase is NOT a valid reason to use merge. If
|
|
||||||
you do decide to use merge instead of rebase, you MUST NOT use a mixture
|
|
||||||
of both methods, pick one and stick to it.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Pull Requests
|
|
||||||
<ol>
|
|
||||||
<li>To merge a change branch into its merge target, you MUST open a "pull
|
|
||||||
request" (or equivalent).</li>
|
|
||||||
<li>The purpose of a pull request is to allow others to review your changes
|
|
||||||
and give feedback. You can then fix any issues, complaints, and more that
|
|
||||||
might arise, and then let people review again.</li>
|
|
||||||
<li>Before creating a pull request, it is RECOMMENDED that you consider the
|
|
||||||
state of your change branch's commit history. If it is messy and
|
|
||||||
confusing, it might be a good idea to rebase your branch with "git rebase
|
|
||||||
-i" to present a cleaner and easier to follow commit history for your
|
|
||||||
reviewers.</li>
|
|
||||||
<li>A pull request MUST only be merged when the change branch is up-to-date
|
|
||||||
with its source branch, the test suite is passing, and you and others are
|
|
||||||
happy with the change. This is especially important if the merge target
|
|
||||||
is the master branch.</li>
|
|
||||||
<li>To get feedback, help, or generally just discuss a change branch with
|
|
||||||
others, the RECOMMENDED way to do so is by creating a pull request and
|
|
||||||
discuss the changes with others there.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Versioning
|
|
||||||
<ol>
|
|
||||||
<li>A "version string" is a typically mostly numeric string that identifies a
|
|
||||||
specific version of a project. The version string itself MUST NOT have a
|
|
||||||
"v" prefix, but the version string can be displayed with a "v" prefix to
|
|
||||||
indicate it is a version that is being referred to.</li>
|
|
||||||
<li>The source of truth for a project's version MUST be a git tag with a name
|
|
||||||
based on the version string. This kind of tag MUST be referred to as a
|
|
||||||
"release tag".</li>
|
|
||||||
<li>It is OPTIONAL, but RECOMMENDED to also keep the version string
|
|
||||||
hard-coded somewhere in the project code-base.</li>
|
|
||||||
<li>If you hard-code the version string into the code-base, it is RECOMMENDED
|
|
||||||
that you do so in a file called "VERSION" located in the root of the
|
|
||||||
project. But be mindful of the conventions of your programming language
|
|
||||||
and community when choosing if, where and how to hard-code the version
|
|
||||||
string.</li>
|
|
||||||
<li>If you are using a "VERSION" file in the root of the project, this file
|
|
||||||
MUST only contain the exact version string, meaning it MUST NOT have a
|
|
||||||
"v" prefix. For example "v2.11.4" is bad, and "2.11.4" is good.</li>
|
|
||||||
<li>It is OPTIONAL, but RECOMMENDED that that the version string follows
|
|
||||||
Semantic Versioning (<a href="http://semver.org/">http://semver.org/</a>).</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Releases
|
|
||||||
<ol>
|
|
||||||
<li>To create a new release, you MUST create a git tag named as the exact
|
|
||||||
version string of the release. This kind of tag MUST be referred to as a
|
|
||||||
"release tag".</li>
|
|
||||||
<li>The release tag name can OPTIONALLY be prefixed with "v". For example the
|
|
||||||
tag name can be either "2.11.4" or "v2.11.4". It is however RECOMMENDED
|
|
||||||
that you do not use a "v" prefix. You MUST NOT use a mixture of "v"
|
|
||||||
prefixed and non-prefixed tags. Pick one form and stick to it.</li>
|
|
||||||
<li>If the version string is hard-coded into the code-base, you MUST create a
|
|
||||||
"version bump" commit which changes the hard-coded version string of the
|
|
||||||
project.</li>
|
|
||||||
<li>When using version bump commits, the release tag MUST be placed on the
|
|
||||||
version bump commit.</li>
|
|
||||||
<li>If you are not using a release branch, then the release tag, and if
|
|
||||||
relevant the version bump commit, MUST be created directly on the master
|
|
||||||
branch.</li>
|
|
||||||
<li>The version bump commit SHOULD have a commit message title of "Bump
|
|
||||||
version to VERSION". For example, if the new version string is "2.11.4",
|
|
||||||
the first line of the commit message SHOULD read: "Bump version to
|
|
||||||
2.11.4"</li>
|
|
||||||
<li>It is RECOMMENDED that release tags are lightweight tags, but you can
|
|
||||||
OPTIONALLY use annotated tags if you want to include changelog
|
|
||||||
information in the release tag itself.</li>
|
|
||||||
<li>If you use annotated release tags, the first line of the annotation
|
|
||||||
SHOULD read "Release VERSION". For example for version "2.11.4" the first
|
|
||||||
line of the tag annotation SHOULD read "Release 2.11.4". The second line
|
|
||||||
MUST be blank, and the changelog MUST start on the third line.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Short-Term Release Branches
|
|
||||||
<ol>
|
|
||||||
<li>Any branch that has a name starting with "release-" SHOULD be referred to
|
|
||||||
as a "release branch".</li>
|
|
||||||
<li>Any release branch which has a name ending with a specific version
|
|
||||||
string, MUST be referred to as a "short-term release branch".</li>
|
|
||||||
<li>Use of short-term release branches are OPTIONAL, and intended to be used
|
|
||||||
to create a specific versioned release.</li>
|
|
||||||
<li>A short-term release branch is RECOMMENDED if there is a lengthy
|
|
||||||
pre-release verification process to avoid a code freeze on the master
|
|
||||||
branch.</li>
|
|
||||||
<li>Short-term release branches MUST have a name of "release-VERSION". For
|
|
||||||
example for version "2.11.4" the release branch name MUST be
|
|
||||||
"release-2.11.4".</li>
|
|
||||||
<li>When using a short-term release branch to create a release, the release
|
|
||||||
tag and if used, version bump commit, MUST be placed directly on the
|
|
||||||
short-term release branch itself.</li>
|
|
||||||
<li>Only very minor changes should be performed on a short-term release
|
|
||||||
branch directly. Any larger changes SHOULD be done in the master branch,
|
|
||||||
and SHOULD be pulled into the release branch by rebasing it on top of the
|
|
||||||
master branch the same way a change branch pulls in updates from its
|
|
||||||
source branch.</li>
|
|
||||||
<li>After a release tag has been created, the release branch MUST be merged
|
|
||||||
back into its source branch and then deleted. Typically the source branch
|
|
||||||
will be the master branch.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Long-term Release Branches
|
|
||||||
<ol>
|
|
||||||
<li>Any release branch which has a name ending with a non-specific version
|
|
||||||
string, MUST be referred to as a "long-term release branch". For example
|
|
||||||
"release-2.11" is a long-term release branch, while "release-2.11.4" is a
|
|
||||||
short-term release branch.</li>
|
|
||||||
<li>Use of long-term release branches are OPTIONAL, and intended for work on
|
|
||||||
versions which are not currently part of the master branch. Typically
|
|
||||||
this is useful when you need to create a new maintenance release for a
|
|
||||||
older version.</li>
|
|
||||||
<li>A long-term release branch MUST have a name with a non-specific version
|
|
||||||
number. For example a long-term release branch for creating new 2.9.x
|
|
||||||
releases MUST be named "release-2.9".</li>
|
|
||||||
<li>Long-term release branches for maintenance releases of older versions
|
|
||||||
MUST be created from the relevant release tag. For example if the master
|
|
||||||
branch is on version 2.11.4 and there is a security fix for all 2.9.x
|
|
||||||
releases, the latest of which is "2.9.7". Create a new branch called
|
|
||||||
"release-2.9" off of the "2.9.7" release tag. The security fix release
|
|
||||||
will then end up being version "2.9.8".</li>
|
|
||||||
<li>To create a new release from a long-term release branch, you MUST follow
|
|
||||||
the same process as a release from the master branch, except the
|
|
||||||
long-term release branch takes the place of the master branch.</li>
|
|
||||||
<li>A long-term release branch should be treated with the same respect as the
|
|
||||||
master branch. It is effectively the master branch for the release series
|
|
||||||
in question. Meaning it MUST always be in a non-broken state, MUST NOT be
|
|
||||||
force pushed to, etc.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Bug Fixes & Rollback
|
|
||||||
<ol>
|
|
||||||
<li>You MUST NOT under any circumstances force push to the master branch or
|
|
||||||
to long-term release branches.</li>
|
|
||||||
<li>If a change branch which has been merged into the master branch is found
|
|
||||||
to have a bug in it, the bug fix work MUST be done as a new separate
|
|
||||||
change branch and MUST follow the same workflow as any other change
|
|
||||||
branch.</li>
|
|
||||||
<li>If a change branch is wrongfully merged into master, or for any other
|
|
||||||
reason the merge must be undone, you MUST undo the merge by reverting the
|
|
||||||
merge commit itself. Effectively creating a new commit that reverses all
|
|
||||||
the relevant changes.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
<li>Git Best Practices
|
|
||||||
<ol>
|
|
||||||
<li>All commit messages SHOULD follow the Commit Guidelines and format from
|
|
||||||
the official git
|
|
||||||
documentation:
|
|
||||||
<a href="https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project#_commit_guidelines">https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project#_commit_guidelines</a></li>
|
|
||||||
<li>You SHOULD never blindly commit all changes with "git commit -a". It is
|
|
||||||
RECOMMENDED you use "git add -i" or "git add -p" to add individual
|
|
||||||
changes to the staging area so you are fully aware of what you are
|
|
||||||
committing.</li>
|
|
||||||
<li>You SHOULD always use "--force-with-lease" when doing a force push. The
|
|
||||||
regular "--force" option is dangerous and destructive. More
|
|
||||||
information:
|
|
||||||
<a href="https://developer.atlassian.com/blog/2015/04/force-with-lease/">https://developer.atlassian.com/blog/2015/04/force-with-lease/</a></li>
|
|
||||||
<li>You SHOULD understand and be comfortable with
|
|
||||||
rebasing: <a href="https://git-scm.com/book/en/v2/Git-Branching-Rebasing">https://git-scm.com/book/en/v2/Git-Branching-Rebasing</a></li>
|
|
||||||
<li>It is RECOMMENDED that you always do "git pull --rebase" instead of "git
|
|
||||||
pull" to avoid unnecessary merge commits. You can make this the default
|
|
||||||
behavior of "git pull" with "git config --global pull.rebase true".</li>
|
|
||||||
<li>It is RECOMMENDED that all branches be merged using "git merge --no-ff".
|
|
||||||
This makes sure the reference to the original branch is kept in the
|
|
||||||
commits, allows one to revert a merge by reverting a single merge commit,
|
|
||||||
and creates a merge commit to mark the integration of the branch with
|
|
||||||
master.</li>
|
|
||||||
</ol>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
<h2 id="faq">FAQ</h2>
|
|
||||||
<h3 id="why-use-common-flow-instead-of-git-flow-and-how-does-it-differ">Why use Common-Flow instead of Git Flow, and how does it differ?</h3>
|
|
||||||
<p>Common-Flow tries to be a lot less complicated than Git Flow by having fewer
|
|
||||||
types of branches, and simpler rules. Normal day to day development doesn't
|
|
||||||
really change much:</p>
|
|
||||||
<ul>
|
|
||||||
<li>You create change branches instead of feature branches, without the need of a
|
|
||||||
"feature/" or "change/" prefix in the branch name.</li>
|
|
||||||
<li>Change branches are typically created off of and merged back into "master"
|
|
||||||
instead of "develop".</li>
|
|
||||||
<li>Creating a release is done by simply creating a git tag, typically on the
|
|
||||||
master branch.</li>
|
|
||||||
</ul>
|
|
||||||
<p>In detail, the main differences between Git Flow and Common-Flow are:</p>
|
|
||||||
<ul>
|
|
||||||
<li>There is no "develop" branch, there is only a "master" branch which contains
|
|
||||||
the latest work. In Git Flow the master branch effectively ends up just being
|
|
||||||
a pointer to the latest release, despite the fact that Git Flow includes
|
|
||||||
release tags too. In Common-Flow you just look at the tags to find the latest
|
|
||||||
release.</li>
|
|
||||||
<li>There are no "feature" or "hotfix" branches, there's only "change"
|
|
||||||
branches. Any branch that is not master and introduces changes is a change
|
|
||||||
branch. Change branches also don't have a enforced naming convention, they
|
|
||||||
just have to have a "descriptive name". This makes things simpler and allows
|
|
||||||
more flexibility.</li>
|
|
||||||
<li>Release branches are available, but optional. Instead of enforcing the use of
|
|
||||||
release branches like Git Flow, Common-Flow only recommends the use of release
|
|
||||||
branches when it makes things easier. If creating a new release by tagging
|
|
||||||
"master" works for you, great, do that.</li>
|
|
||||||
</ul>
|
|
||||||
<h3 id="why-use-common-flow-instead-of-github-flow-and-how-does-it-differ">Why use Common-Flow instead of GitHub Flow, and how does it differ?</h3>
|
|
||||||
<p>Common-Flow is essentially GitHub Flow with the addition of a "Release" concept
|
|
||||||
that uses tags. It also attempts to define how certain common tasks are done,
|
|
||||||
like updating change/feature branches from their source branches for
|
|
||||||
example. This is to help end arguments about how such things are done.</p>
|
|
||||||
<p>If a deployment/release for you is just getting the latest code in the master
|
|
||||||
branch out, without caring about bumping version numbers or anything, then
|
|
||||||
GitHub Flow is a good fit for you, and you probably don't need the extras of
|
|
||||||
Common-Flow.</p>
|
|
||||||
<p>However if your deployments/releases have specific version numbers, then
|
|
||||||
Common-Flow gives you a simple set of rules of how to create and manage
|
|
||||||
releases, on top of what GitHub Flow already does.</p>
|
|
||||||
<h3 id="what-does-descriptive-name-mean-for-change-branches">What does "descriptive name" mean for change branches?</h3>
|
|
||||||
<p>It means what it sounds like. The name should be descriptive, as in by just
|
|
||||||
reading the name of the branch you should understand what the branch's purpose
|
|
||||||
is and what it does. Here's a few examples:</p>
|
|
||||||
<ul>
|
|
||||||
<li>add-2fa-support</li>
|
|
||||||
<li>fix-login-issue</li>
|
|
||||||
<li>remove-sort-by-middle-name-functionality</li>
|
|
||||||
<li>update-font-awesome</li>
|
|
||||||
<li>change-search-behavior</li>
|
|
||||||
<li>tweak-footer-style</li>
|
|
||||||
</ul>
|
|
||||||
<p>Notice how none of these have any prefixes like "feature/" or "hotfix/", they're
|
|
||||||
not needed when branch names are properly descriptive. However there's nothing
|
|
||||||
to say you can't use such prefixes if you want. That also means that you can add
|
|
||||||
ticket number prefixes if your team/org has that as part of it's process.</p>
|
|
||||||
<h3 id="how-do-we-release-an-emergency-hotfix-when-the-master-branch-is-broken">How do we release an emergency hotfix when the master branch is broken?</h3>
|
|
||||||
<p>This should ideally never happen, however if it does you can do one of the
|
|
||||||
following:</p>
|
|
||||||
<ul>
|
|
||||||
<li>Review why the master branch is broken and revert the changes that caused the
|
|
||||||
issues. Then apply the hotfix and release.</li>
|
|
||||||
<li>Or use a short-term release branch created from the latest release tag instead
|
|
||||||
of the master branch. Apply the hotfix to the release branch, create a release
|
|
||||||
tag on the release branch, and then merge it back into master.</li>
|
|
||||||
</ul>
|
|
||||||
<p>In this situation, it is recommended you try to revert the offending changes
|
|
||||||
that's preventing a new release from master. But if that proves to be a
|
|
||||||
complicated task and you're short on time, a short-term release branch gives you
|
|
||||||
a instant fix to the situation at hand, and let's you resolve the issues with
|
|
||||||
the master branch when you have more time on your hands.</p>
|
|
||||||
<h2 id="about">About</h2>
|
|
||||||
<p>The Git Common-Flow specification is authored
|
|
||||||
by <a href="http://jimeh.me">Jim Myhrberg</a>.</p>
|
|
||||||
<p>If you'd like to leave feedback,
|
|
||||||
please <a href="https://github.com/jimeh/common-flow/issues">open an issue on GitHub</a>.</p>
|
|
||||||
<h2 id="license">License</h2>
|
|
||||||
<p><a href="http://creativecommons.org/licenses/by/3.0/">Creative Commons - CC BY 3.0</a></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script type="text/javascript" src="/assets/main-870855580c69dec57be4c965d0cf8afe78afa6b7b6f6bdb5aff91ac0256c0a1a.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
Before Width: | Height: | Size: 18 KiB |
21
eslint.config.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import eslint from "@eslint/js";
|
||||||
|
import tseslint from "typescript-eslint";
|
||||||
|
import astro from "eslint-plugin-astro";
|
||||||
|
|
||||||
|
export default tseslint.config(
|
||||||
|
eslint.configs.recommended,
|
||||||
|
...tseslint.configs.recommended,
|
||||||
|
...astro.configs.recommended,
|
||||||
|
{
|
||||||
|
ignores: ["docs/**", ".astro/**"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rules: {
|
||||||
|
// Allow unused vars prefixed with _
|
||||||
|
"@typescript-eslint/no-unused-vars": [
|
||||||
|
"error",
|
||||||
|
{ argsIgnorePattern: "^_", varsIgnorePattern: "^_" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
10102
package-lock.json
generated
Normal file
46
package.json
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"name": "commonflow-org",
|
||||||
|
"type": "module",
|
||||||
|
"version": "2.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "astro dev",
|
||||||
|
"build": "astro build",
|
||||||
|
"preview": "astro preview",
|
||||||
|
"check": "astro check",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"format": "prettier --write .",
|
||||||
|
"format:check": "prettier --check .",
|
||||||
|
"update-specs": "tsx scripts/update-specs.ts",
|
||||||
|
"astro": "astro"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@astrojs/sitemap": "^3.7.0",
|
||||||
|
"@iconify-json/heroicons": "^1.2.3",
|
||||||
|
"@iconify-json/simple-icons": "^1.2.69",
|
||||||
|
"astro": "^5.17.1",
|
||||||
|
"astro-icon": "^1.1.5",
|
||||||
|
"rehype-autolink-headings": "^7.1.0",
|
||||||
|
"rehype-slug": "^6.0.0",
|
||||||
|
"rehype-stringify": "^10.0.1",
|
||||||
|
"remark-parse": "^11.0.0",
|
||||||
|
"remark-rehype": "^11.1.2",
|
||||||
|
"semver": "^7.7.3",
|
||||||
|
"unified": "^11.0.5"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@astrojs/check": "^0.9.6",
|
||||||
|
"@eslint/js": "^10.0.1",
|
||||||
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
|
"@tailwindcss/vite": "^4.2.0",
|
||||||
|
"@types/semver": "^7.7.1",
|
||||||
|
"eslint": "^10.0.0",
|
||||||
|
"eslint-plugin-astro": "^1.6.0",
|
||||||
|
"prettier": "^3.8.1",
|
||||||
|
"prettier-plugin-astro": "^0.14.1",
|
||||||
|
"svgo": "^4.0.0",
|
||||||
|
"tailwindcss": "^4.1.18",
|
||||||
|
"tsx": "^4.21.0",
|
||||||
|
"typescript": "^5.9.3",
|
||||||
|
"typescript-eslint": "^8.56.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
12
prettier.config.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/** @type {import("prettier").Config} */
|
||||||
|
export default {
|
||||||
|
plugins: ["prettier-plugin-astro"],
|
||||||
|
overrides: [
|
||||||
|
{
|
||||||
|
files: "*.astro",
|
||||||
|
options: {
|
||||||
|
parser: "astro",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
BIN
public/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
public/favicon.ico
Normal file
|
After Width: | Height: | Size: 14 KiB |
17
public/favicon.svg
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
||||||
|
<!-- Main horizontal line -->
|
||||||
|
<path d="M 4 10 L 28 10" fill="none" stroke="currentColor" stroke-width="2"/>
|
||||||
|
|
||||||
|
<!-- Branch line: down from left, curve, up to right -->
|
||||||
|
<path d="M 8 10 L 8 16 Q 8 22 16 22 Q 24 22 24 16 L 24 10"
|
||||||
|
fill="none" stroke="currentColor" stroke-width="2"/>
|
||||||
|
|
||||||
|
<!-- Left dot on main line -->
|
||||||
|
<circle cx="8" cy="10" r="3" fill="currentColor"/>
|
||||||
|
|
||||||
|
<!-- Right dot on main line -->
|
||||||
|
<circle cx="24" cy="10" r="3" fill="currentColor"/>
|
||||||
|
|
||||||
|
<!-- Middle dot on branch -->
|
||||||
|
<circle cx="16" cy="22" r="3" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 625 B |
243
scripts/update-specs.ts
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
/**
|
||||||
|
* Fetches spec documents and diagrams from the common-flow GitHub repo
|
||||||
|
* and writes them to the appropriate locations for Astro to consume.
|
||||||
|
*
|
||||||
|
* Versions are discovered from git tags and filtered based on config.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { execSync } from "node:child_process";
|
||||||
|
import * as fs from "node:fs";
|
||||||
|
import * as path from "node:path";
|
||||||
|
import * as semver from "semver";
|
||||||
|
import { optimize as optimizeSvg, type Config as SvgoConfig } from "svgo";
|
||||||
|
import { config } from "../src/config";
|
||||||
|
|
||||||
|
const updateConfig = {
|
||||||
|
bodyTemplate: `---
|
||||||
|
title: {{title}}
|
||||||
|
version: {{version}}
|
||||||
|
---
|
||||||
|
{{content}}`,
|
||||||
|
outputDir: "src/content/spec",
|
||||||
|
};
|
||||||
|
|
||||||
|
// SVGO config: use removeDimensions to convert width/height to viewBox
|
||||||
|
// for responsive scaling while preserving aspect ratio
|
||||||
|
const svgoConfig: SvgoConfig = {
|
||||||
|
plugins: ["preset-default", "removeDimensions"],
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch all tags from the GitHub repository.
|
||||||
|
*/
|
||||||
|
function fetchTags(repository: string): string[] {
|
||||||
|
const repoUrl = `https://github.com/${repository}.git`;
|
||||||
|
console.log(`Fetching tags from ${repoUrl}...`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = execSync(`git ls-remote --tags ${repoUrl}`, {
|
||||||
|
encoding: "utf-8",
|
||||||
|
});
|
||||||
|
|
||||||
|
return result
|
||||||
|
.split("\n")
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((line: string) => line.match(/refs\/tags\/(.+)$/)?.[1])
|
||||||
|
.filter(
|
||||||
|
(tag: string | undefined): tag is string =>
|
||||||
|
tag !== undefined && !tag.endsWith("^{}"),
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(
|
||||||
|
`Failed to fetch tags: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the prerelease type of a version (e.g., "rc", "draft", or null for
|
||||||
|
* stable).
|
||||||
|
*/
|
||||||
|
function getPrereleaseType(version: string): string | null {
|
||||||
|
const prerelease = semver.prerelease(version);
|
||||||
|
if (!prerelease) return null;
|
||||||
|
return String(prerelease[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter tags based on discovery configuration.
|
||||||
|
* Stable versions are always included; prereleases only if their type is in
|
||||||
|
* the includePrereleaseTypes list.
|
||||||
|
*/
|
||||||
|
function filterVersions(tags: string[]): string[] {
|
||||||
|
const { includePrereleaseTypes, excludeVersions } = config.update.discovery;
|
||||||
|
|
||||||
|
return tags.filter((tag) => {
|
||||||
|
// Must be valid semver
|
||||||
|
if (!semver.valid(tag)) return false;
|
||||||
|
|
||||||
|
// Check explicit exclusions
|
||||||
|
if (excludeVersions.includes(tag)) return false;
|
||||||
|
|
||||||
|
// Stable versions are always included
|
||||||
|
const prereleaseType = getPrereleaseType(tag);
|
||||||
|
if (prereleaseType === null) return true;
|
||||||
|
|
||||||
|
// Prereleases only if their type is in the list
|
||||||
|
return includePrereleaseTypes.includes(prereleaseType);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildFileUrl(
|
||||||
|
fileType: "document" | "diagram",
|
||||||
|
version: string,
|
||||||
|
): string {
|
||||||
|
const file = config.update.files[fileType];
|
||||||
|
return config.update.urlTemplate
|
||||||
|
.replace("{{version}}", version)
|
||||||
|
.replace("{{file}}", file);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchFile(url: string): Promise<string | null> {
|
||||||
|
try {
|
||||||
|
const response = await fetch(url);
|
||||||
|
if (!response.ok) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return await response.text();
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeFile(filePath: string, content: string, comment = ""): void {
|
||||||
|
const dir = path.dirname(filePath);
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
|
}
|
||||||
|
fs.writeFileSync(filePath, content);
|
||||||
|
console.log(` - ${filePath}${comment}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove spec files for versions not in the provided list.
|
||||||
|
* Files for versions in the list are left alone (they'll be overwritten).
|
||||||
|
*/
|
||||||
|
function removeStaleSpecs(versionsToKeep: string[]): void {
|
||||||
|
const keepSet = new Set(versionsToKeep);
|
||||||
|
let removedAny = false;
|
||||||
|
|
||||||
|
if (!fs.existsSync(updateConfig.outputDir)) return;
|
||||||
|
|
||||||
|
const files = fs.readdirSync(updateConfig.outputDir);
|
||||||
|
for (const file of files) {
|
||||||
|
// Extract version from filename (e.g., "1.0.0-rc.1.md" -> "1.0.0-rc.1")
|
||||||
|
const version = path.basename(file, path.extname(file));
|
||||||
|
if (!keepSet.has(version)) {
|
||||||
|
if (!removedAny) {
|
||||||
|
console.log("\nRemoving stale spec files:");
|
||||||
|
removedAny = true;
|
||||||
|
}
|
||||||
|
const filePath = path.join(updateConfig.outputDir, file);
|
||||||
|
fs.unlinkSync(filePath);
|
||||||
|
console.log(` ${filePath}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!removedAny) {
|
||||||
|
console.log("\nNo stale spec files to remove.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Spec {
|
||||||
|
version: string;
|
||||||
|
title: string;
|
||||||
|
body: string;
|
||||||
|
diagram: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchSpec(version: string): Promise<Spec> {
|
||||||
|
const documentUrl = buildFileUrl("document", version);
|
||||||
|
const diagramUrl = buildFileUrl("diagram", version);
|
||||||
|
|
||||||
|
let document = await fetchFile(documentUrl);
|
||||||
|
const diagram = await fetchFile(diagramUrl);
|
||||||
|
|
||||||
|
if (!document) {
|
||||||
|
throw new Error(`Failed to fetch document for version ${version}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace {{version}} placeholder with v-prefixed version
|
||||||
|
document = document.replaceAll("{{version}}", `v${version}`);
|
||||||
|
|
||||||
|
// Handle setext-style H1 heading (title with === underline)
|
||||||
|
const lines = document.split("\n");
|
||||||
|
if (lines.length >= 2 && /^=+$/.test(lines[1])) {
|
||||||
|
// Adjust the underline length to match the title
|
||||||
|
lines[1] = "=".repeat(lines[0].length);
|
||||||
|
document = lines.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract title from first line (after version replacement)
|
||||||
|
// Handle both ATX-style (# Title) and setext-style (Title\n===) headings
|
||||||
|
const title = lines[0].replace(/^#\s+/, "");
|
||||||
|
|
||||||
|
// Build body with frontmatter
|
||||||
|
const body = updateConfig.bodyTemplate
|
||||||
|
.replace("{{content}}", document)
|
||||||
|
.replace("{{title}}", title)
|
||||||
|
.replace("{{version}}", version);
|
||||||
|
|
||||||
|
return {
|
||||||
|
version,
|
||||||
|
title,
|
||||||
|
body,
|
||||||
|
diagram,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main(): Promise<void> {
|
||||||
|
// 1. Discover and filter versions
|
||||||
|
const tags = fetchTags(config.update.repository);
|
||||||
|
console.log(`Found ${tags.length} tags`);
|
||||||
|
|
||||||
|
const filtered = filterVersions(tags);
|
||||||
|
const sorted = semver.rsort([...filtered]);
|
||||||
|
|
||||||
|
console.log(`\nIncluded ${sorted.length} versions after filtering:`);
|
||||||
|
console.log(` ${sorted.join(", ")}`);
|
||||||
|
|
||||||
|
if (sorted.length === 0) {
|
||||||
|
console.error("\nNo versions to process. Exiting.");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Remove spec files for versions no longer in the list
|
||||||
|
removeStaleSpecs(sorted);
|
||||||
|
|
||||||
|
// 3. Fetch specs for all versions
|
||||||
|
console.log("\nFetching spec documents:");
|
||||||
|
|
||||||
|
for (const version of sorted) {
|
||||||
|
try {
|
||||||
|
const spec = await fetchSpec(version);
|
||||||
|
|
||||||
|
// Write markdown file to content collection
|
||||||
|
const mdPath = path.join(updateConfig.outputDir, `${version}.md`);
|
||||||
|
writeFile(mdPath, spec.body);
|
||||||
|
|
||||||
|
// Write SVG diagram next to markdown (with metadata stripped)
|
||||||
|
if (spec.diagram) {
|
||||||
|
const svgPath = path.join(updateConfig.outputDir, `${version}.svg`);
|
||||||
|
const optimizedSvg = optimizeSvg(spec.diagram, svgoConfig).data;
|
||||||
|
writeFile(svgPath, optimizedSvg);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error processing version ${version}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("\nDone! Run `npm run build` to rebuild the site.");
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 18 KiB |
72
src/components/AboutSection.astro
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
---
|
||||||
|
import SectionHeader from "./SectionHeader.astro";
|
||||||
|
import { config } from "../config";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
introduction: string;
|
||||||
|
summary: string;
|
||||||
|
license: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { introduction, summary, license } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<section id="about" class="py-20 sm:py-28">
|
||||||
|
<div class="section-container">
|
||||||
|
<div class="max-w-3xl mx-auto">
|
||||||
|
<SectionHeader
|
||||||
|
title="About Common-Flow"
|
||||||
|
subtitle="A practical git workflow that combines the best of GitHub Flow with versioned releases"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Introduction -->
|
||||||
|
<div class="prose-spec mb-12" set:html={introduction} />
|
||||||
|
|
||||||
|
<!-- Summary as feature cards -->
|
||||||
|
<div class="mb-16">
|
||||||
|
<h3
|
||||||
|
class="text-xl font-display font-semibold mb-6
|
||||||
|
text-gray-950 dark:text-neutral-50"
|
||||||
|
>
|
||||||
|
Key Principles
|
||||||
|
</h3>
|
||||||
|
<div class="prose-spec" set:html={summary} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Feedback & License -->
|
||||||
|
<div class="pt-8 border-t border-gray-200 dark:border-neutral-800">
|
||||||
|
<div class="grid sm:grid-cols-2 gap-8">
|
||||||
|
<div>
|
||||||
|
<h4
|
||||||
|
class="text-sm font-semibold uppercase tracking-wider mb-3
|
||||||
|
text-gray-500 dark:text-neutral-500"
|
||||||
|
>
|
||||||
|
Feedback
|
||||||
|
</h4>
|
||||||
|
<p class="text-gray-600 dark:text-neutral-400">
|
||||||
|
Please{" "}
|
||||||
|
<a
|
||||||
|
href={`${config.repoUrl}/issues`}
|
||||||
|
class="text-sky-600 hover:text-sky-400"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer">open an issue on GitHub</a
|
||||||
|
>.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h4
|
||||||
|
class="text-sm font-semibold uppercase tracking-wider mb-3
|
||||||
|
text-gray-500 dark:text-neutral-500"
|
||||||
|
>
|
||||||
|
License
|
||||||
|
</h4>
|
||||||
|
<div
|
||||||
|
class="text-gray-600 dark:text-neutral-400"
|
||||||
|
set:html={license}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
92
src/components/FAQSection.astro
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
---
|
||||||
|
import { Icon } from "astro-icon/components";
|
||||||
|
import SectionHeader from "./SectionHeader.astro";
|
||||||
|
import type { FAQItem } from "../utils/parseSpecContent";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
items: FAQItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const { items } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<section id="faq" class="py-20 sm:py-28">
|
||||||
|
<div class="section-container">
|
||||||
|
<div class="max-w-3xl mx-auto">
|
||||||
|
<SectionHeader
|
||||||
|
title="FAQ"
|
||||||
|
subtitle="Common questions about Git Common-Flow"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- FAQ Items -->
|
||||||
|
<div class="space-y-0">
|
||||||
|
{
|
||||||
|
items.map((item) => (
|
||||||
|
<div
|
||||||
|
class="border-b border-gray-200 dark:border-neutral-800"
|
||||||
|
data-faq-item
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="flex justify-between items-center w-full py-6 text-left
|
||||||
|
font-display text-lg font-semibold cursor-pointer
|
||||||
|
transition-colors
|
||||||
|
text-gray-950 dark:text-neutral-50
|
||||||
|
hover:text-sky-600"
|
||||||
|
aria-expanded="false"
|
||||||
|
data-faq-trigger
|
||||||
|
>
|
||||||
|
<span class="pr-4">{item.question}</span>
|
||||||
|
<Icon
|
||||||
|
name="heroicons:chevron-down"
|
||||||
|
class="w-5 h-5 flex-shrink-0 transition-transform duration-200"
|
||||||
|
data-faq-icon
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
class="grid grid-rows-[0fr] transition-[grid-template-rows] duration-300 ease-out"
|
||||||
|
data-faq-content
|
||||||
|
>
|
||||||
|
<div class="overflow-hidden">
|
||||||
|
<div
|
||||||
|
class="pb-6 text-gray-600 dark:text-neutral-400 prose-spec"
|
||||||
|
set:html={item.answer}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function initFAQ() {
|
||||||
|
const items = document.querySelectorAll("[data-faq-item]");
|
||||||
|
|
||||||
|
items.forEach((item) => {
|
||||||
|
const trigger = item.querySelector("[data-faq-trigger]");
|
||||||
|
const content = item.querySelector("[data-faq-content]");
|
||||||
|
const icon = item.querySelector("[data-faq-icon]");
|
||||||
|
|
||||||
|
if (!trigger || !content || !icon) return;
|
||||||
|
|
||||||
|
trigger.addEventListener("click", () => {
|
||||||
|
const isExpanded = trigger.getAttribute("aria-expanded") === "true";
|
||||||
|
|
||||||
|
// Toggle current item
|
||||||
|
trigger.setAttribute("aria-expanded", isExpanded ? "false" : "true");
|
||||||
|
content.classList.toggle("grid-rows-[1fr]", !isExpanded);
|
||||||
|
content.classList.toggle("grid-rows-[0fr]", isExpanded);
|
||||||
|
icon.classList.toggle("rotate-180", !isExpanded);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize on load
|
||||||
|
initFAQ();
|
||||||
|
|
||||||
|
// Re-initialize on Astro page transitions
|
||||||
|
document.addEventListener("astro:after-swap", initFAQ);
|
||||||
|
</script>
|
||||||
37
src/components/Footer.astro
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
---
|
||||||
|
import { config } from "../config";
|
||||||
|
---
|
||||||
|
|
||||||
|
<footer
|
||||||
|
class="pt-12 pb-6 my-28 text-sm
|
||||||
|
text-gray-500 dark:text-neutral-500
|
||||||
|
border-t border-gray-200 dark:border-neutral-800"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="section-container flex flex-col items-center text-center
|
||||||
|
sm:flex-row sm:justify-between sm:items-center sm:text-left gap-2"
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
License:
|
||||||
|
<a
|
||||||
|
href={config.license.url}
|
||||||
|
class="hover:text-sky-600"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{config.license.name}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{config.title} by
|
||||||
|
<a
|
||||||
|
href={config.authorUrl}
|
||||||
|
class="hover:text-sky-600"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
>
|
||||||
|
{config.author}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
182
src/components/Header.astro
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
---
|
||||||
|
import { Icon } from "astro-icon/components";
|
||||||
|
import VersionSelector from "./VersionSelector.astro";
|
||||||
|
import ThemeToggle from "./ThemeToggle.astro";
|
||||||
|
import { config } from "../config";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
version: string;
|
||||||
|
versions: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const { version, versions } = Astro.props;
|
||||||
|
|
||||||
|
const navItems = [
|
||||||
|
{ id: "about", label: "About", icon: "heroicons:information-circle" },
|
||||||
|
{ id: "spec", label: "Spec", icon: "heroicons:document-text" },
|
||||||
|
{ id: "faq", label: "FAQ", icon: "heroicons:question-mark-circle" },
|
||||||
|
];
|
||||||
|
---
|
||||||
|
|
||||||
|
<header
|
||||||
|
id="site-header"
|
||||||
|
class="fixed top-0 inset-x-0 z-50 border-b border-transparent
|
||||||
|
translate-y-[-100%] transition-transform duration-300
|
||||||
|
backdrop-blur-xl bg-gray-50/85 dark:bg-neutral-950/85"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="max-w-6xl mx-auto px-4 sm:px-6 h-16 flex items-center justify-between"
|
||||||
|
>
|
||||||
|
<!-- Logo / Title + Version -->
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<a
|
||||||
|
href="#top"
|
||||||
|
class="flex items-center gap-3 no-underline
|
||||||
|
text-gray-950 dark:text-neutral-50
|
||||||
|
hover:text-sky-600 transition-colors"
|
||||||
|
>
|
||||||
|
<span class="font-display font-bold text-lg tracking-tight">
|
||||||
|
Git Common-Flow
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<div class="hidden md:block">
|
||||||
|
<VersionSelector currentVersion={version} versions={versions} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Desktop Navigation -->
|
||||||
|
<nav class="hidden md:flex items-center gap-1">
|
||||||
|
{
|
||||||
|
navItems.map((item) => (
|
||||||
|
<a
|
||||||
|
href={`#${item.id}`}
|
||||||
|
class="nav-link inline-flex items-center gap-1.5 px-4 py-2 text-sm
|
||||||
|
font-medium rounded-lg transition-colors cursor-pointer
|
||||||
|
text-gray-600 dark:text-neutral-400
|
||||||
|
hover:bg-gray-100 hover:text-gray-950
|
||||||
|
dark:hover:bg-neutral-800 dark:hover:text-neutral-50"
|
||||||
|
data-nav-link
|
||||||
|
data-section-id={item.id}
|
||||||
|
>
|
||||||
|
<Icon name={item.icon} class="w-4 h-4" />
|
||||||
|
{item.label}
|
||||||
|
</a>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Right side: Theme, GitHub -->
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<ThemeToggle />
|
||||||
|
|
||||||
|
<a
|
||||||
|
href={config.repoUrl}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="p-2 rounded-lg transition-colors
|
||||||
|
text-gray-500 dark:text-neutral-500
|
||||||
|
hover:text-gray-950 dark:hover:text-neutral-50
|
||||||
|
hover:bg-gray-100 dark:hover:bg-neutral-800"
|
||||||
|
aria-label="View on GitHub"
|
||||||
|
>
|
||||||
|
<Icon name="simple-icons:github" class="w-5 h-5" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- Mobile menu button -->
|
||||||
|
<button
|
||||||
|
id="mobile-menu-btn"
|
||||||
|
class="md:hidden p-2 rounded-lg
|
||||||
|
text-gray-500 dark:text-neutral-500
|
||||||
|
hover:bg-gray-100 dark:hover:bg-neutral-800"
|
||||||
|
aria-label="Toggle menu"
|
||||||
|
>
|
||||||
|
<Icon name="heroicons:bars-3" class="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Mobile Navigation -->
|
||||||
|
<nav
|
||||||
|
id="mobile-nav"
|
||||||
|
class="md:hidden hidden border-t border-gray-200 dark:border-neutral-800"
|
||||||
|
>
|
||||||
|
<div class="px-4 py-3 space-y-1 text-center">
|
||||||
|
<div class="py-2 flex justify-center">
|
||||||
|
<VersionSelector currentVersion={version} versions={versions} />
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
navItems.map((item) => (
|
||||||
|
<a
|
||||||
|
href={`#${item.id}`}
|
||||||
|
class="nav-link flex items-center justify-center gap-1.5 py-2
|
||||||
|
text-gray-600 dark:text-neutral-400 hover:text-sky-600"
|
||||||
|
data-nav-link
|
||||||
|
data-section-id={item.id}
|
||||||
|
>
|
||||||
|
<Icon name={item.icon} class="w-4 h-4" />
|
||||||
|
{item.label}
|
||||||
|
</a>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { initActiveSectionTracker } from "../scripts/activeSectionTracker";
|
||||||
|
|
||||||
|
function initHeader() {
|
||||||
|
const header = document.getElementById("site-header");
|
||||||
|
const hero = document.getElementById("top");
|
||||||
|
const mobileMenuBtn = document.getElementById("mobile-menu-btn");
|
||||||
|
const mobileNav = document.getElementById("mobile-nav");
|
||||||
|
|
||||||
|
if (!header || !hero) return;
|
||||||
|
|
||||||
|
// Show/hide header based on scroll position
|
||||||
|
// Show header once scrolled down by the navbar height (64px)
|
||||||
|
const navbarHeight = 64;
|
||||||
|
|
||||||
|
function updateHeaderVisibility() {
|
||||||
|
if (!header) return;
|
||||||
|
if (window.scrollY >= navbarHeight) {
|
||||||
|
header.classList.remove("translate-y-[-100%]", "border-transparent");
|
||||||
|
header.classList.add("border-gray-200", "dark:border-neutral-800");
|
||||||
|
} else {
|
||||||
|
header.classList.add("translate-y-[-100%]", "border-transparent");
|
||||||
|
header.classList.remove("border-gray-200", "dark:border-neutral-800");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("scroll", updateHeaderVisibility, {
|
||||||
|
passive: true,
|
||||||
|
});
|
||||||
|
updateHeaderVisibility();
|
||||||
|
|
||||||
|
// Mobile menu toggle
|
||||||
|
if (mobileMenuBtn && mobileNav) {
|
||||||
|
mobileMenuBtn.addEventListener("click", () => {
|
||||||
|
mobileNav.classList.toggle("hidden");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close menu when clicking a link
|
||||||
|
mobileNav.querySelectorAll("a").forEach((link) => {
|
||||||
|
link.addEventListener("click", () => {
|
||||||
|
mobileNav.classList.add("hidden");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Active section tracking for nav links
|
||||||
|
initActiveSectionTracker({
|
||||||
|
linkSelector: "[data-nav-link]",
|
||||||
|
defaultToFirst: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize on load
|
||||||
|
initHeader();
|
||||||
|
|
||||||
|
// Re-initialize on Astro page transitions
|
||||||
|
document.addEventListener("astro:after-swap", initHeader);
|
||||||
|
</script>
|
||||||
199
src/components/Hero.astro
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
---
|
||||||
|
import { Icon } from "astro-icon/components";
|
||||||
|
import VersionSelector from "./VersionSelector.astro";
|
||||||
|
import ThemeToggle from "./ThemeToggle.astro";
|
||||||
|
import { config } from "../config";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
version: string;
|
||||||
|
versions: string[];
|
||||||
|
svgContent?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { version, versions, svgContent } = Astro.props;
|
||||||
|
|
||||||
|
const navItems = [
|
||||||
|
{ id: "about", label: "About", align: "end" },
|
||||||
|
{ id: "spec", label: "Read the Spec", primary: true },
|
||||||
|
{ id: "faq", label: "FAQ", align: "start" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const baseClasses = `inline-flex items-center justify-center gap-2
|
||||||
|
px-4 py-2.5 sm:px-6 sm:py-3
|
||||||
|
text-sm sm:text-base font-medium rounded-lg
|
||||||
|
transition-all cursor-pointer`;
|
||||||
|
|
||||||
|
const primaryClasses = `bg-sky-600 text-white
|
||||||
|
hover:bg-sky-500 hover:-translate-y-0.5 hover:shadow-md`;
|
||||||
|
|
||||||
|
const secondaryClasses = `text-gray-600 dark:text-neutral-400
|
||||||
|
hover:bg-gray-100 hover:text-gray-950
|
||||||
|
dark:hover:bg-neutral-800 dark:hover:text-neutral-50`;
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<section
|
||||||
|
id="top"
|
||||||
|
class="relative min-h-[75vh] flex flex-col items-center justify-center
|
||||||
|
px-6 pt-16 pb-24 overflow-hidden"
|
||||||
|
>
|
||||||
|
<!-- Background gradient/texture -->
|
||||||
|
<div
|
||||||
|
class="absolute inset-0 bg-gradient-to-b
|
||||||
|
from-gray-100 to-gray-50
|
||||||
|
dark:from-neutral-900 dark:to-neutral-950"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Subtle grid pattern with fade -->
|
||||||
|
<div
|
||||||
|
class="absolute inset-0 opacity-[0.06] dark:opacity-[0.12]
|
||||||
|
bg-[linear-gradient(theme(colors.gray.950)_1px,transparent_1px),linear-gradient(90deg,theme(colors.gray.950)_1px,transparent_1px)]
|
||||||
|
dark:bg-[linear-gradient(theme(colors.neutral.600)_1px,transparent_1px),linear-gradient(90deg,theme(colors.neutral.600)_1px,transparent_1px)]
|
||||||
|
bg-[size:60px_60px] bg-center
|
||||||
|
[-webkit-mask-image:linear-gradient(to_bottom,black_20%,transparent_80%)]
|
||||||
|
[mask-image:linear-gradient(to_bottom,black_20%,transparent_80%)]"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Top bar with version & theme -->
|
||||||
|
<div
|
||||||
|
class="absolute top-0 inset-x-0 z-20 flex items-center justify-between
|
||||||
|
px-6 py-4 animate-fade-in-down"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<VersionSelector currentVersion={version} versions={versions} />
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<ThemeToggle />
|
||||||
|
<a
|
||||||
|
href={config.repoUrl}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="p-2 rounded-lg transition-colors
|
||||||
|
text-gray-500 dark:text-neutral-500
|
||||||
|
hover:text-gray-950 dark:hover:text-neutral-50
|
||||||
|
hover:bg-white/50 dark:hover:bg-neutral-800/50"
|
||||||
|
aria-label="View on GitHub"
|
||||||
|
>
|
||||||
|
<Icon name="simple-icons:github" class="w-5 h-5" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Main content -->
|
||||||
|
<div class="relative z-10 w-full max-w-4xl mx-auto text-center">
|
||||||
|
<!-- Title -->
|
||||||
|
<h1
|
||||||
|
class="animate-fade-in-up mb-4
|
||||||
|
text-gray-950 dark:text-neutral-50"
|
||||||
|
>
|
||||||
|
Git Common-Flow
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<!-- Tagline -->
|
||||||
|
<p
|
||||||
|
class="animate-fade-in-up delay-100
|
||||||
|
text-lg sm:text-xl max-w-2xl mx-auto mb-8
|
||||||
|
text-gray-600 dark:text-neutral-400"
|
||||||
|
>
|
||||||
|
A sensible git workflow for teams who ship
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Version badge -->
|
||||||
|
<div class="animate-fade-in-up delay-200 mb-10">
|
||||||
|
<span
|
||||||
|
class="inline-flex items-center px-3 py-1 font-mono text-xs font-medium
|
||||||
|
rounded-full border
|
||||||
|
bg-gray-100 border-gray-200 text-gray-500
|
||||||
|
dark:bg-neutral-800/50 dark:border-neutral-700 dark:text-neutral-400"
|
||||||
|
>
|
||||||
|
v{version}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- SVG Diagram -->
|
||||||
|
{
|
||||||
|
svgContent && (
|
||||||
|
<div
|
||||||
|
class="animate-fade-in-up delay-300
|
||||||
|
relative mx-auto mb-12 py-8 px-6 sm:py-16 sm:px-14
|
||||||
|
bg-white dark:bg-neutral-900
|
||||||
|
rounded-2xl shadow-lg dark:shadow-none
|
||||||
|
border border-gray-200 dark:border-neutral-800"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="w-full max-w-3xl mx-auto [&>svg]:w-full [&>svg]:h-auto
|
||||||
|
dark:invert dark:hue-rotate-180 dark:contrast-90"
|
||||||
|
set:html={svgContent}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- Navigation links -->
|
||||||
|
<nav
|
||||||
|
class="animate-fade-in-up delay-400
|
||||||
|
grid grid-cols-[1fr_auto_1fr] items-center gap-2 sm:gap-4"
|
||||||
|
>
|
||||||
|
{
|
||||||
|
navItems.map((item) => (
|
||||||
|
<a
|
||||||
|
href={`#${item.id}`}
|
||||||
|
class:list={[
|
||||||
|
baseClasses,
|
||||||
|
item.primary ? primaryClasses : secondaryClasses,
|
||||||
|
item.align === "end" && "justify-self-end",
|
||||||
|
item.align === "start" && "justify-self-start",
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{item.label}
|
||||||
|
</a>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Scroll indicator -->
|
||||||
|
<a
|
||||||
|
href="#about"
|
||||||
|
class="absolute bottom-8 left-1/2 -translate-x-1/2
|
||||||
|
animate-fade-in delay-700
|
||||||
|
text-gray-500 dark:text-neutral-500
|
||||||
|
hover:text-sky-600 transition-colors"
|
||||||
|
aria-label="Scroll to content"
|
||||||
|
>
|
||||||
|
<Icon name="heroicons:arrow-down" class="w-6 h-6 animate-bounce-subtle" />
|
||||||
|
</a>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Remove animation classes after they complete to prevent re-triggering
|
||||||
|
// on theme toggle.
|
||||||
|
const animationClasses = [
|
||||||
|
"animate-fade-in",
|
||||||
|
"animate-fade-in-up",
|
||||||
|
"animate-fade-in-down",
|
||||||
|
"animate-slide-in-left",
|
||||||
|
];
|
||||||
|
|
||||||
|
function cleanupAnimations() {
|
||||||
|
const selector = animationClasses.map((c) => `.${c}`).join(", ");
|
||||||
|
const animatedElements = document.querySelectorAll(selector);
|
||||||
|
|
||||||
|
animatedElements.forEach((el) => {
|
||||||
|
el.addEventListener(
|
||||||
|
"animationend",
|
||||||
|
() => {
|
||||||
|
animationClasses.forEach((cls) => el.classList.remove(cls));
|
||||||
|
// Also remove delay classes
|
||||||
|
el.className = el.className.replace(/\bdelay-\d+\b/g, "").trim();
|
||||||
|
},
|
||||||
|
{ once: true },
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanupAnimations();
|
||||||
|
document.addEventListener("astro:after-swap", cleanupAnimations);
|
||||||
|
</script>
|
||||||
16
src/components/SectionHeader.astro
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
interface Props {
|
||||||
|
title: string;
|
||||||
|
subtitle: string;
|
||||||
|
class?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { title, subtitle, class: className } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class:list={["mb-12 text-center", className]}>
|
||||||
|
<h2 class="text-3xl sm:text-4xl mb-4">{title}</h2>
|
||||||
|
<p class="text-lg text-gray-600 dark:text-neutral-400">
|
||||||
|
{subtitle}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
71
src/components/SpecSection.astro
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
---
|
||||||
|
import { Icon } from "astro-icon/components";
|
||||||
|
import SectionHeader from "./SectionHeader.astro";
|
||||||
|
import SpecSidebar from "./SpecSidebar.astro";
|
||||||
|
import type { TocItem } from "../utils/parseSpecContent";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
terminology: string;
|
||||||
|
terminologyTitle: string;
|
||||||
|
specification: string;
|
||||||
|
tocItems: TocItem[];
|
||||||
|
version: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { terminology, terminologyTitle, specification, tocItems, version } =
|
||||||
|
Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<section id="spec" class="py-20 sm:py-28">
|
||||||
|
<div class="section-container">
|
||||||
|
<SectionHeader
|
||||||
|
title="The Specification"
|
||||||
|
subtitle="The complete Git Common-Flow specification"
|
||||||
|
class="max-w-3xl mx-auto"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="flex justify-center mb-8 -mt-8">
|
||||||
|
<a
|
||||||
|
href={`/spec/${version}/md`}
|
||||||
|
class="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm
|
||||||
|
font-medium rounded-lg transition-colors
|
||||||
|
text-gray-500 dark:text-neutral-500
|
||||||
|
hover:bg-gray-100 hover:text-gray-700
|
||||||
|
dark:hover:bg-neutral-800 dark:hover:text-neutral-300"
|
||||||
|
>
|
||||||
|
<Icon name="heroicons:code-bracket" class="w-4 h-4" />
|
||||||
|
View Markdown Version
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Content with sidebar -->
|
||||||
|
<div class="lg:flex lg:gap-8">
|
||||||
|
<!-- Sidebar -->
|
||||||
|
<div class="lg:w-64 lg:flex-shrink-0">
|
||||||
|
<SpecSidebar items={tocItems} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Main content -->
|
||||||
|
<div class="flex-1 min-w-0">
|
||||||
|
<article class="prose-spec">
|
||||||
|
<!-- Terminology -->
|
||||||
|
<section id="terminology">
|
||||||
|
<h2>{terminologyTitle}</h2>
|
||||||
|
<Fragment set:html={terminology} />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Main specification -->
|
||||||
|
<section id="specification">
|
||||||
|
<h2>Specification</h2>
|
||||||
|
<Fragment set:html={specification} />
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { initClauseHighlight } from "../scripts/clauseHighlight";
|
||||||
|
initClauseHighlight();
|
||||||
|
</script>
|
||||||
132
src/components/SpecSidebar.astro
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
---
|
||||||
|
import { Icon } from "astro-icon/components";
|
||||||
|
import TocLink from "./TocLink.astro";
|
||||||
|
import type { TocItem } from "../utils/parseSpecContent";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
items: TocItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const { items } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<aside
|
||||||
|
id="spec-sidebar"
|
||||||
|
class="hidden lg:block lg:sticky lg:top-24 lg:self-start
|
||||||
|
lg:max-h-[calc(100vh-8rem)] lg:overflow-y-auto
|
||||||
|
lg:pr-8 lg:mr-8 lg:border-r border-gray-200 dark:border-neutral-800"
|
||||||
|
>
|
||||||
|
<nav class="space-y-1 py-2">
|
||||||
|
<div
|
||||||
|
class="text-xs font-semibold uppercase tracking-wider mb-4
|
||||||
|
text-gray-500 dark:text-neutral-500"
|
||||||
|
>
|
||||||
|
Table of Contents
|
||||||
|
</div>
|
||||||
|
{items.map((item) => <TocLink item={item} trackActive />)}
|
||||||
|
</nav>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<!-- Mobile floating button -->
|
||||||
|
<button
|
||||||
|
id="spec-toc-toggle"
|
||||||
|
class="lg:hidden fixed bottom-6 right-6 z-40
|
||||||
|
w-12 h-12 rounded-full shadow-lg
|
||||||
|
bg-sky-600 text-white
|
||||||
|
flex items-center justify-center
|
||||||
|
hover:bg-sky-500
|
||||||
|
transition-all duration-200"
|
||||||
|
aria-label="Jump to section"
|
||||||
|
>
|
||||||
|
<Icon name="heroicons:bars-3-bottom-left" class="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Mobile TOC drawer -->
|
||||||
|
<div
|
||||||
|
id="spec-toc-drawer"
|
||||||
|
class="lg:hidden fixed inset-0 z-50 hidden"
|
||||||
|
data-toc-drawer
|
||||||
|
>
|
||||||
|
<!-- Backdrop -->
|
||||||
|
<div class="absolute inset-0 bg-black/50" data-toc-backdrop></div>
|
||||||
|
|
||||||
|
<!-- Drawer -->
|
||||||
|
<div
|
||||||
|
class="absolute bottom-0 inset-x-0 max-h-[70vh] overflow-y-auto
|
||||||
|
bg-gray-50 dark:bg-neutral-950
|
||||||
|
rounded-t-2xl shadow-xl p-6"
|
||||||
|
>
|
||||||
|
<div class="flex items-center justify-between mb-4">
|
||||||
|
<span
|
||||||
|
class="text-sm font-semibold uppercase tracking-wider
|
||||||
|
text-gray-500 dark:text-neutral-500"
|
||||||
|
>
|
||||||
|
Jump to Section
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
class="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-neutral-800"
|
||||||
|
data-toc-close
|
||||||
|
aria-label="Close"
|
||||||
|
>
|
||||||
|
<Icon name="heroicons:x-mark" class="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav class="space-y-1">
|
||||||
|
{items.map((item) => <TocLink item={item} />)}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { initActiveSectionTracker } from "../scripts/activeSectionTracker";
|
||||||
|
|
||||||
|
function initSpecSidebar() {
|
||||||
|
// Active section tracking for sidebar links
|
||||||
|
initActiveSectionTracker({
|
||||||
|
linkSelector: "[data-sidebar-link]",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Mobile TOC drawer
|
||||||
|
const toggleBtn = document.getElementById("spec-toc-toggle");
|
||||||
|
const drawer = document.getElementById("spec-toc-drawer");
|
||||||
|
const backdrop = drawer?.querySelector("[data-toc-backdrop]");
|
||||||
|
const closeBtn = drawer?.querySelector("[data-toc-close]");
|
||||||
|
const tocLinks = drawer?.querySelectorAll("[data-toc-link]");
|
||||||
|
|
||||||
|
function openDrawer() {
|
||||||
|
drawer?.classList.remove("hidden");
|
||||||
|
document.body.style.overflow = "hidden";
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeDrawer() {
|
||||||
|
drawer?.classList.add("hidden");
|
||||||
|
document.body.style.overflow = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleBtn?.addEventListener("click", openDrawer);
|
||||||
|
backdrop?.addEventListener("click", closeDrawer);
|
||||||
|
closeBtn?.addEventListener("click", closeDrawer);
|
||||||
|
tocLinks?.forEach((link) => {
|
||||||
|
link.addEventListener("click", closeDrawer);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show/hide mobile toggle based on spec section visibility
|
||||||
|
const specSection = document.getElementById("spec");
|
||||||
|
if (specSection && toggleBtn) {
|
||||||
|
const specObserver = new IntersectionObserver(
|
||||||
|
([entry]) => {
|
||||||
|
toggleBtn.classList.toggle("hidden", !entry.isIntersecting);
|
||||||
|
},
|
||||||
|
{ threshold: 0 },
|
||||||
|
);
|
||||||
|
specObserver.observe(specSection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize on load
|
||||||
|
initSpecSidebar();
|
||||||
|
|
||||||
|
// Re-initialize on Astro page transitions
|
||||||
|
document.addEventListener("astro:after-swap", initSpecSidebar);
|
||||||
|
</script>
|
||||||
126
src/components/ThemeToggle.astro
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
---
|
||||||
|
import { Icon } from "astro-icon/components";
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="relative group">
|
||||||
|
<button
|
||||||
|
data-theme-toggle
|
||||||
|
type="button"
|
||||||
|
class="p-2 rounded-lg cursor-pointer transition-colors duration-200
|
||||||
|
text-gray-500 dark:text-neutral-500
|
||||||
|
hover:text-gray-950 dark:hover:text-neutral-50
|
||||||
|
hover:bg-gray-100 dark:hover:bg-neutral-800"
|
||||||
|
aria-label="Toggle theme"
|
||||||
|
>
|
||||||
|
<Icon name="heroicons:sun" data-theme-icon="light" class="hidden w-5 h-5" />
|
||||||
|
<Icon name="heroicons:moon" data-theme-icon="dark" class="hidden w-5 h-5" />
|
||||||
|
<Icon name="sun-moon" data-theme-icon="auto" class="hidden w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
<!-- Tooltip -->
|
||||||
|
<div
|
||||||
|
class="absolute left-1/2 -translate-x-1/2 top-full mt-2
|
||||||
|
px-2 py-1 text-xs font-medium whitespace-nowrap rounded-md shadow-sm
|
||||||
|
bg-gray-900 text-white dark:bg-white dark:text-gray-900
|
||||||
|
opacity-0 group-hover:opacity-100
|
||||||
|
transition-opacity duration-200 pointer-events-none"
|
||||||
|
>
|
||||||
|
<span data-tooltip-text="light" class="hidden">Light</span>
|
||||||
|
<span data-tooltip-text="dark" class="hidden">Dark</span>
|
||||||
|
<span data-tooltip-text="auto" class="hidden">System</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
type ThemeMode = "light" | "dark" | "auto";
|
||||||
|
|
||||||
|
function initTheme() {
|
||||||
|
const toggles = document.querySelectorAll(
|
||||||
|
"[data-theme-toggle]",
|
||||||
|
) as NodeListOf<HTMLButtonElement>;
|
||||||
|
|
||||||
|
function getStoredMode(): ThemeMode {
|
||||||
|
const stored = localStorage.getItem("theme");
|
||||||
|
if (stored === "dark" || stored === "light" || stored === "auto") {
|
||||||
|
return stored;
|
||||||
|
}
|
||||||
|
return "auto";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSystemTheme(): "dark" | "light" {
|
||||||
|
return window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||||
|
? "dark"
|
||||||
|
: "light";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEffectiveTheme(mode: ThemeMode): "dark" | "light" {
|
||||||
|
if (mode === "auto") {
|
||||||
|
return getSystemTheme();
|
||||||
|
}
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateAllIcons(mode: ThemeMode) {
|
||||||
|
document.querySelectorAll("[data-theme-icon]").forEach((icon) => {
|
||||||
|
const iconMode = (icon as HTMLElement).dataset.themeIcon;
|
||||||
|
icon.classList.toggle("hidden", iconMode !== mode);
|
||||||
|
});
|
||||||
|
document.querySelectorAll("[data-tooltip-text]").forEach((text) => {
|
||||||
|
const textMode = (text as HTMLElement).dataset.tooltipText;
|
||||||
|
text.classList.toggle("hidden", textMode !== mode);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyTheme(mode: ThemeMode) {
|
||||||
|
const effective = getEffectiveTheme(mode);
|
||||||
|
if (effective === "dark") {
|
||||||
|
document.documentElement.classList.add("dark");
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove("dark");
|
||||||
|
}
|
||||||
|
updateAllIcons(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMode(mode: ThemeMode) {
|
||||||
|
localStorage.setItem("theme", mode);
|
||||||
|
applyTheme(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cycleMode(): ThemeMode {
|
||||||
|
const current = getStoredMode();
|
||||||
|
// Cycle: light → dark → auto → light
|
||||||
|
if (current === "light") return "dark";
|
||||||
|
if (current === "dark") return "auto";
|
||||||
|
return "light";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize theme
|
||||||
|
const currentMode = getStoredMode();
|
||||||
|
applyTheme(currentMode);
|
||||||
|
|
||||||
|
// Attach click handlers to all toggle buttons
|
||||||
|
toggles.forEach((toggle) => {
|
||||||
|
// Avoid adding duplicate listeners
|
||||||
|
if (toggle.dataset.initialized) return;
|
||||||
|
toggle.dataset.initialized = "true";
|
||||||
|
|
||||||
|
toggle.addEventListener("click", () => {
|
||||||
|
setMode(cycleMode());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Listen for system preference changes (only affects auto mode)
|
||||||
|
window
|
||||||
|
.matchMedia("(prefers-color-scheme: dark)")
|
||||||
|
.addEventListener("change", () => {
|
||||||
|
if (getStoredMode() === "auto") {
|
||||||
|
applyTheme("auto");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run on initial load
|
||||||
|
initTheme();
|
||||||
|
|
||||||
|
// Re-run on Astro page transitions
|
||||||
|
document.addEventListener("astro:after-swap", initTheme);
|
||||||
|
</script>
|
||||||
34
src/components/TocLink.astro
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
---
|
||||||
|
import type { TocItem } from "../utils/parseSpecContent";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
item: TocItem;
|
||||||
|
trackActive?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { item, trackActive = false } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<a
|
||||||
|
href={`#${item.id}`}
|
||||||
|
class:list={[
|
||||||
|
"sidebar-link block py-2 px-4 text-sm rounded-md",
|
||||||
|
"transition-colors",
|
||||||
|
"text-gray-500 hover:text-gray-950 hover:bg-gray-100",
|
||||||
|
"dark:text-neutral-500 dark:hover:text-neutral-50 dark:hover:bg-neutral-800",
|
||||||
|
item.level === 3 && "pl-6 text-[0.8125rem]",
|
||||||
|
item.clause && "flex",
|
||||||
|
]}
|
||||||
|
data-sidebar-link={trackActive ? "" : undefined}
|
||||||
|
data-section-id={trackActive ? item.id : undefined}
|
||||||
|
data-toc-link={!trackActive ? "" : undefined}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
item.clause && (
|
||||||
|
<span class="shrink-0 w-6 text-gray-400 dark:text-neutral-600">
|
||||||
|
{item.clause}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
<span>{item.title}</span>
|
||||||
|
</a>
|
||||||
136
src/components/VersionSelector.astro
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
---
|
||||||
|
import { Icon } from "astro-icon/components";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
currentVersion: string;
|
||||||
|
versions: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const { currentVersion, versions } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="relative" data-version-selector>
|
||||||
|
<!-- Trigger button -->
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
data-version-trigger
|
||||||
|
aria-haspopup="listbox"
|
||||||
|
aria-expanded="false"
|
||||||
|
class="flex items-center gap-1.5 px-2.5 py-1.5 text-sm font-mono
|
||||||
|
border border-gray-200 dark:border-neutral-700
|
||||||
|
rounded-md bg-transparent cursor-pointer transition-colors
|
||||||
|
text-gray-600 dark:text-neutral-400
|
||||||
|
hover:border-sky-600 hover:text-gray-950 dark:hover:text-neutral-50"
|
||||||
|
>
|
||||||
|
<span>v{currentVersion}</span>
|
||||||
|
<Icon
|
||||||
|
name="heroicons:chevron-down"
|
||||||
|
data-arrow-icon
|
||||||
|
class="w-3.5 h-3.5 transition-transform duration-150"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Dropdown menu -->
|
||||||
|
<div
|
||||||
|
data-version-dropdown
|
||||||
|
role="listbox"
|
||||||
|
aria-label="Select version"
|
||||||
|
class="absolute top-full left-0 mt-2 w-max min-w-full p-1.5 z-50
|
||||||
|
bg-gray-50 dark:bg-neutral-900
|
||||||
|
border border-gray-200 dark:border-neutral-700
|
||||||
|
rounded-lg shadow-lg
|
||||||
|
opacity-0 invisible -translate-y-1 transition-all duration-150
|
||||||
|
data-[open]:opacity-100 data-[open]:visible data-[open]:translate-y-0"
|
||||||
|
>
|
||||||
|
{
|
||||||
|
versions.map((v) => (
|
||||||
|
<a
|
||||||
|
href={`/spec/${v}`}
|
||||||
|
role="option"
|
||||||
|
aria-selected={v === currentVersion}
|
||||||
|
class:list={[
|
||||||
|
"block px-3 py-2 font-mono text-sm rounded transition-colors",
|
||||||
|
"text-gray-600 dark:text-neutral-400",
|
||||||
|
"hover:bg-gray-100 dark:hover:bg-neutral-800",
|
||||||
|
"hover:text-gray-950 dark:hover:text-neutral-50",
|
||||||
|
v === currentVersion && [
|
||||||
|
"bg-sky-500/15 dark:bg-sky-500/20",
|
||||||
|
"text-sky-600 dark:text-sky-400",
|
||||||
|
],
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
v{v}
|
||||||
|
</a>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function initVersionSelectors() {
|
||||||
|
const selectors = document.querySelectorAll("[data-version-selector]");
|
||||||
|
|
||||||
|
selectors.forEach((selector) => {
|
||||||
|
if ((selector as HTMLElement).dataset.initialized) return;
|
||||||
|
(selector as HTMLElement).dataset.initialized = "true";
|
||||||
|
|
||||||
|
const trigger = selector.querySelector(
|
||||||
|
"[data-version-trigger]",
|
||||||
|
) as HTMLButtonElement;
|
||||||
|
const dropdown = selector.querySelector(
|
||||||
|
"[data-version-dropdown]",
|
||||||
|
) as HTMLElement;
|
||||||
|
const arrow = selector.querySelector("[data-arrow-icon]") as HTMLElement;
|
||||||
|
|
||||||
|
if (!trigger || !dropdown) return;
|
||||||
|
|
||||||
|
const open = () => {
|
||||||
|
dropdown.dataset.open = "true";
|
||||||
|
trigger.setAttribute("aria-expanded", "true");
|
||||||
|
if (arrow) arrow.style.transform = "rotate(180deg)";
|
||||||
|
};
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
delete dropdown.dataset.open;
|
||||||
|
trigger.setAttribute("aria-expanded", "false");
|
||||||
|
if (arrow) arrow.style.transform = "";
|
||||||
|
};
|
||||||
|
|
||||||
|
trigger.addEventListener("click", (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
const isOpen = dropdown.dataset.open === "true";
|
||||||
|
|
||||||
|
// Close all other dropdowns
|
||||||
|
document
|
||||||
|
.querySelectorAll("[data-version-dropdown][data-open]")
|
||||||
|
.forEach((d) => {
|
||||||
|
delete (d as HTMLElement).dataset.open;
|
||||||
|
const t = d.previousElementSibling as HTMLElement;
|
||||||
|
t?.setAttribute("aria-expanded", "false");
|
||||||
|
const a = t?.querySelector("[data-arrow-icon]") as HTMLElement;
|
||||||
|
if (a) a.style.transform = "";
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isOpen) {
|
||||||
|
close();
|
||||||
|
} else {
|
||||||
|
open();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("click", (e) => {
|
||||||
|
if (!selector.contains(e.target as Node)) close();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("keydown", (e) => {
|
||||||
|
if (e.key === "Escape" && dropdown.dataset.open === "true") {
|
||||||
|
close();
|
||||||
|
trigger.focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
initVersionSelectors();
|
||||||
|
document.addEventListener("astro:after-swap", initVersionSelectors);
|
||||||
|
</script>
|
||||||
38
src/config.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
export const config = {
|
||||||
|
title: "Git Common-Flow",
|
||||||
|
description:
|
||||||
|
"An attempt to gather a sensible selection of the most common usage " +
|
||||||
|
"patterns of git into a single and concise specification.",
|
||||||
|
author: "Jim Myhrberg",
|
||||||
|
authorUrl: "https://jimeh.me/",
|
||||||
|
hostname: "commonflow.org",
|
||||||
|
url: "https://commonflow.org",
|
||||||
|
repoUrl: "https://github.com/jimeh/common-flow",
|
||||||
|
license: {
|
||||||
|
name: "CC BY 4.0",
|
||||||
|
url: "https://creativecommons.org/licenses/by/4.0/",
|
||||||
|
},
|
||||||
|
|
||||||
|
// Optional override for current version (null = auto-detect from specs)
|
||||||
|
currentVersionOverride: null as string | null,
|
||||||
|
|
||||||
|
// Used by update script
|
||||||
|
update: {
|
||||||
|
repository: "jimeh/common-flow",
|
||||||
|
urlTemplate:
|
||||||
|
"https://github.com/jimeh/common-flow/raw/{{version}}/{{file}}",
|
||||||
|
files: {
|
||||||
|
document: "common-flow.md",
|
||||||
|
diagram: "common-flow.svg",
|
||||||
|
},
|
||||||
|
// Version discovery settings
|
||||||
|
discovery: {
|
||||||
|
// Prerelease types to include (stable versions are always included)
|
||||||
|
includePrereleaseTypes: ["rc"] as string[],
|
||||||
|
// Explicit versions to exclude
|
||||||
|
excludeVersions: [] as string[],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type Config = typeof config;
|
||||||
17
src/content.config.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { defineCollection, z } from "astro:content";
|
||||||
|
import { glob } from "astro/loaders";
|
||||||
|
|
||||||
|
const spec = defineCollection({
|
||||||
|
loader: glob({
|
||||||
|
pattern: "**/*.md",
|
||||||
|
base: "./src/content/spec",
|
||||||
|
// Use filename (without extension) as ID to preserve version strings
|
||||||
|
generateId: ({ entry }) => entry.replace(/\.md$/, ""),
|
||||||
|
}),
|
||||||
|
schema: z.object({
|
||||||
|
title: z.string(),
|
||||||
|
version: z.string(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const collections = { spec };
|
||||||
@@ -3,9 +3,7 @@ title: Git Common-Flow 1.0.0-rc.1
|
|||||||
version: 1.0.0-rc.1
|
version: 1.0.0-rc.1
|
||||||
---
|
---
|
||||||
Git Common-Flow 1.0.0-rc.1
|
Git Common-Flow 1.0.0-rc.1
|
||||||
==============================
|
==========================
|
||||||
|
|
||||||
<img src="/spec/1.0.0-rc.1.svg" width="100%" />
|
|
||||||
|
|
||||||
Summary
|
Summary
|
||||||
-------
|
-------
|
||||||
@@ -181,4 +179,3 @@ License
|
|||||||
-------
|
-------
|
||||||
|
|
||||||
[Creative Commons - CC BY 3.0](http://creativecommons.org/licenses/by/3.0/)
|
[Creative Commons - CC BY 3.0](http://creativecommons.org/licenses/by/3.0/)
|
||||||
|
|
||||||
1
src/content/spec/1.0.0-rc.1.svg
Normal file
|
After Width: | Height: | Size: 13 KiB |
@@ -1,11 +1,9 @@
|
|||||||
---
|
---
|
||||||
title: Git Common-Flow 1.0.0-rc.2
|
title: Git Common-Flow v1.0.0-rc.2
|
||||||
version: 1.0.0-rc.2
|
version: 1.0.0-rc.2
|
||||||
---
|
---
|
||||||
Git Common-Flow 1.0.0-rc.2
|
Git Common-Flow v1.0.0-rc.2
|
||||||
==============================
|
===========================
|
||||||
|
|
||||||
<img src="/spec/1.0.0-rc.2.svg" width="100%" />
|
|
||||||
|
|
||||||
Summary
|
Summary
|
||||||
-------
|
-------
|
||||||
@@ -206,4 +204,3 @@ License
|
|||||||
-------
|
-------
|
||||||
|
|
||||||
[Creative Commons - CC BY 3.0](http://creativecommons.org/licenses/by/3.0/)
|
[Creative Commons - CC BY 3.0](http://creativecommons.org/licenses/by/3.0/)
|
||||||
|
|
||||||
1
src/content/spec/1.0.0-rc.2.svg
Normal file
|
After Width: | Height: | Size: 13 KiB |
@@ -1,12 +1,10 @@
|
|||||||
---
|
---
|
||||||
title: Git Common-Flow 1.0.0-rc.3
|
title: Git Common-Flow v1.0.0-rc.3
|
||||||
version: 1.0.0-rc.3
|
version: 1.0.0-rc.3
|
||||||
---
|
---
|
||||||
Git Common-Flow 1.0.0-rc.3
|
Git Common-Flow v1.0.0-rc.3
|
||||||
===========================
|
===========================
|
||||||
|
|
||||||
<img src="/spec/1.0.0-rc.3.svg" width="100%" />
|
|
||||||
|
|
||||||
Summary
|
Summary
|
||||||
-------
|
-------
|
||||||
|
|
||||||
@@ -212,4 +210,3 @@ License
|
|||||||
-------
|
-------
|
||||||
|
|
||||||
[Creative Commons - CC BY 3.0](http://creativecommons.org/licenses/by/3.0/)
|
[Creative Commons - CC BY 3.0](http://creativecommons.org/licenses/by/3.0/)
|
||||||
|
|
||||||
1
src/content/spec/1.0.0-rc.3.svg
Normal file
|
After Width: | Height: | Size: 13 KiB |
@@ -1,12 +1,10 @@
|
|||||||
---
|
---
|
||||||
title: Git Common-Flow 1.0.0-rc.4
|
title: Git Common-Flow v1.0.0-rc.4
|
||||||
version: 1.0.0-rc.4
|
version: 1.0.0-rc.4
|
||||||
---
|
---
|
||||||
Git Common-Flow 1.0.0-rc.4
|
Git Common-Flow v1.0.0-rc.4
|
||||||
===========================
|
===========================
|
||||||
|
|
||||||
<img src="/spec/1.0.0-rc.4.svg" width="100%" />
|
|
||||||
|
|
||||||
Summary
|
Summary
|
||||||
-------
|
-------
|
||||||
|
|
||||||
@@ -338,4 +336,3 @@ License
|
|||||||
-------
|
-------
|
||||||
|
|
||||||
[Creative Commons - CC BY 3.0](http://creativecommons.org/licenses/by/3.0/)
|
[Creative Commons - CC BY 3.0](http://creativecommons.org/licenses/by/3.0/)
|
||||||
|
|
||||||
1
src/content/spec/1.0.0-rc.4.svg
Normal file
|
After Width: | Height: | Size: 13 KiB |
@@ -1,25 +1,37 @@
|
|||||||
---
|
---
|
||||||
title: Git Common-Flow 1.0.0-rc.4
|
title: Git Common-Flow v1.0.0-rc.5
|
||||||
version: 1.0.0-rc.4
|
version: 1.0.0-rc.5
|
||||||
---
|
---
|
||||||
Git Common-Flow 1.0.0-rc.4
|
Git Common-Flow v1.0.0-rc.5
|
||||||
===========================
|
===========================
|
||||||
|
|
||||||
<img src="/spec/1.0.0-rc.4.svg" width="100%" />
|
Introduction
|
||||||
|
------------
|
||||||
Summary
|
|
||||||
-------
|
|
||||||
|
|
||||||
Common-Flow is an attempt to gather a sensible selection of the most common
|
Common-Flow is an attempt to gather a sensible selection of the most common
|
||||||
usage patterns of git into a single and concise specification. It is based on
|
usage patterns of git into a single and concise specification. It is based on
|
||||||
the [original variant](http://scottchacon.com/2011/08/31/github-flow.html)
|
the [original variant](http://scottchacon.com/2011/08/31/github-flow.html)
|
||||||
of [GitHub Flow](https://guides.github.com/introduction/flow/), while taking
|
of [GitHub Flow](https://guides.github.com/introduction/flow/), while taking
|
||||||
into account how a lot of open source projects use git.
|
into account how a lot of open source projects most commonly use git.
|
||||||
|
|
||||||
In short, Common-Flow is essentially GitHub Flow with the addition of versioned
|
In short, Common-Flow is essentially GitHub Flow with the addition of versioned
|
||||||
releases, optional release branches, and without the requirement to deploy to
|
releases, optional release branches, and without the requirement to deploy to
|
||||||
production all the time.
|
production all the time.
|
||||||
|
|
||||||
|
Summary
|
||||||
|
-------
|
||||||
|
|
||||||
|
- The "master" branch is the mainline branch with latest changes, and must not
|
||||||
|
be broken.
|
||||||
|
- Changes (features, bugfixes, etc.) are done on "change branches" created from
|
||||||
|
the master branch.
|
||||||
|
- Rebase change branches [early and often](https://i.imgur.com/1RS8x2d.png).
|
||||||
|
- When a change branch is stable and ready, it is merged back in to master.
|
||||||
|
- A release is just a git tag who's name is the exact release version string
|
||||||
|
(e.g. "2.11.4").
|
||||||
|
- Release branches can be used to avoid change freezes on master. They are not
|
||||||
|
required, instead they are available if you need them.
|
||||||
|
|
||||||
Terminology
|
Terminology
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
@@ -48,7 +60,7 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD",
|
|||||||
interpreted as described in [RFC 2119](https://tools.ietf.org/html/rfc2119).
|
interpreted as described in [RFC 2119](https://tools.ietf.org/html/rfc2119).
|
||||||
|
|
||||||
1. TL;DR
|
1. TL;DR
|
||||||
1. Don't break the master branch.
|
1. Do not break the master branch.
|
||||||
2. A release is a git tag.
|
2. A release is a git tag.
|
||||||
2. The Master Branch
|
2. The Master Branch
|
||||||
1. A branch named "master" MUST exist and it MUST be referred to as the
|
1. A branch named "master" MUST exist and it MUST be referred to as the
|
||||||
@@ -108,8 +120,9 @@ interpreted as described in [RFC 2119](https://tools.ietf.org/html/rfc2119).
|
|||||||
happy with the change. This is especially important if the merge target
|
happy with the change. This is especially important if the merge target
|
||||||
is the master branch.
|
is the master branch.
|
||||||
5. To get feedback, help, or generally just discuss a change branch with
|
5. To get feedback, help, or generally just discuss a change branch with
|
||||||
others, the RECOMMENDED way to do so is by creating a pull request and
|
others, it is RECOMMENDED you create a pull request and discuss the
|
||||||
discuss the changes with others there.
|
changes with others there. This leaves a clear and visible history of
|
||||||
|
how, when, and why the code looks and behaves the way it does.
|
||||||
5. Versioning
|
5. Versioning
|
||||||
1. A "version string" is a typically mostly numeric string that identifies a
|
1. A "version string" is a typically mostly numeric string that identifies a
|
||||||
specific version of a project. The version string itself MUST NOT have a
|
specific version of a project. The version string itself MUST NOT have a
|
||||||
@@ -197,8 +210,8 @@ interpreted as described in [RFC 2119](https://tools.ietf.org/html/rfc2119).
|
|||||||
MUST be created from the relevant release tag. For example if the master
|
MUST be created from the relevant release tag. For example if the master
|
||||||
branch is on version 2.11.4 and there is a security fix for all 2.9.x
|
branch is on version 2.11.4 and there is a security fix for all 2.9.x
|
||||||
releases, the latest of which is "2.9.7". Create a new branch called
|
releases, the latest of which is "2.9.7". Create a new branch called
|
||||||
"release-2.9" off of the "2.9.7" release tag. The security fix release
|
"release-2.9" from the "2.9.7" release tag. The security fix release will
|
||||||
will then end up being version "2.9.8".
|
then end up being version "2.9.8".
|
||||||
5. To create a new release from a long-term release branch, you MUST follow
|
5. To create a new release from a long-term release branch, you MUST follow
|
||||||
the same process as a release from the master branch, except the
|
the same process as a release from the master branch, except the
|
||||||
long-term release branch takes the place of the master branch.
|
long-term release branch takes the place of the master branch.
|
||||||
@@ -252,7 +265,7 @@ really change much:
|
|||||||
|
|
||||||
- You create change branches instead of feature branches, without the need of a
|
- You create change branches instead of feature branches, without the need of a
|
||||||
"feature/" or "change/" prefix in the branch name.
|
"feature/" or "change/" prefix in the branch name.
|
||||||
- Change branches are typically created off of and merged back into "master"
|
- Change branches are typically created from and merged back into "master"
|
||||||
instead of "develop".
|
instead of "develop".
|
||||||
- Creating a release is done by simply creating a git tag, typically on the
|
- Creating a release is done by simply creating a git tag, typically on the
|
||||||
master branch.
|
master branch.
|
||||||
@@ -301,12 +314,18 @@ is and what it does. Here's a few examples:
|
|||||||
- remove-sort-by-middle-name-functionality
|
- remove-sort-by-middle-name-functionality
|
||||||
- update-font-awesome
|
- update-font-awesome
|
||||||
- change-search-behavior
|
- change-search-behavior
|
||||||
|
- improve-pagination-performance
|
||||||
- tweak-footer-style
|
- tweak-footer-style
|
||||||
|
|
||||||
Notice how none of these have any prefixes like "feature/" or "hotfix/", they're
|
Notice how none of these have any prefixes like "feature/" or "hotfix/", they're
|
||||||
not needed when branch names are properly descriptive. However there's nothing
|
not needed when branch names are properly descriptive. However there's nothing
|
||||||
to say you can't use such prefixes if you want. That also means that you can add
|
to say you can't use such prefixes if you want.
|
||||||
ticket number prefixes if your team/org has that as part of it's process.
|
|
||||||
|
You can also add ticket numbers to the branch name if your team/org has that as
|
||||||
|
part of it's process. But it is recommended that ticket numbers are added to the
|
||||||
|
end of the branch name. The ticket number is essentially metadata, so put it at
|
||||||
|
the end and out of the way of humans trying to read the descriptive name from
|
||||||
|
left to right.
|
||||||
|
|
||||||
### How do we release an emergency hotfix when the master branch is broken?
|
### How do we release an emergency hotfix when the master branch is broken?
|
||||||
|
|
||||||
@@ -329,7 +348,7 @@ About
|
|||||||
-----
|
-----
|
||||||
|
|
||||||
The Git Common-Flow specification is authored
|
The Git Common-Flow specification is authored
|
||||||
by [Jim Myhrberg](http://jimeh.me).
|
by [Jim Myhrberg](https://jimeh.me/).
|
||||||
|
|
||||||
If you'd like to leave feedback,
|
If you'd like to leave feedback,
|
||||||
please [open an issue on GitHub](https://github.com/jimeh/common-flow/issues).
|
please [open an issue on GitHub](https://github.com/jimeh/common-flow/issues).
|
||||||
@@ -337,5 +356,4 @@ please [open an issue on GitHub](https://github.com/jimeh/common-flow/issues).
|
|||||||
License
|
License
|
||||||
-------
|
-------
|
||||||
|
|
||||||
[Creative Commons - CC BY 3.0](http://creativecommons.org/licenses/by/3.0/)
|
[Creative Commons - CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)
|
||||||
|
|
||||||
1
src/content/spec/1.0.0-rc.5.svg
Normal file
|
After Width: | Height: | Size: 13 KiB |
380
src/content/spec/2.0.0.md
Normal file
@@ -0,0 +1,380 @@
|
|||||||
|
---
|
||||||
|
title: Git Common-Flow v2.0.0
|
||||||
|
version: 2.0.0
|
||||||
|
---
|
||||||
|
# Git Common-Flow v2.0.0
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
Common-Flow is an attempt to gather a sensible selection of the most common
|
||||||
|
usage patterns of git into a single and concise specification. It is based on
|
||||||
|
the [original variant](https://scottchacon.com/2011/08/31/github-flow/) of
|
||||||
|
[GitHub Flow](https://docs.github.com/en/get-started/using-github/github-flow),
|
||||||
|
while taking into account how a lot of open source projects most commonly use
|
||||||
|
git.
|
||||||
|
|
||||||
|
In short, Common-Flow is essentially GitHub Flow with the addition of versioned
|
||||||
|
releases, optional release branches, and without the requirement to deploy to
|
||||||
|
production all the time.
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
- The "main" branch is the mainline branch with latest changes, and must not be
|
||||||
|
broken.
|
||||||
|
- Changes (features, bugfixes, etc.) are done on "change branches" created from
|
||||||
|
the main branch.
|
||||||
|
- Rebase change branches early and often.
|
||||||
|
- When a change branch is stable and ready, it is merged back in to main.
|
||||||
|
- A release is just a git tag who's name is the exact release version string
|
||||||
|
(e.g. "2.11.4" or "v2.11.4").
|
||||||
|
- Release branches can be used when the release process and verification might
|
||||||
|
be lengthy, allowing main to remain open for new changes. They are not
|
||||||
|
required, instead they are available if you need them.
|
||||||
|
|
||||||
|
## Terminology
|
||||||
|
|
||||||
|
- **Main Branch** - Must be named "main", must always have passing tests, and is
|
||||||
|
not guaranteed to always work in production environments.
|
||||||
|
- **Change Branches** - Any branch that introduces changes like a new feature, a
|
||||||
|
bugfix, etc.
|
||||||
|
- **Source Branch** - The branch that a change branch was created from. New
|
||||||
|
changes in the source branch should be incorporated into the change branch via
|
||||||
|
rebasing.
|
||||||
|
- **Merge Target** - A branch that is the intended merge target for a change
|
||||||
|
branch. Typically the merge target branch will be the same as the source
|
||||||
|
branch.
|
||||||
|
- **Pull Request** - A means of requesting that a change branch is merged in to
|
||||||
|
its merge target, allowing others to review, discuss and approve the changes.
|
||||||
|
- **Release** - May be considered safe to use in production environments. Is
|
||||||
|
effectively just a git tag named after the version of the release.
|
||||||
|
- **Release Branches** - Used both for short-term preparations of a release, and
|
||||||
|
for long-term maintenance of older versions.
|
||||||
|
|
||||||
|
## Git Common-Flow Specification (Common-Flow)
|
||||||
|
|
||||||
|
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD",
|
||||||
|
"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be
|
||||||
|
interpreted as described in [RFC
|
||||||
|
2119](https://datatracker.ietf.org/doc/html/rfc2119).
|
||||||
|
|
||||||
|
1. TL;DR
|
||||||
|
1. Do not break the main branch.
|
||||||
|
2. A release is a git tag.
|
||||||
|
2. The Main Branch
|
||||||
|
1. A branch named "main" MUST exist and it MUST be referred to as the "main
|
||||||
|
branch".
|
||||||
|
2. The main branch MUST always be in a non-broken state with its test suite
|
||||||
|
passing.
|
||||||
|
3. The main branch is not guaranteed to always work in production
|
||||||
|
environments. Despite test suites passing it may at times contain
|
||||||
|
unfinished work. Only releases may be considered safe for production use.
|
||||||
|
4. The main branch SHOULD always be in a "as near as possibly ready for
|
||||||
|
release/production" state to reduce any friction with creating a new
|
||||||
|
release.
|
||||||
|
3. Change Branches
|
||||||
|
1. Each change (feature, bugfix, etc.) MUST be performed on separate
|
||||||
|
branches that SHOULD be referred to as "change branches".
|
||||||
|
2. All change branches MUST have descriptive names.
|
||||||
|
3. It is RECOMMENDED that you commit often locally, and that you try and
|
||||||
|
keep the commits reasonably structured to avoid a messy and confusing git
|
||||||
|
history.
|
||||||
|
4. You SHOULD regularly push your work to the same named branch on the
|
||||||
|
remote server.
|
||||||
|
5. You SHOULD create separate change branches for each distinctly different
|
||||||
|
change. You SHOULD NOT include multiple unrelated changes into a single
|
||||||
|
change branch.
|
||||||
|
6. When a change branch is created, the branch that it is created from
|
||||||
|
SHOULD be referred to as the "source branch". Each change branch also
|
||||||
|
needs a designated "merge target" branch, typically this will be the same
|
||||||
|
as the source branch.
|
||||||
|
7. Change branches MUST be regularly updated with any changes from their
|
||||||
|
source branch. This MUST be done by rebasing the change branch on top of
|
||||||
|
the source branch.
|
||||||
|
8. After updating a change branch from its source branch you MUST push the
|
||||||
|
change branch to the remote server. Due to the nature of rebasing, you
|
||||||
|
will be required to do a force push, and you MUST use the
|
||||||
|
"--force-with-lease" git push option when doing so instead of the regular
|
||||||
|
"--force".
|
||||||
|
9. If there is a truly valid technical reason to not use rebase when
|
||||||
|
updating change branches, then you MAY update change branches via merge
|
||||||
|
instead of rebase. The decision to use merge MUST only be taken after all
|
||||||
|
possible options to use rebase have been tried and failed. People not
|
||||||
|
understanding how to use rebase is NOT a valid reason to use merge. If
|
||||||
|
you do decide to use merge instead of rebase, you MUST NOT use a mixture
|
||||||
|
of both methods.
|
||||||
|
4. Pull Requests
|
||||||
|
1. To merge a change branch into its merge target, you MUST open a "pull
|
||||||
|
request" (or equivalent).
|
||||||
|
2. The purpose of a pull request is to allow others to review your changes
|
||||||
|
and give feedback. You can then fix any issues, complaints, and more that
|
||||||
|
might arise, and then let people review again.
|
||||||
|
3. Before creating a pull request, it is RECOMMENDED that you consider the
|
||||||
|
state of your change branch's commit history. If it is messy and
|
||||||
|
confusing, it might be a good idea to rebase your branch with "git rebase
|
||||||
|
-i" to present a cleaner and easier to follow commit history for your
|
||||||
|
reviewers.
|
||||||
|
4. A pull request MUST only be merged when the change branch is up-to-date
|
||||||
|
with its source branch, the test suite and other CI checks are passing,
|
||||||
|
and you and others are happy with the changes. This is especially
|
||||||
|
important if the merge target is the main branch.
|
||||||
|
5. To get feedback, help, or generally just discuss a change branch with
|
||||||
|
others, it is RECOMMENDED you create a draft pull request and discuss the
|
||||||
|
changes with others there. This leaves a clear and visible history of
|
||||||
|
how, when, and why the code looks and behaves the way it does.
|
||||||
|
5. Git Best Practices
|
||||||
|
1. It is RECOMMENDED that all commit messages follow the Conventional
|
||||||
|
Commits specification (<https://www.conventionalcommits.org/>). This
|
||||||
|
provides a structured format that integrates well with Semantic
|
||||||
|
Versioning, and enables automated changelog generation. At minimum,
|
||||||
|
commit messages SHOULD follow the Commit Guidelines from the official git
|
||||||
|
documentation:
|
||||||
|
<https://git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project#_commit_guidelines>
|
||||||
|
2. You SHOULD always use "--force-with-lease" when doing a force push. The
|
||||||
|
regular "--force" option is dangerous and destructive. More information:
|
||||||
|
<https://www.codestudy.net/blog/git-push-force-with-lease-vs-force/>
|
||||||
|
3. You SHOULD understand and be comfortable with rebasing:
|
||||||
|
<https://git-scm.com/book/en/v2/Git-Branching-Rebasing>
|
||||||
|
4. It is RECOMMENDED that you always do "git pull --rebase" instead of "git
|
||||||
|
pull" to avoid unnecessary merge commits. You can make this the default
|
||||||
|
behavior of "git pull" with "git config --global pull.rebase true".
|
||||||
|
5. When using Conventional Commits, it is RECOMMENDED to use tooling to
|
||||||
|
automate version bumping and generate changelogs from commit messages.
|
||||||
|
This pairs well with the release process and ensures changelogs are
|
||||||
|
consistent and complete.
|
||||||
|
6. Versioning
|
||||||
|
1. A "version string" is a typically mostly numeric string that identifies a
|
||||||
|
specific version of a project. The version string itself MUST NOT have a
|
||||||
|
"v" prefix, but the version string can be displayed with a "v" prefix.
|
||||||
|
2. The source of truth for a project's version MUST be a git tag with a name
|
||||||
|
based on the version string. This kind of tag MUST be referred to as a
|
||||||
|
"release tag".
|
||||||
|
3. It is OPTIONAL, but RECOMMENDED to also keep the version string
|
||||||
|
hard-coded somewhere in the project code-base.
|
||||||
|
4. If you hard-code the version string into the code-base, it is RECOMMENDED
|
||||||
|
that you do so in a file called "VERSION" located in the root of the
|
||||||
|
project. But be mindful of the conventions of your programming language
|
||||||
|
and community when choosing if, where and how to hard-code the version
|
||||||
|
string.
|
||||||
|
5. If you are using a "VERSION" file in the root of the project, this file
|
||||||
|
MUST only contain the exact version string, meaning it MUST NOT have a
|
||||||
|
"v" prefix. For example, "v2.11.4" is bad, and "2.11.4" is good.
|
||||||
|
6. It is OPTIONAL, but RECOMMENDED that the version string follows Semantic
|
||||||
|
Versioning (<http://semver.org/>).
|
||||||
|
7. Releases
|
||||||
|
1. To create a new release, you MUST create a git tag named as the exact
|
||||||
|
version string of the release. This kind of tag MUST be referred to as a
|
||||||
|
"release tag".
|
||||||
|
2. The release tag name can OPTIONALLY be prefixed with "v". For example,
|
||||||
|
the tag name can be either "2.11.4" or "v2.11.4". Note that this "v"
|
||||||
|
prefix is only for the tag name itself, the version string (as defined in
|
||||||
|
section 6.1) MUST NOT have a "v" prefix.
|
||||||
|
3. If the version string is hard-coded into the code-base, you MUST create a
|
||||||
|
"version bump" commit which changes the hard-coded version string of the
|
||||||
|
project.
|
||||||
|
4. When using version bump commits, the release tag MUST be placed on the
|
||||||
|
version bump commit, unless using a release pull request.
|
||||||
|
5. It is OPTIONAL to use a "release pull request" to propose a release. A
|
||||||
|
release pull request contains the version bump commit and any
|
||||||
|
release-related changes (changelog updates, etc.). When using release
|
||||||
|
pull requests, the release tag SHOULD be placed on the resulting merge
|
||||||
|
commit.
|
||||||
|
6. If you are not using a release branch, then the release tag, and if
|
||||||
|
relevant the version bump commit, MUST be created directly on the main
|
||||||
|
branch.
|
||||||
|
7. If you are using Conventional Commits, the version bump commit MUST also
|
||||||
|
follow the format. For example, "chore(release): 2.11.4". Otherwise, a
|
||||||
|
simple "Bump version to 2.11.4" format is acceptable.
|
||||||
|
8. Release tags SHOULD be lightweight tags unless you need features that
|
||||||
|
annotated tags provide. Annotated tags allow you to include changelog
|
||||||
|
information in the tag itself, GPG sign the tag, or include additional
|
||||||
|
metadata like the tagger's name and email.
|
||||||
|
9. If you use annotated release tags, the first line of the annotation
|
||||||
|
SHOULD read "Release VERSION". For example for version "2.11.4" the first
|
||||||
|
line of the tag annotation SHOULD read "Release 2.11.4". The second line
|
||||||
|
MUST be blank, and the changelog SHOULD start on the third line.
|
||||||
|
10. It is OPTIONAL, but RECOMMENDED for high-security projects, to GPG sign
|
||||||
|
release tags. This provides cryptographic verification that the release
|
||||||
|
was created by a trusted party.
|
||||||
|
8. Short-Term Release Branches
|
||||||
|
1. Any branch that has a name starting with "release-" SHOULD be referred to
|
||||||
|
as a "release branch".
|
||||||
|
2. Any release branch which has a name ending with a specific version
|
||||||
|
string, MUST be referred to as a "short-term release branch".
|
||||||
|
3. Use of short-term release branches are OPTIONAL, and intended to be used
|
||||||
|
to create a specific versioned release.
|
||||||
|
4. A short-term release branch is RECOMMENDED if there is a lengthy release
|
||||||
|
verification process to avoid a code freeze on the main branch.
|
||||||
|
5. Short-term release branches MUST have a name of "release-VERSION". For
|
||||||
|
example for version "2.11.4" the release branch name MUST be
|
||||||
|
"release-2.11.4".
|
||||||
|
6. When using a short-term release branch to create a release, the version
|
||||||
|
bump commit if used, MUST be created on the short-term release branch.
|
||||||
|
The release tag MUST be placed on the version bump commit, or on the
|
||||||
|
merge commit when using a release pull request to merge the release
|
||||||
|
branch.
|
||||||
|
7. Only very minor changes SHOULD be performed on a short-term release
|
||||||
|
branch directly. Any larger changes SHOULD be done in the main branch,
|
||||||
|
and SHOULD be pulled into the release branch by rebasing it on top of the
|
||||||
|
main branch the same way a change branch pulls in updates from its source
|
||||||
|
branch.
|
||||||
|
8. After a release tag has been created, the release branch MUST be merged
|
||||||
|
back into its source branch and then deleted. Typically the source branch
|
||||||
|
will be the main branch.
|
||||||
|
9. Long-Term Release Branches
|
||||||
|
1. Any release branch which has a name ending with a nonspecific version
|
||||||
|
string, MUST be referred to as a "long-term release branch". For example,
|
||||||
|
"release-2.11" is a long-term release branch, while "release-2.11.4" is a
|
||||||
|
short-term release branch.
|
||||||
|
2. Use of long-term release branches are OPTIONAL, and intended for work on
|
||||||
|
versions which are not currently part of the main branch. Typically this
|
||||||
|
is useful when you need to create a new maintenance release for an older
|
||||||
|
version.
|
||||||
|
3. A long-term release branch MUST have a name with a nonspecific version
|
||||||
|
number. For example, a long-term release branch for creating new 2.9.x
|
||||||
|
releases MUST be named "release-2.9", or "release-2" for all 2.x.x
|
||||||
|
releases when main has moved to 3.x.x.
|
||||||
|
4. Long-term release branches for maintenance releases of older versions
|
||||||
|
MUST be created from the relevant release tag. For example, if the main
|
||||||
|
branch is on version 2.11.4 and there is a security fix for all 2.9.x
|
||||||
|
releases, the latest of which is "2.9.7". Create a new branch called
|
||||||
|
"release-2.9" from the "2.9.7" release tag. The security fix release will
|
||||||
|
then end up being version "2.9.8". Similarly, if main is on 3.x.x and you
|
||||||
|
need to maintain the entire 2.x.x line, create a "release-2" branch from
|
||||||
|
the latest 2.x.x release tag.
|
||||||
|
5. To create a new release from a long-term release branch, you MUST follow
|
||||||
|
the same process as a release from the main branch, except the long-term
|
||||||
|
release branch takes the place of the main branch.
|
||||||
|
6. A long-term release branch SHOULD be treated with the same respect as the
|
||||||
|
main branch. It is effectively the main branch for the release series in
|
||||||
|
question. Meaning it MUST always be in a non-broken state, MUST NOT be
|
||||||
|
force pushed to, etc.
|
||||||
|
10. Bug Fixes & Rollback
|
||||||
|
1. You MUST NOT under any circumstances force push to the main branch or to
|
||||||
|
long-term release branches.
|
||||||
|
2. If a change branch which has been merged into the main branch is found to
|
||||||
|
have a bug in it, the bugfix work MUST be done as a new separate change
|
||||||
|
branch. This new change branch MUST follow the same workflow as any other
|
||||||
|
change branch.
|
||||||
|
3. If a change branch is wrongfully merged into main, or for any other
|
||||||
|
reason the merge must be undone, you MUST undo the merge by reverting the
|
||||||
|
merge commit itself. Effectively creating a new commit that reverses all
|
||||||
|
the relevant changes.
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
### Why use Common-Flow instead of Git Flow, and how does it differ?
|
||||||
|
|
||||||
|
Common-Flow tries to be a lot less complicated than Git Flow by having fewer
|
||||||
|
types of branches, and simpler rules. Normal day to day development doesn't
|
||||||
|
really change much:
|
||||||
|
|
||||||
|
- You create change branches instead of feature branches, without the need of a
|
||||||
|
"feature/" or "change/" prefix in the branch name.
|
||||||
|
- Change branches are typically created from and merged back into "main" instead
|
||||||
|
of "develop".
|
||||||
|
- Creating a release is done by simply creating a git tag, typically on the main
|
||||||
|
branch.
|
||||||
|
|
||||||
|
In detail, the main differences between Git Flow and Common-Flow are:
|
||||||
|
|
||||||
|
- There is no "develop" branch, there is only a "main" branch which contains the
|
||||||
|
latest work. In Git Flow the main branch effectively ends up just being a
|
||||||
|
pointer to the latest release, despite the fact that Git Flow includes release
|
||||||
|
tags too. In Common-Flow you just look at the tags to find the latest release.
|
||||||
|
- There are no "feature" or "hotfix" branches, there's only "change" branches.
|
||||||
|
Any branch that is not main and introduces changes is a change branch. Change
|
||||||
|
branches also don't have an enforced naming convention, they just need to have
|
||||||
|
a "descriptive name". This makes things simpler and allows more flexibility.
|
||||||
|
- Release branches are available, but optional. Instead of enforcing the use of
|
||||||
|
release branches like Git Flow, Common-Flow only recommends the use of release
|
||||||
|
branches when it makes things easier. If creating a new release by tagging
|
||||||
|
"main" works for you, great, do that.
|
||||||
|
|
||||||
|
### Why use Common-Flow instead of GitHub Flow, and how does it differ?
|
||||||
|
|
||||||
|
Common-Flow is essentially GitHub Flow with the addition of a "Release" concept
|
||||||
|
that uses tags. It also attempts to define how certain common tasks are done,
|
||||||
|
like updating change/feature branches from their source branches for example.
|
||||||
|
This is to help end arguments about how such things are done.
|
||||||
|
|
||||||
|
If a deployment/release for you is just getting the latest code in the main
|
||||||
|
branch out without caring about bumping version numbers, GitHub Flow is a good
|
||||||
|
fit for you. You probably don't need the extras of Common-Flow.
|
||||||
|
|
||||||
|
However, if your deployments/releases have specific version numbers, then
|
||||||
|
Common-Flow gives you a simple set of rules for how to create and manage
|
||||||
|
releases, on top of what GitHub Flow already does.
|
||||||
|
|
||||||
|
### What does "descriptive name" mean for change branches?
|
||||||
|
|
||||||
|
It means what it sounds like. The name should be descriptive, as in by just
|
||||||
|
reading the name of the branch you should understand what the branch's purpose
|
||||||
|
is and what it does. Here's a few examples:
|
||||||
|
|
||||||
|
- add-2fa-support
|
||||||
|
- fix-login-issue
|
||||||
|
- remove-sort-by-middle-name-functionality
|
||||||
|
- update-font-awesome
|
||||||
|
- change-search-behavior
|
||||||
|
- improve-pagination-performance
|
||||||
|
- tweak-footer-style
|
||||||
|
|
||||||
|
Notice how none of these have any prefixes like "feature/" or "hotfix/", they're
|
||||||
|
not needed when branch names are properly descriptive. However, there's nothing
|
||||||
|
to say you can't use such prefixes if you want.
|
||||||
|
|
||||||
|
You can also add ticket numbers to the branch name if your team/org has that as
|
||||||
|
part of its process. But it is recommended that ticket numbers are added to the
|
||||||
|
end of the branch name. The ticket number is essentially metadata, so put it at
|
||||||
|
the end and out of the way of humans trying to read the descriptive name from
|
||||||
|
left to right.
|
||||||
|
|
||||||
|
### How do we release an emergency hotfix when the main branch is broken?
|
||||||
|
|
||||||
|
This should ideally never happen, however if it does you can do one of the
|
||||||
|
following:
|
||||||
|
|
||||||
|
- Review why the main branch is broken and revert the changes that caused the
|
||||||
|
issues. Then apply the hotfix and release.
|
||||||
|
- Or use a short-term release branch created from the latest release tag instead
|
||||||
|
of the main branch. Apply the hotfix to the release branch, create a release
|
||||||
|
tag on the release branch, and then merge it back into main.
|
||||||
|
|
||||||
|
In this situation, it is recommended you try to revert the offending changes
|
||||||
|
that's preventing a new release from main. But if that proves to be a
|
||||||
|
complicated task and you're short on time, a short-term release branch gives you
|
||||||
|
an instant fix to the situation at hand. You can then resolve the issues with
|
||||||
|
the main branch later.
|
||||||
|
|
||||||
|
### How do I handle monorepos?
|
||||||
|
|
||||||
|
Common-Flow works well with monorepos. The key considerations are:
|
||||||
|
|
||||||
|
- Use a single main branch for the entire monorepo. This keeps things simple and
|
||||||
|
ensures all packages/projects are always in a consistent state.
|
||||||
|
- For versioning, you have two main options:
|
||||||
|
- **Unified versioning**: All packages share the same version number. Simple
|
||||||
|
to manage, but may result in version bumps for packages that haven't
|
||||||
|
changed.
|
||||||
|
- **Independent versioning**: Each package has its own version. Use tags with
|
||||||
|
a package prefix, e.g., "package-a-2.1.0" or "package-a-v2.1.0". This allows
|
||||||
|
packages to evolve at their own pace.
|
||||||
|
- Change branches can span multiple packages. Describe the scope in the branch
|
||||||
|
name if helpful, e.g., "update-auth-across-services".
|
||||||
|
- For releases, if using independent versioning, you can create release branches
|
||||||
|
per package when needed, e.g., "release-package-a-2.1".
|
||||||
|
|
||||||
|
The core workflow remains the same: don't break main, use change branches, and
|
||||||
|
tag releases.
|
||||||
|
|
||||||
|
## About
|
||||||
|
|
||||||
|
The Git Common-Flow specification is authored by [Jim
|
||||||
|
Myhrberg](https://jimeh.me/).
|
||||||
|
|
||||||
|
If you'd like to leave feedback, please [open an issue on
|
||||||
|
GitHub](https://github.com/jimeh/common-flow/issues).
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[Creative Commons - CC BY 4.0](https://creativecommons.org/licenses/by/4.0/)
|
||||||
1
src/content/spec/2.0.0.svg
Normal file
|
After Width: | Height: | Size: 13 KiB |
8
src/icons/sun-moon.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
||||||
|
<!-- Circle outline -->
|
||||||
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z"/>
|
||||||
|
<!-- Right half filled (dark/moon side) -->
|
||||||
|
<path fill="currentColor" d="M12 8.25a3.75 3.75 0 0 1 0 7.5z"/>
|
||||||
|
<!-- Left-side sun rays (top, top-left, left, bottom-left, bottom) -->
|
||||||
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M12 3v2.25M7.227 7.227 5.636 5.636M5.25 12H3M7.227 16.773l-1.591 1.591M12 18.75V21"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 633 B |
87
src/layouts/BaseLayout.astro
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
---
|
||||||
|
import { Font } from "astro:assets";
|
||||||
|
import "../styles/global.css";
|
||||||
|
import { config } from "../config";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
title: string;
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { title, description = config.description } = Astro.props;
|
||||||
|
const fullTitle = title.includes(config.title)
|
||||||
|
? title
|
||||||
|
: `${title} | ${config.title}`;
|
||||||
|
const canonicalUrl = Astro.url.href;
|
||||||
|
---
|
||||||
|
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
|
<link rel="canonical" href={canonicalUrl} />
|
||||||
|
|
||||||
|
<title>{fullTitle}</title>
|
||||||
|
<meta name="description" content={description} />
|
||||||
|
<meta name="author" content={config.author} />
|
||||||
|
|
||||||
|
<!-- Open Graph -->
|
||||||
|
<meta property="og:title" content={fullTitle} />
|
||||||
|
<meta property="og:description" content={description} />
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:url" content={canonicalUrl} />
|
||||||
|
|
||||||
|
<!-- Twitter -->
|
||||||
|
<meta name="twitter:card" content="summary" />
|
||||||
|
<meta name="twitter:title" content={fullTitle} />
|
||||||
|
<meta name="twitter:description" content={description} />
|
||||||
|
|
||||||
|
<!-- Favicon -->
|
||||||
|
<link rel="icon" href="/favicon.ico" sizes="32x32" />
|
||||||
|
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
||||||
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
|
||||||
|
|
||||||
|
<!-- Fonts -->
|
||||||
|
<Font
|
||||||
|
cssVariable="--font-bricolage"
|
||||||
|
preload={[{ weight: "700" }, { weight: "800" }]}
|
||||||
|
/>
|
||||||
|
<Font cssVariable="--font-dm-sans" preload={[{ weight: "400" }]} />
|
||||||
|
<Font cssVariable="--font-jetbrains" preload={[{ weight: "400" }]} />
|
||||||
|
|
||||||
|
<!-- Slot for page-specific head content -->
|
||||||
|
<slot name="head" />
|
||||||
|
|
||||||
|
<!-- Prevent flash of wrong theme -->
|
||||||
|
<script is:inline>
|
||||||
|
(function () {
|
||||||
|
const mode = localStorage.getItem("theme");
|
||||||
|
const prefersDark = window.matchMedia(
|
||||||
|
"(prefers-color-scheme: dark)"
|
||||||
|
).matches;
|
||||||
|
if (mode === "dark" || (mode !== "light" && prefersDark)) {
|
||||||
|
document.documentElement.classList.add("dark");
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body class="min-h-screen">
|
||||||
|
<slot />
|
||||||
|
|
||||||
|
<!-- Re-init theme on Astro page transitions -->
|
||||||
|
<script>
|
||||||
|
document.addEventListener("astro:after-swap", () => {
|
||||||
|
const mode = localStorage.getItem("theme");
|
||||||
|
const prefersDark = window.matchMedia(
|
||||||
|
"(prefers-color-scheme: dark)"
|
||||||
|
).matches;
|
||||||
|
if (mode === "dark" || (mode !== "light" && prefersDark)) {
|
||||||
|
document.documentElement.classList.add("dark");
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove("dark");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
78
src/layouts/SpecLayout.astro
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
---
|
||||||
|
import type { CollectionEntry } from "astro:content";
|
||||||
|
import * as fs from "node:fs";
|
||||||
|
import * as path from "node:path";
|
||||||
|
|
||||||
|
import BaseLayout from "./BaseLayout.astro";
|
||||||
|
import Header from "../components/Header.astro";
|
||||||
|
import Hero from "../components/Hero.astro";
|
||||||
|
import AboutSection from "../components/AboutSection.astro";
|
||||||
|
import SpecSection from "../components/SpecSection.astro";
|
||||||
|
import FAQSection from "../components/FAQSection.astro";
|
||||||
|
import Footer from "../components/Footer.astro";
|
||||||
|
import { parseSpecContent } from "../utils/parseSpecContent";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
spec: CollectionEntry<"spec">;
|
||||||
|
versions: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const { spec, versions } = Astro.props;
|
||||||
|
const version = spec.data.version;
|
||||||
|
|
||||||
|
// Read the markdown file
|
||||||
|
const filePath = path.join(process.cwd(), "src/content/spec", `${version}.md`);
|
||||||
|
const content = fs.readFileSync(filePath, "utf-8");
|
||||||
|
|
||||||
|
// Remove frontmatter
|
||||||
|
const markdown = content.replace(/^---[\s\S]*?---\n/, "");
|
||||||
|
|
||||||
|
// Parse the content into sections (handles markdown -> HTML internally)
|
||||||
|
const parsed = await parseSpecContent(markdown);
|
||||||
|
|
||||||
|
// Read SVG content for inline embedding
|
||||||
|
const svgFilePath = path.join(
|
||||||
|
process.cwd(),
|
||||||
|
"src/content/spec",
|
||||||
|
`${version}.svg`
|
||||||
|
);
|
||||||
|
let svgContent: string | null = null;
|
||||||
|
if (fs.existsSync(svgFilePath)) {
|
||||||
|
svgContent = fs.readFileSync(svgFilePath, "utf-8");
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
<BaseLayout title={spec.data.title}>
|
||||||
|
<Fragment slot="head">
|
||||||
|
<link
|
||||||
|
rel="alternate"
|
||||||
|
type="text/markdown"
|
||||||
|
href={`/spec/git-common-flow-v${version}.md`}
|
||||||
|
title="Raw Markdown"
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
|
||||||
|
<Header version={version} versions={versions} />
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<Hero version={version} versions={versions} svgContent={svgContent} />
|
||||||
|
|
||||||
|
<AboutSection
|
||||||
|
introduction={parsed.introduction}
|
||||||
|
summary={parsed.summary}
|
||||||
|
license={parsed.license}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<SpecSection
|
||||||
|
terminology={parsed.terminology}
|
||||||
|
terminologyTitle={parsed.terminologyTitle}
|
||||||
|
specification={parsed.specification}
|
||||||
|
tocItems={parsed.tocItems}
|
||||||
|
version={version}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FAQSection items={parsed.faq} />
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<Footer />
|
||||||
|
</BaseLayout>
|
||||||
32
src/pages/404.astro
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
import BaseLayout from "../layouts/BaseLayout.astro";
|
||||||
|
---
|
||||||
|
|
||||||
|
<BaseLayout title="Page Not Found">
|
||||||
|
<div class="flex flex-col items-center justify-center min-h-screen p-8">
|
||||||
|
<div class="text-center">
|
||||||
|
<h1
|
||||||
|
class="text-[8rem] sm:text-[12rem] font-display font-bold leading-none
|
||||||
|
text-gray-300 dark:text-neutral-700"
|
||||||
|
>
|
||||||
|
404
|
||||||
|
</h1>
|
||||||
|
<p class="text-xl mb-2 text-gray-600 dark:text-neutral-400">
|
||||||
|
Page not found
|
||||||
|
</p>
|
||||||
|
<p class="text-gray-500 dark:text-neutral-500">
|
||||||
|
The page you're looking for doesn't exist.
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href="/"
|
||||||
|
class="inline-flex items-center justify-center gap-2 mt-8
|
||||||
|
px-5 py-2.5 text-sm font-medium rounded-lg
|
||||||
|
transition-all cursor-pointer
|
||||||
|
bg-sky-600 text-white
|
||||||
|
hover:bg-sky-500 hover:-translate-y-0.5 hover:shadow-md"
|
||||||
|
>
|
||||||
|
Go to homepage
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</BaseLayout>
|
||||||
17
src/pages/index.astro
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
import { getCollection } from "astro:content";
|
||||||
|
|
||||||
|
import SpecLayout from "../layouts/SpecLayout.astro";
|
||||||
|
import { getVersionInfo } from "../utils/versions";
|
||||||
|
|
||||||
|
// Get version info and render the current/latest version
|
||||||
|
const { versions, currentVersion } = await getVersionInfo();
|
||||||
|
const specs = await getCollection("spec");
|
||||||
|
const spec = specs.find((s) => s.data.version === currentVersion);
|
||||||
|
|
||||||
|
if (!spec) {
|
||||||
|
throw new Error(`Spec version ${currentVersion} not found`);
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
<SpecLayout spec={spec} versions={versions} />
|
||||||
25
src/pages/llms.txt.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import type { APIRoute } from "astro";
|
||||||
|
import { getVersionInfo } from "../utils/versions";
|
||||||
|
|
||||||
|
export const GET: APIRoute = async () => {
|
||||||
|
const { currentVersion } = await getVersionInfo();
|
||||||
|
|
||||||
|
const content = `# Common-Flow
|
||||||
|
|
||||||
|
> A Git workflow specification combining GitHub Flow with versioned releases.
|
||||||
|
|
||||||
|
Common-Flow is a sensible git workflow based on GitHub Flow, with the addition
|
||||||
|
of versioned releases, optional release branches, and without the requirement
|
||||||
|
to deploy to production all the time.
|
||||||
|
|
||||||
|
## Docs
|
||||||
|
|
||||||
|
- [Git Common-Flow Specification](/spec/git-common-flow-v${currentVersion}.md): The complete Git Common-Flow v${currentVersion} specification in Markdown format
|
||||||
|
`;
|
||||||
|
|
||||||
|
return new Response(content, {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "text/plain; charset=utf-8",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
19
src/pages/spec/[version].astro
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
import { getCollection } from "astro:content";
|
||||||
|
|
||||||
|
import SpecLayout from "../../layouts/SpecLayout.astro";
|
||||||
|
import { getVersionInfo } from "../../utils/versions";
|
||||||
|
|
||||||
|
export async function getStaticPaths() {
|
||||||
|
const specs = await getCollection("spec");
|
||||||
|
return specs.map((spec) => ({
|
||||||
|
params: { version: spec.data.version },
|
||||||
|
props: { spec },
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const { spec } = Astro.props;
|
||||||
|
const { versions } = await getVersionInfo();
|
||||||
|
---
|
||||||
|
|
||||||
|
<SpecLayout spec={spec} versions={versions} />
|
||||||
531
src/pages/spec/[version]/md.astro
Normal file
@@ -0,0 +1,531 @@
|
|||||||
|
---
|
||||||
|
import { getCollection } from "astro:content";
|
||||||
|
import { Icon } from "astro-icon/components";
|
||||||
|
import * as fs from "node:fs";
|
||||||
|
import * as path from "node:path";
|
||||||
|
import { createHighlighter } from "shiki";
|
||||||
|
import { unified } from "unified";
|
||||||
|
import remarkParse from "remark-parse";
|
||||||
|
import remarkRehype from "remark-rehype";
|
||||||
|
import rehypeSlug from "rehype-slug";
|
||||||
|
import rehypeAutolinkHeadings from "rehype-autolink-headings";
|
||||||
|
import rehypeStringify from "rehype-stringify";
|
||||||
|
import { icons as heroicons } from "@iconify-json/heroicons";
|
||||||
|
import { getIconData, iconToSVG } from "@iconify/utils";
|
||||||
|
|
||||||
|
import BaseLayout from "../../../layouts/BaseLayout.astro";
|
||||||
|
import ThemeToggle from "../../../components/ThemeToggle.astro";
|
||||||
|
import { config } from "../../../config";
|
||||||
|
|
||||||
|
// Get the link icon SVG from heroicons for use in anchor links
|
||||||
|
const linkIconData = getIconData(heroicons, "link-20-solid");
|
||||||
|
const linkIconSvg = linkIconData ? iconToSVG(linkIconData) : null;
|
||||||
|
|
||||||
|
// Parse the icon body into hast nodes for rehype
|
||||||
|
function parseIconBody(body: string): import("hast").ElementContent[] {
|
||||||
|
// Simple regex-based parser for SVG path/g elements
|
||||||
|
const elements: import("hast").ElementContent[] = [];
|
||||||
|
const tagRegex = /<(\w+)([^>]*)(?:\/>|>([\s\S]*?)<\/\1>)/g;
|
||||||
|
let match;
|
||||||
|
|
||||||
|
while ((match = tagRegex.exec(body)) !== null) {
|
||||||
|
const [, tagName, attrs, children] = match;
|
||||||
|
const properties: Record<string, string> = {};
|
||||||
|
|
||||||
|
// Parse attributes
|
||||||
|
const attrRegex = /(\w+(?:-\w+)*)="([^"]*)"/g;
|
||||||
|
let attrMatch;
|
||||||
|
while ((attrMatch = attrRegex.exec(attrs)) !== null) {
|
||||||
|
properties[attrMatch[1]] = attrMatch[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
elements.push({
|
||||||
|
type: "element",
|
||||||
|
tagName,
|
||||||
|
properties,
|
||||||
|
children: children ? parseIconBody(children) : [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getStaticPaths() {
|
||||||
|
const specs = await getCollection("spec");
|
||||||
|
return specs.map((spec) => ({
|
||||||
|
params: { version: spec.data.version },
|
||||||
|
props: { spec },
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const { spec } = Astro.props;
|
||||||
|
const version = spec.data.version;
|
||||||
|
|
||||||
|
// Read the markdown file
|
||||||
|
const filePath = path.join(process.cwd(), "src/content/spec", `${version}.md`);
|
||||||
|
const content = fs.readFileSync(filePath, "utf-8");
|
||||||
|
|
||||||
|
// Remove frontmatter
|
||||||
|
const markdown = content.replace(/^---[\s\S]*?---\n/, "");
|
||||||
|
|
||||||
|
// Create syntax highlighter for code view
|
||||||
|
const highlighter = await createHighlighter({
|
||||||
|
themes: ["github-light", "github-dark"],
|
||||||
|
langs: ["markdown"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const highlightedHtml = highlighter.codeToHtml(markdown, {
|
||||||
|
lang: "markdown",
|
||||||
|
themes: {
|
||||||
|
light: "github-light",
|
||||||
|
dark: "github-dark",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build the anchor link icon content from the heroicons data
|
||||||
|
const anchorIconContent = linkIconSvg
|
||||||
|
? {
|
||||||
|
type: "element" as const,
|
||||||
|
tagName: "svg",
|
||||||
|
properties: {
|
||||||
|
className: ["anchor-icon"],
|
||||||
|
viewBox: `0 0 ${linkIconSvg.attributes.width} ${linkIconSvg.attributes.height}`,
|
||||||
|
fill: "currentColor",
|
||||||
|
"aria-hidden": "true",
|
||||||
|
},
|
||||||
|
children: parseIconBody(linkIconSvg.body),
|
||||||
|
}
|
||||||
|
: { type: "text" as const, value: "#" };
|
||||||
|
|
||||||
|
// Render markdown to HTML for preview view
|
||||||
|
const previewHtml = await unified()
|
||||||
|
.use(remarkParse)
|
||||||
|
.use(remarkRehype)
|
||||||
|
.use(rehypeSlug)
|
||||||
|
.use(rehypeAutolinkHeadings, {
|
||||||
|
behavior: "append",
|
||||||
|
properties: {
|
||||||
|
className: ["anchor-link"],
|
||||||
|
ariaLabel: "Link to this section",
|
||||||
|
},
|
||||||
|
content: anchorIconContent,
|
||||||
|
})
|
||||||
|
.use(rehypeStringify)
|
||||||
|
.process(markdown)
|
||||||
|
.then((file) => String(file));
|
||||||
|
---
|
||||||
|
|
||||||
|
<BaseLayout title={`${spec.data.title} - Markdown`}>
|
||||||
|
<Fragment slot="head">
|
||||||
|
<link
|
||||||
|
rel="alternate"
|
||||||
|
type="text/markdown"
|
||||||
|
href={`/spec/git-common-flow-v${version}.md`}
|
||||||
|
title="Raw Markdown"
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<header
|
||||||
|
class="sticky top-0 z-50 border-b
|
||||||
|
border-gray-200 dark:border-neutral-800
|
||||||
|
backdrop-blur-xl bg-gray-50/85 dark:bg-neutral-950/85"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="max-w-6xl mx-auto px-4 sm:px-6 h-16 flex items-center
|
||||||
|
justify-between"
|
||||||
|
>
|
||||||
|
<!-- Back link and title -->
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<a
|
||||||
|
href={`/spec/${version}`}
|
||||||
|
class="inline-flex items-center gap-1.5 text-sm font-medium
|
||||||
|
text-gray-600 dark:text-neutral-400
|
||||||
|
hover:text-sky-600 dark:hover:text-sky-400 transition-colors"
|
||||||
|
>
|
||||||
|
<Icon name="heroicons:arrow-left" class="w-4 h-4" />
|
||||||
|
<span class="hidden sm:inline">Back to Spec</span>
|
||||||
|
</a>
|
||||||
|
<span class="hidden sm:inline text-gray-300 dark:text-neutral-700"
|
||||||
|
>|</span
|
||||||
|
>
|
||||||
|
<span class="font-display font-bold text-lg tracking-tight">
|
||||||
|
Markdown
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="px-2 py-0.5 text-xs font-semibold rounded-full
|
||||||
|
bg-sky-100 text-sky-700
|
||||||
|
dark:bg-sky-900/50 dark:text-sky-300"
|
||||||
|
>
|
||||||
|
v{version}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right side: Theme, GitHub -->
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<ThemeToggle />
|
||||||
|
|
||||||
|
<a
|
||||||
|
href={config.repoUrl}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="p-2 rounded-lg transition-colors
|
||||||
|
text-gray-500 dark:text-neutral-500
|
||||||
|
hover:text-gray-950 dark:hover:text-neutral-50
|
||||||
|
hover:bg-gray-100 dark:hover:bg-neutral-800"
|
||||||
|
aria-label="View on GitHub"
|
||||||
|
>
|
||||||
|
<Icon name="simple-icons:github" class="w-5 h-5" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Main content -->
|
||||||
|
<main class="max-w-6xl mx-auto px-4 sm:px-6 py-8">
|
||||||
|
<div class="relative">
|
||||||
|
<!-- Hidden raw text for copying -->
|
||||||
|
<div id="markdown-raw" class="hidden">{markdown}</div>
|
||||||
|
<!-- Filename heading and actions -->
|
||||||
|
<div class="flex flex-col items-center mb-6">
|
||||||
|
<h1
|
||||||
|
class="font-mono font-semibold text-sm sm:text-base
|
||||||
|
text-gray-700 dark:text-neutral-300
|
||||||
|
max-w-full overflow-hidden text-ellipsis
|
||||||
|
whitespace-nowrap [direction:rtl] sm:[direction:ltr]"
|
||||||
|
>
|
||||||
|
git-common-flow-v{version}.md
|
||||||
|
</h1>
|
||||||
|
<div
|
||||||
|
class="flex flex-col sm:flex-row items-center gap-2 sm:gap-3 mt-3
|
||||||
|
w-full sm:w-auto"
|
||||||
|
>
|
||||||
|
<!-- Separator (hidden on mobile) -->
|
||||||
|
<span
|
||||||
|
class="hidden sm:inline text-gray-300 dark:text-neutral-700
|
||||||
|
order-2"
|
||||||
|
>|</span
|
||||||
|
>
|
||||||
|
|
||||||
|
<!-- Copy and Download buttons -->
|
||||||
|
<div class="flex items-center gap-3 order-1 sm:order-3">
|
||||||
|
<!-- Copy button -->
|
||||||
|
<button
|
||||||
|
id="copy-btn"
|
||||||
|
type="button"
|
||||||
|
class="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm
|
||||||
|
font-medium rounded-lg transition-colors cursor-pointer
|
||||||
|
text-gray-600 dark:text-neutral-400
|
||||||
|
hover:bg-gray-100 hover:text-gray-950
|
||||||
|
dark:hover:bg-neutral-800 dark:hover:text-neutral-50"
|
||||||
|
>
|
||||||
|
<Icon name="heroicons:clipboard-document" class="w-4 h-4" />
|
||||||
|
<span data-copy-text>Copy</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Raw button -->
|
||||||
|
<a
|
||||||
|
href={`/spec/git-common-flow-v${version}.md`}
|
||||||
|
class="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm
|
||||||
|
font-medium rounded-lg transition-colors
|
||||||
|
text-gray-600 dark:text-neutral-400
|
||||||
|
hover:bg-gray-100 hover:text-gray-950
|
||||||
|
dark:hover:bg-neutral-800 dark:hover:text-neutral-50"
|
||||||
|
>
|
||||||
|
<Icon name="heroicons:document-text" class="w-4 h-4" />
|
||||||
|
<span>Raw</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<!-- Download button -->
|
||||||
|
<button
|
||||||
|
id="download-btn"
|
||||||
|
type="button"
|
||||||
|
class="inline-flex items-center gap-1.5 px-3 py-1.5 text-sm
|
||||||
|
font-medium rounded-lg transition-colors cursor-pointer
|
||||||
|
text-gray-600 dark:text-neutral-400
|
||||||
|
hover:bg-gray-100 hover:text-gray-950
|
||||||
|
dark:hover:bg-neutral-800 dark:hover:text-neutral-50"
|
||||||
|
data-filename={`git-common-flow-v${version}.md`}
|
||||||
|
>
|
||||||
|
<Icon name="heroicons:arrow-down-tray" class="w-4 h-4" />
|
||||||
|
<span>Download</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Code/Preview toggle -->
|
||||||
|
<div
|
||||||
|
id="toggle-container"
|
||||||
|
class="relative inline-flex rounded-lg p-0.5 order-2 sm:order-1
|
||||||
|
mt-2 sm:mt-0 bg-gray-100 dark:bg-neutral-800"
|
||||||
|
>
|
||||||
|
<!-- Sliding indicator -->
|
||||||
|
<div
|
||||||
|
id="toggle-indicator"
|
||||||
|
class="absolute top-0.5 h-[calc(100%-4px)] rounded-md
|
||||||
|
bg-white dark:bg-neutral-700 shadow-sm
|
||||||
|
transition-all duration-200 ease-out"
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
id="toggle-preview"
|
||||||
|
type="button"
|
||||||
|
class="relative z-10 inline-flex items-center gap-1.5 px-3 py-1
|
||||||
|
text-sm font-medium rounded-md cursor-pointer
|
||||||
|
transition-colors duration-200
|
||||||
|
text-gray-900 dark:text-neutral-100"
|
||||||
|
aria-pressed="true"
|
||||||
|
>
|
||||||
|
<Icon name="heroicons:eye" class="w-4 h-4" />
|
||||||
|
Preview
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
id="toggle-code"
|
||||||
|
type="button"
|
||||||
|
class="relative z-10 inline-flex items-center gap-1.5 px-3 py-1
|
||||||
|
text-sm font-medium rounded-md cursor-pointer
|
||||||
|
transition-colors duration-200
|
||||||
|
text-gray-600 dark:text-neutral-400
|
||||||
|
hover:text-gray-900 dark:hover:text-neutral-200"
|
||||||
|
aria-pressed="false"
|
||||||
|
>
|
||||||
|
<Icon name="heroicons:code-bracket" class="w-4 h-4" />
|
||||||
|
Code
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Code view (hidden by default) -->
|
||||||
|
<div
|
||||||
|
id="code-view"
|
||||||
|
class="hidden [&_pre]:overflow-x-auto [&_pre]:rounded-xl [&_pre]:p-6
|
||||||
|
[&_pre]:text-sm [&_pre]:leading-relaxed
|
||||||
|
[&_pre]:border [&_pre]:border-gray-200
|
||||||
|
dark:[&_pre]:border-neutral-800"
|
||||||
|
set:html={highlightedHtml}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Preview view (visible by default) -->
|
||||||
|
<div
|
||||||
|
id="preview-view"
|
||||||
|
class="prose prose-slate dark:prose-invert max-w-none
|
||||||
|
rounded-xl p-6 border border-gray-200
|
||||||
|
dark:border-neutral-800 bg-white dark:bg-neutral-900"
|
||||||
|
set:html={previewHtml}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</BaseLayout>
|
||||||
|
|
||||||
|
<style is:global>
|
||||||
|
/* Shiki dual theme support - override inline styles in dark mode */
|
||||||
|
.dark .shiki {
|
||||||
|
color: var(--shiki-dark) !important;
|
||||||
|
background-color: var(--shiki-dark-bg) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .shiki span {
|
||||||
|
color: var(--shiki-dark) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Preview anchor links */
|
||||||
|
#preview-view :is(h1, h2, h3, h4, h5, h6) {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
#preview-view .anchor-link {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 150ms ease;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
#preview-view :is(h1, h2, h3, h4, h5, h6):hover .anchor-link {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#preview-view .anchor-link:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#preview-view .anchor-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
color: var(--color-slate-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
#preview-view .anchor-link:hover .anchor-icon {
|
||||||
|
color: var(--color-sky-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark #preview-view .anchor-icon {
|
||||||
|
color: var(--color-neutral-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark #preview-view .anchor-link:hover .anchor-icon {
|
||||||
|
color: var(--color-sky-400);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function initPage() {
|
||||||
|
const copyBtn = document.getElementById("copy-btn");
|
||||||
|
const downloadBtn = document.getElementById("download-btn");
|
||||||
|
const rawContent = document.getElementById("markdown-raw");
|
||||||
|
const copyText = copyBtn?.querySelector("[data-copy-text]");
|
||||||
|
|
||||||
|
const toggleContainer = document.getElementById("toggle-container");
|
||||||
|
const toggleCode = document.getElementById("toggle-code");
|
||||||
|
const togglePreview = document.getElementById("toggle-preview");
|
||||||
|
const toggleIndicator = document.getElementById("toggle-indicator");
|
||||||
|
const codeView = document.getElementById("code-view");
|
||||||
|
const previewView = document.getElementById("preview-view");
|
||||||
|
|
||||||
|
if (!rawContent) return;
|
||||||
|
|
||||||
|
// Text color classes for active/inactive states
|
||||||
|
const activeTextClasses = ["text-gray-900", "dark:text-neutral-100"];
|
||||||
|
const inactiveTextClasses = [
|
||||||
|
"text-gray-600",
|
||||||
|
"dark:text-neutral-400",
|
||||||
|
"hover:text-gray-900",
|
||||||
|
"dark:hover:text-neutral-200",
|
||||||
|
];
|
||||||
|
|
||||||
|
function updateIndicator(button: HTMLElement) {
|
||||||
|
if (!toggleIndicator || !toggleContainer) return;
|
||||||
|
|
||||||
|
const containerRect = toggleContainer.getBoundingClientRect();
|
||||||
|
const buttonRect = button.getBoundingClientRect();
|
||||||
|
|
||||||
|
// Calculate position relative to container (accounting for container padding)
|
||||||
|
const left = buttonRect.left - containerRect.left;
|
||||||
|
|
||||||
|
toggleIndicator.style.left = `${left}px`;
|
||||||
|
toggleIndicator.style.width = `${buttonRect.width}px`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setActiveToggle(isCode: boolean) {
|
||||||
|
if (
|
||||||
|
!toggleCode ||
|
||||||
|
!togglePreview ||
|
||||||
|
!toggleIndicator ||
|
||||||
|
!codeView ||
|
||||||
|
!previewView
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (isCode) {
|
||||||
|
// Show code, hide preview
|
||||||
|
codeView.classList.remove("hidden");
|
||||||
|
previewView.classList.add("hidden");
|
||||||
|
|
||||||
|
// Update indicator position and size
|
||||||
|
updateIndicator(toggleCode);
|
||||||
|
|
||||||
|
// Update text colors
|
||||||
|
toggleCode.classList.add(...activeTextClasses);
|
||||||
|
toggleCode.classList.remove(...inactiveTextClasses);
|
||||||
|
toggleCode.setAttribute("aria-pressed", "true");
|
||||||
|
|
||||||
|
togglePreview.classList.remove(...activeTextClasses);
|
||||||
|
togglePreview.classList.add(...inactiveTextClasses);
|
||||||
|
togglePreview.setAttribute("aria-pressed", "false");
|
||||||
|
} else {
|
||||||
|
// Show preview, hide code
|
||||||
|
codeView.classList.add("hidden");
|
||||||
|
previewView.classList.remove("hidden");
|
||||||
|
|
||||||
|
// Update indicator position and size
|
||||||
|
updateIndicator(togglePreview);
|
||||||
|
|
||||||
|
// Update text colors
|
||||||
|
togglePreview.classList.add(...activeTextClasses);
|
||||||
|
togglePreview.classList.remove(...inactiveTextClasses);
|
||||||
|
togglePreview.setAttribute("aria-pressed", "true");
|
||||||
|
|
||||||
|
toggleCode.classList.remove(...activeTextClasses);
|
||||||
|
toggleCode.classList.add(...inactiveTextClasses);
|
||||||
|
toggleCode.setAttribute("aria-pressed", "false");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize indicator position after layout is complete
|
||||||
|
// Use double requestAnimationFrame to ensure layout/paint is finished,
|
||||||
|
// which fixes sizing issues on iOS Safari initial page load
|
||||||
|
function initializeIndicator() {
|
||||||
|
if (togglePreview) {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
updateIndicator(togglePreview);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for fonts to load before measuring, then initialize
|
||||||
|
if (document.fonts && document.fonts.ready) {
|
||||||
|
document.fonts.ready.then(initializeIndicator);
|
||||||
|
} else {
|
||||||
|
initializeIndicator();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle event listeners
|
||||||
|
toggleCode?.addEventListener("click", () => setActiveToggle(true));
|
||||||
|
togglePreview?.addEventListener("click", () => setActiveToggle(false));
|
||||||
|
|
||||||
|
function showCopiedFeedback() {
|
||||||
|
if (copyText) {
|
||||||
|
copyText.textContent = "Copied!";
|
||||||
|
setTimeout(() => {
|
||||||
|
copyText.textContent = "Copy";
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy button
|
||||||
|
if (copyBtn) {
|
||||||
|
copyBtn.addEventListener("click", async () => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(rawContent.textContent || "");
|
||||||
|
showCopiedFeedback();
|
||||||
|
} catch {
|
||||||
|
// Fallback for older browsers
|
||||||
|
const textarea = document.createElement("textarea");
|
||||||
|
textarea.value = rawContent.textContent || "";
|
||||||
|
textarea.style.position = "fixed";
|
||||||
|
textarea.style.opacity = "0";
|
||||||
|
document.body.appendChild(textarea);
|
||||||
|
textarea.select();
|
||||||
|
document.execCommand("copy");
|
||||||
|
document.body.removeChild(textarea);
|
||||||
|
showCopiedFeedback();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download button
|
||||||
|
if (downloadBtn) {
|
||||||
|
downloadBtn.addEventListener("click", () => {
|
||||||
|
const filename = downloadBtn.dataset.filename || "common-flow.md";
|
||||||
|
const content = rawContent.textContent || "";
|
||||||
|
const blob = new Blob([content], { type: "text/markdown" });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.href = url;
|
||||||
|
a.download = filename;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
document.body.removeChild(a);
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initPage();
|
||||||
|
document.addEventListener("astro:after-swap", initPage);
|
||||||
|
</script>
|
||||||
30
src/pages/spec/git-common-flow-v[version].md.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import type { APIRoute, GetStaticPaths } from "astro";
|
||||||
|
import { getCollection } from "astro:content";
|
||||||
|
import * as fs from "node:fs";
|
||||||
|
import * as path from "node:path";
|
||||||
|
|
||||||
|
export const getStaticPaths: GetStaticPaths = async () => {
|
||||||
|
const specs = await getCollection("spec");
|
||||||
|
return specs.map((spec) => ({
|
||||||
|
params: { version: spec.data.version },
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GET: APIRoute = ({ params }) => {
|
||||||
|
const version = params.version;
|
||||||
|
const filePath = path.join(
|
||||||
|
process.cwd(),
|
||||||
|
"src/content/spec",
|
||||||
|
`${version}.md`
|
||||||
|
);
|
||||||
|
const content = fs.readFileSync(filePath, "utf-8");
|
||||||
|
const markdown = content.replace(/^---[\s\S]*?---\n/, "");
|
||||||
|
const filename = `git-common-flow-v${version}.md`;
|
||||||
|
|
||||||
|
return new Response(markdown, {
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "text/markdown; charset=utf-8",
|
||||||
|
"Content-Disposition": `inline; filename="${filename}"`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
64
src/scripts/activeSectionTracker.ts
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
export interface ActiveSectionTrackerOptions {
|
||||||
|
linkSelector: string;
|
||||||
|
sectionIdAttr?: string;
|
||||||
|
headerOffset?: number;
|
||||||
|
defaultToFirst?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initActiveSectionTracker(
|
||||||
|
options: ActiveSectionTrackerOptions,
|
||||||
|
): void {
|
||||||
|
const {
|
||||||
|
linkSelector,
|
||||||
|
sectionIdAttr = "data-section-id",
|
||||||
|
headerOffset = 100,
|
||||||
|
defaultToFirst = true,
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
const links = document.querySelectorAll(linkSelector);
|
||||||
|
const sections: { id: string; element: Element }[] = [];
|
||||||
|
|
||||||
|
// Collect unique section IDs
|
||||||
|
const seenIds = new Set<string>();
|
||||||
|
links.forEach((link) => {
|
||||||
|
const id = link.getAttribute(sectionIdAttr);
|
||||||
|
if (id && !seenIds.has(id)) {
|
||||||
|
seenIds.add(id);
|
||||||
|
const section = document.getElementById(id);
|
||||||
|
if (section) {
|
||||||
|
sections.push({ id, element: section });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function updateActiveSection(): void {
|
||||||
|
let activeId: string | null = defaultToFirst ? sections[0]?.id : null;
|
||||||
|
|
||||||
|
for (const { id, element } of sections) {
|
||||||
|
const rect = element.getBoundingClientRect();
|
||||||
|
if (rect.top <= headerOffset) {
|
||||||
|
activeId = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
links.forEach((link) => {
|
||||||
|
const linkId = link.getAttribute(sectionIdAttr);
|
||||||
|
link.classList.toggle("active", linkId === activeId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update on scroll with throttling
|
||||||
|
let ticking = false;
|
||||||
|
window.addEventListener("scroll", () => {
|
||||||
|
if (!ticking) {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
updateActiveSection();
|
||||||
|
ticking = false;
|
||||||
|
});
|
||||||
|
ticking = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initial update
|
||||||
|
updateActiveSection();
|
||||||
|
}
|
||||||
75
src/scripts/clauseHighlight.ts
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
/**
|
||||||
|
* Highlights clause elements when navigating to them via anchor links.
|
||||||
|
* Works on both initial page load with hash and when clicking anchor links.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const HIGHLIGHT_DURATION = 2000;
|
||||||
|
const HIGHLIGHT_CLASS = "clause-highlight";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Highlight a clause element briefly
|
||||||
|
*/
|
||||||
|
function highlightClause(element: Element): void {
|
||||||
|
// Remove any existing highlight
|
||||||
|
element.classList.remove(HIGHLIGHT_CLASS);
|
||||||
|
|
||||||
|
// Force reflow to restart animation if needed
|
||||||
|
void (element as HTMLElement).offsetWidth;
|
||||||
|
|
||||||
|
// Add highlight class
|
||||||
|
element.classList.add(HIGHLIGHT_CLASS);
|
||||||
|
|
||||||
|
// Remove after animation completes
|
||||||
|
setTimeout(() => {
|
||||||
|
element.classList.remove(HIGHLIGHT_CLASS);
|
||||||
|
}, HIGHLIGHT_DURATION);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle hash change and highlight target clause
|
||||||
|
*/
|
||||||
|
function handleHashChange(): void {
|
||||||
|
const hash = window.location.hash;
|
||||||
|
if (!hash || !hash.startsWith("#clause-")) return;
|
||||||
|
|
||||||
|
const targetId = hash.slice(1);
|
||||||
|
const element = document.getElementById(targetId);
|
||||||
|
if (element) {
|
||||||
|
// Small delay to let scroll complete
|
||||||
|
setTimeout(() => highlightClause(element), 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize clause highlight behavior
|
||||||
|
*/
|
||||||
|
export function initClauseHighlight(): void {
|
||||||
|
// Handle clicks on clause links
|
||||||
|
document.addEventListener("click", (e) => {
|
||||||
|
const link = (e.target as Element).closest('a[href^="#clause-"]');
|
||||||
|
if (!link) return;
|
||||||
|
|
||||||
|
const href = link.getAttribute("href");
|
||||||
|
if (!href) return;
|
||||||
|
|
||||||
|
const targetId = href.slice(1);
|
||||||
|
const element = document.getElementById(targetId);
|
||||||
|
if (element) {
|
||||||
|
// Small delay to let scroll complete
|
||||||
|
setTimeout(() => highlightClause(element), 100);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle hash changes (back/forward navigation)
|
||||||
|
window.addEventListener("hashchange", handleHashChange);
|
||||||
|
|
||||||
|
// Handle initial page load with hash
|
||||||
|
if (window.location.hash?.startsWith("#clause-")) {
|
||||||
|
// Wait for page to be fully ready
|
||||||
|
if (document.readyState === "complete") {
|
||||||
|
handleHashChange();
|
||||||
|
} else {
|
||||||
|
window.addEventListener("load", handleHashChange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
557
src/styles/global.css
Normal file
@@ -0,0 +1,557 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
@plugin "@tailwindcss/typography";
|
||||||
|
|
||||||
|
@custom-variant dark (&:where(.dark, .dark *));
|
||||||
|
|
||||||
|
@theme {
|
||||||
|
/* Accent colors - using Tailwind sky palette */
|
||||||
|
--color-accent: theme(colors.sky.500);
|
||||||
|
--color-accent-light: theme(colors.sky.400);
|
||||||
|
|
||||||
|
/* Fonts - via Astro experimental font API (fontsource provider) */
|
||||||
|
--font-display: var(--font-bricolage);
|
||||||
|
--font-sans: var(--font-dm-sans);
|
||||||
|
--font-mono: var(--font-jetbrains);
|
||||||
|
|
||||||
|
/* Sizing */
|
||||||
|
--header-height: 4rem;
|
||||||
|
--sidebar-width: 260px;
|
||||||
|
--content-max-width: 800px;
|
||||||
|
--section-padding-y: 6rem;
|
||||||
|
--section-padding-x: 2rem;
|
||||||
|
|
||||||
|
/* Transitions */
|
||||||
|
--transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
--transition-base: 250ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
--transition-slow: 400ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
--transition-smooth: 600ms cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
|
|
||||||
|
/* Shadows */
|
||||||
|
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||||
|
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||||
|
--shadow-lg: 0 8px 30px rgba(0, 0, 0, 0.12);
|
||||||
|
--shadow-glow: 0 0 40px theme(colors.sky.500 / 15%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Smooth scroll */
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Scroll margin for anchor links */
|
||||||
|
[id] {
|
||||||
|
scroll-margin-top: calc(var(--header-height) + 2rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Base styles */
|
||||||
|
@layer base {
|
||||||
|
html {
|
||||||
|
height: 100%;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.7;
|
||||||
|
overflow-x: hidden;
|
||||||
|
background-color: theme(colors.slate.50);
|
||||||
|
color: theme(colors.slate.950);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark body {
|
||||||
|
background-color: theme(colors.neutral.950);
|
||||||
|
color: theme(colors.neutral.50);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Typography */
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
font-family: var(--font-display);
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.2;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
color: theme(colors.slate.950);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark h1,
|
||||||
|
.dark h2,
|
||||||
|
.dark h3,
|
||||||
|
.dark h4,
|
||||||
|
.dark h5,
|
||||||
|
.dark h6 {
|
||||||
|
color: theme(colors.neutral.50);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: clamp(2.5rem, 6vw, 4.5rem);
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: -0.03em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: clamp(1.75rem, 4vw, 2.5rem);
|
||||||
|
margin-top: 3rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: clamp(1.25rem, 2vw, 1.5rem);
|
||||||
|
margin-top: 2rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Links */
|
||||||
|
a {
|
||||||
|
color: var(--color-accent);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color var(--transition-fast);
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: var(--color-accent-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Code */
|
||||||
|
code {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 0.875em;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 0.2em 0.4em;
|
||||||
|
background-color: theme(colors.slate.950 / 5%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark code {
|
||||||
|
background-color: theme(colors.neutral.400 / 15%);
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 1.25rem;
|
||||||
|
overflow-x: auto;
|
||||||
|
line-height: 1.6;
|
||||||
|
background-color: theme(colors.slate.200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark pre {
|
||||||
|
background-color: theme(colors.neutral.900);
|
||||||
|
}
|
||||||
|
|
||||||
|
pre>code {
|
||||||
|
background-color: transparent !important;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Lists */
|
||||||
|
ul,
|
||||||
|
ol {
|
||||||
|
margin-bottom: 1.25rem;
|
||||||
|
padding-left: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style-type: disc;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol {
|
||||||
|
list-style-type: decimal;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol ol,
|
||||||
|
ul ol {
|
||||||
|
list-style-type: decimal;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul ul ol,
|
||||||
|
ul ol ol,
|
||||||
|
ol ul ol,
|
||||||
|
ol ol ol {
|
||||||
|
list-style-type: decimal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Blockquotes */
|
||||||
|
blockquote {
|
||||||
|
border-left: 3px solid var(--color-accent);
|
||||||
|
padding-left: 1.5rem;
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
font-style: italic;
|
||||||
|
color: theme(colors.slate.600);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark blockquote {
|
||||||
|
color: theme(colors.neutral.400);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Strong text */
|
||||||
|
strong {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Focus styles */
|
||||||
|
:focus-visible {
|
||||||
|
outline: 2px solid var(--color-accent);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keyframe animations - defined outside layers for proper cascade */
|
||||||
|
@keyframes clause-highlight-pulse {
|
||||||
|
0% {
|
||||||
|
background-color: theme(colors.sky.500 / 15%);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes clause-highlight-pulse-dark {
|
||||||
|
0% {
|
||||||
|
background-color: theme(colors.sky.700 / 20%);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade-in {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade-in-up {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fade-in-down {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slide-in-left {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bounce-subtle {
|
||||||
|
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: translateY(-8px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animation utility classes */
|
||||||
|
.animate-fade-in {
|
||||||
|
opacity: 0;
|
||||||
|
animation: fade-in 400ms ease-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-fade-in-up {
|
||||||
|
opacity: 0;
|
||||||
|
animation: fade-in-up 600ms cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-fade-in-down {
|
||||||
|
opacity: 0;
|
||||||
|
animation: fade-in-down 250ms ease-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-slide-in-left {
|
||||||
|
opacity: 0;
|
||||||
|
animation: slide-in-left 600ms cubic-bezier(0.16, 1, 0.3, 1) forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-bounce-subtle {
|
||||||
|
animation: bounce-subtle 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animation delays */
|
||||||
|
.delay-100 {
|
||||||
|
animation-delay: 100ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delay-200 {
|
||||||
|
animation-delay: 200ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delay-300 {
|
||||||
|
animation-delay: 300ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delay-400 {
|
||||||
|
animation-delay: 400ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delay-500 {
|
||||||
|
animation-delay: 500ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delay-600 {
|
||||||
|
animation-delay: 600ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delay-700 {
|
||||||
|
animation-delay: 700ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delay-800 {
|
||||||
|
animation-delay: 800ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Component styles */
|
||||||
|
@layer components {
|
||||||
|
|
||||||
|
/* Section container - uses CSS vars, keep here */
|
||||||
|
.section-container {
|
||||||
|
max-width: calc(var(--content-max-width) + var(--sidebar-width) + 4rem);
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 var(--section-padding-x);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prose styling for markdown content - can't add classes to rendered HTML */
|
||||||
|
.prose-spec {
|
||||||
|
max-width: var(--content-max-width);
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose-spec h2 {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
margin-top: 3rem;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
padding-bottom: 0.75rem;
|
||||||
|
border-bottom: 1px solid theme(colors.slate.200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .prose-spec h2 {
|
||||||
|
border-bottom-color: theme(colors.neutral.800);
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose-spec h2:first-child {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose-spec h3 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-top: 2rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: theme(colors.slate.600);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .prose-spec h3 {
|
||||||
|
color: theme(colors.neutral.400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose-spec p {
|
||||||
|
color: theme(colors.slate.600);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .prose-spec p {
|
||||||
|
color: theme(colors.neutral.400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose-spec strong {
|
||||||
|
color: theme(colors.neutral.700);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .prose-spec strong {
|
||||||
|
color: theme(colors.neutral.300);
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose-spec li {
|
||||||
|
color: theme(colors.slate.600);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .prose-spec li {
|
||||||
|
color: theme(colors.neutral.400);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Spec clauses - ordered list with CSS counters and hover anchor links */
|
||||||
|
.prose-spec ol {
|
||||||
|
padding-left: 2.25rem;
|
||||||
|
counter-reset: item;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose-spec ol>li {
|
||||||
|
counter-increment: item;
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose-spec ol>li::before {
|
||||||
|
content: counters(item, ".") ".";
|
||||||
|
position: absolute;
|
||||||
|
left: -2.5rem;
|
||||||
|
width: 2rem;
|
||||||
|
text-align: right;
|
||||||
|
font-weight: 500;
|
||||||
|
color: theme(colors.slate.400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .prose-spec ol>li::before {
|
||||||
|
color: theme(colors.neutral.500);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Clause highlight effect using ::after pseudo-element */
|
||||||
|
.prose-spec ol>li::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: -0.25rem -0.5rem;
|
||||||
|
left: -2.75rem;
|
||||||
|
background-color: transparent;
|
||||||
|
border-radius: 4px;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose-spec ol>li.clause-highlight::after {
|
||||||
|
animation: clause-highlight-pulse 2s ease-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .prose-spec ol>li.clause-highlight::after {
|
||||||
|
animation: clause-highlight-pulse-dark 2s ease-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Anchor link with clause number - hidden by default, shown on hover */
|
||||||
|
.prose-spec .clause-link {
|
||||||
|
position: absolute;
|
||||||
|
right: 100%;
|
||||||
|
padding-right: 0.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: theme(colors.slate.400);
|
||||||
|
text-decoration: none;
|
||||||
|
opacity: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .prose-spec .clause-link {
|
||||||
|
color: theme(colors.neutral.500);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Link icon in the anchor link */
|
||||||
|
.prose-spec .clause-link-icon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 0.875rem;
|
||||||
|
height: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* On hover: show anchor link, hide CSS counter */
|
||||||
|
.prose-spec ol>li:hover:not(:has(ol:hover))>.clause-link,
|
||||||
|
.prose-spec ol>li:hover:not(:has(ol:hover))>p>.clause-link,
|
||||||
|
.prose-spec .clause-link:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose-spec ol>li:hover:not(:has(ol:hover))::before {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hover color for clause anchor link */
|
||||||
|
.prose-spec .clause-link:hover {
|
||||||
|
color: theme(colors.sky.500);
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose-spec img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin: 2rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose-spec p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Nav link active state (toggled by JavaScript) */
|
||||||
|
.nav-link.active {
|
||||||
|
color: theme(colors.sky.700) !important;
|
||||||
|
background-color: theme(colors.sky.600 / 15%) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .nav-link.active {
|
||||||
|
color: theme(colors.sky.400) !important;
|
||||||
|
background-color: theme(colors.sky.600 / 20%) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sidebar link active state (toggled by JavaScript) */
|
||||||
|
.sidebar-link.active {
|
||||||
|
color: theme(colors.sky.700) !important;
|
||||||
|
background-color: theme(colors.sky.600 / 15%) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark .sidebar-link.active {
|
||||||
|
color: theme(colors.sky.400) !important;
|
||||||
|
background-color: theme(colors.sky.600 / 20%) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sidebar scrollbar styling */
|
||||||
|
#spec-sidebar::-webkit-scrollbar {
|
||||||
|
width: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#spec-sidebar::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
#spec-sidebar::-webkit-scrollbar-thumb {
|
||||||
|
background-color: theme(colors.slate.200);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark #spec-sidebar::-webkit-scrollbar-thumb {
|
||||||
|
background-color: theme(colors.neutral.800);
|
||||||
|
}
|
||||||
|
}
|
||||||
437
src/utils/parseSpecContent.ts
Normal file
@@ -0,0 +1,437 @@
|
|||||||
|
/**
|
||||||
|
* Parses spec content using markdown AST for robust section extraction.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { unified } from "unified";
|
||||||
|
import remarkParse from "remark-parse";
|
||||||
|
import remarkRehype from "remark-rehype";
|
||||||
|
import rehypeStringify from "rehype-stringify";
|
||||||
|
import { getIconData, iconToSVG, iconToHTML } from "@iconify/utils";
|
||||||
|
import heroicons from "@iconify-json/heroicons/icons.json";
|
||||||
|
import type { Root, RootContent, Heading, List, ListItem, Html } from "mdast";
|
||||||
|
import type { Root as HastRoot } from "hast";
|
||||||
|
|
||||||
|
export interface TocItem {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
level: number;
|
||||||
|
clause?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FAQItem {
|
||||||
|
id: string;
|
||||||
|
question: string;
|
||||||
|
answer: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SpecSection {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
content: string;
|
||||||
|
clause: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ParsedSpec {
|
||||||
|
introduction: string;
|
||||||
|
summary: string;
|
||||||
|
terminology: string;
|
||||||
|
terminologyTitle: string;
|
||||||
|
specification: string;
|
||||||
|
specificationTitle: string;
|
||||||
|
specSections: SpecSection[];
|
||||||
|
faq: FAQItem[];
|
||||||
|
license: string;
|
||||||
|
tocItems: TocItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert text to a URL-friendly ID
|
||||||
|
*/
|
||||||
|
function slugify(text: string): string {
|
||||||
|
return text
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/[^\w\s-]/g, "")
|
||||||
|
.replace(/\s+/g, "-")
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate link icon SVG from heroicons icon set
|
||||||
|
*/
|
||||||
|
function generateLinkIconSvg(): string {
|
||||||
|
const iconData = getIconData(heroicons, "link");
|
||||||
|
if (!iconData) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
const result = iconToSVG(iconData);
|
||||||
|
return iconToHTML(result.body, {
|
||||||
|
...result.attributes,
|
||||||
|
class: "clause-link-icon",
|
||||||
|
stroke: "currentColor",
|
||||||
|
"stroke-width": "2",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
type MdastNode = Root | RootContent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract plain text from an mdast node tree
|
||||||
|
*/
|
||||||
|
function extractText(node: MdastNode): string {
|
||||||
|
if ("value" in node && typeof node.value === "string") {
|
||||||
|
return node.value;
|
||||||
|
}
|
||||||
|
if ("children" in node && Array.isArray(node.children)) {
|
||||||
|
return node.children.map((child) => extractText(child)).join("");
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find index of heading containing specific text
|
||||||
|
*/
|
||||||
|
function findHeadingIndex(
|
||||||
|
nodes: RootContent[],
|
||||||
|
text: string,
|
||||||
|
depth: number = 2,
|
||||||
|
): number {
|
||||||
|
return nodes.findIndex(
|
||||||
|
(node) =>
|
||||||
|
node.type === "heading" &&
|
||||||
|
(node as Heading).depth === depth &&
|
||||||
|
extractText(node).toLowerCase().includes(text.toLowerCase()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract nodes between two headings
|
||||||
|
*/
|
||||||
|
function extractSectionNodes(
|
||||||
|
nodes: RootContent[],
|
||||||
|
startText: string,
|
||||||
|
depth: number = 2,
|
||||||
|
): RootContent[] {
|
||||||
|
const startIdx = findHeadingIndex(nodes, startText, depth);
|
||||||
|
if (startIdx === -1) return [];
|
||||||
|
|
||||||
|
// Find the next heading of same or higher level
|
||||||
|
let endIdx = nodes.length;
|
||||||
|
for (let i = startIdx + 1; i < nodes.length; i++) {
|
||||||
|
const node = nodes[i];
|
||||||
|
if (node.type === "heading" && (node as Heading).depth <= depth) {
|
||||||
|
endIdx = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return nodes after the heading (not including the heading itself)
|
||||||
|
return nodes.slice(startIdx + 1, endIdx);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the full heading text
|
||||||
|
*/
|
||||||
|
function getHeadingText(
|
||||||
|
nodes: RootContent[],
|
||||||
|
text: string,
|
||||||
|
depth: number = 2,
|
||||||
|
): string {
|
||||||
|
const idx = findHeadingIndex(nodes, text, depth);
|
||||||
|
if (idx === -1) return text;
|
||||||
|
return extractText(nodes[idx]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert mdast nodes to HTML string
|
||||||
|
*/
|
||||||
|
async function nodesToHtml(nodes: RootContent[]): Promise<string> {
|
||||||
|
if (nodes.length === 0) return "";
|
||||||
|
|
||||||
|
// Create a root node with these children
|
||||||
|
const root: Root = { type: "root", children: nodes };
|
||||||
|
|
||||||
|
const result = await unified()
|
||||||
|
.use(remarkRehype, { allowDangerousHtml: true })
|
||||||
|
.use(rehypeStringify, { allowDangerousHtml: true })
|
||||||
|
.run(root);
|
||||||
|
|
||||||
|
const html = unified()
|
||||||
|
.use(rehypeStringify, { allowDangerousHtml: true })
|
||||||
|
.stringify(result as HastRoot);
|
||||||
|
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract top-level list item titles from an ordered list
|
||||||
|
*/
|
||||||
|
function extractListItemTitles(list: List): string[] {
|
||||||
|
const titles: string[] = [];
|
||||||
|
|
||||||
|
for (const item of list.children) {
|
||||||
|
if (item.type !== "listItem") continue;
|
||||||
|
|
||||||
|
// Get the first paragraph or text content of the list item
|
||||||
|
// The title is the text before any nested list
|
||||||
|
let title = "";
|
||||||
|
for (const child of item.children) {
|
||||||
|
if (child.type === "list") break; // Stop at nested list
|
||||||
|
if (child.type === "paragraph") {
|
||||||
|
title = extractText(child);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Handle inline text directly in list item
|
||||||
|
title += extractText(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
title = title.split("\n")[0].trim();
|
||||||
|
if (title) {
|
||||||
|
titles.push(title);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return titles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the first ordered list in nodes and extract its structure
|
||||||
|
*/
|
||||||
|
function findSpecSections(nodes: RootContent[]): SpecSection[] {
|
||||||
|
const sections: SpecSection[] = [];
|
||||||
|
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (node.type === "list" && (node as List).ordered) {
|
||||||
|
const titles = extractListItemTitles(node as List);
|
||||||
|
for (let i = 0; i < titles.length; i++) {
|
||||||
|
const title = titles[i];
|
||||||
|
const clauseNum = i + 1;
|
||||||
|
sections.push({
|
||||||
|
id: `clause-${clauseNum}`,
|
||||||
|
title,
|
||||||
|
content: "",
|
||||||
|
clause: `${clauseNum}.`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break; // Only process first ordered list
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sections;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add anchor IDs and links to ordered list items recursively.
|
||||||
|
* Injects an invisible anchor link before content for hover-to-reveal behavior.
|
||||||
|
*/
|
||||||
|
function addClauseAnchors(list: List, prefix: string = ""): void {
|
||||||
|
for (let i = 0; i < list.children.length; i++) {
|
||||||
|
const item = list.children[i];
|
||||||
|
if (item.type !== "listItem") continue;
|
||||||
|
|
||||||
|
// Calculate clause number and ID
|
||||||
|
const clauseNum = prefix ? `${prefix}.${i + 1}` : `${i + 1}`;
|
||||||
|
const clauseId = `clause-${clauseNum.replace(/\./g, "-")}`;
|
||||||
|
|
||||||
|
// Add ID to the list item via hProperties
|
||||||
|
(item as ListItem & { data?: { hProperties?: { id?: string } } }).data = {
|
||||||
|
hProperties: { id: clauseId },
|
||||||
|
};
|
||||||
|
|
||||||
|
// Find the first paragraph in the item and prepend an anchor link
|
||||||
|
for (const child of item.children) {
|
||||||
|
if (child.type === "paragraph") {
|
||||||
|
// Create anchor link HTML with clause number text and link icon
|
||||||
|
const linkIcon = generateLinkIconSvg();
|
||||||
|
const anchorHtml: Html = {
|
||||||
|
type: "html",
|
||||||
|
value: `<a href="#${clauseId}" class="clause-link" aria-hidden="true">${linkIcon}${clauseNum}.</a>`,
|
||||||
|
};
|
||||||
|
// Prepend anchor to paragraph children
|
||||||
|
(child as { children: RootContent[] }).children.unshift(
|
||||||
|
anchorHtml as unknown as RootContent,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively process nested ordered lists
|
||||||
|
for (const child of item.children) {
|
||||||
|
if (child.type === "list" && (child as List).ordered) {
|
||||||
|
addClauseAnchors(child as List, clauseNum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract FAQ items from FAQ section nodes
|
||||||
|
*/
|
||||||
|
function extractFAQFromNodes(nodes: RootContent[]): FAQItem[] {
|
||||||
|
const items: FAQItem[] = [];
|
||||||
|
let currentQuestion = "";
|
||||||
|
let currentId = "";
|
||||||
|
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (node.type === "heading" && (node as Heading).depth === 3) {
|
||||||
|
// Save previous FAQ item if we had one
|
||||||
|
if (currentQuestion) {
|
||||||
|
items.push({
|
||||||
|
id: currentId,
|
||||||
|
question: currentQuestion,
|
||||||
|
answer: "", // Placeholder, will be filled later
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
currentQuestion = extractText(node);
|
||||||
|
currentId = `faq-${slugify(currentQuestion).slice(0, 50)}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't forget the last item
|
||||||
|
if (currentQuestion) {
|
||||||
|
items.push({
|
||||||
|
id: currentId,
|
||||||
|
question: currentQuestion,
|
||||||
|
answer: "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build table of contents from parsed sections
|
||||||
|
*/
|
||||||
|
function buildTocItems(parsed: Partial<ParsedSpec>): TocItem[] {
|
||||||
|
const items: TocItem[] = [];
|
||||||
|
|
||||||
|
if (parsed.terminology) {
|
||||||
|
items.push({
|
||||||
|
id: "terminology",
|
||||||
|
title: parsed.terminologyTitle || "Terminology",
|
||||||
|
level: 2,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (parsed.specification) {
|
||||||
|
items.push({
|
||||||
|
id: "specification",
|
||||||
|
title: "Specification",
|
||||||
|
level: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (parsed.specSections) {
|
||||||
|
for (const section of parsed.specSections) {
|
||||||
|
items.push({
|
||||||
|
id: section.id,
|
||||||
|
title: section.title,
|
||||||
|
level: 3,
|
||||||
|
clause: section.clause,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main parsing function - takes markdown content and returns structured content
|
||||||
|
*/
|
||||||
|
export async function parseSpecContent(
|
||||||
|
markdown: string,
|
||||||
|
): Promise<ParsedSpec> {
|
||||||
|
// Parse markdown to AST
|
||||||
|
const tree = unified().use(remarkParse).parse(markdown) as Root;
|
||||||
|
|
||||||
|
// Remove title (h1) from the tree - it's displayed separately in the Hero
|
||||||
|
const nodes = tree.children.filter((node) => {
|
||||||
|
if (node.type === "heading" && (node as Heading).depth === 1) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get heading titles
|
||||||
|
const terminologyTitle = getHeadingText(nodes, "Terminology");
|
||||||
|
const specificationTitle = getHeadingText(
|
||||||
|
nodes,
|
||||||
|
"Git Common-Flow Specification",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Extract section nodes
|
||||||
|
const introNodes = extractSectionNodes(nodes, "Introduction");
|
||||||
|
const summaryNodes = extractSectionNodes(nodes, "Summary");
|
||||||
|
const terminologyNodes = extractSectionNodes(nodes, "Terminology");
|
||||||
|
const specNodes = extractSectionNodes(nodes, "Git Common-Flow Specification");
|
||||||
|
const faqNodes = extractSectionNodes(nodes, "FAQ");
|
||||||
|
const licenseNodes = extractSectionNodes(nodes, "License");
|
||||||
|
|
||||||
|
// Extract spec sections from the first ordered list
|
||||||
|
const specSections = findSpecSections(specNodes);
|
||||||
|
|
||||||
|
// Add anchor IDs and links to spec list items
|
||||||
|
for (const node of specNodes) {
|
||||||
|
if (node.type === "list" && (node as List).ordered) {
|
||||||
|
addClauseAnchors(node as List);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract FAQ items structure
|
||||||
|
const faqItems = extractFAQFromNodes(faqNodes);
|
||||||
|
|
||||||
|
// Collect FAQ answer nodes for each item
|
||||||
|
const faqAnswerNodes: RootContent[][] = [];
|
||||||
|
let currentAnswerNodes: RootContent[] = [];
|
||||||
|
|
||||||
|
for (const node of faqNodes) {
|
||||||
|
if (node.type === "heading" && (node as Heading).depth === 3) {
|
||||||
|
if (currentAnswerNodes.length > 0) {
|
||||||
|
faqAnswerNodes.push(currentAnswerNodes);
|
||||||
|
}
|
||||||
|
currentAnswerNodes = [];
|
||||||
|
} else {
|
||||||
|
currentAnswerNodes.push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Don't forget the last answer
|
||||||
|
if (currentAnswerNodes.length > 0) {
|
||||||
|
faqAnswerNodes.push(currentAnswerNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert sections to HTML
|
||||||
|
const [introduction, summary, terminology, specification, license] =
|
||||||
|
await Promise.all([
|
||||||
|
nodesToHtml(introNodes),
|
||||||
|
nodesToHtml(summaryNodes),
|
||||||
|
nodesToHtml(terminologyNodes),
|
||||||
|
nodesToHtml(specNodes),
|
||||||
|
nodesToHtml(licenseNodes),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Convert FAQ answers to HTML
|
||||||
|
const faqAnswers = await Promise.all(
|
||||||
|
faqAnswerNodes.map((nodes) => nodesToHtml(nodes)),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Assign FAQ answers
|
||||||
|
const faq = faqItems.map((item, i) => ({
|
||||||
|
...item,
|
||||||
|
answer: faqAnswers[i] || "",
|
||||||
|
}));
|
||||||
|
|
||||||
|
const parsed: ParsedSpec = {
|
||||||
|
introduction,
|
||||||
|
summary,
|
||||||
|
terminology,
|
||||||
|
terminologyTitle,
|
||||||
|
specification,
|
||||||
|
specificationTitle,
|
||||||
|
specSections,
|
||||||
|
faq,
|
||||||
|
license,
|
||||||
|
tocItems: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
parsed.tocItems = buildTocItems(parsed);
|
||||||
|
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
48
src/utils/versions.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import { getCollection } from "astro:content";
|
||||||
|
import * as semver from "semver";
|
||||||
|
import { config } from "../config";
|
||||||
|
|
||||||
|
export interface VersionInfo {
|
||||||
|
versions: string[];
|
||||||
|
currentVersion: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get version information derived from available spec files.
|
||||||
|
* Returns all versions sorted newest-first and determines the current version.
|
||||||
|
*/
|
||||||
|
export async function getVersionInfo(): Promise<VersionInfo> {
|
||||||
|
const specs = await getCollection("spec");
|
||||||
|
const versions = specs
|
||||||
|
.map((s) => s.data.version)
|
||||||
|
.filter((v): v is string => semver.valid(v) !== null)
|
||||||
|
.sort((a, b) => semver.rcompare(a, b)); // newest first
|
||||||
|
|
||||||
|
const currentVersion =
|
||||||
|
config.currentVersionOverride ?? determineCurrentVersion(versions);
|
||||||
|
|
||||||
|
return { versions, currentVersion };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine the current version based on priority:
|
||||||
|
* 1. Latest stable version
|
||||||
|
* 2. Latest RC version
|
||||||
|
* 3. Newest available version
|
||||||
|
*/
|
||||||
|
function determineCurrentVersion(versions: string[]): string {
|
||||||
|
// Priority order: stable (null prerelease) first, then rc
|
||||||
|
const priority: (string | null)[] = [null, "rc"];
|
||||||
|
|
||||||
|
for (const type of priority) {
|
||||||
|
const match = versions.find((v) => {
|
||||||
|
const pre = semver.prerelease(v);
|
||||||
|
if (type === null) return pre === null;
|
||||||
|
return pre?.[0] === type;
|
||||||
|
});
|
||||||
|
if (match) return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to newest overall
|
||||||
|
return versions[0] ?? "";
|
||||||
|
}
|
||||||
10
tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"extends": "astro/tsconfigs/strict",
|
||||||
|
"include": [
|
||||||
|
".astro/types.d.ts",
|
||||||
|
"**/*"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"dist"
|
||||||
|
]
|
||||||
|
}
|
||||||
7
wrangler.jsonc
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"name": "commonflow-org",
|
||||||
|
"compatibility_date": "2026-01-12",
|
||||||
|
"assets": {
|
||||||
|
"directory": "./dist"
|
||||||
|
}
|
||||||
|
}
|
||||||