fix(app): build docs script

This commit is contained in:
Alex 2020-04-23 21:33:02 +03:00
parent 30f0099ddc
commit f33b2502de
7 changed files with 526 additions and 4 deletions

View file

@ -10,7 +10,6 @@ task(
'docs',
series(
'generate-doc-json-and-parse-themes',
'find-full-examples',
),
);
task('create-docs-dirs', (done) => {
@ -60,6 +59,7 @@ function flatten(root, arr) {
function createDirsStructure(dirs) {
const index = readFileSync(join(DOCS_DIST, 'index.html'), 'utf8');
dirs.forEach((dir: any) => {
const fullPath = join(DOCS_DIST, dir);
if (!existsSync(fullPath)) {
mkDirByPathSync(fullPath);

View file

@ -0,0 +1,186 @@
import { dest, src, task, series } from 'gulp';
import { accessSync, readFileSync, writeFileSync } from 'fs';
import { DOCS_OUTPUT, EXTENSIONS } from '../config';
import { join } from 'path';
import { exportThemes } from './export-themes';
const del = require('del');
const replace = require('gulp-replace');
const typedoc = require('gulp-typedoc');
const sass = require('gulp-sass');
const exec = require('child_process').execSync;
/**
* Copy everything from with-layout and without-layout dirs
* directly into examples dir. This way we can reference examples
* without specifying this dirs.
* For example, @stacked-example(..., button/button-showcase.component)
* instead of @stacked-example(..., layout/button/button-showcase.component)
*/
const EXAMPLES_SRC = [
'./src/playground/*.*',
'./src/playground/with-layout/**/*.*',
'./src/playground/without-layout/**/*.*',
];
const EXAMPLES_DEST = './docs/assets/examples';
task('copy-examples', () => {
del.sync(EXAMPLES_DEST);
return src(EXAMPLES_SRC)
.pipe(replace(/\/\*\*.*\*\/\n\s*\n/s, ''))
.pipe(dest(EXAMPLES_DEST));
});
task('generate-doc-json', generateDocJson);
function generateDocJson() {
return src(['src/framework/**/*.ts', '!src/**/*.spec.ts', '!src/framework/theme/**/node_modules{,/**}'])
.pipe(typedoc({
module: 'commonjs',
target: 'ES6',
// TODO: ignoreCompilerErrors, huh?
ignoreCompilerErrors: true,
includeDeclarations: true,
emitDecoratorMetadata: true,
experimentalDecorators: true,
excludeExternals: true,
exclude: 'node_modules/**/*',
json: 'docs/docs.json',
version: true,
noLib: true,
}));
}
task(
'parse-themes',
parseThemes,
);
function parseThemes() {
exec('prsr -g typedoc -f angular -i docs/docs.json -o docs/output.json');
return src('docs/themes.scss')
.pipe(sass({
functions: exportThemes('docs/', ''),
}));
}
task(
'generate-doc-json-and-parse-themes',
series(
'generate-doc-json',
'parse-themes',
),
);
task(
'validate-examples',
series(
'copy-examples',
(done) => {
const docs = JSON.parse(readFileSync(DOCS_OUTPUT, 'utf8'));
docs.classes.forEach(cls => validate(cls));
done();
},
),
);
task(
'find-full-examples',
series(
'validate-examples',
(done) => {
const docs = JSON.parse(readFileSync(DOCS_OUTPUT, 'utf8'));
docs.classes.forEach(cls => {
cls.overview = cls.overview.map(unfold);
cls.liveExamples = cls.liveExamples.map(unfold);
});
writeFileSync(DOCS_OUTPUT, JSON.stringify(docs));
done();
},
),
);
function unfold(tag) {
if (tag.type === 'text') {
return tag;
}
return unfoldWithFiles(tag);
}
function unfoldWithFiles(tag) {
if (isFile(tag.content.id)) {
return unfoldFile(tag);
}
return unfoldComponent(tag);
}
function unfoldFile(tag) {
const id = withoutExtension(tag.content.id);
const files = [tag.content.id];
return createNode(tag, files, id);
}
function unfoldComponent(tag) {
const files = EXTENSIONS
.map(extension => `${tag.content.id}.${extension}`)
.filter(isFileExists);
return createNode(tag, files);
}
function createNode(tag, files, id = tag.content.id) {
return {
...tag,
content: {
...tag.content,
files,
id,
},
};
}
function validate(cls) {
const examples = cls.overview
.filter(({ type }) => type !== 'text')
.map(({ content }) => content);
const missing = examples.filter(({ id }) => !isFileExists(id) && !isComponentExists(id));
if (missing.length) {
throw new Error(createMissingMsg(missing));
}
}
function createMissingMsg(examples): string {
const missing = examples.map(({ id, name }) => `${name}, ${id}`);
return `Can't resolve:\n${missing.join('\n')}`;
}
function isComponentExists(path): boolean {
return EXTENSIONS
.map(extension => `${path}.${extension}`)
.some(isFileExists);
}
function isFileExists(file): boolean {
try {
const path = join(EXAMPLES_DEST, file);
accessSync(path);
return true;
} catch (e) {
return false;
}
}
function isFile(id) {
return EXTENSIONS.some(extension => id.endsWith(extension));
}
function withoutExtension(file) {
return file.replace(/\.(ts|html|scss)/, '');
}

View file

@ -0,0 +1,158 @@
const _ = require('lodash');
const fs = require('fs');
const Colors = require('colors.js');
class Prop {
public name: string;
public value: any = null;
public parents: any[] = [];
public childs: any[] = [];
constructor(name) {
this.name = name;
};
}
class PropLink {
public theme: any;
public prop: any;
constructor(theme, prop) {
this.theme = theme;
this.prop = prop;
}
}
const exporter = {
get_value(a) {
let value, i;
switch (a.constructor.name) {
case 'SassList':
value = [];
for (i = 0; i < a.getLength(); i++) {
value.push(exporter.get_value(a.getValue(i)));
}
break;
case 'SassMap':
value = {};
for (i = 0; i < a.getLength(); i++) {
value[a.getKey(i).getValue()] = exporter.get_value(a.getValue(i));
}
break;
case 'SassColor':
if (1 === a.getA()) {
value = Colors.rgb2hex(a.getR(), a.getG(), a.getB());
} else {
value = 'rgba(' + a.getR() + ', ' + a.getG() + ', ' + a.getB() + ', ' + a.getA() + ')';
}
break;
case 'SassNumber':
value = a.getValue();
if (a.getUnit()) {
value += a.getUnit();
}
break;
case 'SassNull':
value = null;
break;
default:
value = a.getValue();
}
return value;
},
parseThemes(THEMES) {
let result = {};
Object.keys(THEMES).forEach((themeName) => {
result[themeName] = result[themeName] ? result[themeName] : {};
result[themeName].data = result[themeName].data ? result[themeName].data : {};
result[themeName].name = themeName;
result[themeName].parent = THEMES[themeName].parent;
const theme = THEMES[themeName].data;
Object.keys(theme).forEach((prop) => {
result[themeName].data[prop] = result[themeName].data[prop] ? result[themeName].data[prop] : new Prop(prop);
result = exporter.getParent(prop, themeName, themeName, prop, result, THEMES);
});
});
return result;
},
getParent(prop, scopedThemeName, resultThemeName, resultProp, resultObj, THEMES) {
const scopedTheme = THEMES[scopedThemeName].data;
const scopedParent = THEMES[scopedThemeName].parent;
const value = scopedTheme[prop];
if (typeof value === 'string' && scopedTheme[value]) {
if (resultObj[resultThemeName].data[resultProp].parents.length === 0) {
exporter.linkProps(resultObj, scopedThemeName, value, resultThemeName, prop);
} else {
resultObj[resultThemeName].data[resultProp].parents.push(new PropLink(scopedThemeName, value));
}
exporter.getParent(value, scopedThemeName, resultThemeName, resultProp, resultObj, THEMES);
} else {
resultObj[resultThemeName].data[resultProp].value = value;
if (scopedParent && THEMES[scopedParent].data[prop] === value) {
if (resultObj[resultThemeName].data[resultProp].parents.length === 0) {
exporter.linkProps(resultObj, scopedParent, prop, resultThemeName, prop)
} else {
resultObj[resultThemeName].data[resultProp].parents.push(new PropLink(scopedParent, prop));
}
}
}
return resultObj;
},
linkProps(resultObj, parentThemeName, parentPropName, childThemeName, childPropName) {
if (!resultObj.hasOwnProperty(parentThemeName)) {
resultObj[parentThemeName].data = {};
resultObj[parentThemeName].data[parentPropName] = new Prop(parentPropName);
} else if (!resultObj[parentThemeName].data.hasOwnProperty(parentPropName)) {
resultObj[parentThemeName].data[parentPropName] = new Prop(parentPropName);
}
resultObj[childThemeName].data[childPropName].parents.push(new PropLink(parentThemeName, parentPropName));
resultObj[parentThemeName].data[parentPropName].childs.push(new PropLink(childThemeName, childPropName));
return resultObj;
},
function(path) {
return function (file, themes, mapping) {
const themesValue = exporter.get_value(themes);
const mappingValue = exporter.get_value(mapping);
const completeThemes = {};
Object.keys(themesValue).forEach((themeName) => {
const theme = themesValue[themeName];
completeThemes[themeName] = {
...theme,
data: _.defaults(mappingValue, theme.data),
};
});
let output = {
themes: exporter.parseThemes(themesValue),
// TODO: we need to change internal function interface as it very hard to re-use them
completeThemes: exporter.parseThemes(completeThemes),
};
output = _.defaults(JSON.parse(fs.readFileSync(path + '/' + file.getValue())), output);
fs.writeFileSync(path + '/' + file.getValue(), JSON.stringify(output, null, ' '));
return themes;
}
},
interface(name) {
name = name || 'export';
return name + '($file, $themes, $mapping)';
},
};
export function exportThemes(path, name) {
const out = {};
out[exporter.interface(name)] = exporter.function(path);
return out;
}