├── gatsby-node.js ├── .github └── dependabot.yml ├── .editorconfig ├── README.md ├── LICENSE ├── .eslintignore ├── package.json ├── .gitignore ├── Jenkinsfile ├── .eslintrc.js └── makeLayout.js /gatsby-node.js: -------------------------------------------------------------------------------- 1 | const {makeReactLayout, saveReactLayout} = require('./makeLayout'); 2 | 3 | exports.onPreBootstrap = async (_, pluginOptions) => { 4 | await makeReactLayout(pluginOptions).then(saveReactLayout); 5 | }; 6 | 7 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2 3 | updates: 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "daily" 8 | - package-ecosystem: "npm" 9 | directory: "/" 10 | schedule: 11 | interval: "monthly" 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | root = true 3 | 4 | [*.{css,js,jsx,json}] 5 | end_of_line = lf 6 | insert_final_newline = true 7 | charset = utf-8 8 | indent_style = space 9 | indent_size = 4 10 | 11 | [package.json] 12 | indent_size = 2 13 | 14 | [plugins/**/package.json] 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gatsby-plugin-jenkins-layout 2 | 3 | Shared layout for jenkins.io based gatsby sites 4 | 5 | ## Usage 6 | 7 | gatsby-config.js 8 | 9 | ```javascript 10 | module.exports = { 11 | siteMetadata: { 12 | siteUrl: 'https://stories.jenkins.io', 13 | githubRepo: 'jenkins-infra/stories', 14 | }, 15 | plugins: [ 16 | { 17 | resolve: '../gatsby-plugin-jenkins-layout/', 18 | options: { 19 | siteUrl: 'https://stories.jenkins.io', 20 | }, 21 | }, 22 | ] 23 | } 24 | ``` 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Jenkins Infra 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | !.eslintrc.js 2 | !.eslintrc 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Directory for instrumented libs generated by jscoverage/JSCover 17 | lib-cov 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | 22 | # nyc test coverage 23 | .nyc_output 24 | 25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 26 | .grunt 27 | 28 | # Bower dependency directory (https://bower.io/) 29 | bower_components 30 | 31 | # node-waf configuration 32 | .lock-wscript 33 | 34 | # Compiled binary addons (http://nodejs.org/api/addons.html) 35 | build/Release 36 | 37 | # Dependency directories 38 | node_modules/ 39 | jspm_packages/ 40 | 41 | # Typescript v1 declaration files 42 | typings/ 43 | 44 | # Optional npm cache directory 45 | .npm 46 | 47 | # Optional eslint cache 48 | .eslintcache 49 | 50 | # Optional REPL history 51 | .node_repl_history 52 | 53 | # Output of 'npm pack' 54 | *.tgz 55 | 56 | # dotenv environment variable files 57 | .env* 58 | 59 | # gatsby files 60 | .cache/ 61 | public 62 | 63 | # Mac files 64 | .DS_Store 65 | 66 | # Yarn 67 | yarn-error.log 68 | .pnp/ 69 | .pnp.js 70 | # Yarn Integrity file 71 | .yarn-integrity 72 | 73 | src/layout.jsx 74 | src/layout.css 75 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jenkinsci/gatsby-plugin-jenkins-layout", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "publishConfig": { 6 | "access": "public" 7 | }, 8 | "scripts": { 9 | "lint": "eslint --ext .js,.jsx .", 10 | "build": "exit 0", 11 | "semantic-release": "semantic-release" 12 | }, 13 | "keywords": [ 14 | "gatsby", 15 | "jenkins-infra" 16 | ], 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/jenkins-infra/gatsby-plugin-jenkins-layout.git" 20 | }, 21 | "author": "Gavin Mogan (https://www.gavinmogan.com/)", 22 | "license": "MIT", 23 | "description": "", 24 | "dependencies": { 25 | "axios": "^1.6.7", 26 | "cheerio": "^1.0.0-rc.12", 27 | "dedent-js": "^1.0.1", 28 | "prop-types": "^15.8.1", 29 | "react-timeago": "^8.2.0", 30 | "style-to-object": "^1.0.6" 31 | }, 32 | "devDependencies": { 33 | "@microsoft/eslint-formatter-sarif": "^3.0.0", 34 | "eslint": "8.57.0", 35 | "eslint-config-google": "0.14.0", 36 | "eslint-plugin-filenames": "latest", 37 | "eslint-plugin-import": "^2.26.0", 38 | "eslint-plugin-jest": "^29.0.1", 39 | "eslint-plugin-promise": "^7.2.1", 40 | "eslint-plugin-react": "latest", 41 | "husky": "9.1.7", 42 | "lint-staged": "16.1.2", 43 | "semantic-release": "^24.2.6" 44 | }, 45 | "release": { 46 | "branches": [ 47 | "main", 48 | "alpha", 49 | "beta" 50 | ] 51 | }, 52 | "peerDependencies": { 53 | "react": "*", 54 | "react-dom": "*" 55 | }, 56 | "files": [ 57 | "lib/**/*", 58 | "gatsby-node.js", 59 | "gatsby-browser.js", 60 | "makeLayout.js" 61 | ], 62 | "lint-staged": { 63 | "*.{js,jsx,ts,tsx}": "npm run lint -- --fix" 64 | }, 65 | "jest": { 66 | "preset": "ts-jest", 67 | "testEnvironment": "jsdom", 68 | "roots": [ 69 | "/src" 70 | ], 71 | "setupFilesAfterEnv": [ 72 | "@testing-library/jest-dom/extend-expect" 73 | ], 74 | "moduleFileExtensions": [ 75 | "js", 76 | "ts", 77 | "tsx", 78 | "json", 79 | "jsx" 80 | ], 81 | "resetMocks": true, 82 | "transform": { 83 | "^.+\\.tsx?$": [ 84 | "ts-jest", 85 | { 86 | "tsconfig": "./tsconfig.esm.json" 87 | } 88 | ] 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/node 3 | # Edit at https://www.gitignore.io/?templates=node 4 | 5 | ### Node ### 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | 14 | # yarn v2 15 | .yarn/* 16 | !.yarn/patches 17 | !.yarn/releases 18 | !.yarn/plugins 19 | !.yarn/sdks 20 | !.yarn/versions 21 | .pnp.* 22 | 23 | # Diagnostic reports (https://nodejs.org/api/report.html) 24 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 25 | 26 | # Runtime data 27 | pids 28 | *.pid 29 | *.seed 30 | *.pid.lock 31 | 32 | # Directory for instrumented libs generated by jscoverage/JSCover 33 | lib-cov 34 | 35 | # Coverage directory used by tools like istanbul 36 | coverage 37 | *.lcov 38 | 39 | # nyc test coverage 40 | .nyc_output 41 | 42 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 43 | .grunt 44 | 45 | # Bower dependency directory (https://bower.io/) 46 | bower_components 47 | 48 | # node-waf configuration 49 | .lock-wscript 50 | 51 | # Compiled binary addons (https://nodejs.org/api/addons.html) 52 | build/Release 53 | 54 | # Dependency directories 55 | node_modules/ 56 | jspm_packages/ 57 | 58 | # TypeScript v1 declaration files 59 | typings/ 60 | 61 | # TypeScript cache 62 | *.tsbuildinfo 63 | 64 | # Optional npm cache directory 65 | .npm 66 | 67 | # Optional eslint cache 68 | .eslintcache 69 | 70 | # Optional REPL history 71 | .node_repl_history 72 | 73 | # Output of 'npm pack' 74 | *.tgz 75 | 76 | # Yarn Integrity file 77 | .yarn-integrity 78 | 79 | # dotenv environment variables file 80 | .env 81 | .env.test 82 | 83 | # parcel-bundler cache (https://parceljs.org/) 84 | .cache 85 | 86 | # next.js build output 87 | .next 88 | 89 | # nuxt.js build output 90 | .nuxt 91 | 92 | # react / gatsby 93 | public/ 94 | 95 | # vuepress build output 96 | .vuepress/dist 97 | 98 | # Serverless directories 99 | .serverless/ 100 | 101 | # FuseBox cache 102 | .fusebox/ 103 | 104 | # DynamoDB Local files 105 | .dynamodb/ 106 | 107 | # End of https://www.gitignore.io/api/node 108 | 109 | .cache/ 110 | public/ 111 | 112 | src/layout.jsx 113 | src/layout.css 114 | netlifyctl 115 | junit.xml 116 | yarn-error.log 117 | 118 | # IDE 119 | .idea 120 | static/site.webmanifest 121 | schema.graphql 122 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | options { 3 | timeout(time: 60, unit: 'MINUTES') 4 | ansiColor('xterm') 5 | disableConcurrentBuilds(abortPrevious: true) 6 | buildDiscarder logRotator(artifactDaysToKeepStr: '', artifactNumToKeepStr: '', daysToKeepStr: '5', numToKeepStr: '5') 7 | } 8 | 9 | agent { 10 | label 'linux-arm64-docker || arm64linux' 11 | } 12 | 13 | environment { 14 | NODE_ENV = 'production' 15 | TZ = "UTC" 16 | } 17 | 18 | stages { 19 | stage('Check for typos') { 20 | steps { 21 | sh 'typos --format sarif > typos.sarif || true' 22 | } 23 | post { 24 | always { 25 | recordIssues(tools: [sarif(id: 'typos', name: 'Typos', pattern: 'typos.sarif')]) 26 | } 27 | } 28 | } 29 | 30 | stage('Install Dependencies') { 31 | environment { 32 | NODE_ENV = 'development' 33 | } 34 | steps { 35 | sh 'asdf install' 36 | sh 'npm ci' 37 | } 38 | } 39 | 40 | stage('Lint') { 41 | environment { 42 | NODE_ENV = "development" 43 | } 44 | steps { 45 | sh ''' 46 | npx eslint --format checkstyle . > eslint-results.json || true 47 | ''' 48 | } 49 | post { 50 | always { 51 | recordIssues(tools: [ 52 | esLint(pattern: 'eslint-results.json'), 53 | ], qualityGates: [[threshold: 1, type: 'TOTAL', unstable: true]]) 54 | } 55 | } 56 | } 57 | 58 | stage('Build') { 59 | steps { 60 | sh ''' 61 | npm run build --if-present 62 | ''' 63 | } 64 | } 65 | 66 | stage('Release') { 67 | when { 68 | allOf { 69 | anyOf { 70 | branch "main" 71 | branch "beta" 72 | branch "alpha" 73 | } 74 | // Only deploy to production from infra.ci.jenkins.io 75 | expression { infra.isInfra() } 76 | } 77 | } 78 | environment { 79 | NPM_TOKEN = credentials('jenkinsci-npm-token') 80 | } 81 | steps { 82 | script { 83 | withCredentials([usernamePassword(credentialsId: 'jenkins-io-components-ghapp', 84 | usernameVariable: 'GITHUB_APP', 85 | passwordVariable: 'GITHUB_TOKEN')]) { 86 | sh 'npx semantic-release --repositoryUrl https://x-access-token:$GITHUB_TOKEN@github.com/jenkins-infra/gatsby-plugin-jenkins-layout.git' 87 | } 88 | } 89 | } 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'env': { 3 | 'browser': false, 4 | 'es6': true, 5 | 'node': true 6 | }, 7 | 'extends': [ 8 | 'eslint:recommended', 9 | 'plugin:import/recommended', 10 | 'plugin:react/recommended', 11 | 'plugin:promise/recommended' 12 | ], 13 | 'overrides': [ 14 | { 15 | 'env': { 16 | 'jest/globals': true 17 | }, 18 | 'extends': [ 19 | 'plugin:jest/all' 20 | ], 21 | 'files': [ 22 | 'jest-*.js', 23 | '__tests__/**/*.js', 24 | '__tests__/**/*.jsx', 25 | '__mocks__/**/*.js', 26 | '__mocks__/**/*.jsx', 27 | '**/*.test.js', 28 | '**/*.test.jsx', 29 | ], 30 | 'plugins': [ 31 | 'jest' 32 | ], 33 | 'rules': { 34 | 'jest/no-hooks': 'off', 35 | 'jest/prefer-expect-assertions': 'off' 36 | } 37 | }, 38 | { 39 | 'files': [ 40 | '*config.js', 41 | ], 42 | 'rules': { 43 | 'sort-keys': 'error' 44 | } 45 | } 46 | ], 47 | 'plugins': [ 48 | 'promise', 49 | 'react', 50 | 'import' 51 | ], 52 | 'rules': { 53 | 'comma-spacing': 2, 54 | 'eol-last': 2, 55 | 'import/extensions': ['error', 'ignorePackages', {'js': 'never', 'jsx': 'never'}], 56 | 'import/no-unresolved': [2, { 57 | ignore: [ 58 | '@gatsbyjs/reach-router' // we need to load the one provided by gatsby so they match up and stuff 59 | ] 60 | }], 61 | 'indent': ['error', 4], 62 | 'jsx-quotes': 2, 63 | 'key-spacing': [2], 64 | 'max-len': 0, 65 | 'no-console': 2, 66 | 'no-extra-semi': 2, 67 | 'no-multi-spaces': 'error', 68 | 'no-trailing-spaces': [2, {'skipBlankLines': true}], 69 | 'no-undef': 2, 70 | 'no-underscore-dangle': [0], 71 | 'no-unused-vars': [2], 72 | 'no-var': 2, 73 | 'object-curly-spacing': ['error', 'never'], 74 | 'object-shorthand': [0, 'always'], 75 | 'prefer-arrow-callback': 2, 76 | 'prefer-const': 2, 77 | 'prefer-template': 2, 78 | 'quote-props': [0, 'as-needed'], 79 | 'quotes': [2, 'single'], 80 | 'react/display-name': 0, 81 | 'react/jsx-boolean-value': 2, 82 | 'react/jsx-curly-spacing': [2, {'allowMultiline': true, 'when': 'never'}], 83 | 'react/jsx-equals-spacing': [2, 'never'], 84 | 'react/jsx-filename-extension': [1, {'extensions': ['.js', '.jsx']}], 85 | 'react/jsx-indent': ['error', 4, {'checkAttributes': true, 'indentLogicalExpressions': true}], 86 | 'react/jsx-no-undef': 2, 87 | 'react/jsx-one-expression-per-line': ['error', {'allow': 'single-child'}], 88 | 'react/jsx-sort-props': 0, 89 | 'react/jsx-uses-react': 2, 90 | 'react/jsx-uses-vars': 2, 91 | 'react/jsx-wrap-multilines': 2, 92 | 'react/no-did-mount-set-state': 2, 93 | 'react/no-did-update-set-state': 2, 94 | 'react/no-multi-comp': 0, 95 | 'react/no-unknown-property': 2, 96 | 'react/prop-types': 2, 97 | 'react/react-in-jsx-scope': 2, 98 | 'react/self-closing-comp': 2, 99 | 'semi': [2], 100 | 'space-before-function-paren': [2, {'anonymous': 'always', 'named': 'never'}], 101 | 'strict': [2, 'global'], 102 | }, 103 | 'settings': { 104 | 'import/extensions': ['.js', '.jsx'], 105 | 'import/resolver': {'node': {'extensions': ['.js', '.jsx']}}, 106 | 'react': {'version': 'detect'}, 107 | }, 108 | }; 109 | -------------------------------------------------------------------------------- /makeLayout.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | /* eslint-disable no-console */ 3 | const axios = require('axios'); 4 | const styleToObject = require('style-to-object'); 5 | const cheerio = require('cheerio'); 6 | const fs = require('fs/promises'); 7 | 8 | async function makeReactLayout(options = {}) { 9 | options = Object.assign({}, { 10 | siteUrl: 'https://www.jenkins.io/', 11 | githubBranch: 'master', 12 | reportAProblemTemplate: '', 13 | headerUrl: process.env.HEADER_FILE || 'https://www.jenkins.io/template/index.html', 14 | extraCss: [], 15 | manifest: {}, 16 | theme: 'light', 17 | }, options); 18 | const manifestUrl = new URL('/site.webmanifest', options.headerUrl).toString(); 19 | 20 | if (!options.headerUrl) { 21 | return null; 22 | } 23 | 24 | const jsxLines = [ 25 | 'import React from \'react\';', 26 | 'import {useStaticQuery, graphql} from \'gatsby\';', 27 | 'import {Helmet} from \'react-helmet\';', 28 | 'import \'./layout.css\';', 29 | ]; 30 | 31 | const cssLines = [].concat(options.extraCss); 32 | 33 | console.info(`Downloading header file from '${options.headerUrl}'`); 34 | const parsedHeaderUrl = new URL(options.headerUrl); 35 | const baseUrl = `${parsedHeaderUrl.protocol}//${parsedHeaderUrl.hostname}${parsedHeaderUrl.port ? `:${parsedHeaderUrl.port}` : ''}`; 36 | const content = await axios 37 | .get(options.headerUrl) 38 | .then((results) => { 39 | if (results.status !== 200) { 40 | throw results.data; 41 | } 42 | return results.data; 43 | }); 44 | 45 | const $ = cheerio.load(content, {decodeEntities: false}); 46 | $('nav .active.nav-item').removeClass('active'); // remove highlighted link 47 | if (options.siteUrl) { 48 | $(`.nav-item a[href*="${options.siteUrl}"]`).parent('.nav-item').addClass('active'); 49 | $(`.nav-link[href*="${options.siteUrl}"]`).attr('href', '/'); 50 | } 51 | $('img, script').each(function () { 52 | const src = $(this).attr('src'); 53 | if (!src) {return;} 54 | if (src.startsWith('/')) { 55 | $(this).attr('src', `${baseUrl}${src}`); 56 | } else { 57 | $(this).attr('src', src.replace('https://jenkins.io', baseUrl).replace('https://www.jenkins.io', baseUrl)); 58 | } 59 | }); 60 | $('a, link').each(function () { 61 | const href = $(this).attr('href'); 62 | if (!href) {return;} 63 | if (href.startsWith('/')) { 64 | $(this).attr('href', `${baseUrl}${href}`); 65 | } else { 66 | $(this).attr('href', href.replace('https://jenkins.io', baseUrl).replace('https://www.jenkins.io', baseUrl)); 67 | } 68 | }); 69 | // remove canonical as we add our own 70 | $('link[rel="canonical"]').remove(); 71 | // Prevents: Access to resource at 'https://jenkins.io/site.webmanifest' from origin 'https://plugins.jenkins.io' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. 72 | $('link[href$="site.webmanifest"]').attr('href', '/site.webmanifest'); 73 | // lets get rid of all the head tags since we are populating them with the SEO component 74 | $('meta[content*="{{"]').remove(); 75 | //remove title as we replace it most of the time. When we don't we dont want replaceme 76 | $('title').remove(); 77 | $('head').append(''); 78 | 79 | $(`.nav-link[href="${options.siteUrl}"]`).attr('href', '/'); 80 | $('#grid-box').empty(); 81 | $('#grid-box').append('{children}'); 82 | if (process.env.NETLIFY) { 83 | $('#footer .col-md-4').prepend('
Deploys by Netlify
'); 84 | } 85 | $('#footer .col-md-4').prepend($('

