diff --git a/.changeset/afraid-seals-crash.md b/.changeset/afraid-seals-crash.md new file mode 100644 index 0000000..255ed70 --- /dev/null +++ b/.changeset/afraid-seals-crash.md @@ -0,0 +1,5 @@ +--- +"@calycode/cli": patch +--- + +chore: remove tailwind cdn from docs [#131](https://github.com/calycode/xano-tools/issues/131) diff --git a/.changeset/upset-teeth-fetch.md b/.changeset/upset-teeth-fetch.md new file mode 100644 index 0000000..4a418b7 --- /dev/null +++ b/.changeset/upset-teeth-fetch.md @@ -0,0 +1,7 @@ +--- +'@calycode/core': minor +'@calycode/cli': minor +--- + +feat: new command `generate-internal-docs` that creates a directory of docsfiy powered browseable documentation for your workspace rendered from markdown on client (Docsify) +chore: update docs to include the new command diff --git a/.changeset/wise-yaks-scream.md b/.changeset/wise-yaks-scream.md new file mode 100644 index 0000000..eabcca0 --- /dev/null +++ b/.changeset/wise-yaks-scream.md @@ -0,0 +1,6 @@ +--- +"@calycode/core": patch +"@calycode/cli": patch +--- + +fix: fixing multiple issues with the generated markdown of the workspace, that was causing broken links diff --git a/docs/commands/export-backup.md b/docs/commands/export-backup.md index 73994bf..9688b01 100644 --- a/docs/commands/export-backup.md +++ b/docs/commands/export-backup.md @@ -24,9 +24,9 @@ Usage: xano export-backup [options] Backup Xano Workspace via Metadata API Options: - --instance The instance name. This is used to fetch the instance - configuration. The value provided at the setup - command. + --instance The instance name. This is used to fetch the + instance configuration. The value provided at the + setup command. --workspace The workspace name. This is used to fetch the workspace configuration. Same as on Xano interface. --branch The branch name. This is used to select the branch diff --git a/docs/commands/generate-code.md b/docs/commands/generate-code.md index f30122a..a474a59 100644 --- a/docs/commands/generate-code.md +++ b/docs/commands/generate-code.md @@ -32,13 +32,13 @@ $ xano generate-code --help Usage: xano generate-code [options] Create a library based on the OpenAPI specification. If the openapi -specification has not yet been generated, this will generate that as well as the -first step. +specification has not yet been generated, this will generate that as well as +the first step. Options: - --instance The instance name. This is used to fetch the instance - configuration. The value provided at the setup - command. + --instance The instance name. This is used to fetch the + instance configuration. The value provided at the + setup command. --workspace The workspace name. This is used to fetch the workspace configuration. Same as on Xano interface. --branch The branch name. This is used to select the branch diff --git a/docs/commands/generate-oas.md b/docs/commands/generate-oas.md index 524d1dd..50e994b 100644 --- a/docs/commands/generate-oas.md +++ b/docs/commands/generate-oas.md @@ -31,9 +31,9 @@ Scalar API Reference. + this command brings the Swagger docs to OAS 3.1+ version. Options: - --instance The instance name. This is used to fetch the instance - configuration. The value provided at the setup - command. + --instance The instance name. This is used to fetch the + instance configuration. The value provided at the + setup command. --workspace The workspace name. This is used to fetch the workspace configuration. Same as on Xano interface. --branch The branch name. This is used to select the branch diff --git a/docs/commands/generate-repo.md b/docs/commands/generate-repo.md index 8b86ccc..3c4c2cb 100644 --- a/docs/commands/generate-repo.md +++ b/docs/commands/generate-repo.md @@ -32,13 +32,14 @@ API to offer the full details. However that is enriched with the Xanoscripts after Xano 2.0 release. Options: - -I, --input Workspace yaml file from a local source, if present. + -I, --input Workspace yaml file from a local source, if + present. -O, --output Output directory (overrides default config), useful when ran from a CI/CD pipeline and want to ensure consistent output location. - --instance The instance name. This is used to fetch the instance - configuration. The value provided at the setup - command. + --instance The instance name. This is used to fetch the + instance configuration. The value provided at the + setup command. --workspace The workspace name. This is used to fetch the workspace configuration. Same as on Xano interface. --branch The branch name. This is used to select the branch diff --git a/docs/commands/generate-xs-repo.md b/docs/commands/generate-xs-repo.md index a19b9c0..310e1b3 100644 --- a/docs/commands/generate-xs-repo.md +++ b/docs/commands/generate-xs-repo.md @@ -21,15 +21,15 @@ $ xano generate-xs-repo [options] $ xano generate-xs-repo --help Usage: xano generate-xs-repo [options] -Process Xano workspace into repo structure. Supports table, function and apis as -of know. Xano VSCode extension is the preferred solution over this command. +Process Xano workspace into repo structure. Supports table, function and apis +as of know. Xano VSCode extension is the preferred solution over this command. Outputs of this process are also included in the default repo generation command. Options: - --instance The instance name. This is used to fetch the instance - configuration. The value provided at the setup - command. + --instance The instance name. This is used to fetch the + instance configuration. The value provided at the + setup command. --workspace The workspace name. This is used to fetch the workspace configuration. Same as on Xano interface. --branch The branch name. This is used to select the branch diff --git a/docs/commands/registry-add.md b/docs/commands/registry-add.md index 99561bb..2499061 100644 --- a/docs/commands/registry-add.md +++ b/docs/commands/registry-add.md @@ -21,17 +21,17 @@ $ xano registry-add [options] $ xano registry-add --help Usage: xano registry-add [options] -Add a prebuilt component to the current Xano context, essentially by pushing an -item from the registry to the Xano instance. +Add a prebuilt component to the current Xano context, essentially by pushing +an item from the registry to the Xano instance. Arguments: components Space delimited list of components to add to your Xano instance. Options: - --instance The instance name. This is used to fetch the instance - configuration. The value provided at the setup - command. + --instance The instance name. This is used to fetch the + instance configuration. The value provided at the + setup command. --workspace The workspace name. This is used to fetch the workspace configuration. Same as on Xano interface. --branch The branch name. This is used to select the branch diff --git a/docs/commands/registry-scaffold.md b/docs/commands/registry-scaffold.md index 92e8ace..c55ea49 100644 --- a/docs/commands/registry-scaffold.md +++ b/docs/commands/registry-scaffold.md @@ -18,14 +18,15 @@ $ xano registry-scaffold --help Usage: xano registry-scaffold [options] Scaffold a Xano registry folder with a sample component. Xano registry can be -used to share and reuse prebuilt components. In the registry you have to follow -the [registry](https://calycode.com/schemas/registry/registry.json) and +used to share and reuse prebuilt components. In the registry you have to +follow the [registry](https://calycode.com/schemas/registry/registry.json) and [registry item](https://calycode.com/schemas/registry/registry-item.json) schemas. Options: --output Local output path for the registry --instance The instance name. This is used to fetch the instance - configuration. The value provided at the setup command. + configuration. The value provided at the setup + command. -h, --help display help for command ``` \ No newline at end of file diff --git a/docs/commands/restore-backup.md b/docs/commands/restore-backup.md index eed0e18..2f4cf01 100644 --- a/docs/commands/restore-backup.md +++ b/docs/commands/restore-backup.md @@ -21,20 +21,21 @@ $ xano restore-backup [options] $ xano restore-backup --help Usage: xano restore-backup [options] -Restore a backup to a Xano Workspace via Metadata API. DANGER! This action will -override all business logic and restore the original v1 branch. Data will be -also restored from the backup file. +Restore a backup to a Xano Workspace via Metadata API. DANGER! This action +will override all business logic and restore the original v1 branch. Data will +be also restored from the backup file. Options: --instance The instance name. This is used to fetch the - instance configuration. The value provided at the - setup command. + instance configuration. The value provided at + the setup command. --workspace The workspace name. This is used to fetch the workspace configuration. Same as on Xano interface. -S, --source-backup Local path to the backup file to restore. --force Force restoration without confirmation, not advised to be specified, useful when ran from a - CI/CD pipeline and consequences are acknowledged. + CI/CD pipeline and consequences are + acknowledged. -h, --help display help for command ``` \ No newline at end of file diff --git a/docs/commands/run-test.md b/docs/commands/run-test.md index 26e032c..09dbcac 100644 --- a/docs/commands/run-test.md +++ b/docs/commands/run-test.md @@ -36,9 +36,10 @@ Options: instance configuration. The value provided at the setup command. --workspace The workspace name. This is used to fetch the - workspace configuration. Same as on Xano interface. - --branch The branch name. This is used to select the branch - configuration. Same as on Xano Interface. + workspace configuration. Same as on Xano + interface. + --branch The branch name. This is used to select the + branch configuration. Same as on Xano Interface. --group API group name. Same as on Xano Interface. --all Regenerate for all API groups in the workspace / branch of the current context. diff --git a/docs/commands/serve-oas.md b/docs/commands/serve-oas.md index 4d2301c..0b108e7 100644 --- a/docs/commands/serve-oas.md +++ b/docs/commands/serve-oas.md @@ -27,13 +27,13 @@ $ xano serve-oas [options] $ xano serve-oas --help Usage: xano serve-oas [options] -Serve the Open API specification locally for quick visual check, or to test your -APIs via the Scalar API reference. +Serve the Open API specification locally for quick visual check, or to test +your APIs via the Scalar API reference. Options: - --instance The instance name. This is used to fetch the instance - configuration. The value provided at the setup - command. + --instance The instance name. This is used to fetch the + instance configuration. The value provided at the + setup command. --workspace The workspace name. This is used to fetch the workspace configuration. Same as on Xano interface. --branch The branch name. This is used to select the branch diff --git a/docs/commands/serve-registry.md b/docs/commands/serve-registry.md index 4409340..1e1d23e 100644 --- a/docs/commands/serve-registry.md +++ b/docs/commands/serve-registry.md @@ -23,8 +23,8 @@ Serve the registry locally. This allows you to actually use your registry without deploying it to any remote host. Options: - --root Where did you put your registry? (Local path to the registry - directory) + --root Where did you put your registry? (Local path to the + registry directory) --listen The port where you want your registry to be served locally. By default it is 5000. --cors Do you want to enable CORS? By default false. diff --git a/docs/custom-styles.css b/docs/custom-styles.css new file mode 100644 index 0000000..e26bf80 --- /dev/null +++ b/docs/custom-styles.css @@ -0,0 +1,149 @@ +body { + font-family: 'Noto Sans', ui-sans-serif, system-ui; + } + + :root { + --theme-color: #45f3ff; + --content-max-width: 104ch; + --sidebar-toggle-alignment: start; + /* start center end */ + --sidebar-toggle-bg: var(--color-mono-2); + --sidebar-toggle-bg-hover: var(--button-bg); + --sidebar-toggle-color: var(--color-mono-4); + --sidebar-toggle-color-hover: var(--button-color); + --sidebar-toggle-height: 42px; + --sidebar-toggle-margin-block: 24px; + --sidebar-toggle-width: 32px; + + /* termynal style variables */ + --termynal-bg: var(--code-bg); + --termynal-color: var(--code-color); + --termynal-color-subtle: var(--color-mono-5); + --termynal-highlight-color: var(--theme-color); + --termynal-cursor: #00ff00; + --termynal-font: var(--font-family-mono); + --termynal-font-size: var(--font-size-m); + --termynal-border-radius: var(--border-radius); + } + + /* Dark mode toggle */ + #dark-mode-toggle { + display: block; + padding: 4px 8px; + border-radius: 8px; + border: none; + background: var(--color-mono-2); + color: var(--theme-color); + font-size: 1.1em; + cursor: pointer; + transition: background 0.2s; + } + + #dark-mode-toggle:hover { + background: var(--color-mono-4); + color: var(--theme-color); + } + + /* Terminaly plugin styles customized to work with light and dark mode both */ + [data-termynal] { + width: 100%; + max-width: 100%; + background: var(--termynal-bg); + color: var(--termynal-color); + font-size: var(--termynal-font-size); + font-family: var(--termynal-font); + border-radius: var(--termynal-border-radius); + padding: 38px 15px 10px; + position: relative; + -webkit-box-sizing: border-box; + box-sizing: border-box; + z-index: 1; + } + + /* termynal window-control-icons */ + [data-termynal]:before { + content: ''; + position: absolute; + top: 15px; + left: 15px; + display: inline-block; + width: 15px; + height: 15px; + border-radius: 50%; + /* A little hack to display the window buttons in one pseudo element. */ + background: #d9515d; + -webkit-box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930; + box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930; + } + + /* termynal label */ + [data-termynal]:after { + content: 'bash'; + position: absolute; + color: var(--termynal-color-subtle); + top: 5px; + left: 0; + width: 100%; + text-align: center; + } + + a[data-terminal-control] { + color: var(--termynal-highlight-color); + position: absolute; + top: 5px; + right: 15px; + z-index: 2; + text-decoration: none; + } + + a[data-terminal-control]:hover { + color: var(--termynal-highlight-color); + font-weight: 700; + } + + [data-ty] { + display: block; + } + + [data-ty]:before { + /* Set up defaults and ensure empty lines are displayed. */ + content: ''; + display: inline-block; + vertical-align: middle; + } + + [data-ty='input']:before, + [data-ty-prompt]:before { + margin-right: 0.75em; + color: var(--termynal-color); + } + + [data-ty='input']:before { + content: '$'; + } + + [data-ty][data-ty-prompt]:before { + content: attr(data-ty-prompt); + } + + [data-ty-cursor]:after { + content: attr(data-ty-cursor); + font-family: var(--termynal-font-family); + margin-left: 0.5em; + -webkit-animation: blink 1s infinite; + animation: blink 1s infinite; + } + + /* Cursor animation */ + + @-webkit-keyframes blink { + 50% { + opacity: 0; + } + } + + @keyframes blink { + 50% { + opacity: 0; + } + } \ No newline at end of file diff --git a/docs/index.html b/docs/index.html index e33c396..1f05049 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,292 +1,107 @@ - - - @calycode/cli Docs - - - - - - - - - - - - - -
- -
-
-
-
- - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - + } + window.addEventListener('mousemove', updateHighlight); + window.addEventListener( + 'touchstart', + () => { + highlight.style.opacity = 0; + }, + { once: true } + ); + + + + \ No newline at end of file diff --git a/docs/styles.css b/docs/styles.css new file mode 100644 index 0000000..004fbb3 --- /dev/null +++ b/docs/styles.css @@ -0,0 +1 @@ +*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.18 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Noto Sans,ui-sans-serif,system-ui;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.pointer-events-none{pointer-events:none}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{inset:0}.-z-10{z-index:-10}.block{display:block}.h-full{height:100%}.w-full{width:100%}.border{border-width:1px}.bg-\[\#222326\]{--tw-bg-opacity:1;background-color:rgb(34 35 38/var(--tw-bg-opacity,1))}.opacity-85{opacity:.85}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s} \ No newline at end of file diff --git a/package.json b/package.json index f73d71e..350dccb 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "test:watch": "turbo run test:watch", "test:coverage": "turbo run test:coverage", "build": "turbo run build", + "build:docs:styles": "npx tailwindcss -i util-resources/tailwind-input.css -o util-resources/docs-template/styles.css --minify", "clean": "turbo run clean", "build:docs": "pnpm run build && tsx scripts/generate-caly-cli-docs.ts", "xano": "pnpm --filter @calycode/cli run xano", @@ -41,11 +42,12 @@ "rollup": "^4.50.0", "rollup-plugin-dts": "^6.2.3", "strip-ansi": "^7.1.0", + "tailwindcss": "^3.4.18", "trash-cli": "^6.0.0", "ts-jest": "^29.4.4", "tsx": "^4.20.5", "turbo": "^2.5.8", "typescript": "^5.9.2" }, - "packageManager": "pnpm@10.18.3" + "packageManager": "pnpm@10.20.0" } \ No newline at end of file diff --git a/packages/cli/src/commands/generate-internal-docs.ts b/packages/cli/src/commands/generate-internal-docs.ts new file mode 100644 index 0000000..da611c0 --- /dev/null +++ b/packages/cli/src/commands/generate-internal-docs.ts @@ -0,0 +1,159 @@ +import { mkdir, access, readdir, lstat, rm, unlink } from 'node:fs/promises'; +import { log, intro, outro } from '@clack/prompts'; +import { load } from 'js-yaml'; +import { joinPath, dirname, replacePlaceholders, fetchAndExtractYaml } from '@repo/utils'; +import { + addFullContextOptions, + addPrintOutputFlag, + attachCliEventHandlers, + findProjectRoot, + printOutputDir, + resolveConfigs, + withErrorHandler, +} from '../utils/index'; + +/** + * Recursively removes all files and subdirectories in a directory. + * @param {string} directory - The directory to clear. + */ +async function clearDirectory(directory: string): Promise { + try { + await access(directory); + } catch { + // Directory does not exist; nothing to clear + return; + } + + const files = await readdir(directory); + await Promise.all( + files.map(async (file) => { + const curPath = joinPath(directory, file); + const stat = await lstat(curPath); + if (stat.isDirectory()) { + await clearDirectory(curPath); + await rm(curPath, { recursive: true, force: true }); // removes the (now-empty) dir + } else { + await unlink(curPath); + } + }) + ); +} + +async function generateInternalDocs({ + instance, + workspace, + branch, + input, + output, + fetch = false, + printOutput = false, + core, +}) { + attachCliEventHandlers('generate-internal-docs', core, { + instance, + workspace, + branch, + input, + output, + fetch, + printOutput, + }); + + //const resolvedContext = await resolveEffectiveContext({ instance, workspace, branch }, core); + const { instanceConfig, workspaceConfig, branchConfig } = await resolveConfigs({ + cliContext: { instance, workspace, branch }, + core, + }); + + // Resolve output dir + const outputDir = output + ? output + : replacePlaceholders(instanceConfig.internalDocs.output, { + '@': await findProjectRoot(), + instance: instanceConfig.name, + workspace: workspaceConfig.name, + branch: branchConfig.label, + }); + + clearDirectory(outputDir); + await mkdir(outputDir, { recursive: true }); + + // Ensure we have the input file, default to local, but override if --fetch + let inputFile = input; + if (fetch) { + inputFile = await fetchAndExtractYaml({ + baseUrl: instanceConfig.url, + token: await core.loadToken(instanceConfig.name), + workspaceId: workspaceConfig.id, + branchLabel: branchConfig.label, + outDir: outputDir, + core, + }); + } + + intro('Building directory structure...'); + + if (!inputFile) throw new Error('Input YAML file is required'); + if (!outputDir) throw new Error('Output directory is required'); + + log.step(`Reading and parsing YAML file -> ${inputFile}`); + const fileContents = await core.storage.readFile(inputFile, 'utf8'); + const jsonData = load(fileContents); + + const plannedWrites: { path: string; content: string }[] = await core.generateInternalDocs({ + jsonData, + instance: instanceConfig.name, + workspace: workspaceConfig.name, + branch: branchConfig.label, + }); + log.step(`Writing Documentation to the output directory -> ${outputDir}`); + await Promise.all( + plannedWrites.map(async ({ path, content }) => { + const outputPath = joinPath(outputDir, path); + const writeDir = dirname(outputPath); + if (!(await core.storage.exists(writeDir))) { + await core.storage.mkdir(writeDir, { recursive: true }); + } + await core.storage.writeFile(outputPath, content); + }) + ); + + printOutputDir(printOutput, outputDir); + outro('Documentation built successfully!'); +} + +function registergenerateInternalDocsCommand(program, core) { + const cmd = program + .command('generate-internal-docs') + .description( + 'Collect all descriptions, and internal documentation from a Xano instance and combine it into a nice documentation suite that can be hosted on a static hosting.' + ) + .option('-I, --input ', 'Workspace yaml file from a local source, if present.') + .option( + '-O, --output ', + 'Output directory (overrides default config), useful when ran from a CI/CD pipeline and want to ensure consistent output location.' + ); + + addFullContextOptions(cmd); + addPrintOutputFlag(cmd); + + cmd.option( + '-F, --fetch', + 'Forces fetching the workspace schema from the Xano instance via metadata API.' + ).action( + withErrorHandler(async (opts) => { + await generateInternalDocs({ + instance: opts.instance, + workspace: opts.workspace, + branch: opts.branch, + input: opts.input, + output: opts.output, + fetch: opts.fetch, + printOutput: opts.printOutputDir, + core: core, + }); + }) + ); +} + +export { registergenerateInternalDocsCommand }; diff --git a/packages/cli/src/program.ts b/packages/cli/src/program.ts index 5a7c4f0..4decd29 100644 --- a/packages/cli/src/program.ts +++ b/packages/cli/src/program.ts @@ -16,6 +16,7 @@ import { registerBuildXanoscriptRepoCommand } from './commands/generate-xanoscri import { Caly } from '@calycode/core'; import { InitializedPostHog } from './utils/posthog/init'; import { nodeConfigStorage } from './node-config-storage'; +import { registergenerateInternalDocsCommand } from './commands/generate-internal-docs'; const commandStartTimes = new WeakMap(); @@ -94,6 +95,7 @@ registerGenerateOasCommand(program, core); registerOasServeCommand(program, core); registerGenerateCodeCommand(program, core); registerGenerateRepoCommand(program, core); +registergenerateInternalDocsCommand(program, core); registerBuildXanoscriptRepoCommand(program, core); registerRegistryAddCommand(program, core); registerRegistryScaffoldCommand(program, core); @@ -123,7 +125,7 @@ program.configureHelp({ }, { title: font.combo.boldCyan('Code Generation:'), - commands: ['generate-oas', 'oas-serve', 'generate-code', 'generate-repo', 'generate-xs-repo', 'generate-functions'], + commands: ['generate-oas', 'oas-serve', 'generate-code', 'generate-repo', 'generate-internal-docs'], }, { title: font.combo.boldCyan('Registry:'), diff --git a/packages/core/src/features/internal-docs/index.ts b/packages/core/src/features/internal-docs/index.ts new file mode 100644 index 0000000..9ef7fcb --- /dev/null +++ b/packages/core/src/features/internal-docs/index.ts @@ -0,0 +1,301 @@ +const INTERNAL_DOCS_ASSETS = { + html_template: ` + + + + + Internal Documentation + + + + + + + + + + +
+ + + + + + + + + + + + + + + + `, + welcome_readme: ` +# Welcome to Internal Xano Workspace Documentation + +This site contains auto-generated and curated documentation for your workspace. +Browse the sections below to explore APIs, automation functions, database tables, and more. + +--- + +> [!INFO|label:Hint] +> Use the sidebar or search to quickly find any resource. + +--- + +## 'Running in my head... All the things she said...' + +_This is 'all the logic', so don't feel overwhelmed, just dive deeper through the sidebar. That'll help! You can also use full-text search in there._ + +{{ doc_items }} + +___ + +Documentation generated with 💖 for internal developer use via the [@calycode/cli](https://calycode.com/cli/docs) and is powered by [Docsify](https://docsifyjs.org). + `, + sidebar: ` + +{{ doc_items }} + +___ +Docs powered by [Docsify](https:docsifyjs.org) + `, +}; + +function capitalize(str: string) { + return str.charAt(0).toUpperCase() + str.slice(1); +} + +type DocFile = { path: string; content: string }; + +function generateAllFolderReadmes(paths: string[]): DocFile[] { + const fileSet = new Set(paths.map((p) => p.toLowerCase())); + const folderSet = new Set(); + paths.forEach((p) => { + const parts = p.split('/'); + for (let i = 1; i < parts.length; ++i) { + folderSet.add(parts.slice(0, i).join('/')); + } + }); + + const allFolders = Array.from(folderSet); + + const results: DocFile[] = []; + + for (const folder of allFolders) { + const readmePath = `${folder}/README.md`; + if (fileSet.has(readmePath.toLowerCase())) continue; // Already has a README + + // Find all direct children (folders or files, but not README.md itself) + const children = Array.from(paths) + .filter( + (p) => + p.startsWith(folder + '/') && + p !== readmePath && + p.slice(folder.length + 1).indexOf('/') !== -1 // Only direct children + ) + .map((p) => p.slice(folder.length + 1).split('/')[0]) + .filter((v, i, arr) => arr.indexOf(v) === i && v.toLowerCase() !== 'readme.md'); + + if (children.length === 0) continue; + + let md = `# ${folder.split('/').pop()}\n\n`; + md += `˙\n\n > [!INFO|label:Description]\n> This is just a brief table of contents. See what's inside below:`; + md += `## Contents:\n\n`; + for (const child of children.sort()) { + md += `- [${child}](/${folder}/${child}/)\n`; + } + results.push({ path: readmePath, content: md }); + } + return results; +} + +function generateFolderTree(paths: string[]): any { + const tree: any = {}; + for (const path of paths) { + const parts = path.replace(/\/README\.md$/, '').split('/'); + let node = tree; + for (const part of parts) { + if (!part) continue; + node[part] = node[part] || {}; + node = node[part]; + } + } + return tree; +} + +function renderSidebarTree(node: any, parentPath = '', level = 0): string { + let out = ''; + const indent = ' '.repeat(level); + const entries = Object.entries(node).sort(([a], [b]) => a.localeCompare(b)); + for (const [name, child] of entries) { + const linkPath = `${parentPath}/${name}`.replace(/^\/+/, ''); + out += `${indent}- [${capitalize(name)}](/${linkPath}/)\n`; + if (child && Object.keys(child).length > 0) { + out += renderSidebarTree(child, linkPath, level + 1); + } + } + return out; +} + +function generateSidebar(paths: string[], sidebarTemplate: string): string { + const tree = generateFolderTree(paths); + const docItems = renderSidebarTree(tree); + return sidebarTemplate.replace('{{ doc_items }}', docItems.trim()); +} + +function generateWelcomeReadme(paths: string[], welcomeReadmeTemplate: string): string { + const tree = generateFolderTree(paths); + const docItems = renderSidebarTree(tree); + return welcomeReadmeTemplate.replace('{{ doc_items }}', docItems.trim()); +} + +export { INTERNAL_DOCS_ASSETS, generateAllFolderReadmes, generateSidebar, generateWelcomeReadme }; diff --git a/packages/core/src/features/process-xano/core/generateRunReadme.ts b/packages/core/src/features/process-xano/core/generateRunReadme.ts index 5c51063..6907e6e 100644 --- a/packages/core/src/features/process-xano/core/generateRunReadme.ts +++ b/packages/core/src/features/process-xano/core/generateRunReadme.ts @@ -21,7 +21,7 @@ function getFunctionLink(method, functionMapping) { const functionId = method.context?.function?.id; if (functionId && functionMapping[functionId]) { const functionName = functionMapping[functionId].name; - const functionPath = `/src/function/${functionName.replace(/\//g, '_')}/`; + const functionPath = `/src/function/${functionName}/`; return `**[${functionName}](${functionPath})**`; } return ''; diff --git a/packages/core/src/features/process-xano/core/processItem.ts b/packages/core/src/features/process-xano/core/processItem.ts index b70829b..1311a9e 100644 --- a/packages/core/src/features/process-xano/core/processItem.ts +++ b/packages/core/src/features/process-xano/core/processItem.ts @@ -28,6 +28,9 @@ function getItemDir({ // Compute the base directory depending on the item type switch (key) { + case 'function': { + return joinPath(dirPath, item.name); + } case 'query': { // Use mapped app name if available const appId = item.app?.id; diff --git a/packages/core/src/implementations/generate-internal-docs.ts b/packages/core/src/implementations/generate-internal-docs.ts new file mode 100644 index 0000000..71c7c27 --- /dev/null +++ b/packages/core/src/implementations/generate-internal-docs.ts @@ -0,0 +1,121 @@ +import { generateRepoImplementation } from './generate-repo'; +import type { Caly } from '..'; +import { joinPath } from '@repo/utils'; +import { + INTERNAL_DOCS_ASSETS, + generateAllFolderReadmes, + generateSidebar, + generateWelcomeReadme, +} from '../features/internal-docs'; + +/** + * Replace all curly-brace dynamic segments like {slug} with _slug_ in a path or content. + * Does NOT affect slashes. + */ +function normalizeDynamicSegments(str: string): string { + return str.replace(/{([^/}]+)}/g, '-$1-'); +} + +/** + * Remove leading "/src/" or "src/" from a URL or path, if present. + */ +function removeLeadingSrc(url: string): string { + return url.replace(/^\/?src\//, '/'); +} + +/** + * Fix links in markdown content: + * - Normalizes dynamic segments in the URL. + * - Removes leading "src/" or "/src/" from the URL. + * - Adjusts links to README.md and directories for Docsify. + */ +function fixMarkdownLinks(content: string, allPaths: string[]): string { + return content.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, text, url) => { + let cleanUrl = url.replace(/\\/g, '/'); + cleanUrl = removeLeadingSrc(cleanUrl); + cleanUrl = normalizeDynamicSegments(cleanUrl); + + // Handle links to README.md + if (cleanUrl.endsWith('/README.md')) { + cleanUrl = cleanUrl.replace(/README\.md$/, ''); + if (!cleanUrl.endsWith('/')) cleanUrl += '/'; + } else if ( + allPaths.includes(joinPath(cleanUrl, 'README.md')) || + allPaths.includes(cleanUrl + '/README.md') + ) { + // If it's a folder, ensure trailing slash + if (!cleanUrl.endsWith('/')) cleanUrl += '/'; + } + // Remove double slashes except for protocol (e.g., http://) + cleanUrl = cleanUrl.replace(/([^:]\/)\/+/g, '$1'); + return `[${text}](${cleanUrl})`; + }); +} + +/** + * Also normalizes dynamic segments in the whole markdown content (for in-body references). + */ +function fixDynamicLinksInMarkdown(content: string): string { + return normalizeDynamicSegments(content); +} + +async function generateInternalDocsImplementation({ + jsonData, + storage, + core, + instance, + workspace, + branch, +}: { + jsonData: any; + storage: any; + core: Caly; + instance?: string; + workspace?: string; + branch?: string; +}): Promise<{ path: string; content: string }[]> { + const generatedRepoItems = await generateRepoImplementation({ + jsonData, + storage, + core, + instance, + workspace, + branch, + }); + + // First, normalize all paths for folder/file references + const markdownItems = generatedRepoItems + .filter((item) => item.path.endsWith('.md')) + .map((item) => ({ + ...item, + path: normalizeDynamicSegments(item.path), + })); + const allPaths = markdownItems.map((item) => item.path); + + // Now process content with all link fixes + const processedMarkdownItems = markdownItems.map((item) => ({ + ...item, + content: fixDynamicLinksInMarkdown(fixMarkdownLinks(item.content, allPaths)), + })); + + const generatedReadmes = generateAllFolderReadmes(allPaths); + + const coreFiles = [ + { + path: 'index.html', + content: INTERNAL_DOCS_ASSETS.html_template, + }, + { + path: 'README.md', + content: generateWelcomeReadme(allPaths, INTERNAL_DOCS_ASSETS.welcome_readme), + }, + { + path: '_sidebar.md', + content: generateSidebar(allPaths, INTERNAL_DOCS_ASSETS.sidebar), + }, + ]; + + return [...processedMarkdownItems, ...generatedReadmes, ...coreFiles]; +} + +export { generateInternalDocsImplementation }; diff --git a/packages/core/src/implementations/setup.ts b/packages/core/src/implementations/setup.ts index 9a75681..be79199 100644 --- a/packages/core/src/implementations/setup.ts +++ b/packages/core/src/implementations/setup.ts @@ -71,6 +71,9 @@ export async function setupInstanceImplementation( process: { output: '{@}/{workspace}/{branch}/src', }, + internalDocs: { + output: '{@}/{workspace}/{branch}/internal-docs', + }, xanoscript: { output: '{@}/{workspace}/{branch}/xanoscript', }, diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 231ee40..706fa5f 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -20,6 +20,7 @@ import { runTestsImplementation } from './implementations/run-tests'; import { setupInstanceImplementation } from './implementations/setup'; import { switchContextImplementation } from './implementations/switch-context'; import { updateOpenapiSpecImplementation } from './implementations/generate-oas'; +import { generateInternalDocsImplementation } from './implementations/generate-internal-docs'; /** * Main Caly class that provides core functionality for Xano development workflows. @@ -87,6 +88,7 @@ export class Caly extends TypedEmitter { } /** + * @deprecated This has been deprecated in favor for feature invokation level overrides. * Switches the current active context to a different instance, workspace, or branch. * Updates the global configuration to reflect the new active context. * @@ -267,6 +269,33 @@ export class Caly extends TypedEmitter { return response; } + /** + * Creates a browseable internal documentation by processing the 'internal documentation' from each item in the system, combined with the inline comments and descriptions that are found inside the function stacks. + * + */ + async generateInternalDocs({ + jsonData, + instance, + workspace, + branch, + }: { + jsonData: any; + instance?: string; + workspace?: string; + branch?: string; + }): Promise<{ path: string; content: string }[]> { + const response = await generateInternalDocsImplementation({ + jsonData, + storage: this.storage, + core: this, + instance, + workspace, + branch, + }); + + return response; + } + /** * Restores a Xano workspace from backup data. * Uploads prepared FormData containing backup files to restore workspace state. diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..7d6a7f4 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,20 @@ +/* eslint-disable no-undef */ // this file could be converted to esm, but we don't care here +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: ['util-resources/docs-template/index.html', 'docs/index.html'], + theme: { + extend: { + colors: { + 'primary-dark': '#222326', + primary: '#3C3D40', + secondary: '#666A73', + muted: '#9FA3A6', + light: '#D7D7D9', + }, + fontFamily: { + sans: ['Noto Sans', 'ui-sans-serif', 'system-ui'], + }, + }, + }, + plugins: [], +}; diff --git a/util-resources/docs-template/custom-styles.css b/util-resources/docs-template/custom-styles.css new file mode 100644 index 0000000..e26bf80 --- /dev/null +++ b/util-resources/docs-template/custom-styles.css @@ -0,0 +1,149 @@ +body { + font-family: 'Noto Sans', ui-sans-serif, system-ui; + } + + :root { + --theme-color: #45f3ff; + --content-max-width: 104ch; + --sidebar-toggle-alignment: start; + /* start center end */ + --sidebar-toggle-bg: var(--color-mono-2); + --sidebar-toggle-bg-hover: var(--button-bg); + --sidebar-toggle-color: var(--color-mono-4); + --sidebar-toggle-color-hover: var(--button-color); + --sidebar-toggle-height: 42px; + --sidebar-toggle-margin-block: 24px; + --sidebar-toggle-width: 32px; + + /* termynal style variables */ + --termynal-bg: var(--code-bg); + --termynal-color: var(--code-color); + --termynal-color-subtle: var(--color-mono-5); + --termynal-highlight-color: var(--theme-color); + --termynal-cursor: #00ff00; + --termynal-font: var(--font-family-mono); + --termynal-font-size: var(--font-size-m); + --termynal-border-radius: var(--border-radius); + } + + /* Dark mode toggle */ + #dark-mode-toggle { + display: block; + padding: 4px 8px; + border-radius: 8px; + border: none; + background: var(--color-mono-2); + color: var(--theme-color); + font-size: 1.1em; + cursor: pointer; + transition: background 0.2s; + } + + #dark-mode-toggle:hover { + background: var(--color-mono-4); + color: var(--theme-color); + } + + /* Terminaly plugin styles customized to work with light and dark mode both */ + [data-termynal] { + width: 100%; + max-width: 100%; + background: var(--termynal-bg); + color: var(--termynal-color); + font-size: var(--termynal-font-size); + font-family: var(--termynal-font); + border-radius: var(--termynal-border-radius); + padding: 38px 15px 10px; + position: relative; + -webkit-box-sizing: border-box; + box-sizing: border-box; + z-index: 1; + } + + /* termynal window-control-icons */ + [data-termynal]:before { + content: ''; + position: absolute; + top: 15px; + left: 15px; + display: inline-block; + width: 15px; + height: 15px; + border-radius: 50%; + /* A little hack to display the window buttons in one pseudo element. */ + background: #d9515d; + -webkit-box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930; + box-shadow: 25px 0 0 #f4c025, 50px 0 0 #3ec930; + } + + /* termynal label */ + [data-termynal]:after { + content: 'bash'; + position: absolute; + color: var(--termynal-color-subtle); + top: 5px; + left: 0; + width: 100%; + text-align: center; + } + + a[data-terminal-control] { + color: var(--termynal-highlight-color); + position: absolute; + top: 5px; + right: 15px; + z-index: 2; + text-decoration: none; + } + + a[data-terminal-control]:hover { + color: var(--termynal-highlight-color); + font-weight: 700; + } + + [data-ty] { + display: block; + } + + [data-ty]:before { + /* Set up defaults and ensure empty lines are displayed. */ + content: ''; + display: inline-block; + vertical-align: middle; + } + + [data-ty='input']:before, + [data-ty-prompt]:before { + margin-right: 0.75em; + color: var(--termynal-color); + } + + [data-ty='input']:before { + content: '$'; + } + + [data-ty][data-ty-prompt]:before { + content: attr(data-ty-prompt); + } + + [data-ty-cursor]:after { + content: attr(data-ty-cursor); + font-family: var(--termynal-font-family); + margin-left: 0.5em; + -webkit-animation: blink 1s infinite; + animation: blink 1s infinite; + } + + /* Cursor animation */ + + @-webkit-keyframes blink { + 50% { + opacity: 0; + } + } + + @keyframes blink { + 50% { + opacity: 0; + } + } \ No newline at end of file diff --git a/util-resources/docs-template/index.html b/util-resources/docs-template/index.html index e33c396..1f05049 100644 --- a/util-resources/docs-template/index.html +++ b/util-resources/docs-template/index.html @@ -1,292 +1,107 @@ - - - @calycode/cli Docs - - - - - - - - - - - - - -
- -
-
-
-
- - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - + } + window.addEventListener('mousemove', updateHighlight); + window.addEventListener( + 'touchstart', + () => { + highlight.style.opacity = 0; + }, + { once: true } + ); + + + + \ No newline at end of file diff --git a/util-resources/docs-template/styles.css b/util-resources/docs-template/styles.css new file mode 100644 index 0000000..004fbb3 --- /dev/null +++ b/util-resources/docs-template/styles.css @@ -0,0 +1 @@ +*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.18 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Noto Sans,ui-sans-serif,system-ui;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.pointer-events-none{pointer-events:none}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{inset:0}.-z-10{z-index:-10}.block{display:block}.h-full{height:100%}.w-full{width:100%}.border{border-width:1px}.bg-\[\#222326\]{--tw-bg-opacity:1;background-color:rgb(34 35 38/var(--tw-bg-opacity,1))}.opacity-85{opacity:.85}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s} \ No newline at end of file diff --git a/util-resources/tailwind-input.css b/util-resources/tailwind-input.css new file mode 100644 index 0000000..b5c61c9 --- /dev/null +++ b/util-resources/tailwind-input.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities;