@patternfly/documentation-framework 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/CHANGELOG.md +3964 -0
  2. package/README.md +92 -0
  3. package/app.js +118 -0
  4. package/assets/android-chrome-36x36.png +0 -0
  5. package/assets/android-chrome-48x48.png +0 -0
  6. package/assets/android-chrome-96x96.png +0 -0
  7. package/assets/favicon-16x16.png +0 -0
  8. package/assets/favicon-32x32.png +0 -0
  9. package/assets/favicon-48x48.png +0 -0
  10. package/assets/favicon.ico +0 -0
  11. package/assets/manifest.json +29 -0
  12. package/components/autoLinkHeader/autoLinkHeader.css +22 -0
  13. package/components/autoLinkHeader/autoLinkHeader.js +41 -0
  14. package/components/cssVariables/cssSearch.js +32 -0
  15. package/components/cssVariables/cssVariables.css +15 -0
  16. package/components/cssVariables/cssVariables.js +233 -0
  17. package/components/example/example.css +106 -0
  18. package/components/example/example.js +196 -0
  19. package/components/example/exampleToolbar.js +208 -0
  20. package/components/example/missing-thumbnail.jpg +0 -0
  21. package/components/footer/RHLogo.png +0 -0
  22. package/components/footer/footer.css +114 -0
  23. package/components/footer/footer.js +262 -0
  24. package/components/gdprBanner/gdprBanner.css +14 -0
  25. package/components/gdprBanner/gdprBanner.js +31 -0
  26. package/components/index.js +12 -0
  27. package/components/inlineAlert/inlineAlert.js +19 -0
  28. package/components/link/link.js +68 -0
  29. package/components/propsTable/propTypeWithLinks.js +33 -0
  30. package/components/propsTable/propsTable.js +82 -0
  31. package/components/sideNav/sideNav.css +17 -0
  32. package/components/sideNav/sideNav.js +95 -0
  33. package/components/tableOfContents/tableOfContents.css +77 -0
  34. package/components/tableOfContents/tableOfContents.js +89 -0
  35. package/components/topNav/topNav.css +30 -0
  36. package/components/topNav/topNav.js +24 -0
  37. package/global.css +7 -0
  38. package/helpers/asyncComponentFactory.js +41 -0
  39. package/helpers/capitalize.js +7 -0
  40. package/helpers/codeTitleTransformer.js +40 -0
  41. package/helpers/codesandbox.js +204 -0
  42. package/helpers/copy.js +19 -0
  43. package/helpers/createHandlebars.js +26 -0
  44. package/helpers/d3-timer.js +7 -0
  45. package/helpers/extractExampleHelpers.js +24 -0
  46. package/helpers/fonts.js +164 -0
  47. package/helpers/getId.js +6 -0
  48. package/helpers/getTitle.js +5 -0
  49. package/helpers/index.js +9 -0
  50. package/helpers/liveCodeTypes.js +4 -0
  51. package/helpers/slugger.js +35 -0
  52. package/helpers/trackEvent.js +17 -0
  53. package/helpers/xterm-addon-fit.js +3 -0
  54. package/helpers/xterm.js +8 -0
  55. package/images/patternfly-logo.svg +14 -0
  56. package/images/patternfly-orb.svg +28 -0
  57. package/layouts/index.js +1 -0
  58. package/layouts/logo.svg +33 -0
  59. package/layouts/sideNavLayout/sideNavLayout.css +104 -0
  60. package/layouts/sideNavLayout/sideNavLayout.js +237 -0
  61. package/package.json +87 -0
  62. package/pages/404/404.css +16 -0
  63. package/pages/404/404.svg +26 -0
  64. package/pages/404/chart.svg +24 -0
  65. package/pages/404/components.svg +1 -0
  66. package/pages/404/guidelines.svg +1 -0
  67. package/pages/404/index.js +136 -0
  68. package/pages/404/patternfly-flat.svg +1 -0
  69. package/pages/global-css-variables.md +108 -0
  70. package/pages/img/component-variable-mapping.png +0 -0
  71. package/pages/red-hat-font.md +40 -0
  72. package/routes-client.js +1 -0
  73. package/routes-generated.js +1 -0
  74. package/routes.js +155 -0
  75. package/scripts/cli/build.js +95 -0
  76. package/scripts/cli/buildClient.js +23 -0
  77. package/scripts/cli/buildServer.js +24 -0
  78. package/scripts/cli/cli.js +63 -0
  79. package/scripts/cli/generate.js +23 -0
  80. package/scripts/cli/helpers.js +9 -0
  81. package/scripts/cli/init.js +68 -0
  82. package/scripts/cli/serve.js +13 -0
  83. package/scripts/cli/start.js +32 -0
  84. package/scripts/md/anchor-header.js +69 -0
  85. package/scripts/md/auto-link-url.js +33 -0
  86. package/scripts/md/example-captions.js +67 -0
  87. package/scripts/md/jsxAttributes.js +20 -0
  88. package/scripts/md/mdx-ast-to-mdx-hast.js +210 -0
  89. package/scripts/md/mdx-hast-to-jsx.js +233 -0
  90. package/scripts/md/parseMD.js +308 -0
  91. package/scripts/md/remove-comments.js +21 -0
  92. package/scripts/md/styled-tags.js +87 -0
  93. package/scripts/md/typecheck.js +133 -0
  94. package/scripts/tsDocgen.js +155 -0
  95. package/scripts/webpack/getHtmlWebpackPlugins.js +78 -0
  96. package/scripts/webpack/prerender.js +59 -0
  97. package/scripts/webpack/webpack.base.config.js +159 -0
  98. package/scripts/webpack/webpack.client.config.js +146 -0
  99. package/scripts/webpack/webpack.server.config.js +55 -0
  100. package/scripts/writeScreenshots.js +66 -0
  101. package/templates/design-guidelines.js +13 -0
  102. package/templates/html.ejs +52 -0
  103. package/templates/mdx.css +308 -0
  104. package/templates/mdx.js +185 -0
  105. package/templates/patternfly-docs/content/extensions/extension/design-guidelines/design-guidelines.md +25 -0
  106. package/templates/patternfly-docs/content/extensions/extension/design-guidelines/img/card-elements.png +0 -0
  107. package/templates/patternfly-docs/content/extensions/extension/examples/Basic.jsx +4 -0
  108. package/templates/patternfly-docs/content/extensions/extension/examples/basic.md +25 -0
  109. package/templates/patternfly-docs/pages/index.js +27 -0
  110. package/templates/patternfly-docs/patternfly-docs.config.js +6 -0
  111. package/templates/patternfly-docs/patternfly-docs.css.js +8 -0
  112. package/templates/patternfly-docs/patternfly-docs.routes.js +12 -0
  113. package/templates/patternfly-docs/patternfly-docs.source.js +24 -0
  114. package/templates/sitemap.ejs +10 -0
  115. package/versions.json +870 -0
