diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index d5514c8..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index 483346b..0000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1 +0,0 @@ -* @datastax/docs-team diff --git a/.github/workflows/dispatch-deploy-draft.yml b/.github/workflows/dispatch-deploy-draft.yml new file mode 100644 index 0000000..b28d83b --- /dev/null +++ b/.github/workflows/dispatch-deploy-draft.yml @@ -0,0 +1,45 @@ +name: Deploy Draft + +on: + pull_request: + branches: + - '**' + +jobs: + dispatch-deploy: + runs-on: ubuntu-latest + + steps: + # Determine the build branch and draft branch for dispatch. + - name: Determine Dispatch Parameters + run: | + if [ "${{ github.event_name }}" == "pull_request" ]; then + # If this workflow is kicked off by a pull request, build + # a draft using the pull request base branch and PR branch. + build_branch="${{ github.base_ref }}" + draft_branch="${{ github.event.pull_request.head.ref }}" + else + if [ "$(basename ${{ github.event.ref }})" == "stage" ]; then + # This was a merge to stage so kick off a build to update stage draft. + build_branch=stage + draft_branch=stage + else + # Otherwise this is a push to one of the source branches so + # dispatch a build for the main draft to pick up the changes. + build_branch=main + draft_branch=main + fi + fi + echo "build_branch=$build_branch" >> $GITHUB_OUTPUT + echo "draft_branch=$draft_branch" >> $GITHUB_OUTPUT + id: branches + + - name: Deploy Draft + uses: convictional/trigger-workflow-and-wait@v1.6.1 + with: + owner: riptano + repo: datastax-docs-site + github_token: ${{ secrets.DISPATCH_GITHUB_TOKEN }} + github_user: ${{ secrets.DISPATCH_GITHUB_USER }} + workflow_file_name: deploy-draft.yml + client_payload: '{ "build_repository": "${{ github.event.repository.full_name }}", "build_branch": "${{ steps.branches.outputs.build_branch }}", "draft_branch": "${{ steps.branches.outputs.draft_branch }}", "pull_request_number": "${{ github.event.pull_request.number }}" }' diff --git a/.gitignore b/.gitignore index 17eaccc..6bada63 100644 --- a/.gitignore +++ b/.gitignore @@ -1,114 +1,15 @@ -build -.DS_store -*~ -package-lock.json -.vscode -.java-version - -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* - -# Diagnostic reports (https://nodejs.org/api/report.html) -report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage -*.lcov - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release - -# Dependency directories -node_modules/ -jspm_packages/ - -# TypeScript v1 declaration files -typings/ - -# TypeScript cache -*.tsbuildinfo - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Microbundle cache -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env -.env.test - -# parcel-bundler cache (https://parceljs.org/) -.cache - -# Next.js build output -.next - -# Nuxt.js build / generate output -.nuxt -dist - -# Gatsby files -.cache/ -# Comment in the public line in if your project uses Gatsby and *not* Next.js -# https://nextjs.org/blog/next-9-1#public-directory-support -# public - -# vuepress build output -.vuepress/dist - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# TernJS port file -.tern-port - -# IDE -.idea/ \ No newline at end of file +#OS +**/.DS_Store +**/.git-credentials + +#IDE +**/.vscode/ +**/.idea +**/*.iml +*.http +**/.java-version + +#build +**/package-lock.json +**/node_modules/ +/build/ \ No newline at end of file diff --git a/README.adoc b/README.adoc new file mode 100644 index 0000000..1dd0d3c --- /dev/null +++ b/README.adoc @@ -0,0 +1,159 @@ += {company} {product} Docs +// Variables: +:company: DataStax +:product: Luna Streaming +:repo-name: pulsar-docs +:github-org: datastax +// Settings: +:toc: macro +:!example-caption: +:experimental: +:hide-uri-scheme: +ifdef::env-github[] +:icons: font +:toclevels: 1 +:toc-title: Contents +:tip-caption: :bulb: +:note-caption: :information_source: +:important-caption: :heavy_exclamation_mark: +:caution-caption: :fire: +:warning-caption: :warning: +:badges: +endif::[] +// Project URLs: +:url-github-org: https://github.com/{github-org} +:url-project-repo: {url-github-org}/{repo-name} +:url-ui-repo: https://github.com/riptano/docs-ui +:url-playbook-repo: https://github.com/riptano/datastax-docs-site +:url-contribute: +:url-datastax: https://datastax.com +:url-datastax-docs: https://docs.datastax.com +:url-docs-preview: http://docs-preview.datastax.com +// External URLs: +:asciidoc-language: https://docs.asciidoctor.org/asciidoc/latest/ + +This repository contains the source files for the {company} {product} documentation. + +toc::[] + +== Get started + +The documentation is written in {asciidoc-language}[AsciiDoc]-formatted source files located in the `modules` directory. + +=== Make a simple update + +For simple updates like fixing typos or modifying existing prose, it's easiest to edit the source files directly on GitHub. + +NOTE: You'll need Write privileges on the repository to edit files directly on GitHub. + +. Find the file you want to edit in the `modules` directory. + +. Click the *Edit* icon in the upper-right corner of the file view. + +. Make your changes in the editor. + +. Click *Commit changes...* + +. Enter a description for your commit and click *Propose changes*. + +. On the *Open a pull request* screen, enter a title and description for your change, assign reviewers, then click *Create pull request*. + +. Once the pull request is open, an automatic draft preview build is triggered. +Once complete, the build system posts a comment on the pull request with a link to the draft site for you to preview your changes. + +=== Edit docs locally + +If you need to make substantial updates to the documentation, you'll want to clone the repository so you can work with the source files locally. + +. Clone this repository ++ +[source,bash,subs="attributes"] +---- +git clone {url-project-repo}.git +---- + +. https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-personal-access-token-classic[Create a _classic_ personal access token] for your GitHub account. +When configuring the token, set the *Expiration* to at least 90 days and select everything under the *Repo* https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps#available-scopes[scope]. ++ +[IMPORTANT] +==== +Copy your personal access token to a temporary location -- you'll need it later. +==== + +. https://docs.github.com/en/enterprise-cloud@latest/authentication/authenticating-with-saml-single-sign-on/authorizing-a-personal-access-token-for-use-with-saml-single-sign-on[Authorize your personal access token] so that it can access repositories in the Riptano and DataStax organizations in GitHub. + +. https://docs.antora.org/antora/latest/playbook/private-repository-auth/#populate-credentials-directly[Populate the credential store] with your personal access token. +For most people this means doing the following: ++ +.. Create the file `$HOME/.git-credentials` and open it in your editor. +.. Add the following line: ++ +[source,subs="verbatim,quotes"] +---- +https://**TOKEN**:@github.com +---- ++ +Replace *`TOKEN`* with the personal access token you copied from GitHub. +.. Save and close the file. + +. If you don't already have Node.js installed, do the following: + +.. Install https://github.com/nvm-sh/nvm[nvm]. ++ +If you're on macOS, you can install nvm using https://brew.sh/[Homebrew]: ++ +[source,bash] +---- +brew install nvm +---- + +.. Use nvm to install Node.js. ++ +[source,bash] +---- +nvm install --lts +---- ++ +[source,bash] +---- +nvm use --lts +---- ++ +[source,bash] +---- +nvm alias default node +---- + +. Install the project dependencies. ++ +[source,bash,subs="attributes"] +---- +cd {repo-name} +---- ++ +[source,bash] +---- +npm install +---- + +. Build the site. ++ +[source,bash] +---- +npm run build:local +---- ++ +If the build was successful, you'll see the following output in your terminal: ++ +[source,console,subs="attributes"] +---- +Site generation complete! +Open file:///Users/USERNAME/repos/{repo-name}/build/site/index.html in a browser to view your site. +---- ++ +To view the site, paste the entire `\file:///` path into your browser's address bar and press kbd:[Return]. + +[#publish-docs] +== Publish docs + +To learn how to publish documentation to {url-datastax-docs}, see the {url-playbook-repo}#deploy-production[datastax-docs-site README]. diff --git a/README.md b/README.md deleted file mode 100644 index dbdd9bf..0000000 --- a/README.md +++ /dev/null @@ -1,2 +0,0 @@ -``` -README.md \ No newline at end of file diff --git a/antora.yml b/antora.yml index c8faff5..d4a78f8 100644 --- a/antora.yml +++ b/antora.yml @@ -1,5 +1,5 @@ name: luna-streaming -title: Luna Streaming Documentation +title: Luna Streaming start_page: index.adoc asciidoc: diff --git a/lib/remote-include-processor.js b/lib/remote-include-processor.js new file mode 100644 index 0000000..731a0a0 --- /dev/null +++ b/lib/remote-include-processor.js @@ -0,0 +1,199 @@ +const CIRCUMFIX_COMMENT_SUFFIX_RX = / (?:\*[/)]|--%?>)$/ +const NEWLINE_RX = /\r\n?|\n/ +const TAG_DELIMITER_RX = /[,;]/ +const TAG_DIRECTIVE_RX = /\b(?:tag|(end))::(\S+)\[\]$/ +const LINES_DOTDOT_RX = /\.\./ + + +function getTags (attrs) { + if ( 'tag' in attrs ) { + const tag = attrs['tag'] + if (tag && tag !== '!') { + return tag.charAt() === '!' ? new Map().set(tag.substr(1), false) : new Map().set(tag, true) + } + } else if ( 'tags' in attrs ) { + const tags = attrs['tags'] + if (tags) { + let result = new Map() + let any = false + tags.split(TAG_DELIMITER_RX).forEach((tag) => { + if (tag && tag !== '!') { + any = true + tag.charAt() === '!' ? result.set(tag.substr(1), false) : result.set(tag, true) + } + }) + if (any) return result + } + } +} + + +function applyTagFiltering (contents, tags) { + let selecting, selectingDefault, wildcard + if (tags.has('**')) { + if (tags.has('*')) { + selectingDefault = selecting = tags.get('**') + wildcard = tags.get('*') + tags.delete('*') + } else { + selectingDefault = selecting = wildcard = tags.get('**') + } + tags.delete('**') + } else { + selectingDefault = selecting = !Array.from(tags.values()).includes(true) + if (tags.has('*')) { + wildcard = tags.get('*') + tags.delete('*') + } + } + + const lines = [] + const tagStack = [] + const usedTags = [] + let activeTag + let lineNum = 0 + let startLineNum + contents.split(NEWLINE_RX).forEach((line) => { + lineNum++ + let m + let l = line + if ( + (l.endsWith('[]') || + (~l.indexOf('[] ') && + (m = l.match(CIRCUMFIX_COMMENT_SUFFIX_RX)) && + (l = l.substr(0, m.index)).endsWith('[]'))) && + (m = l.match(TAG_DIRECTIVE_RX)) + ) { + const thisTag = m[2] + if (m[1]) { + if (thisTag === activeTag) { + tagStack.shift() + ;[activeTag, selecting] = tagStack.length ? tagStack[0] : [undefined, selectingDefault] + } else if (tags.has(thisTag)) { + const idx = tagStack.findIndex(([name]) => name === thisTag) + if (~idx) { + tagStack.splice(idx, 1) + //console.warn(`line ${lineNum}: mismatched end tag in include: expected ${activeTag}, found ${thisTag}`) + } + //} else { + // //console.warn(`line ${lineNum}: unexpected end tag in include: ${thisTag}`) + //} + } + } else if (tags.has(thisTag)) { + usedTags.push(thisTag) + tagStack.unshift([(activeTag = thisTag), (selecting = tags.get(thisTag))]) + } else if (wildcard !== undefined) { + selecting = activeTag && !selecting ? false : wildcard + tagStack.unshift([(activeTag = thisTag), selecting]) + } + } else if (selecting) { + if (!startLineNum) startLineNum = lineNum + lines.push(line) + } + }) + // Q: use _.difference(Object.keys(tags), usedTags)? + //const missingTags = Object.keys(tags).filter((e) => !usedTags.includes(e)) + //if (missingTags.length) { + // console.warn(`tag${missingTags.length > 1 ? 's' : ''} '${missingTags.join(',')}' not found in include`) + //} + return [lines, startLineNum || 1] +} + +function getLines (attrs) { + if ( 'lines' in attrs ) { + const lines = attrs['lines'] + if (lines) { + // console.warn(`have lines` + lines) + let result = [] // new Map() + let any = false + lines.split(TAG_DELIMITER_RX).forEach((line) => { + if (line && line !== '!') { + let tryMultipleLines = line.split(LINES_DOTDOT_RX) + if ( tryMultipleLines.length === 1 ) { + any = true + result.push([tryMultipleLines[0], tryMultipleLines[0]]) + } + else if ( tryMultipleLines.length === 2 ) { + any = true + result.push([tryMultipleLines[0], tryMultipleLines[1]]) + } + } + }) + if (any) return result + } + } +} + +function filterChars(content){ + let myRegexp = new RegExp(/[^${\}]+(?=})/g); + let matches = myRegexp.exec(content); + + if(matches == null) + return content; + + let ret = content; + // matches.forEach(x => { + // let a = new RegExp("\\${"+x+"}",'gm'); + // ret = ret.replace(a,'asd'); + // }) + + return ret; +} + +function applyLineFiltering (contents, linesToInclude) { + const lines = [] + let lineNum = 0 + let startLineNum + let registerCurrentLine = false + + const nLinesPair = linesToInclude.length + let currentLinePair = 0 + let startLine = linesToInclude[currentLinePair][0] + let endLine = linesToInclude[currentLinePair][1] + // console.warn(`applyLineFiltering ` + startLine + ' and ' + endLine ) + + contents.split(NEWLINE_RX).forEach((line) => { + lineNum++ + + if ( !registerCurrentLine ) { + if ( lineNum == startLine ) + registerCurrentLine = true + } + + if ( registerCurrentLine ) + { + if (!startLineNum) startLineNum = lineNum + lines.push(line) + + if ( lineNum == endLine ) { + registerCurrentLine = false + currentLinePair++ + if ( currentLinePair >= nLinesPair ) + return [lines, startLineNum] + else { + startLine = linesToInclude[currentLinePair][0] + endLine = linesToInclude[currentLinePair][1] + } + } + } + }) + return [lines, startLineNum || 1] +} + +module.exports = function () { + this.includeProcessor(function () { + this.$option('position', '>>') + this.handles((target) => target.startsWith('https://')) + this.process((doc, reader, target, attrs) => { + const contents = require('child_process').execFileSync('curl', ['--silent', '-L', target], { encoding: 'utf8' }) + let includeContents = filterChars(contents) + let startLineNum = 1 + const tags = getTags(attrs) + const lines = getLines(attrs) + if (tags) [includeContents, startLineNum] = applyTagFiltering(includeContents, tags) + else if (lines) [includeContents, startLineNum] = applyLineFiltering(includeContents, lines) + reader.pushInclude(includeContents, target, target, startLineNum, attrs) + // reader.pushInclude(contents, target, target, 1, attrs) + }) + }) +} diff --git a/lib/svg-macro.js b/lib/svg-macro.js new file mode 100644 index 0000000..e7f5928 --- /dev/null +++ b/lib/svg-macro.js @@ -0,0 +1,110 @@ +const logger = require("@antora/logger")("asciidoctor:svg-macro"); + +/** + * @example Inline Embedded SVG + * svg:ROOT:ui/icons/vector.svg[] + */ +function inlineSvgMacro({ contentCatalog, file }) { + return function () { + this.process((parent, target, attrs) => { + svgContent = getSvgContent(target, file, contentCatalog); + if (!svgContent) return; + return this.createInlinePass( + parent, + svgContent.replace(" { + svgContent = getSvgContent(target, file, contentCatalog); + if (!svgContent) return; + const svgHtmlAttrs = htmlAttrs({ ...attrs, role: undefined }); + const containerHtmlAttrs = attrs.role + ? `class="imageblock ${attrs.role}"` + : 'class="imageblock"'; + const html = + `
` + + svgContent.replace("
"; + return this.createBlock(parent, "pass", html); + }); + }; +} + +/** + * This macro relies on the material-icons font being loaded in UI bundle. + * + * @example Material Icon + * icon:material-icons:menu_open[] + * + * @example Embedded SVG + * icon:ROOT:ui/icons/vector.svg[] + */ +function inlineIconMacro({ contentCatalog, file }) { + return function () { + this.process((parent, target, attrs) => { + if (target.startsWith("material-icons")) { + iconTarget = target + .replace("material-icons:", "") + .trim() + .replace("-", "_"); + return this.createInlinePass( + parent, + `${iconTarget}` + ); + } else { + svgContent = getSvgContent(target, file, contentCatalog); + if (!svgContent) return; + return this.createInlinePass( + parent, + svgContent.replace(" { + context.once('sitePublished', () => { + const logger = context.getLogger('tailwind-processor-extension') + logger.info('Building Tailwind') + execSync('npm run tailwindcss', { stdio: 'inherit' }) + logger.info('Tailwind Build Successful') + }) +} diff --git a/lib/unlisted-pages-extension.js b/lib/unlisted-pages-extension.js new file mode 100644 index 0000000..ec1b337 --- /dev/null +++ b/lib/unlisted-pages-extension.js @@ -0,0 +1,41 @@ +module.exports.register = function ({ config }) { + const { addToNavigation, unlistedPagesHeading = 'Unlisted Pages' } = config + const logger = this.getLogger('unlisted-pages-extension') + this + .on('navigationBuilt', ({ contentCatalog }) => { + contentCatalog.getComponents().forEach(({ versions }) => { + versions.forEach(({ name: component, version, navigation: nav, url: defaultUrl }) => { + const navEntriesByUrl = getNavEntriesByUrl(nav) + const unlistedPages = contentCatalog + .findBy({ component, version, family: 'page' }) + .filter((page) => page.out) + .reduce((collector, page) => { + // Check if the 'unlisted-page' attribute is set to true + if (page.asciidoc.attributes['unlisted-page'] === 'true') { + return collector; // Skip this page + } + if ((page.pub.url in navEntriesByUrl) || page.pub.url === defaultUrl) return collector + logger.warn({ file: page.src, source: page.src.origin }, 'detected unlisted page') + return collector.concat(page) + }, []) + if (unlistedPages.length && addToNavigation) { + nav.push({ + content: unlistedPagesHeading, + items: unlistedPages.map((page) => { + return { content: page.asciidoc.navtitle, url: page.pub.url, urlType: 'internal' } + }), + root: true, + }) + } + }) + }) + }) +} + +function getNavEntriesByUrl (items = [], accum = {}) { + items.forEach((item) => { + if (item.urlType === 'internal') accum[item.url.split('#')[0]] = item + getNavEntriesByUrl(item.items, accum) + }) + return accum +} diff --git a/local-preview-playbook.yml b/local-preview-playbook.yml new file mode 100644 index 0000000..a38f428 --- /dev/null +++ b/local-preview-playbook.yml @@ -0,0 +1,72 @@ +runtime: + log: + failure_level: warn +git: + # ensure_git_suffix: false # Enable if necessary -- some git services don’t recognize the URL if it contains the .git extension. + fetch_concurrency: 10 + +site: + title: DataStax Docs + start_page: luna-streaming::index.adoc + robots: disallow + +content: + branches: main # Sources default to this branch if none are specified. + sources: + - url: . + branches: HEAD + +antora: + extensions: + - '@antora/collector-extension' + - lib/tailwind-processor.js + - id: unlisted-pages + enabled: true + require: lib/unlisted-pages-extension.js + add_to_navigation: false + unlisted_pages_heading: Orphans + +asciidoc: + extensions: + - '@asciidoctor/tabs' + - lib/remote-include-processor.js + - lib/svg-macro.js + - asciidoctor-kroki + - asciidoctor-external-callout + attributes: + # BUILT-IN ATTRIBUTES + allow-uri-read: '' # this has no effect in antora, but does help development in Intellij + experimental: '' + idprefix: '' + idseparator: '-' + # kroki-fetch-diagram: true + # kroki-server-url: + max-include-depth: 10 + page-toclevels: 2@ + sectlinks: '' + tabs-sync-option: '' + example-caption: false + figure-caption: false + table-caption: false + xrefstyle: short + # CUSTOM ATTRIBUTES + company: 'DataStax' + astra_db: 'Astra DB' + astra_stream: 'Astra Streaming' + astra_ui: 'Astra Portal' + support_url: 'https://support.datastax.com' + glossary-url: 'https://docs.datastax.com/en/glossary/docs/index.html#' + +urls: + latest_version_segment_strategy: redirect:from + latest_version_segment: 'latest' + +ui: + bundle: + url: https://github.com/riptano/docs-ui/releases/latest/download/ui-bundle.zip + # url: https://gitlab.com/antora/antora-ui-default/-/jobs/artifacts/HEAD/raw/build/ui-bundle.zip?job=bundle-stable + snapshot: true + # supplemental_files: supplemental-ui + +output: + dir: 'build/site' \ No newline at end of file diff --git a/modules/ROOT/.DS_Store b/modules/ROOT/.DS_Store deleted file mode 100644 index ae601d0..0000000 Binary files a/modules/ROOT/.DS_Store and /dev/null differ diff --git a/package.json b/package.json new file mode 100644 index 0000000..0f64fa9 --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "private": true, + "name": "pulsar-docs", + "description": "The content source for DataStax Luna Streaming documentation.", + "author": "DataStax Inc.", + "homepage": "https://docs.datastax.com", + "repository": { + "type": "git", + "url": "https://github.com/datastax/pulsar-docs.git" + }, + "scripts": { + "build:local": "env FORCE_SHOW_EDIT_PAGE_LINK=true antora --clean --stacktrace local-preview-playbook.yml", + "tailwindcss": "tailwindcss build -c ./build/site/_/js/tailwind.config.js -i ./build/site/_/css/site.css -o ./build/site/_/css/site.css --minify" + }, + "dependencies": { + "@antora/collector-extension": "^1.0.0-alpha.3", + "@asciidoctor/tabs": "^1.0.0-beta.6", + "antora": "~3.1", + "asciidoctor-external-callout": "~1.2.1", + "asciidoctor-kroki": "~0.18.1", + "csv-parser": "^3.0.0", + "npm-run-all": "^4.1.5", + "tailwindcss": "^3.3.5" + } +}