Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ on:
- main
- master
workflow_dispatch:
# Daily rebuild so the n8n-nodes-devdeskapp version baked into the pages follows
# new npm releases even when the docs themselves did not change.
schedule:
- cron: '30 3 * * *'

permissions:
contents: read
Expand Down Expand Up @@ -54,7 +58,8 @@ jobs:
path: .vitepress/dist

deploy:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
# push / manual run / daily schedule on main (scheduled runs always use the default branch, main).
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/main'
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
Expand Down
26 changes: 21 additions & 5 deletions .vitepress/config.mts
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
import { defineConfig } from 'vitepress'
import { type DefaultTheme, defineConfigWithTheme } from 'vitepress'
import { resolveN8nNodeVersion } from './n8n-node-version'
import { N8N_NODE_VERSIONS_URL } from './theme/n8n-node'

export default defineConfig({
// Latest n8n-nodes-devdeskapp release, baked into every page at build time (the
// <NodeVersion /> component also refreshes it in the browser). The deploy workflow
// rebuilds daily, so the nav label follows new npm releases without a docs commit.
const n8nNode = await resolveN8nNodeVersion()
console.log(`n8n-nodes-devdeskapp version: ${n8nNode.version} (${n8nNode.source})`)

const OG_IMAGE = 'https://docs.devdeskapp.com/og-docs.jpg'

export default defineConfigWithTheme<DefaultTheme.Config & { n8nNodeVersion: string }>({
srcDir: 'src',
// Files in src/public are static assets (auth.md, llms.txt...). Without this,
// VitePress also renders public/auth.md as an indexable /public/auth page.
srcExclude: ['public/**'],
title: 'DevDeskApp Docs',
description: 'Official Documentation for DevDeskApp suite, developer utilities, and n8n-nodes-devdeskapp community node.',
cleanUrls: true,
// Git commit time of each page: shown as "Last updated" and emitted as <lastmod> in sitemap.xml.
lastUpdated: true,
sitemap: {
hostname: 'https://docs.devdeskapp.com'
},
Expand Down Expand Up @@ -42,12 +54,15 @@ export default defineConfig({
['meta', { property: 'og:site_name', content: 'DevDeskApp Docs' }],
['meta', { property: 'og:type', content: 'website' }],
['meta', { property: 'og:locale', content: 'en_US' }],
['meta', { property: 'og:image', content: 'https://docs.devdeskapp.com/logo-dark.jpg' }],
// 1200x630 social card (the old logo-dark.jpg was 120x120 but declared 1200x630).
['meta', { property: 'og:image', content: OG_IMAGE }],
['meta', { property: 'og:image:width', content: '1200' }],
['meta', { property: 'og:image:height', content: '630' }],
['meta', { property: 'og:image:type', content: 'image/jpeg' }],
['meta', { property: 'og:image:alt', content: 'DevDeskApp Docs: n8n community node and developer utilities' }],
['meta', { name: 'twitter:card', content: 'summary_large_image' }],
['meta', { name: 'twitter:site', content: '@devdeskapp' }],
['meta', { name: 'twitter:image', content: 'https://docs.devdeskapp.com/logo-dark.jpg' }],
['meta', { name: 'twitter:image', content: OG_IMAGE }],
[
'script',
{ type: 'application/ld+json' },
Expand Down Expand Up @@ -78,6 +93,7 @@ export default defineConfig({
]
],
themeConfig: {
n8nNodeVersion: n8nNode.version,
logo: {
light: '/logo-light.jpg',
dark: '/logo-dark.jpg'
Expand All @@ -94,7 +110,7 @@ export default defineConfig({
{ text: '⚙️ Operations Reference', link: '/n8n/operations' },
{ text: '📋 Templates Catalog', link: '/templates/' },
{ text: '🏛️ Architecture & Solutions', link: '/solutions/' },
{ text: '📦 NPM Package (v0.1.4)', link: 'https://www.npmjs.com/package/n8n-nodes-devdeskapp' },
{ text: `📦 NPM Package (v${n8nNode.version})`, link: N8N_NODE_VERSIONS_URL },
{ text: '🌟 n8n Template #21017', link: 'https://n8n.io/workflows/21017-format-json-and-secure-hr-payloads-with-devdeskapp-utilities' },
{ text: '💰 n8n Template #21130', link: 'https://n8n.io/workflows/21130-automate-salary-and-in-hand-ctc-calculations-with-devdeskapp' },
{ text: '🛡️ n8n Template #21376', link: 'https://n8n.io/workflows/21376' },
Expand Down
17 changes: 17 additions & 0 deletions .vitepress/n8n-node-version.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Build-time n8n-nodes-devdeskapp version for the docs (Node only, used by config.mts).
// Order: npm registry -> ../n8n-nodes-devdeskapp/package.json (local dev) -> pinned fallback.
import fs from 'node:fs'
import path from 'node:path'
import { N8N_NODE_FALLBACK_VERSION, fetchLatestN8nNodeVersion, validVersion } from './theme/n8n-node'

export async function resolveN8nNodeVersion(): Promise<{ version: string; source: 'npm' | 'local' | 'fallback' }> {
const fromNpm = await fetchLatestN8nNodeVersion()
if (fromNpm) return { version: fromNpm, source: 'npm' }
try {
// process.cwd() is the docs repo root when running `vitepress build`.
const pkg = JSON.parse(fs.readFileSync(path.resolve(process.cwd(), '..', 'n8n-nodes-devdeskapp', 'package.json'), 'utf8'))
const local = validVersion(pkg.version)
if (local) return { version: local, source: 'local' }
} catch {}
return { version: N8N_NODE_FALLBACK_VERSION, source: 'fallback' }
}
22 changes: 4 additions & 18 deletions .vitepress/theme/components/HeroShowcase.vue
Original file line number Diff line number Diff line change
@@ -1,28 +1,14 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { ref } from 'vue'
import NodeVersion from './NodeVersion.vue'

const activeTab = ref<'json' | 'crypto' | 'ctc'>('json')
const nodeVersion = ref('v0.1.4')

const tabs = [
{ id: 'json', label: '⚡ JSON Prettify', icon: '{ }' },
{ id: 'crypto', label: '🔐 SHA-256 Hash', icon: '#' },
{ id: 'ctc', label: '💼 CTC Calculator', icon: '₹' }
]

onMounted(async () => {
try {
const res = await fetch('https://registry.npmjs.org/n8n-nodes-devdeskapp/latest')
if (res.ok) {
const data = await res.json()
if (data && data.version) {
nodeVersion.value = `v${data.version}`
}
}
} catch {
// Keep default v0.1.4 on network error or offline
}
})
</script>

<template>
Expand All @@ -45,8 +31,8 @@ onMounted(async () => {
<span class="pulse-ping-ring"></span>
<span class="pulse-ping-dot"></span>
</span>
<span class="badge-text-full">⚡ 330+ Tools • Official n8n Community Node • {{ nodeVersion }} Live</span>
<span class="badge-text-short">⚡ 330+ Tools • n8n Node {{ nodeVersion }} Live</span>
<span class="badge-text-full">⚡ 330+ Tools • Official n8n Community Node • <NodeVersion /> Live</span>
<span class="badge-text-short">⚡ 330+ Tools • n8n Node <NodeVersion /> Live</span>
</div>

<!-- Main Headline with DevDeskApp Gradient Shimmer -->
Expand Down
14 changes: 14 additions & 0 deletions .vitepress/theme/components/NodeVersion.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<script setup lang="ts">
import { onMounted } from 'vue'
import { useData } from 'vitepress'
import { scheduleN8nNodeVersionRefresh, useN8nNodeVersion } from '../n8n-node'

// Latest n8n-nodes-devdeskapp version: baked in at build time, refreshed from npm in the browser.
const { theme } = useData()
const version = useN8nNodeVersion(theme.value.n8nNodeVersion)
onMounted(scheduleN8nNodeVersionRefresh)
</script>

<template>
<span class="n8n-node-version">v{{ version }}</span>
</template>
24 changes: 3 additions & 21 deletions .vitepress/theme/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import DefaultTheme from 'vitepress/theme'
import './custom.css'
import HeroShowcase from './components/HeroShowcase.vue'
import QuickNavGrid from './components/QuickNavGrid.vue'
import NodeVersion from './components/NodeVersion.vue'
import { onMounted, watch, nextTick } from 'vue'
import { useRoute } from 'vitepress'
import mediumZoom from 'medium-zoom'
Expand All @@ -20,40 +21,21 @@ export default {
})
}

const syncNpmVersion = async () => {
try {
const res = await fetch('https://registry.npmjs.org/n8n-nodes-devdeskapp/latest')
if (res.ok) {
const data = await res.json()
if (data && data.version) {
const ver = `v${data.version}`
document.querySelectorAll('.VPNavBarMenuLink, .VPFlyout button, .VPMenuLink, .badge-live').forEach(el => {
if (el.textContent && el.textContent.includes('(Latest)')) {
el.textContent = `${ver} (Latest)`
}
})
}
}
} catch {
// Fallback silently
}
}

onMounted(() => {
initZoom()
syncNpmVersion()
})

watch(
() => route.path,
() => nextTick(() => {
initZoom()
syncNpmVersion()
})
)
},
enhanceApp({ app }) {
app.component('HeroShowcase', HeroShowcase)
app.component('QuickNavGrid', QuickNavGrid)
// <NodeVersion /> in Markdown: latest n8n-nodes-devdeskapp version (build-time + live refresh).
app.component('NodeVersion', NodeVersion)
}
}
90 changes: 90 additions & 0 deletions .vitepress/theme/n8n-node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// n8n-nodes-devdeskapp version for the docs (browser-safe; also imported by the build).
//
// The build bakes the latest npm version into the HTML (.vitepress/n8n-node-version.ts),
// then <NodeVersion /> refreshes it in the browser so a new release shows up before the
// next docs deploy. The refresh runs at most once per CACHE_TTL_MS and only moves forward.
import { ref } from 'vue'

export const N8N_NODE_PACKAGE = 'n8n-nodes-devdeskapp'
export const N8N_NODE_NPM_URL = `https://www.npmjs.com/package/${N8N_NODE_PACKAGE}`
export const N8N_NODE_VERSIONS_URL = `${N8N_NODE_NPM_URL}?activeTab=versions`
export const N8N_NODE_REGISTRY_LATEST_URL = `https://registry.npmjs.org/${N8N_NODE_PACKAGE}/latest`
/** Used only when the registry and the sibling repo are both unavailable at build time. */
export const N8N_NODE_FALLBACK_VERSION = '0.1.6'

const SEMVER = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/

export function validVersion(value: unknown): string | null {
return typeof value === 'string' && SEMVER.test(value.trim()) ? value.trim() : null
}

/** Semver precedence: negative if a < b, 0 if equal, positive if a > b. */
export function compareVersions(a: string, b: string): number {
const pa = SEMVER.exec(a)
const pb = SEMVER.exec(b)
if (!pa || !pb) return 0
for (let i = 1; i <= 3; i++) {
const diff = Number(pa[i]) - Number(pb[i])
if (diff !== 0) return diff
}
if (!pa[4] !== !pb[4]) return pa[4] ? -1 : 1
return (pa[4] || '').localeCompare(pb[4] || '', 'en', { numeric: true })
}

export async function fetchLatestN8nNodeVersion(timeoutMs = 5000): Promise<string | null> {
const controller = new AbortController()
const timer = setTimeout(() => controller.abort(), timeoutMs)
try {
const res = await fetch(N8N_NODE_REGISTRY_LATEST_URL, { signal: controller.signal })
if (!res.ok) return null
const body = await res.json()
return validVersion(body?.version)
} catch {
return null
} finally {
clearTimeout(timer)
}
}

// --- browser refresh ------------------------------------------------------------------
const CACHE_KEY = 'devdesk-docs:n8n-node-version'
const CACHE_TTL_MS = 6 * 60 * 60 * 1000
const version = ref('')
let refreshStarted = false

function apply(candidate: string | null) {
if (candidate && (!version.value || compareVersions(candidate, version.value) > 0)) {
version.value = candidate
}
}

async function refresh() {
try {
const cached = JSON.parse(localStorage.getItem(CACHE_KEY) || 'null')
if (cached && Date.now() - cached.at < CACHE_TTL_MS && validVersion(cached.version)) {
apply(cached.version)
return
}
} catch {}
const latest = await fetchLatestN8nNodeVersion()
if (!latest) return
apply(latest)
try {
localStorage.setItem(CACHE_KEY, JSON.stringify({ version: latest, at: Date.now() }))
} catch {}
}

/** Reactive version: starts at the build-time value, refreshed from npm once mounted. */
export function useN8nNodeVersion(buildVersion: string) {
apply(validVersion(buildVersion))
return version
}

/** Call from onMounted (client only). */
export function scheduleN8nNodeVersionRefresh() {
if (refreshStarted || typeof window === 'undefined') return
refreshStarted = true
const idle = (window as Window & { requestIdleCallback?: (cb: () => void, opts?: { timeout: number }) => number }).requestIdleCallback
if (idle) idle(() => { refresh() }, { timeout: 4000 })
else setTimeout(() => { refresh() }, 2000)
}
4 changes: 2 additions & 2 deletions src/n8n/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ description: 'Get started with n8n-nodes-devdeskapp: what the community node doe
`n8n-nodes-devdeskapp` is an official community node for [n8n](https://n8n.io) that brings high-performance, zero-latency developer utilities, formatters, and financial calculation engines directly into your automation pipelines.

<div style="margin: 1.5rem 0; display: flex; gap: 8px; flex-wrap: wrap;">
<span class="badge-live">Version v0.1.4 (Latest)</span>
<span class="badge-live">Version <NodeVersion /> (Latest)</span>
<span class="badge-live" style="background: rgba(139, 92, 246, 0.15); color: #c084fc; border-color: rgba(139, 92, 246, 0.3);">Official n8n Template #21017</span>
<span class="badge-live" style="background: rgba(59, 130, 246, 0.15); color: #60a5fa; border-color: rgba(59, 130, 246, 0.3);">MIT Open Source</span>
</div>
Expand Down Expand Up @@ -56,7 +56,7 @@ Easily switch between resources and operations using intuitive dropdowns and dyn

---

## 🛠️ Complete Operations Matrix (v0.1.4)
## 🛠️ Complete Operations Matrix

| Category | Operation | Description |
| :--- | :--- | :--- |
Expand Down
2 changes: 2 additions & 0 deletions src/n8n/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ description: 'Install n8n-nodes-devdeskapp step by step from the n8n Community N

You can install `n8n-nodes-devdeskapp` into your n8n instance using the graphical Community Nodes manager or via container/npm deployment scripts.

**Latest release:** <NodeVersion /> ([all versions on npm](https://www.npmjs.com/package/n8n-nodes-devdeskapp?activeTab=versions)). Installing without a version number always gets the latest one.

---

## Option 1: Install via n8n Web UI (Recommended)
Expand Down
Binary file added src/public/og-docs.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading