basecampjs 0.0.9 → 0.0.11

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 (2) hide show
  1. package/index.js +196 -29
  2. package/package.json +4 -2
package/index.js CHANGED
@@ -15,6 +15,7 @@ import nunjucks from "nunjucks";
15
15
  import { Liquid } from "liquidjs";
16
16
  import { minify as minifyCss } from "csso";
17
17
  import { minify as minifyHtml } from "html-minifier-terser";
18
+ import sharp from "sharp";
18
19
 
19
20
  const cwd = process.cwd();
20
21
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -29,6 +30,14 @@ const defaultConfig = {
29
30
  minifyCSS: false,
30
31
  minifyHTML: false,
31
32
  cacheBustAssets: false,
33
+ excludeFiles: [],
34
+ compressPhotos: false,
35
+ compressionSettings: {
36
+ quality: 80,
37
+ formats: [".webp"],
38
+ inputFormats: [".jpg", ".jpeg", ".png"],
39
+ preserveOriginal: true
40
+ },
32
41
  integrations: { nunjucks: true, liquid: false, mustache: false, vue: false, alpine: false }
33
42
  };
34
43
 
@@ -61,7 +70,7 @@ function showHelp() {
61
70
  console.log(kolor.dim("Build and manage your static campsite.\n"));
62
71
 
63
72
  console.log(kolor.bold("Usage:"));
64
- console.log(" campsite <command> [arguments] [options]\n");
73
+ console.log(" camper <command> [arguments] [options]\n");
65
74
 
66
75
  console.log(kolor.bold("Project Commands:"));
67
76
  console.log(" " + kolor.cyan("init") + " Initialize a new Campsite project in current directory");
@@ -101,15 +110,15 @@ function showHelp() {
101
110
 
102
111
  console.log(kolor.bold("Examples:"));
103
112
  console.log(" " + kolor.dim("# Initialize a new project"));
104
- console.log(" campsite init\n");
113
+ console.log(" camper init\n");
105
114
  console.log(" " + kolor.dim("# Start development"));
106
- console.log(" campsite dev\n");
115
+ console.log(" camper dev\n");
107
116
  console.log(" " + kolor.dim("# Create new content"));
108
- console.log(" campsite make:page about");
109
- console.log(" campsite make:post \"My First Post\"");
110
- console.log(" campsite make:collection products\n");
117
+ console.log(" camper make:page about");
118
+ console.log(" camper make:post \"My First Post\"");
119
+ console.log(" camper make:collection products\n");
111
120
  console.log(" " + kolor.dim("# Build and preview"));
112
- console.log(" campsite preview\n");
121
+ console.log(" camper preview\n");
113
122
  console.log(kolor.dim("For more information, visit: https://campsitejs.dev"));
114
123
  console.log();
115
124
  }
@@ -145,12 +154,166 @@ async function cleanDir(dir) {
145
154
  await mkdir(dir, { recursive: true });
146
155
  }
147
156
 
148
- async function copyPublic(publicDir, outDir) {
149
- if (existsSync(publicDir)) {
150
- await cp(publicDir, outDir, { recursive: true });
157
+ function shouldExcludeFile(filePath, excludePatterns) {
158
+ if (!excludePatterns || excludePatterns.length === 0) return false;
159
+
160
+ const fileName = basename(filePath).toLowerCase();
161
+ const ext = extname(filePath).toLowerCase();
162
+
163
+ return excludePatterns.some(pattern => {
164
+ const normalized = pattern.toLowerCase();
165
+ // Support extension patterns like '.pdf' or 'pdf'
166
+ if (normalized.startsWith('.')) {
167
+ return ext === normalized;
168
+ }
169
+ if (normalized.startsWith('*.')) {
170
+ return ext === normalized.slice(1);
171
+ }
172
+ // Support exact filename matches
173
+ if (fileName === normalized) {
174
+ return true;
175
+ }
176
+ // Support glob-like patterns with wildcards
177
+ if (normalized.includes('*')) {
178
+ const regex = new RegExp('^' + normalized.replace(/\*/g, '.*').replace(/\?/g, '.') + '$');
179
+ return regex.test(fileName);
180
+ }
181
+ return false;
182
+ });
183
+ }
184
+
185
+ async function copyPublic(publicDir, outDir, excludePatterns = []) {
186
+ if (!existsSync(publicDir)) return;
187
+
188
+ const files = await walkFiles(publicDir);
189
+ for (const file of files) {
190
+ const rel = relative(publicDir, file);
191
+
192
+ // Skip excluded files
193
+ if (shouldExcludeFile(file, excludePatterns)) {
194
+ console.log(kolor.dim(`Skipping excluded file: ${rel}`));
195
+ continue;
196
+ }
197
+
198
+ const destPath = join(outDir, rel);
199
+ await ensureDir(dirname(destPath));
200
+ await cp(file, destPath);
151
201
  }
152
202
  }
153
203
 
204
+ function shouldProcessImage(filePath, config) {
205
+ if (!config.compressPhotos) return false;
206
+ const ext = extname(filePath).toLowerCase();
207
+ const inputFormats = config.compressionSettings?.inputFormats || [".jpg", ".jpeg", ".png"];
208
+ return inputFormats.includes(ext);
209
+ }
210
+
211
+ async function processImage(inputPath, outDir, settings) {
212
+ const ext = extname(inputPath);
213
+ const baseName = basename(inputPath, ext);
214
+ const dir = dirname(inputPath);
215
+ const relDir = relative(outDir, dir);
216
+
217
+ const results = [];
218
+ const quality = settings.quality || 80;
219
+ const formats = settings.formats || [".webp"];
220
+
221
+ for (const format of formats) {
222
+ try {
223
+ const outputName = `${baseName}${format}`;
224
+ const outputPath = join(dir, outputName);
225
+
226
+ const sharpInstance = sharp(inputPath);
227
+
228
+ if (format === ".webp") {
229
+ await sharpInstance.webp({ quality }).toFile(outputPath);
230
+ } else if (format === ".avif") {
231
+ await sharpInstance.avif({ quality }).toFile(outputPath);
232
+ } else if (format === ".jpg" || format === ".jpeg") {
233
+ await sharpInstance.jpeg({ quality }).toFile(outputPath);
234
+ } else if (format === ".png") {
235
+ await sharpInstance.png({ quality }).toFile(outputPath);
236
+ } else {
237
+ continue;
238
+ }
239
+
240
+ const stats = await stat(outputPath);
241
+ results.push({
242
+ path: outputPath,
243
+ format,
244
+ size: stats.size
245
+ });
246
+ } catch (err) {
247
+ console.error(kolor.red(`Failed to convert ${basename(inputPath)} to ${format}: ${err.message}`));
248
+ }
249
+ }
250
+
251
+ return results;
252
+ }
253
+
254
+ async function processImages(outDir, config) {
255
+ if (!config.compressPhotos) return;
256
+
257
+ const settings = {
258
+ quality: config.compressionSettings?.quality || 80,
259
+ formats: config.compressionSettings?.formats || [".webp"],
260
+ preserveOriginal: config.compressionSettings?.preserveOriginal !== false
261
+ };
262
+
263
+ console.log(kolor.cyan("🖼️ Processing images..."));
264
+
265
+ const files = await walkFiles(outDir);
266
+ const imageFiles = files.filter((file) => shouldProcessImage(file, config));
267
+
268
+ if (imageFiles.length === 0) {
269
+ console.log(kolor.dim("No images found to process"));
270
+ return;
271
+ }
272
+
273
+ let totalGenerated = 0;
274
+ let totalOriginalSize = 0;
275
+ let totalConvertedSize = 0;
276
+
277
+ await Promise.all(imageFiles.map(async (file) => {
278
+ const originalStats = await stat(file);
279
+ totalOriginalSize += originalStats.size;
280
+
281
+ const results = await processImage(file, outDir, settings);
282
+
283
+ if (results.length > 0) {
284
+ const formats = results.map(r => r.format.slice(1)).join(", ");
285
+ const rel = relative(outDir, file);
286
+ console.log(kolor.dim(` ${rel} → ${formats}`));
287
+
288
+ results.forEach(r => {
289
+ totalConvertedSize += r.size;
290
+ totalGenerated++;
291
+ });
292
+ }
293
+
294
+ // Remove original if preserveOriginal is false
295
+ if (!settings.preserveOriginal && results.length > 0) {
296
+ await rm(file, { force: true });
297
+ }
298
+ }));
299
+
300
+ const savedBytes = totalOriginalSize - totalConvertedSize;
301
+ const savedPercent = totalOriginalSize > 0 ? ((savedBytes / totalOriginalSize) * 100).toFixed(1) : 0;
302
+
303
+ console.log(kolor.green(`✓ Generated ${totalGenerated} image(s)`));
304
+ if (!settings.preserveOriginal) {
305
+ console.log(kolor.green(` Saved ${formatBytes(savedBytes)} (${savedPercent}% reduction)`));
306
+ }
307
+ }
308
+
309
+ function formatBytes(bytes) {
310
+ if (bytes === 0) return "0 B";
311
+ const k = 1024;
312
+ const sizes = ["B", "KB", "MB", "GB"];
313
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
314
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
315
+ }
316
+
154
317
  async function minifyCSSFiles(outDir) {
155
318
  const files = await walkFiles(outDir);
156
319
  const cssFiles = files.filter((file) => extname(file).toLowerCase() === ".css");
@@ -470,7 +633,11 @@ async function build(cwdArg = cwd) {
470
633
  const data = await loadData([dataDir, collectionsDir]);
471
634
 
472
635
  await cleanDir(outDir);
473
- await copyPublic(publicDir, outDir);
636
+ await copyPublic(publicDir, outDir, config.excludeFiles);
637
+
638
+ if (config.compressPhotos) {
639
+ await processImages(outDir, config);
640
+ }
474
641
 
475
642
  const files = await walkFiles(pagesDir);
476
643
  if (files.length === 0) {
@@ -622,17 +789,17 @@ async function makeContent(type) {
622
789
 
623
790
  if (args.length === 0) {
624
791
  console.log(kolor.red("❌ Missing name argument"));
625
- console.log(kolor.dim(`Usage: campsite make:${type} <name> [name2, name3, ...]`));
792
+ console.log(kolor.dim(`Usage: camper make:${type} <name> [name2, name3, ...]`));
626
793
  console.log(kolor.dim("\nExamples:"));
627
- console.log(kolor.dim(" campsite make:page about"));
628
- console.log(kolor.dim(" campsite make:page home, about, contact"));
629
- console.log(kolor.dim(" campsite make:collection products, categories\n"));
794
+ console.log(kolor.dim(" camper make:page about"));
795
+ console.log(kolor.dim(" camper make:page home, about, contact"));
796
+ console.log(kolor.dim(" camper make:collection products, categories\n"));
630
797
  exit(1);
631
798
  }
632
799
 
633
800
  // Join all args and split by comma to support both formats:
634
- // campsite make:page home about contact
635
- // campsite make:page home, about, contact
801
+ // camper make:page home about contact
802
+ // camper make:page home, about, contact
636
803
  const namesString = args.join(" ");
637
804
  const names = namesString.split(",").map(n => n.trim()).filter(n => n.length > 0);
638
805
 
@@ -857,7 +1024,7 @@ async function init() {
857
1024
  // Check if already initialized
858
1025
  if (existsSync(join(targetDir, "campsite.config.js"))) {
859
1026
  console.log(kolor.yellow("⚠️ This directory already has a campsite.config.js file."));
860
- console.log(kolor.dim("Run 'campsite dev' to start developing.\n"));
1027
+ console.log(kolor.dim("Run 'camper dev' to start developing.\n"));
861
1028
  return;
862
1029
  }
863
1030
 
@@ -920,7 +1087,7 @@ Your cozy static site is ready to build.
920
1087
 
921
1088
  ## Get Started
922
1089
 
923
- - Run \`campsite dev\` to start developing
1090
+ - Run \`camper dev\` to start developing
924
1091
  - Edit pages in \`src/pages/\`
925
1092
  - Customize layouts in \`src/layouts/\`
926
1093
 
@@ -961,10 +1128,10 @@ dist/
961
1128
  version: "0.0.1",
962
1129
  type: "module",
963
1130
  scripts: {
964
- dev: "campsite dev",
965
- build: "campsite build",
966
- serve: "campsite serve",
967
- preview: "campsite preview"
1131
+ dev: "camper dev",
1132
+ build: "camper build",
1133
+ serve: "camper serve",
1134
+ preview: "camper preview"
968
1135
  },
969
1136
  dependencies: {
970
1137
  basecampjs: "^0.0.8"
@@ -975,7 +1142,7 @@ dist/
975
1142
  console.log(kolor.green("✅ Campsite initialized successfully!\n"));
976
1143
  console.log(kolor.bold("Next steps:"));
977
1144
  console.log(kolor.dim(" 1. Install dependencies: npm install"));
978
- console.log(kolor.dim(" 2. Start developing: campsite dev\n"));
1145
+ console.log(kolor.dim(" 2. Start developing: camper dev\n"));
979
1146
  }
980
1147
 
981
1148
  async function clean() {
@@ -1000,7 +1167,7 @@ async function check() {
1000
1167
  const configPath = join(cwd, "campsite.config.js");
1001
1168
  if (!existsSync(configPath)) {
1002
1169
  console.log(kolor.red("❌ campsite.config.js not found"));
1003
- console.log(kolor.dim(" Run 'campsite init' to initialize a project\n"));
1170
+ console.log(kolor.dim(" Run 'camper init' to initialize a project\n"));
1004
1171
  hasIssues = true;
1005
1172
  } else {
1006
1173
  console.log(kolor.green("✅ campsite.config.js found"));
@@ -1133,7 +1300,7 @@ async function upgrade() {
1133
1300
  } catch {}
1134
1301
 
1135
1302
  console.log();
1136
- console.log(kolor.dim("🌲 Tip: Run 'campsite dev' to start developing with the latest version\n"));
1303
+ console.log(kolor.dim("🌲 Tip: Run 'camper dev' to start developing with the latest version\n"));
1137
1304
  resolve();
1138
1305
  } else {
1139
1306
  console.log();
@@ -1236,7 +1403,7 @@ async function list() {
1236
1403
  }
1237
1404
  }
1238
1405
 
1239
- console.log(kolor.dim("🌲 Tip: Use 'campsite make:<type> <name>' to create new content\n"));
1406
+ console.log(kolor.dim("🌲 Tip: Use 'camper make:<type> <name>' to create new content\n"));
1240
1407
  }
1241
1408
 
1242
1409
  async function preview() {
@@ -1269,7 +1436,7 @@ async function main() {
1269
1436
  const type = command.substring(5); // Remove 'make:' prefix
1270
1437
  if (!type) {
1271
1438
  console.log(kolor.red("❌ No type specified"));
1272
- console.log(kolor.dim("Run 'campsite --help' for available make commands.\n"));
1439
+ console.log(kolor.dim("Run 'camper --help' for available make commands.\n"));
1273
1440
  exit(1);
1274
1441
  }
1275
1442
  await makeContent(type);
@@ -1312,7 +1479,7 @@ async function main() {
1312
1479
  break;
1313
1480
  default:
1314
1481
  console.log(kolor.yellow(`Unknown command: ${command}`));
1315
- console.log(kolor.dim("Run 'campsite --help' for usage information."));
1482
+ console.log(kolor.dim("Run 'camper --help' for usage information."));
1316
1483
  exit(1);
1317
1484
  }
1318
1485
  }
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "basecampjs",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "type": "module",
5
5
  "description": "BasecampJS engine for CampsiteJS static site generator.",
6
6
  "bin": {
7
+ "camper": "./index.js",
7
8
  "campsite": "./index.js"
8
9
  },
9
10
  "license": "MIT",
@@ -19,7 +20,8 @@
19
20
  "liquidjs": "^10.12.0",
20
21
  "markdown-it": "^14.1.0",
21
22
  "mustache": "^4.2.0",
22
- "nunjucks": "^3.2.4"
23
+ "nunjucks": "^3.2.4",
24
+ "sharp": "^0.33.5"
23
25
  },
24
26
  "engines": {
25
27
  "node": ">=18"