@tdocs/tdocs 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.
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.compileMarkdown = compileMarkdown;
7
+ const marked_1 = require("marked");
8
+ const marked_highlight_1 = require("marked-highlight");
9
+ const highlight_js_1 = __importDefault(require("highlight.js"));
10
+ marked_1.marked.use((0, marked_highlight_1.markedHighlight)({
11
+ langPrefix: 'hljs language-',
12
+ highlight(code, lang) {
13
+ const language = highlight_js_1.default.getLanguage(lang) ? lang : 'plaintext';
14
+ return highlight_js_1.default.highlight(code, { language }).value;
15
+ }
16
+ }));
17
+ // Phase 2: Markdown Compilation
18
+ // This module will iterate through the AST and transform raw Markdown strings into valid HTML strings.
19
+ function compileMarkdown(ast) {
20
+ // Recursively compile markdown content to html
21
+ function compileNode(node) {
22
+ if (node.markdownContent) {
23
+ node.htmlContent = marked_1.marked.parse(node.markdownContent);
24
+ }
25
+ else {
26
+ node.htmlContent = '';
27
+ }
28
+ if (node.children && node.children.length > 0) {
29
+ node.children.forEach(compileNode);
30
+ }
31
+ }
32
+ ast.tree.forEach(compileNode);
33
+ return ast; // Return mutablility-modified AST
34
+ }
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.generateStaticSite = generateStaticSite;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const ejs_1 = __importDefault(require("ejs"));
10
+ // Phase 3: Static Site Generation (Rendering)
11
+ // This module will take the compiled AST and inject it into UI templates.
12
+ async function generateStaticSite(ast, destDir = './dist') {
13
+ // 1. Ensure dest exists
14
+ if (!fs_1.default.existsSync(destDir)) {
15
+ fs_1.default.mkdirSync(destDir, { recursive: true });
16
+ }
17
+ // 2. Read template and render
18
+ const templatePath = path_1.default.join(__dirname, '../templates/layout.ejs');
19
+ const templateStr = fs_1.default.readFileSync(templatePath, 'utf8');
20
+ const htmlOutput = ejs_1.default.render(templateStr, {
21
+ config: ast.config,
22
+ tree: ast.tree,
23
+ serializeTree: (nodes) => JSON.stringify(nodes) // Pass the AST to the client to render content dynamically
24
+ });
25
+ // 3. Write index.html
26
+ fs_1.default.writeFileSync(path_1.default.join(destDir, 'index.html'), htmlOutput);
27
+ // 4. Copy CSS
28
+ const cssPath = path_1.default.join(__dirname, '../templates/style.css');
29
+ if (fs_1.default.existsSync(cssPath)) {
30
+ fs_1.default.copyFileSync(cssPath, path_1.default.join(destDir, 'style.css'));
31
+ }
32
+ // 5. Copy user assets if specified
33
+ if (ast.config && ast.config.assets) {
34
+ const assetsSrcPath = path_1.default.resolve(ast.config.assets);
35
+ if (fs_1.default.existsSync(assetsSrcPath)) {
36
+ const assetsDestPath = path_1.default.join(destDir, path_1.default.basename(assetsSrcPath));
37
+ fs_1.default.cpSync(assetsSrcPath, assetsDestPath, { recursive: true });
38
+ console.log(`Copied assets from ${assetsSrcPath} to ${assetsDestPath}`);
39
+ }
40
+ else {
41
+ console.warn(`Asset folder not found: ${assetsSrcPath}`);
42
+ }
43
+ }
44
+ console.log(`Generated documentation at ${destDir}`);
45
+ }
package/build/index.js ADDED
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const commander_1 = require("commander");
8
+ const chokidar_1 = __importDefault(require("chokidar"));
9
+ const parser_1 = require("./parser");
10
+ const compiler_1 = require("./compiler");
11
+ const generator_1 = require("./generator");
12
+ const program = new commander_1.Command();
13
+ program
14
+ .name('tdoc')
15
+ .description('A custom Static Site Generator for rapid documentation prototyping.')
16
+ .version('1.0.0');
17
+ async function build(file, outDir = './dist') {
18
+ try {
19
+ const ast = (0, parser_1.parseTDoc)(file);
20
+ const compiledAst = (0, compiler_1.compileMarkdown)(ast);
21
+ await (0, generator_1.generateStaticSite)(compiledAst, outDir);
22
+ }
23
+ catch (error) {
24
+ console.error('Error during build:', error);
25
+ }
26
+ }
27
+ program
28
+ .command('build')
29
+ .description('Compile a .tdoc file into a static documentation site')
30
+ .argument('<file>', 'The .tdoc file to compile')
31
+ .option('-o, --out <dir>', 'Output directory', './dist')
32
+ .action((file, options) => {
33
+ console.log(`Building site from ${file}...`);
34
+ build(file, options.out);
35
+ });
36
+ const browser_sync_1 = __importDefault(require("browser-sync"));
37
+ // ... (in watch command)
38
+ program
39
+ .command('watch')
40
+ .description('Watch for changes on a .tdoc file and hot-reload')
41
+ .argument('<file>', 'The .tdoc file to watch')
42
+ .option('-o, --out <dir>', 'Output directory', './dist')
43
+ .action(async (file, options) => {
44
+ console.log(`Watching ${file} for changes...`);
45
+ // Initial build
46
+ await build(file, options.out);
47
+ // Initialize BrowserSync
48
+ const bsInstance = browser_sync_1.default.create();
49
+ bsInstance.init({
50
+ server: {
51
+ baseDir: options.out
52
+ },
53
+ open: true,
54
+ notify: false
55
+ });
56
+ chokidar_1.default.watch(file).on('change', async () => {
57
+ console.log(`\nFile ${file} changed. Rebuilding...`);
58
+ await build(file, options.out);
59
+ bsInstance.reload();
60
+ });
61
+ });
62
+ program.parse();
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.parseTDoc = parseTDoc;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ // The config is simple key-value pairs, let's parse it manually.
9
+ function parseTDoc(filePath) {
10
+ const fileContent = fs_1.default.readFileSync(filePath, 'utf-8');
11
+ let config = {};
12
+ let rawBody = fileContent;
13
+ if (fileContent.startsWith('@config')) {
14
+ const endConfigIdx = fileContent.indexOf('---');
15
+ if (endConfigIdx !== -1) {
16
+ const configBlock = fileContent.substring('@config'.length, endConfigIdx).trim();
17
+ rawBody = fileContent.substring(endConfigIdx + 3).trim();
18
+ // Parse key: value
19
+ const configLines = configBlock.split('\n');
20
+ for (const line of configLines) {
21
+ const colonIdx = line.indexOf(':');
22
+ if (colonIdx !== -1) {
23
+ const key = line.substring(0, colonIdx).trim();
24
+ let val = line.substring(colonIdx + 1).trim();
25
+ // remove surrounding quotes if any
26
+ if (val.startsWith('"') && val.endsWith('"'))
27
+ val = val.substring(1, val.length - 1);
28
+ if (val.startsWith("'") && val.endsWith("'"))
29
+ val = val.substring(1, val.length - 1);
30
+ config[key] = val;
31
+ }
32
+ }
33
+ }
34
+ }
35
+ // 2. Parse structural markers and markdown content
36
+ const tree = buildTree(rawBody);
37
+ return {
38
+ config,
39
+ tree
40
+ };
41
+ }
42
+ function buildTree(content) {
43
+ const lines = content.split('\n');
44
+ const rootNodes = [];
45
+ const stack = []; // Stack to keep track of parent hierarchy based on indentation
46
+ let currentNode = null;
47
+ const markerRegex = /^(\s*)\[(.*?)\]\s*$/;
48
+ for (let i = 0; i < lines.length; i++) {
49
+ const line = lines[i];
50
+ const match = line.match(markerRegex);
51
+ if (match) {
52
+ // It's a structural marker
53
+ const indentStr = match[1];
54
+ const title = match[2];
55
+ const level = indentStr.length;
56
+ const newNode = {
57
+ id: title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, ''),
58
+ title: title,
59
+ markdownContent: '',
60
+ children: [],
61
+ level: level
62
+ };
63
+ // Determine parent based on indentation level
64
+ while (stack.length > 0 && stack[stack.length - 1].level >= level) {
65
+ stack.pop(); // Pop nodes that are siblings or deeper
66
+ }
67
+ if (stack.length > 0) {
68
+ // We found a parent
69
+ stack[stack.length - 1].children.push(newNode);
70
+ }
71
+ else {
72
+ // It's a root node
73
+ rootNodes.push(newNode);
74
+ }
75
+ // Make this the current node to append markdown to and push to stack
76
+ currentNode = newNode;
77
+ stack.push(currentNode);
78
+ }
79
+ else {
80
+ // It's markdown content belonging to the current node
81
+ if (currentNode) {
82
+ // Avoid adding extraneous leading empty lines if content is just starting
83
+ if (currentNode.markdownContent === '' && line.trim() === '') {
84
+ continue;
85
+ }
86
+ currentNode.markdownContent += line + '\n';
87
+ }
88
+ }
89
+ }
90
+ // Clean up trailing whitespace on markdown content
91
+ const cleanMarkdown = (nodes) => {
92
+ for (const node of nodes) {
93
+ node.markdownContent = node.markdownContent.trim();
94
+ cleanMarkdown(node.children);
95
+ }
96
+ };
97
+ cleanMarkdown(rootNodes);
98
+ return rootNodes;
99
+ }
package/build/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ // Central location for AST Node interfaces
3
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@tdocs/tdocs",
3
+ "version": "1.0.0",
4
+ "description": "Custom Static Site Generator for rapid documentation prototyping",
5
+ "main": "./build/index.js",
6
+ "bin": {
7
+ "tdoc": "./build/index.js"
8
+ },
9
+ "files": [
10
+ "build/**/*",
11
+ "templates/**/*"
12
+ ],
13
+ "scripts": {
14
+ "build:cli": "tsc",
15
+ "test": "echo \"Error: no test specified\" && exit 1"
16
+ },
17
+ "dependencies": {
18
+ "browser-sync": "^3.0.4",
19
+ "chokidar": "^5.0.0",
20
+ "commander": "^14.0.3",
21
+ "ejs": "^5.0.1",
22
+ "highlight.js": "^11.11.1",
23
+ "marked": "^17.0.4",
24
+ "marked-highlight": "^2.2.3"
25
+ },
26
+ "keywords": [],
27
+ "author": "",
28
+ "license": "ISC",
29
+ "devDependencies": {
30
+ "@types/browser-sync": "^2.29.1",
31
+ "@types/ejs": "^3.1.5",
32
+ "@types/marked": "^5.0.2",
33
+ "@types/node": "^25.5.0",
34
+ "gray-matter": "^4.0.3",
35
+ "ts-node": "^10.9.2",
36
+ "typescript": "^5.9.3"
37
+ }
38
+ }
@@ -0,0 +1,73 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title><%= config.title || 'Documentation' %></title>
7
+ <link rel="stylesheet" href="style.css">
8
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
9
+ <style>
10
+ .nav-item { margin-left: var(--indent, 0px); padding: 5px 0; cursor: pointer; color: var(--accent-color); }
11
+ .nav-item:hover { text-decoration: underline; }
12
+ .nav-item.active { font-weight: bold; color: var(--text-color); }
13
+ </style>
14
+ </head>
15
+ <body>
16
+ <header>
17
+ <h1><%= config.header || 'TDoc Site' %></h1>
18
+ </header>
19
+
20
+ <div class="layout">
21
+ <aside class="sidebar" id="sidebar">
22
+ <!-- Sidebar navigation populated by JS -->
23
+ </aside>
24
+
25
+ <main class="content" id="content-area">
26
+ <!-- Main documentation content will go here -->
27
+ </main>
28
+ </div>
29
+
30
+ <footer>
31
+ <p><%= config.footer || 'Generated with TDoc' %></p>
32
+ </footer>
33
+
34
+ <script>
35
+ const tree = <%- serializeTree(tree) %>;
36
+
37
+ const sidebar = document.getElementById('sidebar');
38
+ const contentArea = document.getElementById('content-area');
39
+
40
+ // Flatten tree to a map for easy lookup
41
+ const nodeMap = {};
42
+
43
+ function renderSidebar(nodes, parentEl) {
44
+ nodes.forEach(node => {
45
+ nodeMap[node.id] = node;
46
+
47
+ const div = document.createElement('div');
48
+ div.className = 'nav-item';
49
+ div.style.setProperty('--indent', `${node.level * 15}px`);
50
+ div.textContent = node.title;
51
+ div.onclick = () => {
52
+ document.querySelectorAll('.nav-item').forEach(el => el.classList.remove('active'));
53
+ div.classList.add('active');
54
+ contentArea.innerHTML = node.htmlContent;
55
+ };
56
+
57
+ parentEl.appendChild(div);
58
+
59
+ if (node.children && node.children.length > 0) {
60
+ renderSidebar(node.children, parentEl);
61
+ }
62
+ });
63
+ }
64
+
65
+ renderSidebar(tree, sidebar);
66
+
67
+ // Render the first item by default if exists
68
+ if (tree.length > 0) {
69
+ sidebar.firstChild.click();
70
+ }
71
+ </script>
72
+ </body>
73
+ </html>
@@ -0,0 +1,51 @@
1
+ :root {
2
+ --bg-color: #ffffff;
3
+ --text-color: #333333;
4
+ --sidebar-bg: #f5f5f5;
5
+ --header-bg: #1a1a1a;
6
+ --header-text: #ffffff;
7
+ --accent-color: #0066cc;
8
+ }
9
+
10
+ body {
11
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
12
+ margin: 0;
13
+ padding: 0;
14
+ background-color: var(--bg-color);
15
+ color: var(--text-color);
16
+ display: flex;
17
+ flex-direction: column;
18
+ min-height: 100vh;
19
+ }
20
+
21
+ header {
22
+ background-color: var(--header-bg);
23
+ color: var(--header-text);
24
+ padding: 1rem 2rem;
25
+ }
26
+
27
+ .layout {
28
+ display: flex;
29
+ flex: 1;
30
+ }
31
+
32
+ .sidebar {
33
+ width: 250px;
34
+ background-color: var(--sidebar-bg);
35
+ padding: 2rem 1rem;
36
+ border-right: 1px solid #e0e0e0;
37
+ }
38
+
39
+ .content {
40
+ flex: 1;
41
+ padding: 2rem 4rem;
42
+ max-width: 900px;
43
+ }
44
+
45
+ footer {
46
+ padding: 1rem 2rem;
47
+ text-align: center;
48
+ border-top: 1px solid #e0e0e0;
49
+ font-size: 0.9rem;
50
+ color: #666;
51
+ }