@@ -0,0 +1,308 @@
1
+ const path = require('path');
2
+ const fs = require('fs-extra');
3
+ const unified = require('unified');
4
+ const remove = require('unist-util-remove');
5
+ const visit = require('unist-util-visit');
6
+ const toVfile = require('to-vfile'); // https://github.com/vfile/vfile
7
+ const vfileReport = require('vfile-reporter');
8
+ const yaml = require('js-yaml'); // https://github.com/nodeca/js-yaml
9
+ const chokidar = require('chokidar');
10
+ const { sync } = require('glob');
11
+ const { typecheck } = require('./typecheck');
12
+ const { makeSlug } = require('../../helpers/slugger');
13
+ const { liveCodeTypes } = require('../../helpers/liveCodeTypes');
14
+ const { tsDocgen } = require('../tsDocgen');
15
+
16
+ let exitCode = 0;
17
+ const outputBase = path.join(process.cwd(), `patternfly-docs/generated`);
18
+ const tsDocs = {};
19
+ const routes = {};
20
+ const globs = {
21
+ props: [],
22
+ md: [],
23
+ };
24
+
25
+ function toReactComponent(mdFilePath, source, buildMode) {
26
+ // vfiles allow for nicer error messages and have native `unified` support
27
+ const vfile = toVfile.readSync(mdFilePath);
28
+
29
+ const relPath = path.relative(path.join(process.cwd(), '../..'), vfile.path).split(path.sep).join(path.posix.sep);
30
+
31
+ let jsx;
32
+ let outPath;
33
+ let pageData = {};
34
+ let frontmatter = {};
35
+
36
+ unified()
37
+ .use(require('remark-parse'))
38
+ .use(require('remark-frontmatter'), ['yaml'])
39
+ // Extract frontmatter
40
+ .use(() => (tree, file) => {
41
+ const yamlNode = tree.children.shift();
42
+ if (!yamlNode) {
43
+ return file.info('no frontmatter, skipping');
44
+ }
45
+ frontmatter = yaml.safeLoad(yamlNode.value);
46
+
47
+ // Fail early
48
+ if (!frontmatter.id) {
49
+ file.fail('id attribute is required in frontmatter for PatternFly docs');
50
+ }
51
+ source = frontmatter.source || source;
52
+ const slug = makeSlug(source, frontmatter.section, frontmatter.id);
53
+ outPath = path.join(outputBase, `${slug}.js`);
54
+
55
+ let sourceRepo = 'patternfly-org';
56
+ if (source.includes('html')) {
57
+ sourceRepo = 'patternfly';
58
+ }
59
+ else if (source.includes('react')) {
60
+ sourceRepo = 'patternfly-react';
61
+ }
62
+
63
+ const propComponents = [...new Set(frontmatter.propComponents || [])]
64
+ .filter(propComponent => {
65
+ if (tsDocs[propComponent]) {
66
+ return true;
67
+ }
68
+ file.message(`Prop component ${propComponent} missing from tsDocgen`);
69
+ return false;
70
+ })
71
+ .map(propComponent => tsDocs[propComponent])
72
+
73
+ const normalizedPath = relPath
74
+ .replace('node_modules/@patternfly/patternfly/docs', 'src/patternfly')
75
+ .replace(/node_modules\/@patternfly\/react-([\w-])/, (_, match) => `packages/react-${match}`)
76
+ .replace(/\.\.\//g, '');
77
+
78
+ pageData = {
79
+ id: frontmatter.id,
80
+ section: frontmatter.section || '',
81
+ source,
82
+ slug,
83
+ sourceLink: `https://github.com/patternfly/${
84
+ sourceRepo}/blob/main/${
85
+ normalizedPath}`,
86
+ hideTOC: frontmatter.hideTOC || false
87
+ };
88
+ // Temporarily override section for Demo tabs until we port this upstream
89
+ if (frontmatter.section === 'demos' && routes[slug.replace('demos', 'components')]) {
90
+ // Temporarily override section until https://github.com/patternfly/patternfly-react/pull/4862 is in react-docs
91
+ pageData.section = 'components';
92
+ pageData.source = `${source}-demos`;
93
+ pageData.slug = makeSlug(pageData.source, pageData.section, pageData.id);
94
+ outPath = path.join(outputBase, `${pageData.slug}.js`);
95
+ }
96
+ if (frontmatter.title) {
97
+ pageData.title = frontmatter.title;
98
+ }
99
+ if (propComponents.length > 0) {
100
+ pageData.propComponents = propComponents;
101
+ }
102
+ if (frontmatter.optIn) {
103
+ pageData.optIn = frontmatter.optIn;
104
+ }
105
+ if (frontmatter.beta) {
106
+ pageData.beta = frontmatter.beta;
107
+ }
108
+ if (frontmatter.cssPrefix) {
109
+ pageData.cssPrefix = Array.isArray(frontmatter.cssPrefix)
110
+ ? frontmatter.cssPrefix
111
+ : [frontmatter.cssPrefix];
112
+ }
113
+ if (frontmatter.katacodaBroken) {
114
+ pageData.katacodaBroken = frontmatter.katacodaBroken;
115
+ }
116
+ if (frontmatter.katacodaLayout) {
117
+ pageData.katacodaLayout = frontmatter.katacodaLayout;
118
+ }
119
+ if (frontmatter.hideNavItem) {
120
+ pageData.hideNavItem = frontmatter.hideNavItem;
121
+ }
122
+ })
123
+ // Delete HTML comments
124
+ .use(require('./remove-comments'))
125
+ // remark-mdx removes auto-link support
126
+ // this adds it back ONLY for links which are easily differentiable from JSX
127
+ .use(require('./auto-link-url'))
128
+ // Support for JSX in MD
129
+ .use(require('remark-mdx'))
130
+ // remark-mdx leaves paragraphs as normal MD, but inside MDX we expect it not to.
131
+ .use(() => tree => {
132
+ visit(tree, 'mdxBlockElement', node => {
133
+ if (node.children[0] && node.children[0].type === 'paragraph') {
134
+ const newChildren = node.children[0].children;
135
+ node.children.shift();
136
+ node.children = newChildren.concat(node.children);
137
+ }
138
+ });
139
+ })
140
+ // Support for import/exports in MD
141
+ .use(require('remark-mdxjs'))
142
+ // Insert footnotes
143
+ .use(require('remark-footnotes'))
144
+ // Remove whitespace
145
+ .use(require('remark-squeeze-paragraphs'))
146
+ // Support example captions
147
+ .use(require('./example-captions'))
148
+ // .use(require('remark-rehype'))
149
+ // .use(require('rehype-react'), { createElement: require('react').createElement })
150
+ // Transform AST to JSX elements. Includes special code block parsing
151
+ .use(require('./mdx-ast-to-mdx-hast'), {
152
+ watchExternal(file) {
153
+ if (buildMode === 'start') {
154
+ const watcher = chokidar.watch(file, { ignoreInitial: true });
155
+ watcher.on('change', () => {
156
+ sourceMDFile(mdFilePath, source, buildMode);
157
+ writeIndex();
158
+ });
159
+ }
160
+ }
161
+ })
162
+ // Don't allow exports
163
+ .use(() => tree => remove(tree, 'export'))
164
+ // Comments aren't very useful in generated files no one wants to look at
165
+ .use(() => tree => remove(tree, 'comment'))
166
+ // Extract examples to create fullscreen page routes
167
+ // Needs to be run after mdx-ast-to-mdx-hast which parses meta properties
168
+ .use(() => (tree, file) => {
169
+ const isExample = node =>
170
+ node.type === 'element'
171
+ && node.tagName === 'Example'
172
+ && liveCodeTypes.includes(node.properties.lang)
173
+ && !node.properties.noLive;
174
+ visit(tree, isExample, node => {
175
+ if (node.properties.isFullscreen) {
176
+ pageData.fullscreenExamples = pageData.fullscreenExamples || [];
177
+ pageData.fullscreenExamples.push(node.title);
178
+ }
179
+ else {
180
+ pageData.examples = pageData.examples || [];
181
+ pageData.examples.push(node.title);
182
+ }
183
+ // Typecheck TS examples
184
+ if (node.properties.lang === 'ts') {
185
+ const typerrors = typecheck(
186
+ path.join(pageData.id, node.title + '.tsx'), // Needs to be unique per-example
187
+ node.properties.code
188
+ );
189
+ typerrors.forEach(({ line, character, message }) => {
190
+ line = node.position.start.line + line + 1;
191
+ const column = character;
192
+ if (buildMode === 'start') {
193
+ // Don't fail to start over types
194
+ file.message(`\u001b[31m THIS WILL FAIL THE BUILD\u001b[0m\n ${message}`, { line, column });
195
+ } else {
196
+ console.log('\u001b[31m');
197
+ file.fail(`\n ${message}\n`, { line, column });
198
+ console.log('\u001b[0m');
199
+ }
200
+ });
201
+ }
202
+ });
203
+ })
204
+ // Add custom PatternFly doc design things
205
+ .use(require('./anchor-header'), toc => {
206
+ if (!pageData.hideTOC && toc.length > 0) {
207
+ pageData.toc = toc;
208
+ }
209
+ delete pageData.hideTOC;
210
+ })
211
+ .use(require('./styled-tags'))
212
+ // Transform HAST object to JSX string
213
+ .use(require('./mdx-hast-to-jsx'), {
214
+ getOutPath: () => outPath,
215
+ getRelPath: () => path.relative(path.dirname(outPath), vfile.dirname).split(path.sep).join(path.posix.sep), // for imports
216
+ getPageData: () => pageData // For @reach/router routing
217
+ })
218
+ .process(vfile, (err, file) => {
219
+ if (err) {
220
+ console.error(vfileReport(err));
221
+ exitCode = 2;
222
+ } else {
223
+ // console.log(relPath, '->', path.relative(process.cwd(), outPath));
224
+ if (file.messages.length > 0) {
225
+ console.log(vfileReport(file));
226
+ }
227
+ jsx = file.contents;
228
+ }
229
+ });
230
+
231
+ return {
232
+ jsx,
233
+ pageData,
234
+ outPath
235
+ };
236
+ }
237
+
238
+ function sourcePropsFile(file) {
239
+ tsDocgen(file)
240
+ .filter(({ hide }) => !hide)
241
+ .forEach(({ name, description, props }) => {
242
+ tsDocs[name] = { name, description, props };
243
+ });
244
+ }
245
+
246
+ function sourceMDFile(file, source, buildMode) {
247
+ if (path.basename(file).startsWith('_')) {
248
+ return;
249
+ }
250
+ const { jsx, pageData, outPath } = toReactComponent(file, source, buildMode);
251
+
252
+ if (jsx) {
253
+ fs.outputFileSync(outPath, jsx);
254
+ routes[pageData.slug] = {
255
+ id: pageData.id,
256
+ title: pageData.title || pageData.id,
257
+ toc: pageData.toc || [],
258
+ ...(pageData.examples && { examples: pageData.examples }),
259
+ ...(pageData.fullscreenExamples && { fullscreenExamples: pageData.fullscreenExamples }),
260
+ section: pageData.section,
261
+ source: pageData.source,
262
+ ...(pageData.katacodaLayout && { katacodaLayout: pageData.katacodaLayout }),
263
+ ...(pageData.hideNavItem && { hideNavItem: pageData.hideNavItem })
264
+ };
265
+ }
266
+ }
267
+
268
+ function writeIndex() {
269
+ const stringifyRoute = ([route, pageData]) => `'${route}': {\n ${Object.entries(pageData)
270
+ .map(([key, val]) => `${key}: ${JSON.stringify(val)}`)
271
+ .concat(`Component: () => import(/* webpackChunkName: "${route.substr(1)}/index" */ '.${route}')`)
272
+ .join(',\n ')}\n }`;
273
+
274
+ const indexContent = `module.exports = {\n ${Object.entries(routes)
275
+ .map(stringifyRoute)
276
+ .join(',\n ')}\n};`;
277
+ fs.outputFileSync(path.join(outputBase, 'index.js'), indexContent);
278
+
279
+ return exitCode;
280
+ }
281
+
282
+ module.exports = {
283
+ sourceProps(glob, ignore) {
284
+ globs.props.push({ glob, ignore });
285
+ sync(glob, { ignore }).forEach(sourcePropsFile);
286
+ },
287
+ sourceMD(glob, source, ignore, buildMode) {
288
+ globs.md.push({ glob, source, ignore });
289
+ sync(glob, { ignore }).forEach(file => sourceMDFile(file, source, buildMode));
290
+ },
291
+ writeIndex,
292
+ watchMD() {
293
+ globs.props.forEach(({ glob, ignore }) => {
294
+ const propWatcher = chokidar.watch(glob, { ignored: ignore, ignoreInitial: true });
295
+ propWatcher.on('add', sourcePropsFile);
296
+ propWatcher.on('change', sourcePropsFile);
297
+ });
298
+ globs.md.forEach(({ glob, source, ignore }) => {
299
+ const mdWatcher = chokidar.watch(glob, { ignored: ignore, ignoreInitial: true });
300
+ function onMDFileChange(file) {
301
+ sourceMDFile(file, source, 'start');
302
+ writeIndex();
303
+ }
304
+ mdWatcher.on('add', onMDFileChange);
305
+ mdWatcher.on('change', onMDFileChange);
306
+ });
307
+ }
308
+ };
@@ -0,0 +1,21 @@
1
+ // https://github.com/remarkjs/remark/tree/main/packages/remark-parse#processoruseparse-options
2
+
3
+ function plugin({ beginMarker = '<!--', endMarker = '-->' } = {}) {
4
+ const Parser = this.Parser
5
+ const tokenizers = Parser.prototype.blockTokenizers
6
+ const methods = Parser.prototype.blockMethods
7
+
8
+ tokenizers.comments = function tokenizeComment(eat, value) {
9
+ const trimmed = value.trimRight();
10
+ const endIndex = trimmed.indexOf(endMarker);
11
+ if (trimmed.startsWith(beginMarker) && endIndex >= 1) {
12
+ eat(value.substr(0, endIndex + endMarker.length));
13
+ }
14
+ return true;
15
+ }
16
+
17
+ // Run it just before `html`.
18
+ methods.splice(methods.indexOf('html'), 0, 'comments')
19
+ }
20
+
21
+ module.exports = plugin;
@@ -0,0 +1,87 @@
1
+ const visit = require('unist-util-visit');
2
+
3
+ const styledMdTags = [
4
+ 'p',
5
+ 'ul',
6
+ 'ul',
7
+ 'ul',
8
+ 'ol',
9
+ 'li',
10
+ 'dl',
11
+ 'blockquote',
12
+ 'small',
13
+ 'hr',
14
+ 'dt',
15
+ 'code',
16
+ 'table',
17
+ 'img'
18
+ ];
19
+
20
+ function styledTags() {
21
+ return tree => {
22
+ visit(tree, 'element', node => {
23
+ if (styledMdTags.includes(node.tagName)) {
24
+ node.properties.className = node.properties.className || '';
25
+ node.properties.className += node.properties.className ? ' ' : '';
26
+ node.properties.className += `ws-${node.tagName}`;
27
+ // Match pf-c-table implementation
28
+ // https://pf4.patternfly.org/components/table/html/basic-table/
29
+ if (node.tagName === 'table') {
30
+ node.properties.className += ' pf-c-table pf-m-grid-lg pf-m-compact';
31
+ node.properties.role = 'grid';
32
+ let columnHeaders = [];
33
+ for (let child of node.children) {
34
+ if (child.tagName === 'thead') {
35
+ // Find column headers
36
+ const tr = child.children.find(child => child.tagName === 'tr');
37
+ tr.properties.role = 'row';
38
+ tr.children
39
+ .filter(child => child.tagName === 'th')
40
+ .forEach(th => {
41
+ th.properties.className = th.properties.className || '';
42
+ th.properties.className += ' pf-m-wrap';
43
+ th.properties.role = 'columnheader';
44
+ th.properties.scope = 'col';
45
+ let colName = '';
46
+ visit(th, 'text', node2 => colName += node2.value);
47
+ columnHeaders.push(colName);
48
+ // Need to wrap in div for css-grid stuffs
49
+ th.children = [{
50
+ type: 'element',
51
+ tagName: 'div',
52
+ properties: {},
53
+ children: th.children
54
+ }];
55
+ });
56
+ }
57
+ else if (child.tagName === 'tbody') {
58
+ child.properties.role = 'rowgroup';
59
+ child.children
60
+ .filter(tr => tr.tagName === 'tr')
61
+ .forEach(tr => {
62
+ tr.properties.role = 'row';
63
+ tr.children
64
+ .filter(td => td.tagName === 'td')
65
+ .forEach((td, i) => {
66
+ td.properties.role = 'cell';
67
+ if (columnHeaders[i]) {
68
+ td.properties['data-label'] = columnHeaders[i];
69
+ }
70
+ // Need to wrap in div for css-grid stuffs
71
+ td.children = [{
72
+ type: 'element',
73
+ tagName: 'div',
74
+ properties: {},
75
+ children: td.children
76
+ }];
77
+ });
78
+ });
79
+ }
80
+ }
81
+ }
82
+ }
83
+ });
84
+ }
85
+ }
86
+
87
+ module.exports = styledTags;
@@ -0,0 +1,133 @@
1
+ const path = require('path');
2
+ const glob = require('glob');
3
+ const ts = require('typescript');
4
+ const versions = require('../../versions.json');
5
+
6
+ // matches webpack.base.config.js
7
+ const defaultDeclarations = `
8
+ declare module '\\*.png' {
9
+ const content: string;
10
+ export default content;
11
+ }
12
+ declare module '\\*.jpg' {
13
+ const content: string;
14
+ export default content;
15
+ }
16
+ declare module '\\*.jpeg' {
17
+ const content: string;
18
+ export default content;
19
+ }
20
+ declare module '\\*.webp' {
21
+ const content: string;
22
+ export default content;
23
+ }
24
+ declare module '\\*.gif' {
25
+ const content: string;
26
+ export default content;
27
+ }
28
+ declare module '\\*.svg' {
29
+ const content: string;
30
+ export default content;
31
+ }
32
+ `;
33
+
34
+ const reactStylesDir = path.join(require.resolve('@patternfly/react-styles/package.json'), '../');
35
+ const reactStyles = glob.sync(path.join(reactStylesDir, 'css/**/*.d.ts'))
36
+ .map(f => f.replace(reactStylesDir, '@patternfly/react-styles/').replace(/\.d.ts$/, ''));
37
+ const files = {
38
+ 'imports.ts': ['react', '@reach/router']
39
+ .concat(Object.keys(versions.Releases[0].versions))
40
+ .concat(reactStyles)
41
+ .filter(p => p !== '@patternfly/patternfly')
42
+ .map(p => `import '${p}';`)
43
+ .join('\n'),
44
+ 'declarations.d.ts': defaultDeclarations
45
+ };
46
+
47
+ const compilerOptions = {
48
+ target: 'ES2017',
49
+ noEmit: true,
50
+ skipLibCheck: true,
51
+ jsx: 'react',
52
+ esModuleInterop: true
53
+ };
54
+ const pfPackageRegex = /@patternfly\/([^\/]+)\/dist\/.*/;
55
+
56
+ const compilerHost = ts.createCompilerHost(compilerOptions);
57
+ compilerHost.readFile = fname => {
58
+ if (files[fname]) {
59
+ // console.log('readFile', fname, files[fname].text);
60
+ return files[fname];
61
+ }
62
+ return ts.sys.readFile(fname);
63
+ };
64
+ compilerHost.useCaseSensitiveFileNames = () => false; // saves 200ms on startup
65
+
66
+ const program = ts.createProgram(
67
+ Object.keys(files),
68
+ compilerOptions,
69
+ compilerHost
70
+ );
71
+ const defaultImportsFile = program.getSourceFile('imports.ts');
72
+ const typechecker = ts.createTypeChecker(program, true);
73
+
74
+ let fileCounter = 0;
75
+ function typecheck(fname, text) {
76
+ fname += fileCounter++; // Needs a new fname to bust diagnostics cache per-file :(
77
+ files[fname] = text;
78
+ const sourceFile = ts.createSourceFile(
79
+ fname,
80
+ text,
81
+ ts.ScriptTarget.ES2017,
82
+ true,
83
+ ts.ScriptKind.TSX
84
+ );
85
+ sourceFile.resolvedModules = defaultImportsFile.resolvedModules;
86
+ // Fix @patternfly/react-* imports so types resolve
87
+ sourceFile.statements
88
+ .filter(s => s.moduleSpecifier && s.moduleSpecifier.text.match(pfPackageRegex))
89
+ .forEach(s => {
90
+ s.moduleSpecifier.text = s.moduleSpecifier.text.replace(pfPackageRegex, (_, m) => `@patternfly/${m}`);
91
+ // Copied from a test file with `import { CogIcon } from '@patternfly/react-icons';`
92
+ if (!s.importClause.namedBindings) {
93
+ s.importClause.namedBindings = {
94
+ flags: 0,
95
+ modifierFlagsCache: 0,
96
+ kind: 265,
97
+ transformFlags: 0,
98
+ elements: [{
99
+ flags: 0,
100
+ modifierFlagsCache: 0,
101
+ kind: 266,
102
+ transformFlags: 0,
103
+ propertyName: undefined,
104
+ name: s.importClause.name
105
+ }]
106
+ };
107
+ s.importClause.name = undefined;
108
+ }
109
+ });
110
+ ts.bindSourceFile(sourceFile, compilerOptions);
111
+
112
+ const allDiagnostics = [...typechecker.getDiagnostics(sourceFile)];
113
+ return allDiagnostics.map(diagnostic => {
114
+ if (diagnostic.file) {
115
+ let { line, character } = ts.getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start);
116
+ let message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
117
+ return {
118
+ line,
119
+ character: character + 1,
120
+ message
121
+ };
122
+ } else {
123
+ return {
124
+ message: ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n")
125
+ };
126
+ }
127
+ });
128
+ }
129
+
130
+ module.exports = {
131
+ typecheck
132
+ };
133
+
@@ -0,0 +1,155 @@
1
+ const fs = require('fs');
2
+ const reactDocgen = require('react-docgen');
3
+ const ts = require('typescript');
4
+
5
+ const annotations = [
6
+ {
7
+ regex: /@deprecated/,
8
+ name: 'deprecated',
9
+ type: 'Boolean'
10
+ },
11
+ {
12
+ regex: /@hide/,
13
+ name: 'hide',
14
+ type: 'Boolean'
15
+ },
16
+ {
17
+ regex: /@beta/,
18
+ name: 'beta',
19
+ type: 'Boolean'
20
+ },
21
+ {
22
+ regex: /@propType\s+(.*)/,
23
+ name: 'type',
24
+ type: 'String'
25
+ }
26
+ ];
27
+
28
+ function addAnnotations(prop) {
29
+ if (prop.description) {
30
+ annotations.forEach(({ regex, name }) => {
31
+ const match = prop.description.match(regex);
32
+ if (match) {
33
+ prop.description = prop.description.replace(regex, '').trim();
34
+ if (name) {
35
+ prop[name] = match[2] || match[1] || true;
36
+ }
37
+ }
38
+ })
39
+ }
40
+
41
+ return prop;
42
+ }
43
+
44
+ function getComponentMetadata(filename, sourceText) {
45
+ let parsedComponents = null;
46
+ try {
47
+ parsedComponents = reactDocgen.parse(
48
+ sourceText,
49
+ reactDocgen.resolver.findAllExportedComponentDefinitions,
50
+ null,
51
+ { filename }
52
+ );
53
+ } catch (err) {
54
+ // eslint-disable-next-line no-console
55
+ // console.warn(`No component found in ${filename}:`, err);
56
+ }
57
+
58
+ return (parsedComponents || []).filter(parsed => parsed && parsed.displayName);
59
+ }
60
+
61
+ function getInterfaceMetadata(filename, sourceText) {
62
+ const ast = ts.createSourceFile(
63
+ filename,
64
+ sourceText,
65
+ ts.ScriptTarget.Latest // languageVersion
66
+ );
67
+
68
+ function getText(node) {
69
+ if (!node || !node.pos || !node.end) {
70
+ return undefined;
71
+ }
72
+ return sourceText.substring(node.pos, node.end).trim();
73
+ }
74
+
75
+ const interfaces = [];
76
+
77
+ ast.statements
78
+ .filter(statement => statement.kind === ts.SyntaxKind.InterfaceDeclaration)
79
+ .forEach(statement => {
80
+ const props = statement.members.reduce((acc, member) => {
81
+ const name = (member.name && member.name.escapedText)
82
+ || (member.parameters && `[${getText(member.parameters[0])}]`)
83
+ || 'Unknown';
84
+ acc[name] = {
85
+ description: member.jsDoc
86
+ ? member.jsDoc.map(doc => doc.comment).join('\n')
87
+ : null,
88
+ required: member.questionToken === undefined,
89
+ type: {
90
+ raw: getText(member.type).trim()
91
+ }
92
+ };
93
+ return acc;
94
+ }, {});
95
+
96
+ interfaces.push({
97
+ displayName: statement.name.escapedText,
98
+ props
99
+ });
100
+ });
101
+ return interfaces;
102
+ }
103
+
104
+ function normalizeProp([
105
+ name,
106
+ {
107
+ required,
108
+ annotatedType,
109
+ type,
110
+ tsType,
111
+ description,
112
+ defaultValue
113
+ }
114
+ ]) {
115
+ const res = {
116
+ name,
117
+ type: annotatedType
118
+ || (type && type.name)
119
+ || (type && (type.raw || type.name))
120
+ || (tsType && (tsType.raw || tsType.name))
121
+ || 'No type info',
122
+ description
123
+ };
124
+ if (required) {
125
+ res.required = true;
126
+ }
127
+ if (defaultValue && defaultValue.value) {
128
+ res.defaultValue = defaultValue.value;
129
+ }
130
+
131
+ return res;
132
+ }
133
+
134
+ function tsDocgen(file) {
135
+ const sourceText = fs.readFileSync(file, 'utf8');
136
+ const componentMeta = getComponentMetadata(file, sourceText); // Array of components with props
137
+ const interfaceMeta = getInterfaceMetadata(file, sourceText); // Array of interfaces with props
138
+
139
+ return componentMeta
140
+ .concat(interfaceMeta)
141
+ .map(parsed => ({
142
+ name: parsed.displayName,
143
+ description: parsed.description || '',
144
+ props: Object.entries(parsed.props || {})
145
+ .map(normalizeProp)
146
+ .map(addAnnotations)
147
+ .filter(prop => !prop.hide)
148
+ .sort((p1, p2) => p1.name.localeCompare(p2.name))
149
+ }));
150
+ }
151
+
152
+ module.exports = {
153
+ tsDocgen
154
+ };
155
+