Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

repo sync #7645

Merged
merged 47 commits into from
Jun 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
3a75d49
add middleware to enable feature-based versioning
sarahs May 28, 2021
6baeaff
add dummy files (do not merge)
sarahs May 28, 2021
02915d5
add comments
sarahs May 28, 2021
c8e0418
Update middleware/contextualizers/features.js
sarahs May 29, 2021
3cc18b0
restore original conditionals
sarahs May 29, 2021
1f9858c
Merge branch 'feature-versioning' of github.com:github/docs-internal …
sarahs May 29, 2021
a1c1a81
Merge branch 'main' of github.com:github/docs-internal into feature-v…
sarahs Jun 15, 2021
737b18b
Merge branch 'main' of github.com:github/docs-internal into feature-v…
sarahs Jun 16, 2021
ba105bf
move dummy pages into test fixtures
sarahs Jun 17, 2021
7b6611a
test feature versioning in frontmatter
sarahs Jun 17, 2021
0049c78
test feature versioning in liquid conditionals
sarahs Jun 17, 2021
3ad7f24
allow feature versioning in frontmatter schema
sarahs Jun 17, 2021
0947adb
support feature versions in frontmatter
sarahs Jun 17, 2021
139f9e0
name the boolean for clarity
sarahs Jun 17, 2021
7f0737b
add feature versions schema and tests
sarahs Jun 17, 2021
f74f531
add README
sarahs Jun 17, 2021
53291a9
Merge branch 'main' of github.com:github/docs-internal into feature-v…
sarahs Jun 17, 2021
62ba27c
Merge branch 'main' of github.com:github/docs-internal into feature-v…
sarahs Jun 17, 2021
3f87580
use __dirname instead of process cwd
sarahs Jun 17, 2021
04b6c07
Merge branch 'main' of github.com:github/docs-internal into feature-v…
sarahs Jun 22, 2021
c4d6ae7
fix versions schema
sarahs Jun 22, 2021
512d29b
support ghae = next in frontmatter
sarahs Jun 22, 2021
d639b74
check if feature dir is present
sarahs Jun 22, 2021
4c0e21e
lint
sarahs Jun 22, 2021
1188fa5
Merge branch 'main' into feature-versioning
sarahs Jun 22, 2021
7ee7a1a
Merge branch 'main' into feature-versioning
JamesMGreene Jun 22, 2021
b702b00
use short versions in feature placeholder and add comment that it sho…
sarahs Jun 23, 2021
2427f92
write tests to ensure that ghes versioning in a feature gets combined…
sarahs Jun 23, 2021
92dc4e2
ensure that ghes versioning in a feature gets combined with ghes vers…
sarahs Jun 23, 2021
96e6c90
feedback from @heiskr
sarahs Jun 23, 2021
a8709c4
lint
sarahs Jun 23, 2021
d454e40
Merge branch 'main' of github.com:github/docs-internal into feature-v…
sarahs Jun 23, 2021
494e3f5
move cp data step earlier so the build succeeds
sarahs Jun 23, 2021
7e3e756
Merge branch 'main' of github.com:github/docs-internal into feature-v…
sarahs Jun 23, 2021
9115cb5
remove visualization beta (#18750)
skedwards88 Jun 23, 2021
e4c36f6
Branch was updated using the 'autoupdate branch' Actions workflow.
Octomerger Jun 23, 2021
fb3d0f1
Merge pull request #20106 from github/repo-sync
Octomerger Jun 23, 2021
68d48f3
Merge branch 'main' into feature-versioning
sarahs Jun 23, 2021
72dea68
Merge branch 'main' into repo-sync
Octomerger Jun 23, 2021
31188d9
Merge pull request #19636 from github/feature-versioning
sarahs Jun 23, 2021
a1e1815
Branch was updated using the 'autoupdate branch' Actions workflow.
Octomerger Jun 23, 2021
7e692e4
Merge pull request #20107 from github/repo-sync
Octomerger Jun 23, 2021
8b01c89
switch <code> for <em> in the code block (#20082)
joshuawalker Jun 23, 2021
6f4439a
Fix code snippet in the Installing Apple Certificate article (#20091)
AlenaSviridenko Jun 23, 2021
a92d820
Remove remaining JSON requires (#20110)
heiskr Jun 23, 2021
3052acb
Merge branch 'main' into repo-sync
Octomerger Jun 23, 2021
e4e09d7
Merge pull request #20109 from github/repo-sync
Octomerger Jun 23, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ COPY tsconfig.json ./tsconfig.json

RUN npx tsc

# We need to copy data in order to do the build
COPY --chown=node:node data ./data

RUN npm run build

# --------------------------------------------------------------------------------
Expand Down Expand Up @@ -85,7 +88,6 @@ ENV AIRGAP true
# Copy only what's needed to run the server
COPY --chown=node:node assets ./assets
COPY --chown=node:node content ./content
COPY --chown=node:node data ./data
COPY --chown=node:node includes ./includes
COPY --chown=node:node layouts ./layouts
COPY --chown=node:node lib ./lib
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,12 @@ jobs:
echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode --output $PP_PATH

# create temporary keychain
security create-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN_PATH
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN_PATH
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH

# import certificate to keychain
security import $CERTIFICATE_PATH -P $P12_PASSWORD -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
security list-keychain -d user -s $KEYCHAIN_PATH

# apply provisioning profile
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ versions:
---

{% data reusables.actions.enterprise-beta %}
{% data reusables.actions.visualization-beta %}
{% data reusables.actions.enterprise-github-hosted-runners %}
{% data reusables.actions.ae-beta %}

Expand Down
52 changes: 52 additions & 0 deletions data/features/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
## Feature-based versioning

Feature-based versioning allows us to define and control the versions of an arbitrarily named "feature" in one place.

**Note**: Do not delete `data/features/placeholder.yml` because it is used by tests.

## How it works

Add a new YAML file with the feature name you want to use in this directory. For a feature named `meow`, that would be `data/features/meow.yml`.

Add a `versions` block to the YML file with the short names of the versions the feature is available in. For example:

```yaml
versions:
fpt: '*'
ghes: '>3.1'
ghae: '*'
```

The format and allowed values are the same as the [frontmatter versions property](/content#versions).

### Liquid conditionals

Now you can use `{% if meow %} ... {% endif %}` in content files! Note this is the `if` tag, not the new `ifversion` tag.

### Frontmatter

You can also use the feature in frontmatter in content files:

```yaml
versions:
fpt: '*'
ghes: '>3.1'
feature: 'meow'
```

If you want a content file to apply to more than one feature, you can do this:

```yaml
versions:
fpt: '*'
ghes: '>3.1'
feature: ['meow', 'blorp']
```

## Schema enforcement

The schema for validating the feature versioning lives in [`tests/helpers/schemas/feature-versions.js`](tests/helpers/schemas/feature-versions.js) and is exercised by [`tests/content/lint-files.js`](tests/content/lint-files.js).

## Script to remove feature tags

TBD!
4 changes: 4 additions & 0 deletions data/features/placeholder.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Do not delete! Used by tests.
versions:
ghes: '>3.0'
ghae: '*'
7 changes: 0 additions & 7 deletions data/reusables/actions/visualization-beta.md

This file was deleted.

2 changes: 1 addition & 1 deletion data/reusables/command_line/providing-token-as-password.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ For example, on the command line you would enter the following:

```shell
$ git clone https://{% data variables.command_line.codeblock %}/<em>username</em>/<em>repo</em>.git
Username: <code>your_username</code>
Username: <em>your_username</em>
Password: <em>your_token</em>
```
3 changes: 2 additions & 1 deletion lib/feature-flags.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const featureFlags = require('../feature-flags')
const readJsonFile = require('./read-json-file')
const featureFlags = readJsonFile('./feature-flags.json')

// add feature flags as environment variables
Object.entries(featureFlags).forEach(([feature, value]) => {
Expand Down
28 changes: 24 additions & 4 deletions lib/frontmatter.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const fs = require('fs')
const path = require('path')
const parse = require('./read-frontmatter')
const semver = require('semver')
const layouts = require('./layouts')
Expand All @@ -9,8 +11,10 @@ const semverRange = {
conform: semverValidRange,
message: 'Must be a valid SemVer range'
}
const versionIds = Object.keys(require('./all-versions'))
const versionObjs = Object.values(require('./all-versions'))
const guideTypes = ['overview', 'quick_start', 'tutorial', 'how_to', 'reference']
const featureVersions = fs.readdirSync(path.posix.join(process.cwd(), 'data/features'))
.map(file => path.basename(file, '.yml'))

const schema = {
properties: {
Expand Down Expand Up @@ -197,15 +201,31 @@ const schema = {
}
}

const featureVersionsProp = {
feature: {
type: ['string', 'array'],
enum: featureVersions,
items: {
type: 'string'
},
message: 'must be the name (or names) of a feature that matches "filename" in data/features/_filename_.yml'
}
}

schema.properties.versions = {
type: ['object', 'string'], // allow a '*' string to indicate all versions
required: true,
properties: versionIds.reduce((acc, versionId) => {
acc[versionId] = semverRange
properties: versionObjs.reduce((acc, versionObj) => {
acc[versionObj.plan] = semverRange
acc[versionObj.shortName] = semverRange
return acc
}, {})
}, featureVersionsProp)
}

// Support 'github-ae': next
schema.properties.versions.properties['github-ae'] = 'next'
schema.properties.versions.properties.ghae = 'next'

function frontmatter (markdown, opts = {}) {
const defaults = {
schema,
Expand Down
73 changes: 63 additions & 10 deletions lib/get-applicable-versions.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
const path = require('path')
const { reduce, sortBy } = require('lodash')
const allVersions = require('./all-versions')
const versionSatisfiesRange = require('./version-satisfies-range')
const checkIfNextVersionOnly = require('./check-if-next-version-only')
const dataDirectory = require('./data-directory')
const encodeBracketedParentheses = require('./encode-bracketed-parentheses')
const featuresDir = path.posix.join(__dirname, '../data/features')

const featureData = dataDirectory(featuresDir, {
preprocess: dataString =>
encodeBracketedParentheses(dataString.trimEnd()),
ignorePatterns: [/README\.md$/]
})

// return an array of versions that an article's product versions encompasses
function getApplicableVersions (frontmatterVersions, filepath) {
Expand All @@ -13,17 +24,63 @@ function getApplicableVersions (frontmatterVersions, filepath) {
return Object.keys(allVersions)
}

// get an array like: [ 'free-pro-team@latest', '[email protected]', 'enterprise-cloud@latest' ]
const applicableVersions = []
// Check for frontmatter that includes a feature name, like:
// fpt: '*'
// feature: 'foo'
// or multiple feature names, like:
// fpt: '*'
// feature: ['foo', 'bar']
// and add the versions affiliated with the feature (e.g., foo) to the frontmatter versions object:
// fpt: '*'
// ghes: '>=2.23'
// ghae: '*'
// where the feature is bringing the ghes and ghae versions into the mix.
const featureVersions = reduce(frontmatterVersions, (result, value, key) => {
if (key === 'feature') {
if (typeof value === 'string') {
Object.assign(result, { ...featureData[value].versions })
} else if (Array.isArray(value)) {
value.forEach(str => {
Object.assign(result, { ...featureData[str].versions })
})
}
delete result[key]
}
return result
}, {})

// We will be evaluating feature versions separately, so we can remove this.
delete frontmatterVersions.feature

// Get available versions for frontmatter and for feature versions.
const foundFeatureVersions = evaluateVersions(featureVersions)
const foundFrontmatterVersions = evaluateVersions(frontmatterVersions)

// Combine them!
const applicableVersions = [...new Set(foundFrontmatterVersions.versions.concat(foundFeatureVersions.versions))]

if (!applicableVersions.length && !foundFrontmatterVersions.isNextVersionOnly && !foundFeatureVersions.isNextVersionOnly) {
throw new Error(`No applicable versions found for ${filepath}. Please double-check the page's \`versions\` frontmatter.`)
}

// Sort them by the order in lib/all-versions.
const sortedVersions = sortBy(applicableVersions, (v) => { return Object.keys(allVersions).indexOf(v) })

return sortedVersions
}

function evaluateVersions (versionsObj) {
let isNextVersionOnly = false

// where frontmatter is something like:
// get an array like: [ 'free-pro-team@latest', '[email protected]', 'enterprise-cloud@latest' ]
const versions = []

// where versions obj is something like:
// fpt: '*'
// ghes: '>=2.19'
// ghae: '*'
// ^ where each key corresponds to a plan's short name (defined in lib/all-versions.js)
Object.entries(frontmatterVersions)
Object.entries(versionsObj)
.forEach(([plan, planValue]) => {
// Special handling for frontmatter that evalues to the next GHES release number or a hardcoded `next`.
isNextVersionOnly = checkIfNextVersionOnly(planValue)
Expand All @@ -37,16 +94,12 @@ function getApplicableVersions (frontmatterVersions, filepath) {
const versionToCompare = relevantVersion.hasNumberedReleases ? relevantVersion.currentRelease : '1.0'

if (versionSatisfiesRange(versionToCompare, planValue)) {
applicableVersions.push(relevantVersion.version)
versions.push(relevantVersion.version)
}
})
})

if (!applicableVersions.length && !isNextVersionOnly) {
throw new Error(`No applicable versions found for ${filepath}. Please double-check the page's \`versions\` frontmatter.`)
}

return applicableVersions
return { versions, isNextVersionOnly }
}

module.exports = getApplicableVersions
14 changes: 14 additions & 0 deletions lib/read-json-file.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const fs = require('fs')
const path = require('path')

module.exports = function readJsonFile (xpath) {
return JSON.parse(
fs.readFileSync(
path.join(
process.cwd(),
xpath
),
'utf8'
)
)
}
3 changes: 2 additions & 1 deletion lib/redirects/precompile.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const developerRedirects = require('../redirects/static/developer')
const readJsonFile = require('../read-json-file')
const developerRedirects = readJsonFile('./lib/redirects/static/developer.json')
const { latest } = require('../../lib/enterprise-server-releases')
const latestDevRedirects = {}

Expand Down
6 changes: 4 additions & 2 deletions lib/render-content/plugins/rewrite-local-links.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
const path = require('path')
const visit = require('unist-util-visit')
const externalRedirects = Object.keys(require('../../redirects/external-sites'))
const { getPathWithoutLanguage, getVersionStringFromPath } = require('../../path-utils')
const { getNewVersionedPath } = require('../../old-versions-utils')
const patterns = require('../../patterns')
const { deprecated, latest } = require('../../enterprise-server-releases')
const nonEnterpriseDefaultVersion = require('../../non-enterprise-default-version')
const allVersions = require('../../all-versions')
const removeFPTFromPath = require('../../remove-fpt-from-path')
const supportedVersions = Object.keys(allVersions)
const supportedPlans = Object.values(allVersions).map(v => v.plan)
const removeFPTFromPath = require('../../remove-fpt-from-path')
const readJsonFile = require('../../read-json-file')
const externalRedirects = Object.keys(readJsonFile('./lib/redirects/external-sites.json'))


// Matches any <a> tags with an href that starts with `/`
const matcher = node => (
Expand Down
3 changes: 2 additions & 1 deletion lib/rewrite-local-links.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
const assert = require('assert')
const path = require('path')
const externalRedirects = Object.keys(require('./redirects/external-sites'))
const { getPathWithoutLanguage, getVersionStringFromPath } = require('./path-utils')
const { getNewVersionedPath } = require('./old-versions-utils')
const patterns = require('./patterns')
Expand All @@ -10,6 +9,8 @@ const allVersions = require('./all-versions')
const supportedVersions = Object.keys(allVersions)
const supportedPlans = Object.values(allVersions).map(v => v.plan)
const removeFPTFromPath = require('./remove-fpt-from-path')
const readJsonFile = require('./read-json-file')
const externalRedirects = readJsonFile('./lib/redirects/external-sites.json')

// Content authors write links like `/some/article/path`, but they need to be
// rewritten on the fly to match the current language and page version
Expand Down
2 changes: 1 addition & 1 deletion lib/search/algolia-search.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const algoliasearch = require('algoliasearch')
const { get } = require('lodash')
const { namePrefix } = require('./config')
const { namePrefix } = require('./config.js')

// https://www.algolia.com/apps/ZI5KPY1HBE/dashboard
// This API key is public. There's also a private API key for writing to the Algolia API
Expand Down
2 changes: 1 addition & 1 deletion lib/search/lunr-search.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ require('lunr-languages/lunr.pt')(lunr)
require('lunr-languages/lunr.de')(lunr)
const { get } = require('lodash')
const readFileAsync = require('../readfile-async')
const { namePrefix } = require('./config')
const { namePrefix } = require('./config.js')
const { decompress } = require('./compress')

const LUNR_DIR = './indexes'
Expand Down
5 changes: 3 additions & 2 deletions middleware/archived-enterprise-versions.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ const patterns = require('../lib/patterns')
const versionSatisfiesRange = require('../lib/version-satisfies-range')
const isArchivedVersion = require('../lib/is-archived-version')
const got = require('got')
const archvivedRedirects = require('../lib/redirects/static/archived-redirects-from-213-to-217')
const archivedFrontmatterFallbacks = require('../lib/redirects/static/archived-frontmatter-fallbacks')
const readJsonFile = require('../lib/read-json-file')
const archvivedRedirects = readJsonFile('./lib/redirects/static/archived-redirects-from-213-to-217.json')
const archivedFrontmatterFallbacks = readJsonFile('./lib/redirects/static/archived-frontmatter-fallbacks.json')

// This module handles requests for deprecated GitHub Enterprise versions
// by routing them to static content in help-docs-archived-enterprise-versions
Expand Down
3 changes: 2 additions & 1 deletion middleware/context.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ const {
} = require('../lib/path-utils')
const productNames = require('../lib/product-names')
const warmServer = require('../lib/warm-server')
const featureFlags = Object.keys(require('../feature-flags'))
const readJsonFile = require('../lib/read-json-file')
const featureFlags = Object.keys(readJsonFile('./feature-flags.json'))
const builtAssets = require('../lib/built-asset-urls')
const searchVersions = require('../lib/search/versions')
const nonEnterpriseDefaultVersion = require('../lib/non-enterprise-default-version')
Expand Down
18 changes: 18 additions & 0 deletions middleware/contextualizers/features.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const getApplicableVersions = require('../../lib/get-applicable-versions')

module.exports = async function features (req, res, next) {
if (!req.context.page) return next()

// Determine whether the currentVersion belongs to the list of versions the feature is available in.
Object.keys(req.context.site.data.features).forEach(featureName => {
const { versions } = req.context.site.data.features[featureName]
const applicableVersions = getApplicableVersions(versions, req.path)

// Adding the resulting boolean to the context object gives us the ability to use
// `{% if featureName ... %}` conditionals in content files.
const isFeatureAvailableInCurrentVersion = applicableVersions.includes(req.context.currentVersion)
req.context[featureName] = isFeatureAvailableInCurrentVersion
})

return next()
}
Loading