Merge pull request #3 from actions/paper-spa-updates

Pull in latest updates
This commit is contained in:
Yoann Chaudet
2022-07-26 11:49:12 -07:00
committed by GitHub
55 changed files with 1190 additions and 724 deletions

1
.gitignore vendored
View File

@@ -1,4 +1,5 @@
node_modules/
/src/fixtures/tmp
# Editors
.vscode/

10
.prettierrc.yaml Normal file
View File

@@ -0,0 +1,10 @@
# Prettier (formatter) configuration
---
printWidth: 80
tabWidth: 2
useTabs: false
semi: false
singleQuote: true
trailingComma: none
bracketSpacing: false
arrowParens: avoid

View File

@@ -2,6 +2,8 @@
An action to enable Pages and extract various metadata about a site. It can also be used to configure various static site generators we support as [starter workflows][starter-workflows].
See [`set-pages-path.js`](./src/set-pages-path.js) for more details on how we configure static site generators to work "out of the box" with GitHub Pages.
# Usage
See [action.yml](action.yml) and the [Pages starter workflows][starter-workflows].

2
dist/gatsby.js vendored Normal file
View File

@@ -0,0 +1,2 @@
// Default Pages configuration for Gatsby
module.exports = {}

593
dist/index.js vendored
View File

@@ -14000,107 +14000,6 @@ function plural(ms, msAbs, n, name) {
}
/***/ }),
/***/ 3259:
/***/ (function(module) {
void function(global) {
'use strict';
// ValueError :: String -> Error
function ValueError(message) {
var err = new Error(message);
err.name = 'ValueError';
return err;
}
// defaultTo :: a,a? -> a
function defaultTo(x, y) {
return y == null ? x : y;
}
// create :: Object -> String,*... -> String
function create(transformers) {
return function(template) {
var args = Array.prototype.slice.call(arguments, 1);
var idx = 0;
var state = 'UNDEFINED';
return template.replace(
/([{}])\1|[{](.*?)(?:!(.+?))?[}]/g,
function(match, literal, _key, xf) {
if (literal != null) {
return literal;
}
var key = _key;
if (key.length > 0) {
if (state === 'IMPLICIT') {
throw ValueError('cannot switch from ' +
'implicit to explicit numbering');
}
state = 'EXPLICIT';
} else {
if (state === 'EXPLICIT') {
throw ValueError('cannot switch from ' +
'explicit to implicit numbering');
}
state = 'IMPLICIT';
key = String(idx);
idx += 1;
}
var value = defaultTo('', lookup(args, key.split('.')));
if (xf == null) {
return value;
} else if (Object.prototype.hasOwnProperty.call(transformers, xf)) {
return transformers[xf](value);
} else {
throw ValueError('no transformer named "' + xf + '"');
}
}
);
};
}
function lookup(_obj, _path) {
var obj = _obj;
var path = _path;
if (!/^\d+$/.test(path[0])) {
path = ['0'].concat(path);
}
for (var idx = 0; idx < path.length; idx += 1) {
var key = path[idx];
obj = typeof obj[key] === 'function' ? obj[key]() : obj[key];
}
return obj;
}
// format :: String,*... -> String
var format = create({});
// format.create :: Object -> String,*... -> String
format.create = create;
// format.extend :: Object,Object -> ()
format.extend = function(prototype, transformers) {
var $format = create(transformers);
prototype.format = function() {
var args = Array.prototype.slice.call(arguments);
args.unshift(this);
return $format.apply(global, args);
};
};
/* istanbul ignore else */
if (true) {
module.exports = format;
} else {}
}.call(this, this);
/***/ }),
/***/ 9318:
@@ -14531,161 +14430,282 @@ exports.debug = debug; // for test
const fs = __nccwpck_require__(7147)
const espree = __nccwpck_require__(6910)
const format = __nccwpck_require__(3259)
const core = __nccwpck_require__(2186)
// Parse the AST
const espreeOptions = {
ecmaVersion: 6,
sourceType: "module",
range: true,
}
/*
Parse a JavaScript based configuration file and inject arbitrary key/value in it.
This is used to make sure most static site generators can automatically handle
Pages's path based routing (and work).
Supported configuration initializations:
(1) Default export:
export default {
// configuration object here
}
(2) Direct module export:
module.exports = {
// configuration object here
}
(3) Indirect module export:
const config = // configuration object here
module.exports = config
*/
class ConfigParser {
constructor(staticSiteConfig) {
this.pathPropertyNuxt = `router: {\n base: '{0}'\n }`
this.pathPropertyNext = `basePath: '{0}'`
this.pathPropertyGatsby = `pathPrefix: '{0}'`
this.configskeleton = `export default {\n {0}\n}`
this.staticSiteConfig = staticSiteConfig
this.config = fs.existsSync(this.staticSiteConfig.filePath) ? fs.readFileSync(this.staticSiteConfig.filePath, "utf8") : null
this.validate()
// Ctor
// - configurationFile: path to the configuration file
// - blankConfigurationFile: a blank configuration file to use if non was previously found
constructor({configurationFile, blankConfigurationFile, properties}) {
// Save field
this.configurationFile = configurationFile
this.properties = properties
// If the configuration file does not exist, initialize it with the blank configuration file
if (!fs.existsSync(this.configurationFile)) {
core.info('Using default blank configuration')
const blankConfiguration = fs.readFileSync(blankConfigurationFile, 'utf8')
fs.writeFileSync(this.configurationFile, blankConfiguration, {
encoding: 'utf8'
})
}
validate() {
if (!this.config) {
core.info(`original raw configuration was empty:\n${this.config}`)
core.info('Generating a default configuration to start from...')
// Read the configuration file
this.configuration = fs.readFileSync(this.configurationFile, 'utf8')
}
// Update the `config` property with a default configuration file
this.config = this.generateConfigFile()
// Find the configuration object in an AST.
// Look for a default export, a direct module export or an indirect module
// export (in that order).
//
// Return the configuration object or null.
findConfigurationObject(ast) {
// Try to find a default export
var defaultExport = ast.body.find(
node =>
node.type === 'ExportDefaultDeclaration' &&
node.declaration.type === 'ObjectExpression'
)
if (defaultExport) {
core.info('Found configuration object in default export declaration')
return defaultExport.declaration
}
// Try to find a module export
var moduleExport = ast.body.find(
node =>
node.type === 'ExpressionStatement' &&
node.expression.type === 'AssignmentExpression' &&
node.expression.operator === '=' &&
node.expression.left.type === 'MemberExpression' &&
node.expression.left.object.type === 'Identifier' &&
node.expression.left.object.name === 'module' &&
node.expression.left.property.type === 'Identifier' &&
node.expression.left.property.name === 'exports'
)
// Direct module export
if (
moduleExport &&
moduleExport.expression.right.type === 'ObjectExpression'
) {
core.info('Found configuration object in direct module export')
return moduleExport.expression.right
}
// Indirect module export
else if (
moduleExport &&
moduleExport.expression.right.type === 'Identifier'
) {
const identifierName = moduleExport && moduleExport.expression.right.name
const identifierDefinition = ast.body.find(
node =>
node.type === 'VariableDeclaration' &&
node.declarations.length == 1 &&
node.declarations[0].type === 'VariableDeclarator' &&
node.declarations[0].id.type === 'Identifier' &&
node.declarations[0].id.name === identifierName &&
node.declarations[0].init.type === 'ObjectExpression'
)
if (identifierDefinition) {
core.info('Found configuration object in indirect module export')
return identifierDefinition.declarations[0].init
}
}
generateConfigFile() {
switch (this.staticSiteConfig.type) {
case "nuxt":
return format(this.configskeleton, format(this.pathPropertyNuxt, this.staticSiteConfig.newPath))
break
case "next":
return format(this.configskeleton, format(this.pathPropertyNext, this.staticSiteConfig.newPath))
break
case "gatsby":
return format(this.configskeleton, format(this.pathPropertyGatsby, this.staticSiteConfig.newPath))
break
default:
throw "Unknown config type"
}
// No configuration object found
return null
}
generateConfigProperty() {
switch (this.staticSiteConfig.type) {
case "nuxt":
return format(this.pathPropertyNuxt, this.staticSiteConfig.newPath)
break
case "next":
return format(this.pathPropertyNext, this.staticSiteConfig.newPath)
break
case "gatsby":
return format(this.pathPropertyGatsby, this.staticSiteConfig.newPath)
break
default:
throw "Unknown config type"
}
}
parse() {
core.info(`original configuration:\n${this.config}`)
const ast = espree.parse(this.config, espreeOptions);
// Find the default export declaration node
var exportNode = ast.body.find(node => node.type === 'ExpressionStatement')
if (exportNode) {
var property = this.getPropertyModuleExport(exportNode)
} else {
exportNode = ast.body.find(node => node.type === 'ExportDefaultDeclaration')
if (!exportNode) throw "Unable to find default export"
var property = this.getPropertyExportDefault(exportNode)
}
// Find a property with a given name on a given object.
//
// Return the matching property or null.
findProperty(object, name) {
// Try to find a property matching a given name
const property =
object.type === 'ObjectExpression' &&
object.properties.find(
node => node.key.type === 'Identifier' && node.key.name === name
)
// Return the property's value (if found) or null
if (property) {
switch (this.staticSiteConfig.type) {
case "nuxt":
this.parseNuxt(property)
break
case "next":
case "gatsby":
this.parseNextGatsby(property)
break
default:
throw "Unknown config type"
return property.value
}
}
core.info(`parsed configuration:\n${this.config}`)
fs.writeFileSync(this.staticSiteConfig.filePath, this.config)
return this.config
return null
}
getPropertyModuleExport(exportNode) {
var propertyNode = exportNode.expression.right.properties.find(
node => node.key.type === 'Identifier' && node.key.name === this.staticSiteConfig.pathName
// Generate a (nested) property declaration.
// - properties: list of properties to generate
// - startIndex: the index at which to start in the declaration
// - propertyValue: the value of the property
//
// Return a nested property declaration as a string.
getPropertyDeclaration(properties, startIndex, propertyValue) {
if (startIndex === properties.length - 1) {
return `${properties[startIndex]}: ${JSON.stringify(propertyValue)}`
} else {
return (
`${properties[startIndex]}: {` +
this.getPropertyDeclaration(properties, startIndex + 1, propertyValue) +
'}'
)
}
}
// Inject all properties into the configuration
injectAll() {
for (var [propertyName, propertyValue] of Object.entries(this.properties)) {
this.inject(propertyName, propertyValue)
}
}
// Inject an arbitrary property into the configuration
// - propertyName: the name of the property (may use . to target nested objects)
// - propertyValue: the value of the property
inject(propertyName, propertyValue) {
// Logging
core.info(`Injecting property=${propertyName} and value=${propertyValue} in:`)
core.info(this.configuration)
// Parse the AST out of the configuration file
const espreeOptions = {
ecmaVersion: 'latest',
sourceType: 'module',
range: true
}
const ast = espree.parse(this.configuration, espreeOptions)
// Find the configuration object
var configurationObject = this.findConfigurationObject(ast)
if (!configurationObject) {
throw 'Could not find a configuration object in the configuration file'
}
// A property may be nested in the configuration file. Split the property name with `.`
// then walk the configuration object one property at a time.
var depth = 0
const properties = propertyName.split('.')
var lastNode = configurationObject
while (1) {
// Find the node for the current property
var propertyNode = this.findProperty(lastNode, properties[depth])
// Update last node
if (propertyNode != null) {
lastNode = propertyNode
depth++
}
// Exit when exiting the current configuration object
if (propertyNode == null || depth >= properties.length) {
break
}
}
// If the configuration file is defining the property we are after, update it.
if (depth == properties.length) {
// The last node identified is an object expression, so do the assignment
if (lastNode.type === 'ObjectExpression') {
this.configuration =
this.configuration.slice(0, lastNode.range[0]) +
JSON.stringify(propertyValue) +
this.configuration.slice(lastNode.range[1])
}
// A misc object was found in the configuration file (e.g. an array, a string, a boolean,
// a number, etc.), just replace the whole range by our declaration
else {
this.configuration =
this.configuration.slice(0, lastNode.range[0]) +
JSON.stringify(propertyValue) +
this.configuration.slice(lastNode.range[1])
}
}
// Create nested properties in the configuration file
else {
// Build the declaration to inject
const declaration = this.getPropertyDeclaration(
properties,
depth,
propertyValue
)
if (!propertyNode) {
core.info("Unable to find property, insert it : " + this.staticSiteConfig.pathName)
if (exportNode.expression.right.properties.length > 0) {
this.config = this.config.slice(0, exportNode.expression.right.properties[0].range[0]) + this.generateConfigProperty() + ',\n' + this.config.slice(exportNode.expression.right.properties[0].range[0])
core.info("new config = \n" + this.config)
} else {
this.config = this.config.slice(0, exportNode.expression.right.range[0] + 1) + '\n ' + this.generateConfigProperty() + '\n' + this.config.slice(exportNode.expression.right.range[1] - 1)
core.info("new config = \n" + this.config)
}
}
return propertyNode
// The last node identified is an object expression, so do the assignment
if (lastNode.type === 'ObjectExpression') {
// The object is blank (no properties) so replace the whole range by a new object containing the declaration
if (lastNode.properties.length === 0) {
this.configuration =
this.configuration.slice(0, lastNode.range[0]) +
'{' +
declaration +
'}' +
this.configuration.slice(lastNode.range[1])
}
getPropertyExportDefault(exportNode) {
var propertyNode = exportNode.declaration.properties.find(
node => node.key.type === 'Identifier' && node.key.name === this.staticSiteConfig.pathName
)
if (!propertyNode) {
core.info("Unable to find property, insert it " + this.staticSiteConfig.pathName)
if (exportNode.declaration.properties.length > 0) {
this.config = this.config.slice(0, exportNode.declaration.properties[0].range[0]) + this.generateConfigProperty() + ',\n' + this.config.slice(exportNode.declaration.properties[0].range[0])
core.info("new config = \n" + this.config)
} else {
this.config = this.config.slice(0, exportNode.declaration.range[0] + 1) + '\n ' + this.generateConfigProperty() + '\n' + this.config.slice(exportNode.declaration.range[1] - 1)
core.info("new config = \n" + this.config)
// The object contains other properties, prepend our new one at the beginning
else {
this.configuration =
this.configuration.slice(0, lastNode.properties[0].range[0]) +
declaration +
',' +
this.configuration.slice(lastNode.properties[0].range[0])
}
}
return propertyNode
}
parseNuxt(propertyNode) {
// Find the base node
if (propertyNode && propertyNode.value.type === 'ObjectExpression') {
var baseNode = propertyNode.value.properties.find(node => node.key.type === 'Identifier' && node.key.name === this.staticSiteConfig.subPathName)//'base')
if (baseNode) {
// Swap the base value by a hardcoded string and print it
this.config = this.config.slice(0, baseNode.value.range[0]) + `'${this.staticSiteConfig.newPath}'` + this.config.slice(baseNode.value.range[1])
}
// A misc object was found in the configuration file (e.g. an array, a string, a boolean,
// a number, etc.), just replace the whole range by our declaration
else {
this.configuration =
this.configuration.slice(0, lastNode.range[0]) +
'{' +
declaration +
'}' +
this.configuration.slice(lastNode.range[1])
}
}
parseNextGatsby(pathNode) {
if (pathNode) {
this.config = this.config.slice(0, pathNode.value.range[0]) + `'${this.staticSiteConfig.newPath}'` + this.config.slice(pathNode.value.range[1])
}
// Logging
core.info(`Injection successful, new configuration:`)
core.info(this.configuration)
// Finally write the new configuration in the file
fs.writeFileSync(this.configurationFile, this.configuration, {
encoding: 'utf8'
})
}
}
module.exports = {ConfigParser}
/***/ }),
/***/ 1319:
@@ -14702,7 +14722,8 @@ function getRequiredVars() {
}
}
module.exports = function getContext() {
// Return the context object
function getContext() {
const requiredVars = getRequiredVars()
for (const variable in requiredVars) {
if (requiredVars[variable] === undefined) {
@@ -14713,6 +14734,8 @@ module.exports = function getContext() {
return requiredVars
}
module.exports = {getContext}
/***/ }),
@@ -14722,19 +14745,19 @@ module.exports = function getContext() {
const core = __nccwpck_require__(2186)
const axios = __nccwpck_require__(6545)
async function enablePages({ repositoryNwo, githubToken }) {
async function enablePages({repositoryNwo, githubToken}) {
const pagesEndpoint = `https://api.github.com/repos/${repositoryNwo}/pages`
try {
const response = await axios.post(
pagesEndpoint,
{ build_type: 'workflow' },
{build_type: 'workflow'},
{
headers: {
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${githubToken}`,
'Content-type': 'application/json',
},
'Content-type': 'application/json'
}
}
)
core.info('Created pages site')
@@ -14744,7 +14767,7 @@ async function enablePages({ repositoryNwo, githubToken }) {
return
}
core.error('Couldn\'t create pages site', error)
core.error("Couldn't create pages site", error)
throw error
}
}
@@ -14759,28 +14782,29 @@ module.exports = enablePages
const core = __nccwpck_require__(2186)
const axios = __nccwpck_require__(6545)
const setPagesPath = __nccwpck_require__(4770)
const {setPagesPath} = __nccwpck_require__(4770)
async function getPagesBaseUrl({ repositoryNwo, githubToken, staticSiteGenerator}) {
async function getPagesBaseUrl({
repositoryNwo,
githubToken,
staticSiteGenerator
}) {
try {
const pagesEndpoint = `https://api.github.com/repos/${repositoryNwo}/pages`
core.info(`Get the Base URL to the page with endpoint ${pagesEndpoint}`)
const response = await axios.get(
pagesEndpoint,
{
const response = await axios.get(pagesEndpoint, {
headers: {
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${githubToken}`
}
}
)
})
pageObject = response.data
core.info(JSON.stringify(pageObject))
const siteUrl = new URL(pageObject.html_url)
if ( staticSiteGenerator ) {
if (staticSiteGenerator) {
setPagesPath({staticSiteGenerator, path: siteUrl.pathname})
}
core.setOutput('base_url', siteUrl.href)
@@ -14802,51 +14826,73 @@ module.exports = getPagesBaseUrl
/***/ ((module, __unused_webpack_exports, __nccwpck_require__) => {
const core = __nccwpck_require__(2186)
const axios = __nccwpck_require__(6545)
const { ConfigParser } = __nccwpck_require__(8395)
const {ConfigParser} = __nccwpck_require__(8395)
async function setPagesPath({staticSiteGenerator, path}) {
try {
switch(staticSiteGenerator)
{
// Return the settings to be passed to a {ConfigParser} for a given
// static site generator and a Pages path value to inject
function getConfigParserSettings(staticSiteGenerator, path) {
switch (staticSiteGenerator) {
case 'nuxt':
var ssConfig = {
filePath:"./nuxt.config.js",
type: "nuxt",
pathName: "router",
subPathName: "base",
newPath: path
return {
configurationFile: './nuxt.config.js',
blankConfigurationFile: __nccwpck_require__.ab + "nuxt.js",
properties: {
// Configure a base path on the router
'router.base': path,
// Set the target to static too
// https://nuxtjs.org/docs/configuration-glossary/configuration-target/
target: 'static'
}
}
break;
case 'next':
var ssConfig = {
filePath:"./next.config.js",
type: "next",
pathName: "basePath",
newPath: path
// Next does not want a trailing slash
if (path.endsWith('/')) {
path = path.slice(0, -1)
}
return {
configurationFile: './next.config.js',
blankConfigurationFile: __nccwpck_require__.ab + "next.js",
properties: {
// Configure a base path
basePath: path,
// Disable server side image optimization too
// https://nextjs.org/docs/api-reference/next/image#unoptimized
'experimental.images.unoptimized': true
}
}
break;
case 'gatsby':
var ssConfig = {
filePath: "./gatsby-config.js",
type: "gatsby",
pathName: "pathPrefix",
newPath: path
return {
configurationFile: './gatsby-config.js',
blankConfigurationFile: __nccwpck_require__.ab + "gatsby.js",
properties: {
// Configure a path prefix
pathPrefix: path
}
}
break;
default:
throw "Unknown config type"
}
let configParser = new ConfigParser(ssConfig)
configParser.parse()
} catch (error) {
core.warning(`We were unable to determine how to inject the site metadata into your config. Generated URLs may be incorrect. The base URL for this site should be ${path}. Please ensure your framework is configured to generate relative links appropriately.`, error)
throw `Unsupported static site generator: ${staticSiteGenerator}`
}
}
module.exports = setPagesPath
// Inject Pages configuration in a given static site generator's configuration file
function setPagesPath({staticSiteGenerator, path}) {
try {
// Parse the configuration file and try to inject the Pages configuration in it
const settings = getConfigParserSettings(staticSiteGenerator, path)
new ConfigParser(settings).injectAll()
} catch (error) {
// Logging
core.warning(
`We were unable to determine how to inject the site metadata into your config. Generated URLs may be incorrect. The base URL for this site should be ${path}. Please ensure your framework is configured to generate relative links appropriately.`,
error
)
}
}
module.exports = {getConfigParserSettings, setPagesPath}
/***/ }),
@@ -16333,7 +16379,7 @@ const enablePages = __nccwpck_require__(5424)
const getPagesBaseUrl = __nccwpck_require__(9965)
// All variables we need from the runtime are loaded here
const getContext = __nccwpck_require__(1319)
const {getContext} = __nccwpck_require__(1319)
async function main() {
try {
@@ -16346,7 +16392,6 @@ async function main() {
}
}
// Main
main()

2
dist/index.js.map vendored

File diff suppressed because one or more lines are too long

18
dist/licenses.txt vendored
View File

@@ -572,24 +572,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
string-format
(WTFPL OR MIT)
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (c) 2018 David Chambers <dc@davidchambers.me>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.
supports-color
MIT
MIT License

3
dist/next.js vendored Normal file
View File

@@ -0,0 +1,3 @@
// Default Pages configuration for Next
const nextConfig = {}
module.exports = nextConfig

2
dist/nuxt.js vendored Normal file
View File

@@ -0,0 +1,2 @@
// Default Pages configuration for Nuxt
export default {}

24
package-lock.json generated
View File

@@ -17,7 +17,8 @@
},
"devDependencies": {
"@vercel/ncc": "^0.34.0",
"jest": "^28.1.1"
"jest": "^28.1.1",
"prettier": "^2.7.1"
}
},
"node_modules/@actions/core": {
@@ -3041,6 +3042,21 @@
"node": ">=8"
}
},
"node_modules/prettier": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
"integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
"dev": true,
"bin": {
"prettier": "bin-prettier.js"
},
"engines": {
"node": ">=10.13.0"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/pretty-format": {
"version": "28.1.1",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.1.tgz",
@@ -5878,6 +5894,12 @@
"find-up": "^4.0.0"
}
},
"prettier": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
"integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
"dev": true
},
"pretty-format": {
"version": "28.1.1",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.1.tgz",

View File

@@ -1,7 +1,7 @@
{
"name": "configure-pages",
"version": "1.0.0",
"description": "",
"description": "An action to enable Pages and extract various metadata about a site. It can also be used to configure various static site generators we support as starter workflows.",
"main": "src/index.js",
"scripts": {
"prepare": "ncc build src/index.js -o dist --source-map --license licenses.txt",
@@ -26,6 +26,7 @@
},
"devDependencies": {
"@vercel/ncc": "^0.34.0",
"jest": "^28.1.1"
"jest": "^28.1.1",
"prettier": "^2.7.1"
}
}

View File

@@ -0,0 +1,2 @@
// Default Pages configuration for Gatsby
module.exports = {}

View File

@@ -0,0 +1,3 @@
// Default Pages configuration for Next
const nextConfig = {}
module.exports = nextConfig

View File

@@ -0,0 +1,2 @@
// Default Pages configuration for Nuxt
export default {}

View File

@@ -1,155 +1,275 @@
const fs = require("fs")
const espree = require("espree")
const format = require("string-format")
const fs = require('fs')
const espree = require('espree')
const core = require('@actions/core')
// Parse the AST
const espreeOptions = {
ecmaVersion: 6,
sourceType: "module",
range: true,
}
/*
Parse a JavaScript based configuration file and inject arbitrary key/value in it.
This is used to make sure most static site generators can automatically handle
Pages's path based routing (and work).
Supported configuration initializations:
(1) Default export:
export default {
// configuration object here
}
(2) Direct module export:
module.exports = {
// configuration object here
}
(3) Indirect module export:
const config = // configuration object here
module.exports = config
*/
class ConfigParser {
constructor(staticSiteConfig) {
this.pathPropertyNuxt = `router: {\n base: '{0}'\n }`
this.pathPropertyNext = `basePath: '{0}'`
this.pathPropertyGatsby = `pathPrefix: '{0}'`
this.configskeleton = `export default {\n {0}\n}`
this.staticSiteConfig = staticSiteConfig
this.config = fs.existsSync(this.staticSiteConfig.filePath) ? fs.readFileSync(this.staticSiteConfig.filePath, "utf8") : null
this.validate()
// Ctor
// - configurationFile: path to the configuration file
// - blankConfigurationFile: a blank configuration file to use if non was previously found
constructor({configurationFile, blankConfigurationFile, properties}) {
// Save field
this.configurationFile = configurationFile
this.properties = properties
// If the configuration file does not exist, initialize it with the blank configuration file
if (!fs.existsSync(this.configurationFile)) {
core.info('Using default blank configuration')
const blankConfiguration = fs.readFileSync(blankConfigurationFile, 'utf8')
fs.writeFileSync(this.configurationFile, blankConfiguration, {
encoding: 'utf8'
})
}
validate() {
if (!this.config) {
core.info(`original raw configuration was empty:\n${this.config}`)
core.info('Generating a default configuration to start from...')
// Read the configuration file
this.configuration = fs.readFileSync(this.configurationFile, 'utf8')
}
// Update the `config` property with a default configuration file
this.config = this.generateConfigFile()
// Find the configuration object in an AST.
// Look for a default export, a direct module export or an indirect module
// export (in that order).
//
// Return the configuration object or null.
findConfigurationObject(ast) {
// Try to find a default export
var defaultExport = ast.body.find(
node =>
node.type === 'ExportDefaultDeclaration' &&
node.declaration.type === 'ObjectExpression'
)
if (defaultExport) {
core.info('Found configuration object in default export declaration')
return defaultExport.declaration
}
// Try to find a module export
var moduleExport = ast.body.find(
node =>
node.type === 'ExpressionStatement' &&
node.expression.type === 'AssignmentExpression' &&
node.expression.operator === '=' &&
node.expression.left.type === 'MemberExpression' &&
node.expression.left.object.type === 'Identifier' &&
node.expression.left.object.name === 'module' &&
node.expression.left.property.type === 'Identifier' &&
node.expression.left.property.name === 'exports'
)
// Direct module export
if (
moduleExport &&
moduleExport.expression.right.type === 'ObjectExpression'
) {
core.info('Found configuration object in direct module export')
return moduleExport.expression.right
}
// Indirect module export
else if (
moduleExport &&
moduleExport.expression.right.type === 'Identifier'
) {
const identifierName = moduleExport && moduleExport.expression.right.name
const identifierDefinition = ast.body.find(
node =>
node.type === 'VariableDeclaration' &&
node.declarations.length == 1 &&
node.declarations[0].type === 'VariableDeclarator' &&
node.declarations[0].id.type === 'Identifier' &&
node.declarations[0].id.name === identifierName &&
node.declarations[0].init.type === 'ObjectExpression'
)
if (identifierDefinition) {
core.info('Found configuration object in indirect module export')
return identifierDefinition.declarations[0].init
}
}
generateConfigFile() {
switch (this.staticSiteConfig.type) {
case "nuxt":
return format(this.configskeleton, format(this.pathPropertyNuxt, this.staticSiteConfig.newPath))
break
case "next":
return format(this.configskeleton, format(this.pathPropertyNext, this.staticSiteConfig.newPath))
break
case "gatsby":
return format(this.configskeleton, format(this.pathPropertyGatsby, this.staticSiteConfig.newPath))
break
default:
throw "Unknown config type"
}
// No configuration object found
return null
}
generateConfigProperty() {
switch (this.staticSiteConfig.type) {
case "nuxt":
return format(this.pathPropertyNuxt, this.staticSiteConfig.newPath)
break
case "next":
return format(this.pathPropertyNext, this.staticSiteConfig.newPath)
break
case "gatsby":
return format(this.pathPropertyGatsby, this.staticSiteConfig.newPath)
break
default:
throw "Unknown config type"
}
}
parse() {
core.info(`original configuration:\n${this.config}`)
const ast = espree.parse(this.config, espreeOptions);
// Find the default export declaration node
var exportNode = ast.body.find(node => node.type === 'ExpressionStatement')
if (exportNode) {
var property = this.getPropertyModuleExport(exportNode)
} else {
exportNode = ast.body.find(node => node.type === 'ExportDefaultDeclaration')
if (!exportNode) throw "Unable to find default export"
var property = this.getPropertyExportDefault(exportNode)
}
// Find a property with a given name on a given object.
//
// Return the matching property or null.
findProperty(object, name) {
// Try to find a property matching a given name
const property =
object.type === 'ObjectExpression' &&
object.properties.find(
node => node.key.type === 'Identifier' && node.key.name === name
)
// Return the property's value (if found) or null
if (property) {
switch (this.staticSiteConfig.type) {
case "nuxt":
this.parseNuxt(property)
break
case "next":
case "gatsby":
this.parseNextGatsby(property)
break
default:
throw "Unknown config type"
return property.value
}
}
core.info(`parsed configuration:\n${this.config}`)
fs.writeFileSync(this.staticSiteConfig.filePath, this.config)
return this.config
return null
}
getPropertyModuleExport(exportNode) {
var propertyNode = exportNode.expression.right.properties.find(
node => node.key.type === 'Identifier' && node.key.name === this.staticSiteConfig.pathName
// Generate a (nested) property declaration.
// - properties: list of properties to generate
// - startIndex: the index at which to start in the declaration
// - propertyValue: the value of the property
//
// Return a nested property declaration as a string.
getPropertyDeclaration(properties, startIndex, propertyValue) {
if (startIndex === properties.length - 1) {
return `${properties[startIndex]}: ${JSON.stringify(propertyValue)}`
} else {
return (
`${properties[startIndex]}: {` +
this.getPropertyDeclaration(properties, startIndex + 1, propertyValue) +
'}'
)
}
}
// Inject all properties into the configuration
injectAll() {
for (var [propertyName, propertyValue] of Object.entries(this.properties)) {
this.inject(propertyName, propertyValue)
}
}
// Inject an arbitrary property into the configuration
// - propertyName: the name of the property (may use . to target nested objects)
// - propertyValue: the value of the property
inject(propertyName, propertyValue) {
// Logging
core.info(`Injecting property=${propertyName} and value=${propertyValue} in:`)
core.info(this.configuration)
// Parse the AST out of the configuration file
const espreeOptions = {
ecmaVersion: 'latest',
sourceType: 'module',
range: true
}
const ast = espree.parse(this.configuration, espreeOptions)
// Find the configuration object
var configurationObject = this.findConfigurationObject(ast)
if (!configurationObject) {
throw 'Could not find a configuration object in the configuration file'
}
// A property may be nested in the configuration file. Split the property name with `.`
// then walk the configuration object one property at a time.
var depth = 0
const properties = propertyName.split('.')
var lastNode = configurationObject
while (1) {
// Find the node for the current property
var propertyNode = this.findProperty(lastNode, properties[depth])
// Update last node
if (propertyNode != null) {
lastNode = propertyNode
depth++
}
// Exit when exiting the current configuration object
if (propertyNode == null || depth >= properties.length) {
break
}
}
// If the configuration file is defining the property we are after, update it.
if (depth == properties.length) {
// The last node identified is an object expression, so do the assignment
if (lastNode.type === 'ObjectExpression') {
this.configuration =
this.configuration.slice(0, lastNode.range[0]) +
JSON.stringify(propertyValue) +
this.configuration.slice(lastNode.range[1])
}
// A misc object was found in the configuration file (e.g. an array, a string, a boolean,
// a number, etc.), just replace the whole range by our declaration
else {
this.configuration =
this.configuration.slice(0, lastNode.range[0]) +
JSON.stringify(propertyValue) +
this.configuration.slice(lastNode.range[1])
}
}
// Create nested properties in the configuration file
else {
// Build the declaration to inject
const declaration = this.getPropertyDeclaration(
properties,
depth,
propertyValue
)
if (!propertyNode) {
core.info("Unable to find property, insert it : " + this.staticSiteConfig.pathName)
if (exportNode.expression.right.properties.length > 0) {
this.config = this.config.slice(0, exportNode.expression.right.properties[0].range[0]) + this.generateConfigProperty() + ',\n' + this.config.slice(exportNode.expression.right.properties[0].range[0])
core.info("new config = \n" + this.config)
} else {
this.config = this.config.slice(0, exportNode.expression.right.range[0] + 1) + '\n ' + this.generateConfigProperty() + '\n' + this.config.slice(exportNode.expression.right.range[1] - 1)
core.info("new config = \n" + this.config)
}
}
return propertyNode
// The last node identified is an object expression, so do the assignment
if (lastNode.type === 'ObjectExpression') {
// The object is blank (no properties) so replace the whole range by a new object containing the declaration
if (lastNode.properties.length === 0) {
this.configuration =
this.configuration.slice(0, lastNode.range[0]) +
'{' +
declaration +
'}' +
this.configuration.slice(lastNode.range[1])
}
getPropertyExportDefault(exportNode) {
var propertyNode = exportNode.declaration.properties.find(
node => node.key.type === 'Identifier' && node.key.name === this.staticSiteConfig.pathName
)
if (!propertyNode) {
core.info("Unable to find property, insert it " + this.staticSiteConfig.pathName)
if (exportNode.declaration.properties.length > 0) {
this.config = this.config.slice(0, exportNode.declaration.properties[0].range[0]) + this.generateConfigProperty() + ',\n' + this.config.slice(exportNode.declaration.properties[0].range[0])
core.info("new config = \n" + this.config)
} else {
this.config = this.config.slice(0, exportNode.declaration.range[0] + 1) + '\n ' + this.generateConfigProperty() + '\n' + this.config.slice(exportNode.declaration.range[1] - 1)
core.info("new config = \n" + this.config)
// The object contains other properties, prepend our new one at the beginning
else {
this.configuration =
this.configuration.slice(0, lastNode.properties[0].range[0]) +
declaration +
',' +
this.configuration.slice(lastNode.properties[0].range[0])
}
}
return propertyNode
}
parseNuxt(propertyNode) {
// Find the base node
if (propertyNode && propertyNode.value.type === 'ObjectExpression') {
var baseNode = propertyNode.value.properties.find(node => node.key.type === 'Identifier' && node.key.name === this.staticSiteConfig.subPathName)//'base')
if (baseNode) {
// Swap the base value by a hardcoded string and print it
this.config = this.config.slice(0, baseNode.value.range[0]) + `'${this.staticSiteConfig.newPath}'` + this.config.slice(baseNode.value.range[1])
}
// A misc object was found in the configuration file (e.g. an array, a string, a boolean,
// a number, etc.), just replace the whole range by our declaration
else {
this.configuration =
this.configuration.slice(0, lastNode.range[0]) +
'{' +
declaration +
'}' +
this.configuration.slice(lastNode.range[1])
}
}
parseNextGatsby(pathNode) {
if (pathNode) {
this.config = this.config.slice(0, pathNode.value.range[0]) + `'${this.staticSiteConfig.newPath}'` + this.config.slice(pathNode.value.range[1])
}
// Logging
core.info(`Injection successful, new configuration:`)
core.info(this.configuration)
// Finally write the new configuration in the file
fs.writeFileSync(this.configurationFile, this.configuration, {
encoding: 'utf8'
})
}
}

View File

@@ -1,83 +1,156 @@
const { ConfigParser } = require('./config-parser')
const fs = require("fs")
const assert = require('assert')
const fs = require('fs')
const srcFolder = `${process.cwd()}/src/fixtures`
const tmpFolder = `${process.cwd()}/src/fixtures/tmp`
const expectedFolder = `${process.cwd()}/src/fixtures/expected`
const {ConfigParser} = require('./config-parser')
const {getTempFolder, compareFiles} = require('./test-helpers')
const repoPath = "/amazing-new-repo/"
// Get the temp folder
const tempFolder = getTempFolder()
// Cases to test
const cases = [
["next.config.js", {
filePath: `${tmpFolder}/next.config.js`,
type: "next",
pathName: "basePath",
newPath: repoPath
}],
["next.config.old.js", {
filePath: `${tmpFolder}/next.config.old.js`,
type: "next",
pathName: "basePath",
newPath: repoPath
}],
["next.config.old.missing.js", {
filePath: `${tmpFolder}/next.config.old.missing.js`,
type: "next",
pathName: "basePath",
newPath: repoPath
}],
["gatsby-config.js", {
filePath: `${tmpFolder}/gatsby-config.js`,
type: "gatsby",
pathName: "pathPrefix",
newPath: repoPath
}],
["gatsby-config.old.js", {
filePath: `${tmpFolder}/gatsby-config.old.js`,
type: "gatsby",
pathName: "pathPrefix",
newPath: repoPath
}],
["nuxt.config.js", {
filePath:`${tmpFolder}/nuxt.config.js`,
type: "nuxt",
pathName: "router",
subPathName: "base",
newPath: repoPath
}],
["nuxt.config.missing.js", {
filePath:`${tmpFolder}/nuxt.config.missing.js`,
type: "nuxt",
pathName: "router",
subPathName: "base",
newPath: repoPath
}],
["nuxt.config.old.js", {
filePath:`${tmpFolder}/nuxt.config.old.js`,
type: "nuxt",
pathName: "router",
subPathName: "base",
newPath: repoPath
}],
];
//
// Default export
//
describe('configParser', () => {
test.each(cases)(
"%p parsed correctly",
(fileName, configuration) => {
srcFileName = `${srcFolder}/${fileName}`
tmpFileName = `${tmpFolder}/${fileName}`
expectedFileName = `${expectedFolder}/${fileName}`
fs.mkdirSync(tmpFolder, {recursive: true})
fs.copyFileSync(srcFileName, tmpFileName)
const parser = new ConfigParser(configuration)
parser.parse()
{
property: 'property',
source: `export default {}`,
expected: `export default { property: "value" }`
},
{
property: 'property',
source: `export default { property: 0 }`, // property exists and is a number
expected: `export default { property: "value" }`
},
{
property: 'property',
source: `export default { property: false }`, // property exists and is a boolean
expected: `export default { property: "value" }`
},
{
property: 'property',
source: `export default { property: "test" }`, // property exists and is a string
expected: `export default { property: "value" }`
},
{
property: 'property',
source: `export default { property: [1,2] }`, // property exists and is an array
expected: `export default { property: "value" }`
},
{
property: 'property',
source: `export default { property: null }`, // property exists and is null
expected: `export default { property: "value" }`
},
{
property: 'property',
source: `export default { property: {}}`, // property exists and is an object
expected: `export default { property: "value" }`
},
var expectedContent = fs.readFileSync(expectedFileName).toString()
var actualContent = fs.readFileSync(tmpFileName).toString()
assert.equal(actualContent, expectedContent)
fs.rmSync(tmpFileName)
// Deep properties (injection 1)
{
property: 'property.b.c',
source: `export default {}`,
expected: `export default { property: { b: { c: "value" }}}`
},
{
property: 'property.b.c',
source: `export default { property: 0 }`, // property exists and is a number
expected: `export default { property: { b: { c: "value" }}}`
},
{
property: 'property.b.c',
source: `export default { property: {}}`, // property exists and is an object
expected: `export default { property: { b: { c: "value" }}}`
},
// Deep properties (injection 2)
{
property: 'property.b.c',
source: `export default { property: { b: 0 }}`, // property exists and is a number
expected: `export default { property: { b: { c: "value" }}}`
},
{
property: 'property.b.c',
source: `export default { property: { b: {}}}`, // property exists and is an object
expected: `export default { property: { b: { c: "value" }}}`
},
{
property: 'property.b.c',
source: `export default { property: { b: { hello: 123}}}`, // property exists and is a non-empty object
expected: `export default { property: { b: { c: "value", hello: 123 }}}`
},
// Deep properties (existing properties)
{
property: 'a1.a2',
source: `export default { a2: false, a1: { a3: [12]}}`, // property exists and is a non-empty object
expected: `export default { a2: false, a1: { a2: "value", a3: [12]}}`
},
//
// Direct module exports
//
{
property: 'property',
source: `module.exports = {}`,
expected: `module.exports = { property: "value"}`
},
{
property: 'property',
source: `module.exports = { p1: 0}`,
expected: `module.exports = { property: "value", p1: 0}`
},
{
property: 'a.b.c',
source: `module.exports = { p1: 0}`,
expected: `module.exports = { a: { b: { c: "value" }}, p1: 0}`
},
//
// Indirect module exports
//
{
property: 'property',
source: `const config = {}; module.exports = config`,
expected: `const config = { property: "value"}; module.exports = config`
},
{
property: 'property',
source: `var config = {}; module.exports = config`,
expected: `var config = { property: "value"}; module.exports = config`
},
{
property: 'a.b.c',
source: `var config = {}; module.exports = config`,
expected: `var config = { a: { b: { c: "value"}}}; module.exports = config`
},
{
property: 'a.b.c',
source: `var config = { a: { b: [], c: "hello"}}; module.exports = config`,
expected: `var config = { a: { b: { c: "value"}, c: "hello"}}; module.exports = config`
}
)
]
describe('config-parser', () => {
cases.forEach(({property, source, expected}, index) => {
it(`Inject path properly for case #${index}`, () => {
// Write the source file
const sourceFile = `${tempFolder}/source.js`
fs.writeFileSync(sourceFile, source, {encoding: 'utf8'})
// Write the expected file
const expectedFile = `${tempFolder}/expected.js`
fs.writeFileSync(expectedFile, expected, {encoding: 'utf8'})
// Update the settings and do the injection
new ConfigParser({
configurationFile: sourceFile
}).inject(property, 'value')
// Compare the files
compareFiles(sourceFile, expectedFile)
})
})
})

View File

@@ -9,7 +9,8 @@ function getRequiredVars() {
}
}
module.exports = function getContext() {
// Return the context object
function getContext() {
const requiredVars = getRequiredVars()
for (const variable in requiredVars) {
if (requiredVars[variable] === undefined) {
@@ -19,3 +20,5 @@ module.exports = function getContext() {
core.debug('all variables are set')
return requiredVars
}
module.exports = {getContext}

View File

@@ -1,19 +1,19 @@
const core = require('@actions/core')
const axios = require('axios')
async function enablePages({ repositoryNwo, githubToken }) {
async function enablePages({repositoryNwo, githubToken}) {
const pagesEndpoint = `https://api.github.com/repos/${repositoryNwo}/pages`
try {
const response = await axios.post(
pagesEndpoint,
{ build_type: 'workflow' },
{build_type: 'workflow'},
{
headers: {
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${githubToken}`,
'Content-type': 'application/json',
},
'Content-type': 'application/json'
}
}
)
core.info('Created pages site')
@@ -23,7 +23,7 @@ async function enablePages({ repositoryNwo, githubToken }) {
return
}
core.error('Couldn\'t create pages site', error)
core.error("Couldn't create pages site", error)
throw error
}
}

View File

@@ -18,29 +18,36 @@ describe('enablePages', () => {
})
it('makes a request to create a page', async () => {
jest
.spyOn(axios, 'post')
.mockImplementationOnce(() => Promise.resolve({ }))
jest.spyOn(axios, 'post').mockImplementationOnce(() => Promise.resolve({}))
await enablePages({ repositoryNwo: GITHUB_REPOSITORY, githubToken: GITHUB_TOKEN })
await enablePages({
repositoryNwo: GITHUB_REPOSITORY,
githubToken: GITHUB_TOKEN
})
})
it('handles a 409 response when the page already exists', async () => {
jest
.spyOn(axios, 'post')
.mockImplementationOnce(() => Promise.reject({ response: { status: 409 } }))
.mockImplementationOnce(() => Promise.reject({response: {status: 409}}))
// Simply assert that no error is raised
await enablePages({ repositoryNwo: GITHUB_REPOSITORY, githubToken: GITHUB_TOKEN })
await enablePages({
repositoryNwo: GITHUB_REPOSITORY,
githubToken: GITHUB_TOKEN
})
})
it('re-raises errors on failure status codes', async () => {
jest
.spyOn(axios, 'post')
.mockImplementationOnce(() => Promise.reject({ response: { status: 404 } }))
.mockImplementationOnce(() => Promise.reject({response: {status: 404}}))
try {
await enablePages({ repositoryNwo: GITHUB_REPOSITORY, githubToken: GITHUB_TOKEN })
await enablePages({
repositoryNwo: GITHUB_REPOSITORY,
githubToken: GITHUB_TOKEN
})
} catch (error) {
expect(error.response.status).toEqual(404)
}

View File

@@ -1,8 +0,0 @@
import { resolve } from 'path'
export default {
alias: {
'style': resolve(__dirname, './assets/style')
},
pathPrefix: '/amazing-new-repo/',/* test */
}

View File

@@ -1,3 +0,0 @@
module.exports = {
pathPrefix: '/amazing-new-repo/'
}

View File

@@ -1,9 +0,0 @@
import { resolve } from 'path'
export default {
alias: {
'style': resolve(__dirname, './assets/style')
},
basePath: '/amazing-new-repo/'/* test */,
}

View File

@@ -1,4 +0,0 @@
module.exports={
basePath: '/amazing-new-repo/'/* test */,
}

View File

@@ -1,4 +0,0 @@
module.exports={
basePath: '/amazing-new-repo/'
}

View File

@@ -1,11 +0,0 @@
import { resolve } from 'path'
export default {
alias: {
'style': resolve(__dirname, './assets/style')
},
target: 'static',
router: {
base: '/amazing-new-repo/'
}
}

View File

@@ -1,11 +0,0 @@
import { resolve } from 'path'
export default {
router: {
base: '/amazing-new-repo/'
},
alias: {
'style': resolve(__dirname, './assets/style')
},
target: 'static'
}

View File

@@ -1,10 +0,0 @@
module.exports={
alias: {
'style': resolve(__dirname, './assets/style')
},
target: 'static',
router: {
base: '/amazing-new-repo/'
}
}

View File

@@ -1,8 +0,0 @@
import { resolve } from 'path'
export default {
alias: {
'style': resolve(__dirname, './assets/style')
},
pathPrefix: '/prefix',/* test */
}

View File

@@ -1,3 +0,0 @@
module.exports = {
pathPrefix: '/prefix'
}

View File

@@ -0,0 +1,2 @@
// Default Pages configuration for Gatsby
module.exports = { pathPrefix: "/docs/" }

View File

@@ -0,0 +1 @@
// This file is not read by the test suite

View File

@@ -0,0 +1,8 @@
module.exports = {
pathPrefix: "/docs/",
siteMetadata: {
title: `My Gatsby Site`,
siteUrl: `https://www.yourdomain.tld`,
},
plugins: [],
}

View File

@@ -0,0 +1,7 @@
module.exports = {
siteMetadata: {
title: `My Gatsby Site`,
siteUrl: `https://www.yourdomain.tld`,
},
plugins: [],
}

View File

@@ -1,9 +0,0 @@
import { resolve } from 'path'
export default {
alias: {
'style': resolve(__dirname, './assets/style')
},
basePath: '/gh-pages-test'/* test */,
}

View File

@@ -1,4 +0,0 @@
module.exports={
basePath: '/gh-pages-test'/* test */,
}

View File

@@ -1,4 +0,0 @@
module.exports={
}

View File

@@ -0,0 +1,6 @@
// Default Pages configuration for Next
const nextConfig = {
experimental: {images: {unoptimized: true}},
basePath: '/docs'
}
module.exports = nextConfig

View File

@@ -0,0 +1 @@
// This file is not read by the test suite

View File

@@ -0,0 +1,9 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {images: {unoptimized: true}},
basePath: '/docs',
reactStrictMode: true,
swcMinify: true
}
module.exports = nextConfig

View File

@@ -0,0 +1,7 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
swcMinify: true,
}
module.exports = nextConfig

View File

@@ -1,11 +0,0 @@
import { resolve } from 'path'
export default {
alias: {
'style': resolve(__dirname, './assets/style')
},
target: 'static',
router: {
base: 'some/path'
}
}

View File

@@ -1,8 +0,0 @@
import { resolve } from 'path'
export default {
alias: {
'style': resolve(__dirname, './assets/style')
},
target: 'static'
}

View File

@@ -1,10 +0,0 @@
module.exports={
alias: {
'style': resolve(__dirname, './assets/style')
},
target: 'static',
router: {
base: 'some/path'
}
}

View File

@@ -0,0 +1,17 @@
const getAllDynamicRoute = async function() {
const routes = await (async () => {
return ['/posts/hello-world', '/posts/hello-again'];
})();
return routes;
};
export default {
target: 'static',
router: {base: '/docs/'},
mode: 'universal',
generate: {
async routes () {
return getAllDynamicRoute();
}
}
};

View File

@@ -0,0 +1,15 @@
const getAllDynamicRoute = async function() {
const routes = await (async () => {
return ['/posts/hello-world', '/posts/hello-again'];
})();
return routes;
};
export default {
mode: 'universal',
generate: {
async routes () {
return getAllDynamicRoute();
}
}
};

View File

@@ -0,0 +1,2 @@
// Default Pages configuration for Nuxt
export default {target: 'static', router: {base: '/docs/'}}

View File

@@ -0,0 +1 @@
// This file is not read by the test suite

View File

@@ -0,0 +1,46 @@
export default {
// Disable server-side rendering: https://go.nuxtjs.dev/ssr-mode
target: 'static',
router: { base: "/docs/" },
ssr: false,
// Global page headers: https://go.nuxtjs.dev/config-head
head: {
title: 'nuxt',
htmlAttrs: {
lang: 'en'
},
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: '' },
{ name: 'format-detection', content: 'telephone=no' }
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
]
},
// Global CSS: https://go.nuxtjs.dev/config-css
css: [
],
// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
plugins: [
],
// Auto import components: https://go.nuxtjs.dev/config-components
components: true,
// Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules
buildModules: [
],
// Modules: https://go.nuxtjs.dev/config-modules
modules: [
],
// Build Configuration: https://go.nuxtjs.dev/config-build
build: {
}
}

View File

@@ -0,0 +1,44 @@
export default {
// Disable server-side rendering: https://go.nuxtjs.dev/ssr-mode
ssr: false,
// Global page headers: https://go.nuxtjs.dev/config-head
head: {
title: 'nuxt',
htmlAttrs: {
lang: 'en'
},
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: '' },
{ name: 'format-detection', content: 'telephone=no' }
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
]
},
// Global CSS: https://go.nuxtjs.dev/config-css
css: [
],
// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
plugins: [
],
// Auto import components: https://go.nuxtjs.dev/config-components
components: true,
// Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules
buildModules: [
],
// Modules: https://go.nuxtjs.dev/config-modules
modules: [
],
// Build Configuration: https://go.nuxtjs.dev/config-build
build: {
}
}

View File

@@ -1,27 +1,28 @@
const core = require('@actions/core')
const axios = require('axios')
const setPagesPath = require('./set-pages-path')
const {setPagesPath} = require('./set-pages-path')
async function getPagesBaseUrl({ repositoryNwo, githubToken, staticSiteGenerator}) {
async function getPagesBaseUrl({
repositoryNwo,
githubToken,
staticSiteGenerator
}) {
try {
const pagesEndpoint = `https://api.github.com/repos/${repositoryNwo}/pages`
core.info(`Get the Base URL to the page with endpoint ${pagesEndpoint}`)
const response = await axios.get(
pagesEndpoint,
{
const response = await axios.get(pagesEndpoint, {
headers: {
Accept: 'application/vnd.github.v3+json',
Authorization: `Bearer ${githubToken}`
}
}
)
})
pageObject = response.data
core.info(JSON.stringify(pageObject))
const siteUrl = new URL(pageObject.html_url)
if ( staticSiteGenerator ) {
if (staticSiteGenerator) {
setPagesPath({staticSiteGenerator, path: siteUrl.pathname})
}
core.setOutput('base_url', siteUrl.href)

View File

@@ -1,6 +1,5 @@
const core = require('@actions/core')
const axios = require('axios')
//const { expect, jest } = require('@jest/globals')
const getPagesBaseUrl = require('./get-pages-base-url')
@@ -11,7 +10,9 @@ describe('getPagesBaseUrl', () => {
beforeEach(() => {
jest.restoreAllMocks()
jest.spyOn(core, 'setOutput').mockImplementation((key, value) => { key, value })
jest.spyOn(core, 'setOutput').mockImplementation((key, value) => {
key, value
})
jest.spyOn(core, 'setFailed').mockImplementation(param => param)
// Mock error/warning/info/debug
@@ -26,12 +27,20 @@ describe('getPagesBaseUrl', () => {
jest
.spyOn(axios, 'get')
.mockImplementationOnce(() => Promise.resolve({ data: { html_url: baseUrl } }))
.mockImplementationOnce(() =>
Promise.resolve({data: {html_url: baseUrl}})
)
await getPagesBaseUrl({ repositoryNwo: GITHUB_REPOSITORY, githubToken: GITHUB_TOKEN })
await getPagesBaseUrl({
repositoryNwo: GITHUB_REPOSITORY,
githubToken: GITHUB_TOKEN
})
expect(core.setOutput).toHaveBeenCalledWith('base_url', baseUrl)
expect(core.setOutput).toHaveBeenCalledWith('origin', 'https://octocat.github.io')
expect(core.setOutput).toHaveBeenCalledWith(
'origin',
'https://octocat.github.io'
)
expect(core.setOutput).toHaveBeenCalledWith('host', 'octocat.github.io')
expect(core.setOutput).toHaveBeenCalledWith('base_path', '/')
})
@@ -41,12 +50,20 @@ describe('getPagesBaseUrl', () => {
jest
.spyOn(axios, 'get')
.mockImplementationOnce(() => Promise.resolve({ data: { html_url: baseUrl } }))
.mockImplementationOnce(() =>
Promise.resolve({data: {html_url: baseUrl}})
)
await getPagesBaseUrl({ repositoryNwo: GITHUB_REPOSITORY, githubToken: GITHUB_TOKEN })
await getPagesBaseUrl({
repositoryNwo: GITHUB_REPOSITORY,
githubToken: GITHUB_TOKEN
})
expect(core.setOutput).toHaveBeenCalledWith('base_url', baseUrl)
expect(core.setOutput).toHaveBeenCalledWith('origin', 'https://octocat.github.io')
expect(core.setOutput).toHaveBeenCalledWith(
'origin',
'https://octocat.github.io'
)
expect(core.setOutput).toHaveBeenCalledWith('host', 'octocat.github.io')
expect(core.setOutput).toHaveBeenCalledWith('base_path', '/my-repo/')
})
@@ -56,12 +73,20 @@ describe('getPagesBaseUrl', () => {
jest
.spyOn(axios, 'get')
.mockImplementationOnce(() => Promise.resolve({ data: { html_url: baseUrl } }))
.mockImplementationOnce(() =>
Promise.resolve({data: {html_url: baseUrl}})
)
await getPagesBaseUrl({ repositoryNwo: GITHUB_REPOSITORY, githubToken: GITHUB_TOKEN })
await getPagesBaseUrl({
repositoryNwo: GITHUB_REPOSITORY,
githubToken: GITHUB_TOKEN
})
expect(core.setOutput).toHaveBeenCalledWith('base_url', baseUrl)
expect(core.setOutput).toHaveBeenCalledWith('origin', 'https://www.example.com')
expect(core.setOutput).toHaveBeenCalledWith(
'origin',
'https://www.example.com'
)
expect(core.setOutput).toHaveBeenCalledWith('host', 'www.example.com')
expect(core.setOutput).toHaveBeenCalledWith('base_path', '/')
})

View File

@@ -4,7 +4,7 @@ const enablePages = require('./enable-pages')
const getPagesBaseUrl = require('./get-pages-base-url')
// All variables we need from the runtime are loaded here
const getContext = require('./context')
const {getContext} = require('./context')
async function main() {
try {
@@ -17,6 +17,5 @@ async function main() {
}
}
// Main
main()

View File

@@ -1,46 +1,68 @@
const core = require('@actions/core')
const axios = require('axios')
const { ConfigParser } = require('./config-parser')
const {ConfigParser} = require('./config-parser')
async function setPagesPath({staticSiteGenerator, path}) {
try {
switch(staticSiteGenerator)
{
// Return the settings to be passed to a {ConfigParser} for a given
// static site generator and a Pages path value to inject
function getConfigParserSettings(staticSiteGenerator, path) {
switch (staticSiteGenerator) {
case 'nuxt':
var ssConfig = {
filePath:"./nuxt.config.js",
type: "nuxt",
pathName: "router",
subPathName: "base",
newPath: path
return {
configurationFile: './nuxt.config.js',
blankConfigurationFile: `${__dirname}/blank-configurations/nuxt.js`,
properties: {
// Configure a base path on the router
'router.base': path,
// Set the target to static too
// https://nuxtjs.org/docs/configuration-glossary/configuration-target/
target: 'static'
}
}
break;
case 'next':
var ssConfig = {
filePath:"./next.config.js",
type: "next",
pathName: "basePath",
newPath: path
// Next does not want a trailing slash
if (path.endsWith('/')) {
path = path.slice(0, -1)
}
return {
configurationFile: './next.config.js',
blankConfigurationFile: `${__dirname}/blank-configurations/next.js`,
properties: {
// Configure a base path
basePath: path,
// Disable server side image optimization too
// https://nextjs.org/docs/api-reference/next/image#unoptimized
'experimental.images.unoptimized': true
}
}
break;
case 'gatsby':
var ssConfig = {
filePath: "./gatsby-config.js",
type: "gatsby",
pathName: "pathPrefix",
newPath: path
return {
configurationFile: './gatsby-config.js',
blankConfigurationFile: `${__dirname}/blank-configurations/gatsby.js`,
properties: {
// Configure a path prefix
pathPrefix: path
}
}
break;
default:
throw "Unknown config type"
}
let configParser = new ConfigParser(ssConfig)
configParser.parse()
} catch (error) {
core.warning(`We were unable to determine how to inject the site metadata into your config. Generated URLs may be incorrect. The base URL for this site should be ${path}. Please ensure your framework is configured to generate relative links appropriately.`, error)
throw `Unsupported static site generator: ${staticSiteGenerator}`
}
}
module.exports = setPagesPath
// Inject Pages configuration in a given static site generator's configuration file
function setPagesPath({staticSiteGenerator, path}) {
try {
// Parse the configuration file and try to inject the Pages configuration in it
const settings = getConfigParserSettings(staticSiteGenerator, path)
new ConfigParser(settings).injectAll()
} catch (error) {
// Logging
core.warning(
`We were unable to determine how to inject the site metadata into your config. Generated URLs may be incorrect. The base URL for this site should be ${path}. Please ensure your framework is configured to generate relative links appropriately.`,
error
)
}
}
module.exports = {getConfigParserSettings, setPagesPath}

View File

@@ -0,0 +1,53 @@
const fs = require('fs')
const path = require('path')
const {getConfigParserSettings} = require('./set-pages-path')
const {ConfigParser} = require('./config-parser')
const {getTempFolder, compareFiles} = require('./test-helpers')
// Get the temp folder
const tempFolder = getTempFolder()
// Test suite
describe('configParser', () => {
// Iterate over the static site generators
;['next', 'nuxt', 'gatsby'].forEach(staticSiteGenerator => {
// Folder containing the fixtures for a given static site generator
const fixtureFolder = `${__dirname}/fixtures/${staticSiteGenerator}`
// Iterate over the fixtures
fs.readdirSync(fixtureFolder).forEach(configurationFile => {
// Ignore expectation
if (configurationFile.endsWith('.expected.js')) {
return
}
it(`Inject path properly for ${staticSiteGenerator} in ${configurationFile}`, async () => {
// Get settings for the static site generator
const settings = getConfigParserSettings(staticSiteGenerator, '/docs/')
// Copy the source fixture to a temp file
const fixtureSourceFile = `${fixtureFolder}/${configurationFile}`
const fixtureTargetFile = `${tempFolder}/${configurationFile}`
if (configurationFile != 'blank.js') {
fs.copyFileSync(fixtureSourceFile, fixtureTargetFile)
} else if (fs.existsSync(fixtureTargetFile)) {
fs.rmSync(fixtureTargetFile)
}
// Update the settings and do the injection
settings.configurationFile = fixtureTargetFile
new ConfigParser(settings).injectAll()
// Read the expected file
const expectedFile = `${fixtureFolder}/${path.basename(
configurationFile,
'.js'
)}.expected.js`
// Compare the actual and expected files
compareFiles(settings.configurationFile, expectedFile)
})
})
})
})

37
src/test-helpers.js Normal file
View File

@@ -0,0 +1,37 @@
const fs = require('fs')
const prettier = require('prettier')
const assert = require('assert')
// Create and return the path to a temp folder
function getTempFolder() {
const tmpFolder = `${__dirname}/fixtures/tmp`
if (!fs.existsSync(tmpFolder)) {
fs.mkdirSync(tmpFolder, {recursive: true})
}
return tmpFolder
}
// Read a JavaScript file and return it formatted
function formatFile(file) {
const fileContent = fs.readFileSync(file, 'utf8')
return prettier.format(fileContent, {
parser: 'espree',
// Prettier options
printWidth: 80,
tabWidth: 2,
useTabs: false,
semi: false,
singleQuote: true,
trailingComma: 'none',
bracketSpacing: false,
arrowParens: 'avoid'
}).trim()
}
// Compare two JavaScript files
function compareFiles(actualFile, expectedFile) {
assert.equal(formatFile(actualFile), formatFile(expectedFile))
}
module.exports = {getTempFolder, formatFile, compareFiles}