@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/LICENSE +9 -0
- package/README.md +174 -17
- package/index.html.template +303 -52
- package/package.json +23 -8
- package/scripts/build.js +192 -0
- package/scripts/encrypt-urls.js +76 -0
- package/worker.js +480 -0
- package/wrangler.toml.example +34 -0
- package/build.js +0 -53
package/package.json
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dytsou/calendar-build",
|
|
3
|
-
"version": "
|
|
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
|
}
|
package/scripts/build.js
ADDED
|
@@ -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
|
+
|