@knowcode/doc-builder 1.7.5 → 1.7.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +24 -1
  3. package/add-user-clive.sql +35 -0
  4. package/add-user-lindsay-fixed.sql +85 -0
  5. package/add-user-lindsay.sql +68 -0
  6. package/add-user-pmorgan.sql +35 -0
  7. package/add-user-robbie.sql +35 -0
  8. package/add-wru-users.sql +105 -0
  9. package/cli.js +9 -1
  10. package/html/README.html +3 -3
  11. package/html/documentation-index.html +3 -3
  12. package/html/guides/authentication-default-change.html +3 -3
  13. package/html/guides/authentication-guide.html +3 -3
  14. package/html/guides/cache-control-anti-pattern.html +3 -3
  15. package/html/guides/claude-workflow-guide.html +3 -3
  16. package/html/guides/documentation-standards.html +3 -3
  17. package/html/guides/next-steps-walkthrough.html +3 -3
  18. package/html/guides/phosphor-icons-guide.html +3 -3
  19. package/html/guides/public-site-deployment.html +3 -3
  20. package/html/guides/search-engine-verification-guide.html +3 -3
  21. package/html/guides/seo-guide.html +3 -3
  22. package/html/guides/seo-optimization-guide.html +3 -3
  23. package/html/guides/supabase-auth-implementation-plan.html +3 -3
  24. package/html/guides/supabase-auth-integration-plan.html +3 -3
  25. package/html/guides/supabase-auth-setup-guide.html +3 -3
  26. package/html/guides/troubleshooting-guide.html +3 -3
  27. package/html/guides/vercel-deployment-auth-setup.html +3 -3
  28. package/html/guides/windows-setup-guide.html +3 -3
  29. package/html/index.html +3 -3
  30. package/html/launch/README.html +3 -3
  31. package/html/launch/bubble-plugin-specification.html +3 -3
  32. package/html/launch/go-to-market-strategy.html +3 -3
  33. package/html/launch/launch-announcements.html +3 -3
  34. package/html/sitemap.xml +29 -29
  35. package/html/vercel-cli-setup-guide.html +3 -3
  36. package/html/vercel-first-time-setup-guide.html +3 -3
  37. package/lib/config.js +22 -3
  38. package/lib/core-builder.js +91 -1
  39. package/manage-users.sql +191 -0
  40. package/package.json +1 -1
  41. package/view-all-users.sql +40 -0
  42. package/wru-auth-config.js +17 -0
  43. package/assets/js/auth.js +0 -67
package/lib/config.js CHANGED
@@ -30,7 +30,8 @@ const defaultConfig = {
30
30
  phosphorSize: '1.2em', // Relative to text size
31
31
  normalizeTitle: true, // Auto-normalize all-caps titles to title case
32
32
  showPdfDownload: true, // Show PDF download icon in header
33
- menuDefaultOpen: true // Menu/sidebar open by default
33
+ menuDefaultOpen: true, // Menu/sidebar open by default
34
+ attachments: true // Copy attachments (Excel, PDF, etc.) to output
34
35
  },
35
36
 
36
37
  // Authentication - Supabase only (basic auth removed for security)
@@ -78,7 +79,21 @@ const defaultConfig = {
78
79
  generateSitemap: true,
79
80
  generateRobotsTxt: true,
80
81
  customMetaTags: []
81
- }
82
+ },
83
+
84
+ // Attachment file types to copy
85
+ attachmentTypes: [
86
+ // Documents
87
+ '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.csv', '.ppt', '.pptx', '.txt', '.rtf',
88
+ // Archives
89
+ '.zip', '.tar', '.gz', '.7z', '.rar',
90
+ // Images
91
+ '.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.ico', '.bmp',
92
+ // Data files
93
+ '.json', '.xml', '.yaml', '.yml', '.toml',
94
+ // Other
95
+ '.mp4', '.mp3', '.wav', '.avi', '.mov'
96
+ ]
82
97
  };
83
98
 