').append('').append('')); 86 | // $('#creativecommons').append(''); 87 | $('link[rel="stylesheet"]').each((_, elm) => { 88 | elm = $(elm); 89 | cssLines.push(`@import url('${elm.attr('href')}');`); 90 | elm.remove(); 91 | }); 92 | $('.searchbox').remove(); 93 | $('script[src*="docsearch"]').remove(); 94 | $('script:contains("docsearch")').remove(); 95 | $('script:contains("google-analytics.com")').remove(); 96 | 97 | const keyConversion = { 98 | class: 'className', 99 | 'charSet': 'charset', 100 | 'http-equiv': 'httpEquiv', 101 | 'stop-color': 'stopColor', 102 | 'crossorigin': 'crossOrigin', 103 | 'lineargradient': 'linearGradient', 104 | 'gradienttransform': 'gradientTransform', 105 | 'gradientunits': 'gradientUnits', 106 | 'viewbox': 'viewBox', 107 | 'xlink:href': 'xlinkHref', 108 | 'xmlns:xlink': 'xmlnsXlink', 109 | 'nomodule': 'noModule', 110 | }; 111 | 112 | const nodeConversions = { 113 | 'lineargradient': 'linearGradient', 114 | 'gradienttransform': 'gradientTransform', 115 | }; 116 | 117 | const handleNode = (node, indent = 0) => { 118 | const prefix = ''.padStart(6 + indent); 119 | 120 | if (node.name) { 121 | node.name = nodeConversions[node.name] || node.name; 122 | } 123 | 124 | if (node.name === 'link' && node.attribs && node.attribs.rel === 'stylesheet') { 125 | delete node.attribs.crossorigin; 126 | node.attribs.type = 'text/css'; 127 | node.attribs.media = 'all'; 128 | } 129 | let attrs = Object.entries(node.attribs || {}).map(([key, val]) => { 130 | key = keyConversion[key] || key; 131 | if (val === '') { 132 | return key; 133 | } 134 | if (key === 'style') { 135 | return `${key}={${JSON.stringify(styleToObject(val))}}`; 136 | } 137 | return `${key}=${JSON.stringify(val)}`; 138 | }).join(' '); 139 | if (node.name === 'script') { 140 | const text = node.children.map(child => { 141 | if (child.type === 'text') { 142 | return child.data; 143 | } 144 | throw new Error(`not sure how to handle ${child.type}`); 145 | }); 146 | if (text && text.length) { 147 | attrs = `${attrs} dangerouslySetInnerHTML={{__html: ${JSON.stringify(text)}}}`; 148 | } 149 | jsxLines.push(`${prefix}<${node.name} ${attrs} />`); 150 | return; 151 | } else if (node.type === 'comment') { 152 | return; 153 | } else if (node.type === 'text') { 154 | const text = node.data.replace('\u00A0', ' '); 155 | jsxLines.push(`${prefix}${text}`); 156 | } else if (node.children && node.children.length) { 157 | jsxLines.push(`${prefix}<${node.name} ${attrs}>`); 158 | node.children.forEach(child => handleNode(child, indent + 2)); 159 | jsxLines.push(`${prefix}`); 160 | } else { 161 | if (!node.name) { 162 | console.log(node); 163 | } 164 | if (node.name === 'siteversion') { 165 | throw new Error('Site Version'); 166 | // jsxLines.push(`${prefix}`); 167 | } else if (node.name === 'improvethispage') { 168 | throw new Error('Improve This Page'); 169 | // jsxLines.push(`${prefix}`); 170 | } else if (node.name === 'reportaproblem') { 171 | jsxLines.push(`${prefix}`); 172 | } else if (node.name === 'jio-navbar') { 173 | jsxLines.push(``); 174 | } else if (node.name === 'jio-footer') { 175 | jsxLines.push(``); 176 | } else { 177 | jsxLines.push(`${prefix}<${node.name} ${attrs} />`); 178 | } 179 | } 180 | }; 181 | 182 | jsxLines.push('export default function Layout({ children, id, sourcePath}) {'); 183 | jsxLines.push(` const {site: { buildTime, siteMetadata: { githubRepo, siteUrl }}} = useStaticQuery(graphql\` 184 | query { 185 | site { 186 | buildTime 187 | siteMetadata { 188 | githubRepo 189 | siteUrl 190 | } 191 | } 192 | } 193 | \`);`); 194 | jsxLines.push(' return ('); 195 | jsxLines.push('

