@dytsou/calendar-build 1.1.1 → 2.0.1

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.
package/package.json CHANGED
@@ -1,19 +1,20 @@
1
1
  {
2
2
  "name": "@dytsou/calendar-build",
3
- "version": "1.1.1",
3
+ "version": "2.0.1",
4
4
  "description": "Build script for calendar HTML from template and environment variables",
5
- "main": "build.js",
5
+ "main": "scripts/build.js",
6
6
  "bin": {
7
- "calendar-build": "build.js"
7
+ "calendar-build": "scripts/build.js"
8
8
  },
9
9
  "files": [
10
- "build.js",
10
+ "scripts/build.js",
11
+ "scripts/encrypt-urls.js",
12
+ "worker.js",
13
+ "wrangler.toml.example",
11
14
  "index.html.template",
12
- "README.md"
15
+ "README.md",
16
+ "LICENSE"
13
17
  ],
14
- "scripts": {
15
- "build": "node build.js"
16
- },
17
18
  "keywords": [
18
19
  "calendar",
19
20
  "build",
@@ -31,5 +32,19 @@
31
32
  "homepage": "https://github.com/dytsou/cal#readme",
32
33
  "engines": {
33
34
  "node": ">=12.0.0"
35
+ },
36
+ "devDependencies": {
37
+ "browserify": "^17.0.1",
38
+ "prettier": "^3.7.4",
39
+ "wrangler": "4.54.0"
40
+ },
41
+ "dependencies": {
42
+ "fernet": "^0.3.3",
43
+ "glob": "13.0.0"
44
+ },
45
+ "scripts": {
46
+ "build": "node scripts/build.js",
47
+ "format": "prettier --write .",
48
+ "format:check": "prettier --check ."
34
49
  }
35
50
  }
@@ -0,0 +1,192 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const Fernet = require('fernet');
6
+ const { execSync } = require('child_process');
7
+
8
+ // Read template HTML
9
+ const templatePath = path.join(__dirname, '..', 'index.html.template');
10
+ if (!fs.existsSync(templatePath)) {
11
+ console.error('Error: index.html.template not found');
12
+ process.exit(1);
13
+ }
14
+
15
+ let html = fs.readFileSync(templatePath, 'utf-8');
16
+
17
+ // Always check for placeholders that need replacement
18
+ const hasPlaceholders = html.includes('{{CALENDAR_SOURCES}}') || html.includes('{{CALENDAR_URLS}}');
19
+ const needsWorkerUrl = html.includes('{{WORKER_URL}}');
20
+ let workerUrl = '';
21
+
22
+ // Always read .env for WORKER_URL if needed
23
+ if (needsWorkerUrl || hasPlaceholders) {
24
+ const envPath = path.join(__dirname, '..', '.env');
25
+ if (hasPlaceholders && !fs.existsSync(envPath)) {
26
+ console.error('Error: .env file not found (required for placeholders)');
27
+ process.exit(1);
28
+ }
29
+
30
+ if (fs.existsSync(envPath)) {
31
+ const envContent = fs.readFileSync(envPath, 'utf-8');
32
+ const envLines = envContent.split('\n');
33
+
34
+ // Parse environment variables
35
+ let calendarSources = [];
36
+ let calendarUrls = [];
37
+
38
+ for (const line of envLines) {
39
+ if (line.startsWith('CALENDAR_SOURCES=')) {
40
+ const value = line.substring('CALENDAR_SOURCES='.length).trim();
41
+ calendarSources = value
42
+ .split(',')
43
+ .map(s => s.trim())
44
+ .filter(s => s);
45
+ } else if (line.startsWith('CALENDAR_URL=')) {
46
+ const value = line.substring('CALENDAR_URL='.length).trim();
47
+ // Support both single URL and comma-separated multiple URLs
48
+ const urls = value
49
+ .split(',')
50
+ .map(s => s.trim())
51
+ .filter(s => s);
52
+ calendarUrls.push(...urls);
53
+ } else if (line.startsWith('WORKER_URL=')) {
54
+ workerUrl = line.substring('WORKER_URL='.length).trim();
55
+ }
56
+ }
57
+ }
58
+ }
59
+
60
+ if (hasPlaceholders) {
61
+
62
+ // Replace CALENDAR_SOURCES placeholder
63
+ if (html.includes('{{CALENDAR_SOURCES}}')) {
64
+ if (calendarSources.length === 0) {
65
+ console.error('Error: No calendar sources found in .env file');
66
+ process.exit(1);
67
+ }
68
+
69
+ const calendarArrayCode =
70
+ '[\n' + calendarSources.map(cal => ` "${cal}"`).join(',\n') + '\n ]';
71
+
72
+ html = html.replace('{{CALENDAR_SOURCES}}', calendarArrayCode);
73
+ console.log(
74
+ `✓ Replaced {{CALENDAR_SOURCES}} with ${calendarSources.length} calendar source(s)`
75
+ );
76
+ }
77
+
78
+ // Replace CALENDAR_URLS placeholder
79
+ if (html.includes('{{CALENDAR_URLS}}')) {
80
+ if (calendarUrls.length === 0) {
81
+ console.error('Error: CALENDAR_URL not found in .env file');
82
+ process.exit(1);
83
+ }
84
+
85
+ // Get encryption settings from .env
86
+ let encryptionMethod = '';
87
+ let encryptionKey = '';
88
+
89
+ for (const line of envLines) {
90
+ if (line.startsWith('ENCRYPTION_METHOD=')) {
91
+ encryptionMethod = line.substring('ENCRYPTION_METHOD='.length).trim().toLowerCase();
92
+ } else if (line.startsWith('ENCRYPTION_KEY=')) {
93
+ encryptionKey = line.substring('ENCRYPTION_KEY='.length).trim();
94
+ }
95
+ }
96
+
97
+ let calendarUrlsArrayCode;
98
+
99
+ if (encryptionMethod === 'fernet' && encryptionKey) {
100
+ // Use Fernet encryption
101
+ try {
102
+ const secret = new Fernet.Secret(encryptionKey);
103
+ const token = new Fernet.Token({
104
+ secret: secret,
105
+ ttl: 0, // No expiration
106
+ });
107
+
108
+ const encryptedUrls = calendarUrls.map(url => {
109
+ const encrypted = token.encode(url);
110
+ // Format as fernet:// protocol URL
111
+ return `fernet://${encrypted}`;
112
+ });
113
+
114
+ calendarUrlsArrayCode =
115
+ '[\n' + encryptedUrls.map(url => ` "${url}"`).join(',\n') + '\n ]';
116
+
117
+ html = html.replace('{{ENCRYPTION_METHOD}}', 'fernet');
118
+ html = html.replace('{{ENCRYPTION_KEY}}', encryptionKey);
119
+ console.log(
120
+ `✓ Replaced {{CALENDAR_URLS}} with ${encryptedUrls.length} Fernet-encrypted calendar URL(s)`
121
+ );
122
+
123
+ // Output encrypted URLs for copying to .env
124
+ console.log('\n📋 Encrypted URLs (for .env file):');
125
+ console.log('CALENDAR_URL_ENCRYPTED=' + encryptedUrls.join(','));
126
+ console.log('\nOr individually:');
127
+ encryptedUrls.forEach((encrypted, index) => {
128
+ console.log(`# URL ${index + 1}: ${encrypted}`);
129
+ });
130
+ } catch (error) {
131
+ console.error('Error encrypting with Fernet:', error.message);
132
+ process.exit(1);
133
+ }
134
+ } else {
135
+ // No encryption - plain URLs
136
+ calendarUrlsArrayCode =
137
+ '[\n' + calendarUrls.map(url => ` "${url}"`).join(',\n') + '\n ]';
138
+ html = html.replace('{{ENCRYPTION_METHOD}}', 'none');
139
+ html = html.replace('{{ENCRYPTION_KEY}}', '');
140
+ html = html.replace('{{WORKER_URL}}', 'https://open-web-calendar.hosted.quelltext.eu/calendar.html');
141
+ console.log(
142
+ `✓ Replaced {{CALENDAR_URLS}} with ${calendarUrls.length} calendar URL(s) (no encryption)`
143
+ );
144
+ }
145
+
146
+ html = html.replace('{{CALENDAR_URLS}}', calendarUrlsArrayCode);
147
+
148
+ // Remove encryption key placeholder (not needed with Worker)
149
+ html = html.replace('{{ENCRYPTION_KEY}}', '');
150
+
151
+ // Clean up fernet-bundle.js if it exists (no longer needed with Worker)
152
+ const bundleFile = path.join(__dirname, '..', 'fernet-bundle.js');
153
+ if (fs.existsSync(bundleFile)) {
154
+ fs.unlinkSync(bundleFile);
155
+ console.log('✓ Removed fernet-bundle.js (using Cloudflare Worker instead)');
156
+ }
157
+ }
158
+ }
159
+
160
+ // Always replace WORKER_URL placeholder if it exists (regardless of other placeholders)
161
+ if (html.includes('{{WORKER_URL}}')) {
162
+ const finalWorkerUrl = workerUrl || process.env.WORKER_URL || 'https://open-web-calendar.hosted.quelltext.eu';
163
+ html = html.replace('{{WORKER_URL}}', finalWorkerUrl);
164
+ if (finalWorkerUrl !== 'https://open-web-calendar.hosted.quelltext.eu') {
165
+ console.log(`✓ Using Cloudflare Worker: ${finalWorkerUrl}`);
166
+ } else {
167
+ console.log('✓ Replaced {{WORKER_URL}} with default open-web-calendar URL');
168
+ }
169
+ }
170
+
171
+ // Get current year for placeholders
172
+ const currentYear = new Date().getFullYear().toString();
173
+
174
+ // Replace {{YEAR}} placeholder in HTML template
175
+ if (html.includes('{{YEAR}}')) {
176
+ html = html.replace(/\{\{YEAR\}\}/g, currentYear);
177
+ }
178
+
179
+ // Write built HTML
180
+ const outputPath = path.join(__dirname, '..', 'index.html');
181
+ fs.writeFileSync(outputPath, html, 'utf-8');
182
+
183
+ // Update LICENSE file with current year
184
+ const licensePath = path.join(__dirname, '..', 'LICENSE');
185
+ if (fs.existsSync(licensePath)) {
186
+ let licenseContent = fs.readFileSync(licensePath, 'utf-8');
187
+ if (licenseContent.includes('{{YEAR}}')) {
188
+ licenseContent = licenseContent.replace(/\{\{YEAR\}\}/g, currentYear);
189
+ fs.writeFileSync(licensePath, licenseContent, 'utf-8');
190
+ console.log(`✓ Updated LICENSE with year ${currentYear}`);
191
+ }
192
+ }
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Helper script to encrypt calendar URLs
5
+ * Usage: node encrypt-urls.js <url1> <url2> ...
6
+ * Or set CALENDAR_URL in .env and run: node encrypt-urls.js
7
+ */
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const Fernet = require('fernet');
12
+
13
+ // Read .env file
14
+ const envPath = path.join(__dirname, '..', '.env');
15
+ let encryptionKey = '';
16
+ let calendarUrls = [];
17
+
18
+ if (fs.existsSync(envPath)) {
19
+ const envContent = fs.readFileSync(envPath, 'utf-8');
20
+ const envLines = envContent.split('\n');
21
+
22
+ for (const line of envLines) {
23
+ if (line.startsWith('ENCRYPTION_KEY=')) {
24
+ encryptionKey = line.substring('ENCRYPTION_KEY='.length).trim();
25
+ } else if (line.startsWith('CALENDAR_URL=')) {
26
+ const value = line.substring('CALENDAR_URL='.length).trim();
27
+ calendarUrls = value
28
+ .split(',')
29
+ .map(s => s.trim())
30
+ .filter(s => s);
31
+ }
32
+ }
33
+ }
34
+
35
+ // Get URLs from command line arguments if provided
36
+ const args = process.argv.slice(2);
37
+ if (args.length > 0) {
38
+ calendarUrls = args;
39
+ }
40
+
41
+ if (calendarUrls.length === 0) {
42
+ console.error('Error: No calendar URLs provided');
43
+ console.error('Usage: node encrypt-urls.js <url1> <url2> ...');
44
+ console.error('Or set CALENDAR_URL in .env file');
45
+ process.exit(1);
46
+ }
47
+
48
+ if (!encryptionKey) {
49
+ console.error('Error: ENCRYPTION_KEY not found in .env file');
50
+ console.error('Please set ENCRYPTION_KEY in .env file');
51
+ process.exit(1);
52
+ }
53
+
54
+ try {
55
+ const secret = new Fernet.Secret(encryptionKey);
56
+ const token = new Fernet.Token({
57
+ secret: secret,
58
+ ttl: 0, // No expiration
59
+ });
60
+
61
+ const encryptedUrls = calendarUrls.map(url => {
62
+ return token.encode(url);
63
+ });
64
+
65
+ console.log('\n📋 Encrypted URLs:\n');
66
+ console.log('CALENDAR_URL_ENCRYPTED=' + encryptedUrls.join(','));
67
+ console.log('\nOr individually:');
68
+ encryptedUrls.forEach((encrypted, index) => {
69
+ console.log(`# URL ${index + 1}: ${encrypted}`);
70
+ });
71
+ console.log('\n✅ Copy the encrypted URLs to your .env file\n');
72
+ } catch (error) {
73
+ console.error('Error encrypting URLs:', error.message);
74
+ process.exit(1);
75
+ }
76
+