diff --git a/docs/versions.json b/docs/versions.json new file mode 100644 index 00000000..b2d0c6e1 --- /dev/null +++ b/docs/versions.json @@ -0,0 +1,7 @@ +[ + { + "checkoutTarget": "gh-pages-deploy-fix", + "path": "/ngx-admin/", + "isCurrent": true + } +] diff --git a/package.json b/package.json index 3f9c5a80..41d482be 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "docs": "compodoc -p src/tsconfig.app.json -d docs", "docs:build": "npm run build -- docs --prod --aot --base-href /ngx-admin/", "docs:serve": "npm start -- docs --port 4100", - "docs:gh-pages": "npm run docs:build && npm run ngh -- --dir ./docs/dist", + "docs:gh-pages": "ts-node -P ./scripts/docs/tsconfig.json ./scripts/docs/build-docs.ts", "prepush": "npm run lint:ci", "release:changelog": "npm run conventional-changelog -- -p angular -i CHANGELOG.md -s", "postinstall": "ngcc --properties es2015 es5 browser module main --first-only --create-ivy-entry-points" diff --git a/scripts/docs/build-docs.ts b/scripts/docs/build-docs.ts new file mode 100644 index 00000000..73c89389 --- /dev/null +++ b/scripts/docs/build-docs.ts @@ -0,0 +1,123 @@ +import { join } from 'path'; +import { copy, mkdirp, remove, outputFile, writeJson, readJson } from 'fs-extra'; + +import { generateGithubSpaScript } from './ghspa-template'; +import { runCommand } from './run-command'; +import { log } from './log'; + +import { REPO_URL, OUT_DIR, REPO_OWNER, REPO_NAME } from './config'; +const WORK_DIR = join(process.cwd(), '../_DOCS_BUILD_WORK_DIR_'); +const MASTER_BRANCH_DIR = join(WORK_DIR, 'MASTER'); +const DOCS_VERSIONS_PATH = join(MASTER_BRANCH_DIR, 'docs/versions.json'); + +export interface Version { + checkoutTarget: string; + name: string; + path: string; + isCurrent?: boolean; +} + +(async function () { + log(`Cleaning work dir "${WORK_DIR}"`); + await remove(WORK_DIR); + log(`Cleaning output dir "${OUT_DIR}"`); + await remove(OUT_DIR); + + log(`Creating work dir "${WORK_DIR}"`); + await mkdirp(WORK_DIR); + + log(`Cloning ${REPO_URL} into ${MASTER_BRANCH_DIR}`); + await runCommand(`git clone ${REPO_URL} ${MASTER_BRANCH_DIR}`, { cwd: WORK_DIR }); + + log('Reading versions configuration'); + const config: Version[] = await import(DOCS_VERSIONS_PATH); + ensureSingleCurrentVersion(config); + + log(`Versions configuration:`); + const jsonConfig = JSON.stringify(config, null, ' '); + log(jsonConfig); + + log(`Building docs`); + await buildDocs(config); + + log(`Adding versions.json to ${OUT_DIR}`); + await outputFile(join(OUT_DIR, 'versions.json'), jsonConfig); + + log(`Deploying to ghpages`); + await deploy(OUT_DIR); + + log(`Cleaning up working directory (${WORK_DIR})`); + await remove(WORK_DIR); +}()); + +function ensureSingleCurrentVersion(versions: Version[]) { + const currentVersion = versions.filter(v => v.isCurrent); + if (currentVersion.length !== 1) { + throw new Error(`Versions config error: Only one current version allowed.`) + } +} + +async function buildDocs(versions: Version[]) { + const ghspaScript = generateGithubSpaScript(versions); + + return Promise.all(versions.map((version: Version) => { + const versionDistDir = version.isCurrent + ? OUT_DIR + : join(OUT_DIR, version.name); + + return prepareVersion(version, versionDistDir, ghspaScript); + })); +} + +async function prepareVersion(version: Version, distDir: string, ghspaScript: string) { + const projectDir = join(WORK_DIR, `${version.name}`); + + await copyToBuildDir(MASTER_BRANCH_DIR, projectDir); + await checkoutVersion(version.checkoutTarget, projectDir); + await runCommand('npm install', { cwd: projectDir }); + await addVersionNameToPackageJson(version.name, join(projectDir, 'package.json')); + await buildDocsApp(projectDir, version.path); + await copy(join(projectDir, 'docs/dist'), distDir); + await outputFile(join(distDir, 'assets/ghspa.js'), ghspaScript); + + await remove(projectDir); +} + +async function copyToBuildDir(from: string, to: string) { + try { + await mkdirp(to); + await copy(from, to); + } catch (e) { + throw new Error(`Error copying from ${from} to ${to}: ${e.message}`); + } +} + +async function checkoutVersion(checkoutTarget: string, repoDirectory: string) { + try { + await runCommand(`git checkout ${checkoutTarget}`, { cwd: repoDirectory, showLog: false }); + } catch (e) { + throw new Error(`Error checking out ${checkoutTarget}: ${e.message}`); + } +} + +async function addVersionNameToPackageJson(versionName: string, packageJsonPath: string) { + const packageJsonObject = await readJson(packageJsonPath); + packageJsonObject.versionName = versionName; + await writeJson(packageJsonPath, packageJsonObject); +} + +async function buildDocsApp(projectDir: string, baseHref: string) { + if (!baseHref.endsWith('/')) { + baseHref = baseHref + '/'; + } + await runCommand('npm run docs:prepare', { cwd: projectDir }); + await runCommand(`npm run build -- docs --prod --base-href '${baseHref}'`, { cwd: projectDir }); + await runCommand('npm run docs:dirs', { cwd: projectDir }); +} + +async function deploy(distDir: string) { + await runCommand( + `npx angular-cli-ghpages --dir . --repo=https://GH_TOKEN@github.com/${REPO_OWNER}/${REPO_NAME}.git`, + { cwd: distDir, showLog: true }, + ); +} diff --git a/scripts/docs/config.ts b/scripts/docs/config.ts new file mode 100644 index 00000000..92498fc5 --- /dev/null +++ b/scripts/docs/config.ts @@ -0,0 +1,4 @@ +export const REPO_URL = 'https://github.com/akveo/ngx-admin.git'; +export const REPO_OWNER = 'akveo'; +export const REPO_NAME = 'ngx-admin'; +export const OUT_DIR = 'docs/dist'; // Relative to the directory you run command diff --git a/scripts/docs/ghspa-template.ts b/scripts/docs/ghspa-template.ts new file mode 100644 index 00000000..0ca05003 --- /dev/null +++ b/scripts/docs/ghspa-template.ts @@ -0,0 +1,78 @@ +import { Version } from './build-docs'; + +function removeTrailingSlash(path: string): string { + if (path.endsWith('/')) { + return path.slice(0, -1); + } + return path; +} + +export function generateGithubSpaScript(versionsToRedirect: Version[]): string { + const paths: string[] = versionsToRedirect.map(v => v.path).map(removeTrailingSlash); + + return ` +/** + * + * ____ _ ___ _ _ _ _ ___ ___ ____ ____ ____ ____ ____ ___ ____ + * | __ | | |__| | | |__] |__] |__| | __ |___ [__ [__ |__] |__| + * |__] | | | | |__| |__] | | | |__] |___ ___] ___] | | | + * + * Easy way to enable Single Page Applications for GitHub Pages + * + * This project was released under MIT license. + * + * @link https://github.com/rafrex/spa-github-pages + * @author Rafael Pedicini + * @link http://websemantics.ca + * @author Adnan M.Sagar, PhD. + * + * @param {Object} l, the document current location + * @param {Boolean} projectPages, true by default, https://help.github.com/articles/user-organization-and-project-pages + * + */ + +;(function(l) { + + var redirectPath; + ${JSON.stringify(paths)}.forEach(function (path) { + if (l.pathname.indexOf(path) === 0) { + redirectPath = path; + } + }); + + if (!redirectPath) { + return; + } + + /* redirect all 404 traffic to index.html */ + function redirect() { + l.replace(l.protocol + '//' + l.hostname + (l.port ? ':' + l.port : '') + redirectPath + '/?' + + (l.pathname ? 'p=' + l.pathname.replace(/&/g, '~and~').replace(redirectPath, '') : '') + + (l.search ? '&q=' + l.search.slice(1).replace(/&/g, '~and~') : '') + + (l.hash)) + } + + /* resolve 404 redirects into internal routes */ + function resolve() { + if (l.search) { + var q = {}; + l.search.slice(1).split('&').forEach(function(v) { + var a = v.split('='); + q[a[0]] = a.slice(1).join('=').replace(/~and~/g, '&') + }); + if (q.p !== undefined) { + window.history.replaceState(null, null, + redirectPath + (q.p || '') + + (q.q ? ('?' + q.q) : '') + + l.hash + ) + } + } + } + + /* if current document is 404 page page, redirect to index.html otherwise resolve */ + document.title === '404' ? redirect() : resolve() + +}(window.location)); +`; +} diff --git a/scripts/docs/log.ts b/scripts/docs/log.ts new file mode 100644 index 00000000..bcef87a7 --- /dev/null +++ b/scripts/docs/log.ts @@ -0,0 +1,3 @@ +export function log(message: string) { + console.log(message); +} diff --git a/scripts/docs/run-command.ts b/scripts/docs/run-command.ts new file mode 100644 index 00000000..bcd7df42 --- /dev/null +++ b/scripts/docs/run-command.ts @@ -0,0 +1,37 @@ +import { exec } from 'child_process'; +import { promisify } from 'util'; + +export interface RunCommandOptions { + cwd?: string; + showLog?: boolean; +} + +const DEFAULT_OPTIONS: RunCommandOptions = { cwd: process.cwd(), showLog: false }; + +export async function runCommand(command: string, options?: RunCommandOptions) { + let { cwd, showLog } = Object.assign({}, DEFAULT_OPTIONS, options); + + try { + console.log(`Running "${command}" in "${cwd}"`); + const { stdout, stderr } = await promisify(exec)(command, { cwd }); + + if (showLog && stdout) { + console.log(stdout); + } + + if (stderr) { + console.log(`stderr from "${command}" in "${cwd}"`); + console.warn(stderr); + } + } catch ({ message, stdout, stderr }) { + let errorMessage = `Error running "${command}" in "${cwd}": ${message}.`; + if (stdout) { + errorMessage += `\nstdout: ${stdout}`; + console.error(stdout); + } + if (stderr) { + errorMessage += `\nstderr: ${stderr}`; + } + throw new Error(errorMessage); + } +} diff --git a/scripts/docs/tsconfig.json b/scripts/docs/tsconfig.json new file mode 100644 index 00000000..ad976e84 --- /dev/null +++ b/scripts/docs/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "rootDir": ".", + "module": "commonjs", + "lib": ["es2018"], + "target": "es2018", + "resolveJsonModule": true + }, + "include": [ + "build-docs.ts" + ] +} diff --git a/scripts/docs/tslint.json b/scripts/docs/tslint.json new file mode 100644 index 00000000..e3929e28 --- /dev/null +++ b/scripts/docs/tslint.json @@ -0,0 +1,5 @@ +{ + "rules": { + "no-console": false + } +} diff --git a/scripts/publish.sh b/scripts/publish.sh new file mode 100644 index 00000000..d29f4512 --- /dev/null +++ b/scripts/publish.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +# Iterates over all modules bundled in the src/.lib and publish them +for dir in ./src/.lib/*/ +do + dir=${dir%*/} + npm publish --access=public src/.lib/${dir##*/} +done