84
99
  /**
@@ -102,7 +117,8 @@ const notionInspiredPreset = {
102
117
  phosphorSize: '1.2em',
103
118
  normalizeTitle: true,
104
119
  showPdfDownload: true,
105
- menuDefaultOpen: true
120
+ menuDefaultOpen: true,
121
+ attachments: true
106
122
  },
107
123
 
108
124
  auth: {
@@ -261,6 +277,9 @@ async function loadConfig(configPath, options = {}) {
261
277
  if (options.menuClosed === true) {
262
278
  config.features.menuDefaultOpen = false;
263
279
  }
280
+ if (options.attachments === false) {
281
+ config.features.attachments = false;
282
+ }
264
283
 
265
284
  // Legacy mode - auto-detect structure
266
285
  if (options.legacy) {
@@ -753,6 +753,62 @@ async function getAllMarkdownFiles(dir, baseDir = dir) {
753
753
  return files;
754
754
  }
755
755
 
756
+ // Get all attachment files
757
+ async function getAllAttachmentFiles(dir, baseDir = dir, attachmentTypes) {
758
+ const files = [];
759
+ const items = await fs.readdir(dir);
760
+
761
+ for (const item of items) {
762
+ const fullPath = path.join(dir, item);
763
+ const stat = await fs.stat(fullPath);
764
+
765
+ if (stat.isDirectory() && !item.startsWith('.')) {
766
+ // Recursively scan subdirectories
767
+ const subFiles = await getAllAttachmentFiles(fullPath, baseDir, attachmentTypes);
768
+ files.push(...subFiles);
769
+ } else {
770
+ // Check if file is an attachment type
771
+ const ext = path.extname(item).toLowerCase();
772
+ if (attachmentTypes.includes(ext) && !item.startsWith('.')) {
773
+ const relativePath = path.relative(baseDir, fullPath);
774
+ files.push({
775
+ path: fullPath,
776
+ relativePath,
777
+ size: stat.size
778
+ });
779
+ }
780
+ }
781
+ }
782
+
783
+ return files;
784
+ }
785
+
786
+ // Copy attachment files to output directory
787
+ async function copyAttachmentFiles(attachmentFiles, docsDir, outputDir) {
788
+ let totalSize = 0;
789
+ let copiedCount = 0;
790
+
791
+ for (const file of attachmentFiles) {
792
+ try {
793
+ const outputPath = path.join(outputDir, file.relativePath);
794
+ const outputDirPath = path.dirname(outputPath);
795
+
796
+ // Create directory if it doesn't exist
797
+ await fs.ensureDir(outputDirPath);
798
+
799
+ // Copy the file
800
+ await fs.copy(file.path, outputPath, { overwrite: true });
801
+
802
+ totalSize += file.size;
803
+ copiedCount++;
804
+ } catch (error) {
805
+ console.warn(chalk.yellow(`Warning: Could not copy ${file.relativePath}: ${error.message}`));
806
+ }
807
+ }
808
+
809
+ return { copiedCount, totalSize };
810
+ }
811
+
756
812
  // Main build function
757
813
  async function buildDocumentation(config) {
758
814
  const docsDir = path.join(process.cwd(), config.docsDir);
@@ -946,7 +1002,41 @@ async function buildDocumentation(config) {
946
1002
  }
947
1003
  }
948
1004
 
949
- console.log(chalk.green('✅ Documentation build complete!'));
1005
+ // Copy attachment files if feature is enabled
1006
+ if (config.features?.attachments !== false) {
1007
+ console.log(chalk.blue('\n📎 Processing attachments...'));
1008
+
1009
+ const attachmentTypes = config.attachmentTypes || [
1010
+ '.pdf', '.doc', '.docx', '.xls', '.xlsx', '.csv', '.ppt', '.pptx', '.txt', '.rtf',
1011
+ '.zip', '.tar', '.gz', '.7z', '.rar',
1012
+ '.png', '.jpg', '.jpeg', '.gif', '.svg', '.webp', '.ico', '.bmp',
1013
+ '.json', '.xml', '.yaml', '.yml', '.toml',
1014
+ '.mp4', '.mp3', '.wav', '.avi', '.mov'
1015
+ ];
1016
+
1017
+ try {
1018
+ const attachmentFiles = await getAllAttachmentFiles(docsDir, docsDir, attachmentTypes);
1019
+
1020
+ if (attachmentFiles.length > 0) {
1021
+ const { copiedCount, totalSize } = await copyAttachmentFiles(attachmentFiles, docsDir, outputDir);
1022
+
1023
+ // Format file size
1024
+ const formatSize = (bytes) => {
1025
+ if (bytes < 1024) return bytes + ' B';
1026
+ if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
1027
+ return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
1028
+ };
1029
+
1030
+ console.log(chalk.green(`✅ Copied ${copiedCount} attachments (${formatSize(totalSize)} total)`));
1031
+ } else {
1032
+ console.log(chalk.gray(' No attachments found to copy'));
1033
+ }
1034
+ } catch (error) {
1035
+ console.warn(chalk.yellow(`Warning: Error processing attachments: ${error.message}`));
1036
+ }
1037
+ }
1038
+
1039
+ console.log(chalk.green('\n✅ Documentation build complete!'));
950
1040
  }
951
1041
 
952
1042
  // Create placeholder README.md if missing
@@ -0,0 +1,191 @@
1
+ -- =====================================================
2
+ -- USER MANAGEMENT SQL COMMANDS FOR DOC-BUILDER
3
+ -- =====================================================
4
+ -- Run these in your Supabase SQL Editor
5
+
6
+ -- =====================================================
7
+ -- 1. VIEW YOUR SITES
8
+ -- =====================================================
9
+ -- See all your documentation sites
10
+ SELECT id, domain, name, created_at
11
+ FROM docbuilder_sites
12
+ ORDER BY created_at DESC;
13
+
14
+ -- =====================================================
15
+ -- 2. VIEW EXISTING USERS
16
+ -- =====================================================
17
+ -- See all users in your Supabase project
18
+ SELECT id, email, created_at, last_sign_in_at
19
+ FROM auth.users
20
+ ORDER BY created_at DESC;
21
+
22
+ -- =====================================================
23
+ -- 3. VIEW WHO HAS ACCESS TO A SPECIFIC SITE
24
+ -- =====================================================
25
+ -- Replace 'your-site-id' with actual site ID
26
+ SELECT
27
+ u.email,
28
+ u.id as user_id,
29
+ u.created_at as user_since,
30
+ da.created_at as access_granted,
31
+ ds.name as site_name,
32
+ ds.domain
33
+ FROM docbuilder_access da
34
+ JOIN auth.users u ON u.id = da.user_id
35
+ JOIN docbuilder_sites ds ON ds.id = da.site_id
36
+ WHERE da.site_id = 'your-site-id'
37
+ ORDER BY da.created_at DESC;
38
+
39
+ -- =====================================================
40
+ -- 4. ADD A SINGLE USER TO A SITE
41
+ -- =====================================================
42
+ -- First, create user in Supabase Dashboard (Authentication > Users > Invite)
43
+ -- Then grant access:
44
+ INSERT INTO docbuilder_access (user_id, site_id)
45
+ VALUES (
46
+ (SELECT id FROM auth.users WHERE email = 'user@example.com'),
47
+ 'your-site-id'
48
+ );
49
+
50
+ -- =====================================================
51
+ -- 5. ADD MULTIPLE USERS TO A SITE
52
+ -- =====================================================
53
+ -- Add a list of users all at once
54
+ WITH users_to_add AS (
55
+ SELECT email FROM (VALUES
56
+ ('user1@example.com'),
57
+ ('user2@example.com'),
58
+ ('user3@example.com'),
59
+ ('user4@example.com')
60
+ ) AS t(email)
61
+ )
62
+ INSERT INTO docbuilder_access (user_id, site_id)
63
+ SELECT u.id, 'your-site-id'
64
+ FROM auth.users u
65
+ JOIN users_to_add ua ON u.email = ua.email
66
+ WHERE NOT EXISTS (
67
+ -- Prevent duplicate entries
68
+ SELECT 1 FROM docbuilder_access
69
+ WHERE user_id = u.id AND site_id = 'your-site-id'
70
+ );
71
+
72
+ -- =====================================================
73
+ -- 6. GRANT USER ACCESS TO MULTIPLE SITES
74
+ -- =====================================================
75
+ -- Give one user access to several sites
76
+ WITH sites_to_grant AS (
77
+ SELECT id FROM docbuilder_sites
78
+ WHERE domain IN ('site1.com', 'site2.com', 'site3.com')
79
+ )
80
+ INSERT INTO docbuilder_access (user_id, site_id)
81
+ SELECT
82
+ (SELECT id FROM auth.users WHERE email = 'user@example.com'),
83
+ s.id
84
+ FROM sites_to_grant s
85
+ WHERE NOT EXISTS (
86
+ SELECT 1 FROM docbuilder_access
87
+ WHERE user_id = (SELECT id FROM auth.users WHERE email = 'user@example.com')
88
+ AND site_id = s.id
89
+ );
90
+
91
+ -- =====================================================
92
+ -- 7. REMOVE USER ACCESS FROM A SITE
93
+ -- =====================================================
94
+ -- Remove specific user from specific site
95
+ DELETE FROM docbuilder_access
96
+ WHERE user_id = (SELECT id FROM auth.users WHERE email = 'user@example.com')
97
+ AND site_id = 'your-site-id';
98
+
99
+ -- =====================================================
100
+ -- 8. REMOVE USER FROM ALL SITES
101
+ -- =====================================================
102
+ -- Completely remove user's access to all documentation
103
+ DELETE FROM docbuilder_access
104
+ WHERE user_id = (SELECT id FROM auth.users WHERE email = 'user@example.com');
105
+
106
+ -- =====================================================
107
+ -- 9. BULK REMOVE USERS
108
+ -- =====================================================
109
+ -- Remove multiple users from a site
110
+ WITH users_to_remove AS (
111
+ SELECT email FROM (VALUES
112
+ ('olduser1@example.com'),
113
+ ('olduser2@example.com')
114
+ ) AS t(email)
115
+ )
116
+ DELETE FROM docbuilder_access
117
+ WHERE site_id = 'your-site-id'
118
+ AND user_id IN (
119
+ SELECT u.id FROM auth.users u
120
+ JOIN users_to_remove ur ON u.email = ur.email
121
+ );
122
+
123
+ -- =====================================================
124
+ -- 10. VIEW ACCESS SUMMARY
125
+ -- =====================================================
126
+ -- See how many users each site has
127
+ SELECT
128
+ ds.name as site_name,
129
+ ds.domain,
130
+ ds.id as site_id,
131
+ COUNT(da.user_id) as user_count,
132
+ MAX(da.created_at) as last_access_granted
133
+ FROM docbuilder_sites ds
134
+ LEFT JOIN docbuilder_access da ON ds.id = da.site_id
135
+ GROUP BY ds.id, ds.name, ds.domain
136
+ ORDER BY user_count DESC;
137
+
138
+ -- =====================================================
139
+ -- 11. FIND USERS WITHOUT ACCESS TO ANY SITE
140
+ -- =====================================================
141
+ -- Useful for cleanup
142
+ SELECT u.email, u.created_at, u.last_sign_in_at
143
+ FROM auth.users u
144
+ WHERE NOT EXISTS (
145
+ SELECT 1 FROM docbuilder_access da
146
+ WHERE da.user_id = u.id
147
+ )
148
+ ORDER BY u.created_at DESC;
149
+
150
+ -- =====================================================
151
+ -- 12. AUDIT LOG - RECENT ACCESS GRANTS
152
+ -- =====================================================
153
+ -- See who was granted access recently
154
+ SELECT
155
+ u.email,
156
+ ds.name as site_name,
157
+ ds.domain,
158
+ da.created_at as access_granted
159
+ FROM docbuilder_access da
160
+ JOIN auth.users u ON u.id = da.user_id
161
+ JOIN docbuilder_sites ds ON ds.id = da.site_id
162
+ WHERE da.created_at > NOW() - INTERVAL '30 days'
163
+ ORDER BY da.created_at DESC;
164
+
165
+ -- =====================================================
166
+ -- 13. COPY ACCESS FROM ONE USER TO ANOTHER
167
+ -- =====================================================
168
+ -- Useful when replacing team members
169
+ INSERT INTO docbuilder_access (user_id, site_id)
170
+ SELECT
171
+ (SELECT id FROM auth.users WHERE email = 'newuser@example.com'),
172
+ da.site_id
173
+ FROM docbuilder_access da
174
+ WHERE da.user_id = (SELECT id FROM auth.users WHERE email = 'olduser@example.com');
175
+
176
+ -- =====================================================
177
+ -- COMMON SITE IDs FOR REFERENCE
178
+ -- =====================================================
179
+ -- Your test site: 4d8a53bf-dcdd-48c0-98e0-cd1451518735
180
+ -- Add more site IDs here as you create them:
181
+ -- Production site: xxx
182
+ -- Staging site: xxx
183
+
184
+ -- =====================================================
185
+ -- TIPS
186
+ -- =====================================================
187
+ -- 1. Always check if users exist before granting access
188
+ -- 2. Use the Supabase Dashboard to invite new users first
189
+ -- 3. Run SELECT queries before DELETE to verify
190
+ -- 4. Keep this file updated with your site IDs
191
+ -- 5. Consider creating views for common queries
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@knowcode/doc-builder",
3
- "version": "1.7.5",
3
+ "version": "1.7.6",
4
4
  "description": "Reusable documentation builder for markdown-based sites with Vercel deployment support",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -0,0 +1,40 @@
1
+ -- =====================================================
2
+ -- VIEW ALL USERS WITH ACCESS
3
+ -- Site: https://wru-bid-analysis.vercel.app/
4
+ -- =====================================================
5
+
6
+ -- See everyone who has access to the WRU site
7
+ SELECT
8
+ u.email,
9
+ u.created_at as user_created,
10
+ da.created_at as access_granted,
11
+ CASE
12
+ WHEN u.last_sign_in_at IS NULL THEN '❌ Never logged in'
13
+ ELSE '✅ Has logged in'
14
+ END as login_status,
15
+ u.last_sign_in_at
16
+ FROM docbuilder_access da
17
+ JOIN auth.users u ON u.id = da.user_id
18
+ WHERE da.site_id = '4d8a53bf-dcdd-48c0-98e0-cd1451518735'
19
+ ORDER BY da.created_at DESC;
20
+
21
+ -- Count total users with access
22
+ SELECT COUNT(*) as total_users_with_access
23
+ FROM docbuilder_access
24
+ WHERE site_id = '4d8a53bf-dcdd-48c0-98e0-cd1451518735';
25
+
26
+ -- Check which of the 4 users exist in Supabase
27
+ SELECT
28
+ email,
29
+ CASE
30
+ WHEN id IS NOT NULL THEN '✅ User exists'
31
+ ELSE '❌ Not created yet'
32
+ END as status
33
+ FROM (
34
+ VALUES
35
+ ('lindsay@knowcode.tech'),
36
+ ('pmorgan@wru.cymru'),
37
+ ('clive@hyperforma.co.uk'),
38
+ ('robbie.macintosh@marbledropper.com')
39
+ ) AS emails(email)
40
+ LEFT JOIN auth.users u ON u.email = emails.email;
@@ -0,0 +1,17 @@
1
+ // doc-builder.config.js - Configuration with Supabase Authentication
2
+ module.exports = {
3
+ siteName: 'WRU Bid Analysis Documentation',
4
+ siteDescription: 'Analysis and documentation for WRU bidding process',
5
+
6
+ // Enable Supabase authentication
7
+ features: {
8
+ authentication: 'supabase' // This turns on auth (set to false for public)
9
+ },
10
+
11
+ // Supabase configuration
12
+ auth: {
13
+ supabaseUrl: 'https://xcihhnfcitjrwbynxmka.supabase.co',
14
+ supabaseAnonKey: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InhjaWhobmZjaXRqcndieW54bWthIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTM0Mzc2MzcsImV4cCI6MjA2OTAxMzYzN30.zvWp3JFIR8fBIiwuFF5gqOR_Kxb42baZS5fsBz60XOY',
15
+ siteId: '4d8a53bf-dcdd-48c0-98e0-cd1451518735'
16
+ }
17
+ };
package/assets/js/auth.js DELETED
@@ -1,67 +0,0 @@
1
- /**
2
- * Simple Client-Side Authentication for Documentation
3
- * This runs on every page load to check authentication
4
- *
5
- * IMPORTANT: This is a basic authentication system suitable for
6
- * protecting documentation from casual access. For production
7
- * use with sensitive data, implement server-side authentication.
8
- */
9
-
10
- (function() {
11
- 'use strict';
12
-
13
- // Skip auth check on login and logout pages
14
- const currentPage = window.location.pathname;
15
- if (currentPage === '/login.html' || currentPage === '/logout.html' || currentPage.includes('login') || currentPage.includes('logout')) {
16
- return;
17
- }
18
-
19
- // Check if user is authenticated
20
- function isAuthenticated() {
21
- const authToken = getCookie('doc-auth');
22
- if (!authToken) return false;
23
-
24
- try {
25
- // Simple token validation - just check if it exists and has expected format
26
- // The actual validation happens server-side (or in login page for static sites)
27
- const decoded = atob(authToken);
28
- return decoded && decoded.includes(':');
29
- } catch (error) {
30
- return false;
31
- }
32
- }
33
-
34
- // Get cookie value
35
- function getCookie(name) {
36
- const value = `; ${document.cookie}`;
37
- const parts = value.split(`; ${name}=`);
38
- if (parts.length === 2) return parts.pop().split(';').shift();
39
- return null;
40
- }
41
-
42
- // Redirect to login if not authenticated
43
- function redirectToLogin() {
44
- const currentUrl = window.location.pathname + window.location.search;
45
- const loginUrl = '/login.html' + (currentUrl !== '/' ? '?redirect=' + encodeURIComponent(currentUrl) : '');
46
- window.location.href = loginUrl;
47
- }
48
-
49
- // Check authentication on page load
50
- if (!isAuthenticated()) {
51
- redirectToLogin();
52
- }
53
-
54
- // Add logout functionality to logout buttons
55
- document.addEventListener('DOMContentLoaded', function() {
56
- const logoutLinks = document.querySelectorAll('a[href*="logout"]');
57
- logoutLinks.forEach(link => {
58
- link.addEventListener('click', function(e) {
59
- e.preventDefault();
60
- // Clear auth cookie
61
- document.cookie = 'doc-auth=; path=/; expires=Thu, 01 Jan 1970 00:00:01 GMT;';
62
- window.location.href = '/logout.html';
63
- });
64
- });
65
- });
66
-
67
- })();