'); 196 | jsxLines.push(' '); 197 | $('head').children(':not(link[rel="stylesheet"])').each((_, child) => handleNode(child, 2)); 198 | $('head').children('link[rel="stylesheet"]').each((_, child) => handleNode(child, 2)); 199 | $('head').children('script').each((_, child) => handleNode(child, 2)); 200 | jsxLines.push(' '); 201 | $('body').children(':not(script)').each((_, child) => handleNode(child, 0)); 202 | jsxLines.push(' '); 203 | $('body').children('script').each((_, child) => handleNode(child, 2)); 204 | jsxLines.push(' '); 205 | jsxLines.push('
'); 206 | jsxLines.push(' );'); 207 | 208 | 209 | jsxLines.push('}'); 210 | 211 | 212 | console.info(`Downloading site manifest file from '${manifestUrl}'`); 213 | const manifest = await axios 214 | .get(manifestUrl) 215 | .then((results) => { 216 | if (results.status !== 200) { 217 | throw results.data; 218 | } 219 | 220 | results.data.start_url = options.siteUrl || 'https://www.jenkins.io'; 221 | 222 | Object.assign(results.data, options.manifest); 223 | 224 | results.data.icons.forEach(icon => { 225 | icon.src = new URL(icon.src, manifestUrl).toString(); 226 | }); 227 | return JSON.stringify(results.data); 228 | }); 229 | 230 | return { 231 | manifest: manifest, 232 | jsxLines: jsxLines.map(str => str.trimEnd()).filter(Boolean).join('\n'), 233 | cssLines: cssLines.map(str => str.trimEnd()).filter(Boolean).join('\n') 234 | }; 235 | } 236 | 237 | async function saveReactLayout({jsxLines, cssLines, manifest}) { 238 | if (manifest) { 239 | await fs.mkdir('static', {recursive: true}); 240 | await fs.writeFile('./static/site.webmanifest', manifest); 241 | } 242 | if (jsxLines) { 243 | await fs.writeFile('./src/layout.jsx', jsxLines); 244 | } 245 | if (cssLines) { 246 | await fs.writeFile('./src/layout.css', cssLines); 247 | } 248 | } 249 | 250 | exports.makeReactLayout = makeReactLayout; 251 | exports.saveReactLayout = saveReactLayout; 252 | 253 | if (require.main === module) { 254 | makeReactLayout().then(saveReactLayout).catch(console.error); 255 | } 256 | --------------------------------------------------------------------------------