basecampjs 0.0.8 ā 0.0.9
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/index.js +762 -3
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { createServer } from "http";
|
|
|
4
4
|
import { existsSync } from "fs";
|
|
5
5
|
import { cp, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "fs/promises";
|
|
6
6
|
import { basename, dirname, extname, join, relative, resolve } from "path";
|
|
7
|
-
import { pathToFileURL } from "url";
|
|
7
|
+
import { pathToFileURL, fileURLToPath } from "url";
|
|
8
8
|
import { createHash } from "crypto";
|
|
9
9
|
import * as kolor from "kolorist";
|
|
10
10
|
import chokidar from "chokidar";
|
|
@@ -17,6 +17,7 @@ import { minify as minifyCss } from "csso";
|
|
|
17
17
|
import { minify as minifyHtml } from "html-minifier-terser";
|
|
18
18
|
|
|
19
19
|
const cwd = process.cwd();
|
|
20
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
20
21
|
const md = new MarkdownIt({ html: true, linkify: true, typographer: true });
|
|
21
22
|
|
|
22
23
|
const defaultConfig = {
|
|
@@ -44,6 +45,75 @@ async function loadConfig(root) {
|
|
|
44
45
|
}
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
async function getVersion() {
|
|
49
|
+
try {
|
|
50
|
+
const pkgPath = join(__dirname, "package.json");
|
|
51
|
+
const raw = await readFile(pkgPath, "utf8");
|
|
52
|
+
const pkg = JSON.parse(raw);
|
|
53
|
+
return pkg.version || "0.0.0";
|
|
54
|
+
} catch {
|
|
55
|
+
return "0.0.0";
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function showHelp() {
|
|
60
|
+
console.log(kolor.cyan(kolor.bold("\nšļø CampsiteJS CLI")));
|
|
61
|
+
console.log(kolor.dim("Build and manage your static campsite.\n"));
|
|
62
|
+
|
|
63
|
+
console.log(kolor.bold("Usage:"));
|
|
64
|
+
console.log(" campsite <command> [arguments] [options]\n");
|
|
65
|
+
|
|
66
|
+
console.log(kolor.bold("Project Commands:"));
|
|
67
|
+
console.log(" " + kolor.cyan("init") + " Initialize a new Campsite project in current directory");
|
|
68
|
+
console.log(" Creates config, folder structure, and starter files\n");
|
|
69
|
+
|
|
70
|
+
console.log(kolor.bold("Development Commands:"));
|
|
71
|
+
console.log(" " + kolor.cyan("dev") + " Start development server with hot reloading");
|
|
72
|
+
console.log(" Watches for file changes and rebuilds automatically");
|
|
73
|
+
console.log(" " + kolor.cyan("build") + " Build your site for production");
|
|
74
|
+
console.log(" Optimizes and outputs to dist/ directory");
|
|
75
|
+
console.log(" " + kolor.cyan("serve") + " Serve the built site locally");
|
|
76
|
+
console.log(" Serves from dist/ folder on http://localhost:4173");
|
|
77
|
+
console.log(" " + kolor.cyan("preview") + " Build and serve in production mode");
|
|
78
|
+
console.log(" Combines build + serve for testing production output\n");
|
|
79
|
+
|
|
80
|
+
console.log(kolor.bold("Utility Commands:"));
|
|
81
|
+
console.log(" " + kolor.cyan("list") + " List all content (pages, layouts, components, etc.)");
|
|
82
|
+
console.log(" Overview of your project structure");
|
|
83
|
+
console.log(" " + kolor.cyan("clean") + " Remove build output directory");
|
|
84
|
+
console.log(" Deletes dist/ folder for a fresh build");
|
|
85
|
+
console.log(" " + kolor.cyan("check") + " Validate config and check for issues");
|
|
86
|
+
console.log(" Diagnoses project structure and dependencies");
|
|
87
|
+
console.log(" " + kolor.cyan("upgrade") + " Update CampsiteJS to the latest version");
|
|
88
|
+
console.log(" Checks and upgrades basecampjs and dependencies\n");
|
|
89
|
+
|
|
90
|
+
console.log(kolor.bold("Make Commands:"));
|
|
91
|
+
console.log(" " + kolor.cyan("make:page") + " " + kolor.dim("<name>") + " Create a new page in src/pages/");
|
|
92
|
+
console.log(" " + kolor.cyan("make:post") + " " + kolor.dim("<name>") + " Create a new blog post in src/pages/blog/");
|
|
93
|
+
console.log(" " + kolor.cyan("make:layout") + " " + kolor.dim("<name>") + " Create a new layout in src/layouts/");
|
|
94
|
+
console.log(" " + kolor.cyan("make:component") + " " + kolor.dim("<name>") + " Create a new component in src/components/");
|
|
95
|
+
console.log(" " + kolor.cyan("make:partial") + " " + kolor.dim("<name>") + " Create a new partial in src/partials/");
|
|
96
|
+
console.log(" " + kolor.cyan("make:collection") + " " + kolor.dim("<name>") + " Create a new JSON collection in src/collections/\n");
|
|
97
|
+
|
|
98
|
+
console.log(kolor.bold("Options:"));
|
|
99
|
+
console.log(" -h, --help Show this help message");
|
|
100
|
+
console.log(" -v, --version Show version number\n");
|
|
101
|
+
|
|
102
|
+
console.log(kolor.bold("Examples:"));
|
|
103
|
+
console.log(" " + kolor.dim("# Initialize a new project"));
|
|
104
|
+
console.log(" campsite init\n");
|
|
105
|
+
console.log(" " + kolor.dim("# Start development"));
|
|
106
|
+
console.log(" campsite dev\n");
|
|
107
|
+
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");
|
|
111
|
+
console.log(" " + kolor.dim("# Build and preview"));
|
|
112
|
+
console.log(" campsite preview\n");
|
|
113
|
+
console.log(kolor.dim("For more information, visit: https://campsitejs.dev"));
|
|
114
|
+
console.log();
|
|
115
|
+
}
|
|
116
|
+
|
|
47
117
|
async function ensureDir(dir) {
|
|
48
118
|
await mkdir(dir, { recursive: true });
|
|
49
119
|
}
|
|
@@ -533,10 +603,683 @@ async function dev(cwdArg = cwd) {
|
|
|
533
603
|
serve(outDir);
|
|
534
604
|
}
|
|
535
605
|
|
|
606
|
+
function slugify(text) {
|
|
607
|
+
return text
|
|
608
|
+
.toLowerCase()
|
|
609
|
+
.trim()
|
|
610
|
+
.replace(/[^\w\s-]/g, "")
|
|
611
|
+
.replace(/[\s_-]+/g, "-")
|
|
612
|
+
.replace(/^-+|-+$/g, "");
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function formatDate(date) {
|
|
616
|
+
return date.toISOString().split("T")[0];
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
async function makeContent(type) {
|
|
620
|
+
// Get all arguments after the command and join them
|
|
621
|
+
const args = argv.slice(3);
|
|
622
|
+
|
|
623
|
+
if (args.length === 0) {
|
|
624
|
+
console.log(kolor.red("ā Missing name argument"));
|
|
625
|
+
console.log(kolor.dim(`Usage: campsite make:${type} <name> [name2, name3, ...]`));
|
|
626
|
+
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"));
|
|
630
|
+
exit(1);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// 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
|
|
636
|
+
const namesString = args.join(" ");
|
|
637
|
+
const names = namesString.split(",").map(n => n.trim()).filter(n => n.length > 0);
|
|
638
|
+
|
|
639
|
+
if (names.length === 0) {
|
|
640
|
+
console.log(kolor.red("ā No valid names provided\n"));
|
|
641
|
+
exit(1);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
console.log(kolor.cyan(`\nšļø Creating ${names.length} ${type}(s)...\n`));
|
|
645
|
+
|
|
646
|
+
const config = await loadConfig(cwd);
|
|
647
|
+
const srcDir = resolve(cwd, config.srcDir || "src");
|
|
648
|
+
|
|
649
|
+
// Determine file extension based on template engine
|
|
650
|
+
const engineExtMap = {
|
|
651
|
+
nunjucks: ".njk",
|
|
652
|
+
liquid: ".liquid",
|
|
653
|
+
mustache: ".mustache"
|
|
654
|
+
};
|
|
655
|
+
const defaultExt = engineExtMap[config.templateEngine] || ".njk";
|
|
656
|
+
|
|
657
|
+
let successCount = 0;
|
|
658
|
+
let skipCount = 0;
|
|
659
|
+
|
|
660
|
+
for (const name of names) {
|
|
661
|
+
const result = await createSingleContent(type, name, srcDir, config, defaultExt);
|
|
662
|
+
if (result.success) successCount++;
|
|
663
|
+
if (result.skipped) skipCount++;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
console.log();
|
|
667
|
+
if (successCount > 0) {
|
|
668
|
+
console.log(kolor.green(`ā
Created ${successCount} ${type}(s)`));
|
|
669
|
+
}
|
|
670
|
+
if (skipCount > 0) {
|
|
671
|
+
console.log(kolor.yellow(`ā ļø Skipped ${skipCount} existing file(s)`));
|
|
672
|
+
}
|
|
673
|
+
console.log(kolor.dim("\nš² Happy camping!\n"));
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
async function createSingleContent(type, name, srcDir, config, defaultExt) {
|
|
677
|
+
// Check if user provided an extension
|
|
678
|
+
const hasExtension = name.includes(".");
|
|
679
|
+
const providedExt = hasExtension ? extname(name) : null;
|
|
680
|
+
const nameWithoutExt = hasExtension ? basename(name, providedExt) : name;
|
|
681
|
+
|
|
682
|
+
const slug = slugify(nameWithoutExt);
|
|
683
|
+
const today = formatDate(new Date());
|
|
684
|
+
const title = nameWithoutExt.charAt(0).toUpperCase() + nameWithoutExt.slice(1);
|
|
685
|
+
|
|
686
|
+
let targetPath;
|
|
687
|
+
let content;
|
|
688
|
+
let fileExt;
|
|
689
|
+
|
|
690
|
+
switch (type.toLowerCase()) {
|
|
691
|
+
case "page": {
|
|
692
|
+
// Priority: provided extension > template engine
|
|
693
|
+
if (providedExt) {
|
|
694
|
+
fileExt = providedExt;
|
|
695
|
+
} else {
|
|
696
|
+
fileExt = defaultExt;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
targetPath = join(srcDir, "pages", `${slug}${fileExt}`);
|
|
700
|
+
|
|
701
|
+
// Determine if we should use markdown content based on extension
|
|
702
|
+
const useMarkdown = fileExt === ".md";
|
|
703
|
+
|
|
704
|
+
if (useMarkdown) {
|
|
705
|
+
content = `---
|
|
706
|
+
layout: base${defaultExt}
|
|
707
|
+
title: ${title}
|
|
708
|
+
---
|
|
709
|
+
|
|
710
|
+
# ${title}
|
|
711
|
+
|
|
712
|
+
Your new page content goes here.
|
|
713
|
+
`;
|
|
714
|
+
} else {
|
|
715
|
+
content = `---
|
|
716
|
+
layout: base${defaultExt}
|
|
717
|
+
title: ${title}
|
|
718
|
+
---
|
|
719
|
+
|
|
720
|
+
<h1>${title}</h1>
|
|
721
|
+
<p>Your new page content goes here.</p>
|
|
722
|
+
`;
|
|
723
|
+
}
|
|
724
|
+
break;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
case "post": {
|
|
728
|
+
const postsDir = join(srcDir, "pages", "blog");
|
|
729
|
+
await ensureDir(postsDir);
|
|
730
|
+
|
|
731
|
+
if (providedExt) {
|
|
732
|
+
fileExt = providedExt;
|
|
733
|
+
} else {
|
|
734
|
+
fileExt = defaultExt;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
targetPath = join(postsDir, `${slug}${fileExt}`);
|
|
738
|
+
|
|
739
|
+
const useMarkdown = fileExt === ".md";
|
|
740
|
+
|
|
741
|
+
if (useMarkdown) {
|
|
742
|
+
content = `---
|
|
743
|
+
layout: base${defaultExt}
|
|
744
|
+
title: ${title}
|
|
745
|
+
date: ${today}
|
|
746
|
+
author: Your Name
|
|
747
|
+
---
|
|
748
|
+
|
|
749
|
+
# ${title}
|
|
750
|
+
|
|
751
|
+
Your blog post content goes here.
|
|
752
|
+
`;
|
|
753
|
+
} else {
|
|
754
|
+
content = `---
|
|
755
|
+
layout: base${defaultExt}
|
|
756
|
+
title: ${title}
|
|
757
|
+
date: ${today}
|
|
758
|
+
author: Your Name
|
|
759
|
+
---
|
|
760
|
+
|
|
761
|
+
<h1>${title}</h1>
|
|
762
|
+
<p>Your blog post content goes here.</p>
|
|
763
|
+
`;
|
|
764
|
+
}
|
|
765
|
+
break;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
case "layout": {
|
|
769
|
+
const layoutsDir = join(srcDir, "layouts");
|
|
770
|
+
await ensureDir(layoutsDir);
|
|
771
|
+
targetPath = join(layoutsDir, `${slug}.njk`);
|
|
772
|
+
fileExt = ".njk";
|
|
773
|
+
content = `<!DOCTYPE html>
|
|
774
|
+
<html lang="en">
|
|
775
|
+
<head>
|
|
776
|
+
<meta charset="UTF-8">
|
|
777
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
778
|
+
<title>{{ title or site.name }}</title>
|
|
779
|
+
<link rel="stylesheet" href="/style.css">
|
|
780
|
+
</head>
|
|
781
|
+
<body>
|
|
782
|
+
<main>
|
|
783
|
+
{% block content %}
|
|
784
|
+
{{ content | safe }}
|
|
785
|
+
{% endblock %}
|
|
786
|
+
</main>
|
|
787
|
+
</body>
|
|
788
|
+
</html>
|
|
789
|
+
`;
|
|
790
|
+
break;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
case "component": {
|
|
794
|
+
const componentsDir = join(srcDir, "components");
|
|
795
|
+
await ensureDir(componentsDir);
|
|
796
|
+
targetPath = join(componentsDir, `${slug}.njk`);
|
|
797
|
+
fileExt = ".njk";
|
|
798
|
+
content = `{# ${title} Component #}
|
|
799
|
+
<div class="${slug}">
|
|
800
|
+
{{ content | safe }}
|
|
801
|
+
</div>
|
|
802
|
+
`;
|
|
803
|
+
break;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
case "partial": {
|
|
807
|
+
const partialsDir = join(srcDir, "partials");
|
|
808
|
+
await ensureDir(partialsDir);
|
|
809
|
+
targetPath = join(partialsDir, `${slug}.njk`);
|
|
810
|
+
fileExt = ".njk";
|
|
811
|
+
content = `{# ${title} Partial #}
|
|
812
|
+
<div class="${slug}">
|
|
813
|
+
{# Your partial content here #}
|
|
814
|
+
</div>
|
|
815
|
+
`;
|
|
816
|
+
break;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
case "collection": {
|
|
820
|
+
const collectionsDir = join(srcDir, "collections");
|
|
821
|
+
await ensureDir(collectionsDir);
|
|
822
|
+
targetPath = join(collectionsDir, `${slug}.json`);
|
|
823
|
+
fileExt = ".json";
|
|
824
|
+
content = `[
|
|
825
|
+
{
|
|
826
|
+
"id": 1,
|
|
827
|
+
"title": "Sample ${title} Item",
|
|
828
|
+
"description": "Add your collection items here"
|
|
829
|
+
}
|
|
830
|
+
]
|
|
831
|
+
`;
|
|
832
|
+
break;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
default:
|
|
836
|
+
console.log(kolor.red(`ā Unknown content type: ${type}`));
|
|
837
|
+
console.log(kolor.dim("\nSupported types: page, post, layout, component, partial, collection\n"));
|
|
838
|
+
return { success: false, skipped: false };
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
if (existsSync(targetPath)) {
|
|
842
|
+
console.log(kolor.dim(` ā ļø Skipped ${relative(cwd, targetPath)} (already exists)`));
|
|
843
|
+
return { success: false, skipped: true };
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
await ensureDir(dirname(targetPath));
|
|
847
|
+
await writeFile(targetPath, content, "utf8");
|
|
848
|
+
|
|
849
|
+
console.log(kolor.dim(` ā
${relative(cwd, targetPath)}`));
|
|
850
|
+
return { success: true, skipped: false };
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
async function init() {
|
|
854
|
+
const targetDir = cwd;
|
|
855
|
+
console.log(kolor.cyan(kolor.bold("šļø Initializing Campsite in current directory...")));
|
|
856
|
+
|
|
857
|
+
// Check if already initialized
|
|
858
|
+
if (existsSync(join(targetDir, "campsite.config.js"))) {
|
|
859
|
+
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"));
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
// Create basic structure
|
|
865
|
+
const dirs = [
|
|
866
|
+
join(targetDir, "src", "pages"),
|
|
867
|
+
join(targetDir, "src", "layouts"),
|
|
868
|
+
join(targetDir, "public")
|
|
869
|
+
];
|
|
870
|
+
|
|
871
|
+
for (const dir of dirs) {
|
|
872
|
+
await ensureDir(dir);
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// Create basic config file
|
|
876
|
+
const configContent = `export default {
|
|
877
|
+
siteName: "My Campsite",
|
|
878
|
+
srcDir: "src",
|
|
879
|
+
outDir: "dist",
|
|
880
|
+
templateEngine: "nunjucks",
|
|
881
|
+
markdown: true,
|
|
882
|
+
integrations: {
|
|
883
|
+
nunjucks: true,
|
|
884
|
+
liquid: false,
|
|
885
|
+
mustache: false,
|
|
886
|
+
vue: false,
|
|
887
|
+
alpine: false
|
|
888
|
+
}
|
|
889
|
+
};
|
|
890
|
+
`;
|
|
891
|
+
await writeFile(join(targetDir, "campsite.config.js"), configContent, "utf8");
|
|
892
|
+
|
|
893
|
+
// Create basic layout
|
|
894
|
+
const layoutContent = `<!DOCTYPE html>
|
|
895
|
+
<html lang="en">
|
|
896
|
+
<head>
|
|
897
|
+
<meta charset="UTF-8">
|
|
898
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
899
|
+
<title>{{ title or site.name }}</title>
|
|
900
|
+
<link rel="stylesheet" href="/style.css">
|
|
901
|
+
</head>
|
|
902
|
+
<body>
|
|
903
|
+
{% block content %}
|
|
904
|
+
{{ content | safe }}
|
|
905
|
+
{% endblock %}
|
|
906
|
+
</body>
|
|
907
|
+
</html>
|
|
908
|
+
`;
|
|
909
|
+
await writeFile(join(targetDir, "src", "layouts", "base.njk"), layoutContent, "utf8");
|
|
910
|
+
|
|
911
|
+
// Create sample page
|
|
912
|
+
const pageContent = `---
|
|
913
|
+
layout: base.njk
|
|
914
|
+
title: Welcome to Campsite
|
|
915
|
+
---
|
|
916
|
+
|
|
917
|
+
# Welcome to Campsite! šļø
|
|
918
|
+
|
|
919
|
+
Your cozy static site is ready to build.
|
|
920
|
+
|
|
921
|
+
## Get Started
|
|
922
|
+
|
|
923
|
+
- Run \`campsite dev\` to start developing
|
|
924
|
+
- Edit pages in \`src/pages/\`
|
|
925
|
+
- Customize layouts in \`src/layouts/\`
|
|
926
|
+
|
|
927
|
+
Happy camping! š²š¦
|
|
928
|
+
`;
|
|
929
|
+
await writeFile(join(targetDir, "src", "pages", "index.md"), pageContent, "utf8");
|
|
930
|
+
|
|
931
|
+
// Create basic CSS
|
|
932
|
+
const cssContent = `* {
|
|
933
|
+
margin: 0;
|
|
934
|
+
padding: 0;
|
|
935
|
+
box-sizing: border-box;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
body {
|
|
939
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
940
|
+
line-height: 1.6;
|
|
941
|
+
padding: 2rem;
|
|
942
|
+
max-width: 800px;
|
|
943
|
+
margin: 0 auto;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
h1 { color: #2d5016; margin-bottom: 1rem; }
|
|
947
|
+
h2 { color: #4a7c2c; margin-top: 1.5rem; }
|
|
948
|
+
`;
|
|
949
|
+
await writeFile(join(targetDir, "public", "style.css"), cssContent, "utf8");
|
|
950
|
+
|
|
951
|
+
// Create .gitignore
|
|
952
|
+
const gitignoreContent = `node_modules/
|
|
953
|
+
dist/
|
|
954
|
+
.DS_Store
|
|
955
|
+
`;
|
|
956
|
+
await writeFile(join(targetDir, ".gitignore"), gitignoreContent, "utf8");
|
|
957
|
+
|
|
958
|
+
// Create package.json
|
|
959
|
+
const packageJson = {
|
|
960
|
+
name: basename(targetDir),
|
|
961
|
+
version: "0.0.1",
|
|
962
|
+
type: "module",
|
|
963
|
+
scripts: {
|
|
964
|
+
dev: "campsite dev",
|
|
965
|
+
build: "campsite build",
|
|
966
|
+
serve: "campsite serve",
|
|
967
|
+
preview: "campsite preview"
|
|
968
|
+
},
|
|
969
|
+
dependencies: {
|
|
970
|
+
basecampjs: "^0.0.8"
|
|
971
|
+
}
|
|
972
|
+
};
|
|
973
|
+
await writeFile(join(targetDir, "package.json"), JSON.stringify(packageJson, null, 2), "utf8");
|
|
974
|
+
|
|
975
|
+
console.log(kolor.green("ā
Campsite initialized successfully!\n"));
|
|
976
|
+
console.log(kolor.bold("Next steps:"));
|
|
977
|
+
console.log(kolor.dim(" 1. Install dependencies: npm install"));
|
|
978
|
+
console.log(kolor.dim(" 2. Start developing: campsite dev\n"));
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
async function clean() {
|
|
982
|
+
const config = await loadConfig(cwd);
|
|
983
|
+
const outDir = resolve(cwd, config.outDir || "dist");
|
|
984
|
+
|
|
985
|
+
if (!existsSync(outDir)) {
|
|
986
|
+
console.log(kolor.dim(`Nothing to clean. ${outDir} does not exist.`));
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
console.log(kolor.cyan(`š§¹ Cleaning ${relative(cwd, outDir)}...`));
|
|
991
|
+
await rm(outDir, { recursive: true, force: true });
|
|
992
|
+
console.log(kolor.green(`ā
Cleaned ${relative(cwd, outDir)}\n`));
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
async function check() {
|
|
996
|
+
console.log(kolor.cyan(kolor.bold("š Checking Campsite project...\n")));
|
|
997
|
+
let hasIssues = false;
|
|
998
|
+
|
|
999
|
+
// Check if campsite.config.js exists
|
|
1000
|
+
const configPath = join(cwd, "campsite.config.js");
|
|
1001
|
+
if (!existsSync(configPath)) {
|
|
1002
|
+
console.log(kolor.red("ā campsite.config.js not found"));
|
|
1003
|
+
console.log(kolor.dim(" Run 'campsite init' to initialize a project\n"));
|
|
1004
|
+
hasIssues = true;
|
|
1005
|
+
} else {
|
|
1006
|
+
console.log(kolor.green("ā
campsite.config.js found"));
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// Load and validate config
|
|
1010
|
+
const config = await loadConfig(cwd);
|
|
1011
|
+
const srcDir = resolve(cwd, config.srcDir || "src");
|
|
1012
|
+
const pagesDir = join(srcDir, "pages");
|
|
1013
|
+
const layoutsDir = join(srcDir, "layouts");
|
|
1014
|
+
const publicDir = resolve(cwd, "public");
|
|
1015
|
+
|
|
1016
|
+
// Check src directory
|
|
1017
|
+
if (!existsSync(srcDir)) {
|
|
1018
|
+
console.log(kolor.red(`ā Source directory not found: ${relative(cwd, srcDir)}`));
|
|
1019
|
+
hasIssues = true;
|
|
1020
|
+
} else {
|
|
1021
|
+
console.log(kolor.green(`ā
Source directory exists: ${relative(cwd, srcDir)}`));
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
// Check pages directory
|
|
1025
|
+
if (!existsSync(pagesDir)) {
|
|
1026
|
+
console.log(kolor.yellow(`ā ļø Pages directory not found: ${relative(cwd, pagesDir)}`));
|
|
1027
|
+
hasIssues = true;
|
|
1028
|
+
} else {
|
|
1029
|
+
const files = await walkFiles(pagesDir);
|
|
1030
|
+
if (files.length === 0) {
|
|
1031
|
+
console.log(kolor.yellow(`ā ļø No pages found in ${relative(cwd, pagesDir)}`));
|
|
1032
|
+
hasIssues = true;
|
|
1033
|
+
} else {
|
|
1034
|
+
console.log(kolor.green(`ā
Found ${files.length} page(s) in ${relative(cwd, pagesDir)}`));
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
// Check layouts directory
|
|
1039
|
+
if (existsSync(layoutsDir)) {
|
|
1040
|
+
const layouts = await readdir(layoutsDir).catch(() => []);
|
|
1041
|
+
console.log(kolor.green(`ā
Found ${layouts.length} layout(s) in ${relative(cwd, layoutsDir)}`));
|
|
1042
|
+
} else {
|
|
1043
|
+
console.log(kolor.dim(`ā¹ļø No layouts directory (${relative(cwd, layoutsDir)})`));
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
// Check public directory
|
|
1047
|
+
if (existsSync(publicDir)) {
|
|
1048
|
+
console.log(kolor.green(`ā
Public directory exists: ${relative(cwd, publicDir)}`));
|
|
1049
|
+
} else {
|
|
1050
|
+
console.log(kolor.dim(`ā¹ļø No public directory (${relative(cwd, publicDir)})`));
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// Check for package.json and dependencies
|
|
1054
|
+
const pkgPath = join(cwd, "package.json");
|
|
1055
|
+
if (existsSync(pkgPath)) {
|
|
1056
|
+
try {
|
|
1057
|
+
const pkgRaw = await readFile(pkgPath, "utf8");
|
|
1058
|
+
const pkg = JSON.parse(pkgRaw);
|
|
1059
|
+
if (pkg.dependencies?.basecampjs || pkg.devDependencies?.basecampjs) {
|
|
1060
|
+
console.log(kolor.green("ā
basecampjs dependency found"));
|
|
1061
|
+
} else {
|
|
1062
|
+
console.log(kolor.yellow("ā ļø basecampjs not listed in dependencies"));
|
|
1063
|
+
console.log(kolor.dim(" Consider adding: npm install basecampjs"));
|
|
1064
|
+
}
|
|
1065
|
+
} catch {
|
|
1066
|
+
console.log(kolor.yellow("ā ļø Could not parse package.json"));
|
|
1067
|
+
}
|
|
1068
|
+
} else {
|
|
1069
|
+
console.log(kolor.dim("ā¹ļø No package.json found"));
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
console.log();
|
|
1073
|
+
if (hasIssues) {
|
|
1074
|
+
console.log(kolor.yellow("ā ļø Some issues found. Review the messages above."));
|
|
1075
|
+
} else {
|
|
1076
|
+
console.log(kolor.green(kolor.bold("š Everything looks good! Ready to build.")));
|
|
1077
|
+
}
|
|
1078
|
+
console.log();
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
async function upgrade() {
|
|
1082
|
+
console.log(kolor.cyan(kolor.bold("ā¬ļø Checking for CampsiteJS updates...\n")));
|
|
1083
|
+
|
|
1084
|
+
// Check if package.json exists
|
|
1085
|
+
const pkgPath = join(cwd, "package.json");
|
|
1086
|
+
if (!existsSync(pkgPath)) {
|
|
1087
|
+
console.log(kolor.red("ā package.json not found"));
|
|
1088
|
+
console.log(kolor.dim("This command should be run in a Campsite project directory.\n"));
|
|
1089
|
+
exit(1);
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
// Read current package.json
|
|
1093
|
+
let pkg;
|
|
1094
|
+
try {
|
|
1095
|
+
const pkgRaw = await readFile(pkgPath, "utf8");
|
|
1096
|
+
pkg = JSON.parse(pkgRaw);
|
|
1097
|
+
} catch {
|
|
1098
|
+
console.log(kolor.red("ā Could not read package.json\n"));
|
|
1099
|
+
exit(1);
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
const currentVersion = pkg.dependencies?.basecampjs || pkg.devDependencies?.basecampjs;
|
|
1103
|
+
if (!currentVersion) {
|
|
1104
|
+
console.log(kolor.yellow("ā ļø basecampjs not found in dependencies"));
|
|
1105
|
+
console.log(kolor.dim("Install it with: npm install basecampjs\n"));
|
|
1106
|
+
exit(1);
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
console.log(kolor.dim(`Current version: ${currentVersion}`));
|
|
1110
|
+
console.log(kolor.cyan("\nUpgrading basecampjs to latest version...\n"));
|
|
1111
|
+
|
|
1112
|
+
// Use dynamic import to run npm commands
|
|
1113
|
+
const { spawn } = await import("child_process");
|
|
1114
|
+
|
|
1115
|
+
return new Promise((resolve, reject) => {
|
|
1116
|
+
const child = spawn("npm", ["install", "basecampjs@latest"], {
|
|
1117
|
+
cwd,
|
|
1118
|
+
stdio: "inherit",
|
|
1119
|
+
shell: process.platform === "win32"
|
|
1120
|
+
});
|
|
1121
|
+
|
|
1122
|
+
child.on("close", async (code) => {
|
|
1123
|
+
if (code === 0) {
|
|
1124
|
+
console.log();
|
|
1125
|
+
console.log(kolor.green("ā
CampsiteJS updated successfully!"));
|
|
1126
|
+
|
|
1127
|
+
// Read updated version
|
|
1128
|
+
try {
|
|
1129
|
+
const updatedPkgRaw = await readFile(pkgPath, "utf8");
|
|
1130
|
+
const updatedPkg = JSON.parse(updatedPkgRaw);
|
|
1131
|
+
const newVersion = updatedPkg.dependencies?.basecampjs || updatedPkg.devDependencies?.basecampjs;
|
|
1132
|
+
console.log(kolor.dim(`New version: ${newVersion}`));
|
|
1133
|
+
} catch {}
|
|
1134
|
+
|
|
1135
|
+
console.log();
|
|
1136
|
+
console.log(kolor.dim("š² Tip: Run 'campsite dev' to start developing with the latest version\n"));
|
|
1137
|
+
resolve();
|
|
1138
|
+
} else {
|
|
1139
|
+
console.log();
|
|
1140
|
+
console.log(kolor.red(`ā Update failed with code ${code}\n`));
|
|
1141
|
+
reject(new Error(`npm install failed with code ${code}`));
|
|
1142
|
+
}
|
|
1143
|
+
});
|
|
1144
|
+
|
|
1145
|
+
child.on("error", (err) => {
|
|
1146
|
+
console.log(kolor.red(`ā Update failed: ${err.message}\n`));
|
|
1147
|
+
reject(err);
|
|
1148
|
+
});
|
|
1149
|
+
});
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
async function list() {
|
|
1153
|
+
console.log(kolor.cyan(kolor.bold("šŗļø Listing Campsite content...\n")));
|
|
1154
|
+
|
|
1155
|
+
const config = await loadConfig(cwd);
|
|
1156
|
+
const srcDir = resolve(cwd, config.srcDir || "src");
|
|
1157
|
+
const pagesDir = join(srcDir, "pages");
|
|
1158
|
+
const layoutsDir = join(srcDir, "layouts");
|
|
1159
|
+
const componentsDir = join(srcDir, "components");
|
|
1160
|
+
const partialsDir = join(srcDir, "partials");
|
|
1161
|
+
const collectionsDir = join(srcDir, "collections");
|
|
1162
|
+
const dataDir = join(srcDir, "data");
|
|
1163
|
+
|
|
1164
|
+
// List pages
|
|
1165
|
+
if (existsSync(pagesDir)) {
|
|
1166
|
+
const pages = await walkFiles(pagesDir);
|
|
1167
|
+
if (pages.length > 0) {
|
|
1168
|
+
console.log(kolor.bold("š Pages (") + kolor.cyan(pages.length.toString()) + kolor.bold(")"));
|
|
1169
|
+
pages.forEach(page => {
|
|
1170
|
+
const rel = relative(pagesDir, page);
|
|
1171
|
+
console.log(" " + kolor.dim("⢠") + rel);
|
|
1172
|
+
});
|
|
1173
|
+
console.log();
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
// List layouts
|
|
1178
|
+
if (existsSync(layoutsDir)) {
|
|
1179
|
+
const layouts = await readdir(layoutsDir).catch(() => []);
|
|
1180
|
+
if (layouts.length > 0) {
|
|
1181
|
+
console.log(kolor.bold("š Layouts (") + kolor.cyan(layouts.length.toString()) + kolor.bold(")"));
|
|
1182
|
+
layouts.forEach(layout => {
|
|
1183
|
+
console.log(" " + kolor.dim("⢠") + layout);
|
|
1184
|
+
});
|
|
1185
|
+
console.log();
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
// List components
|
|
1190
|
+
if (existsSync(componentsDir)) {
|
|
1191
|
+
const components = await readdir(componentsDir).catch(() => []);
|
|
1192
|
+
if (components.length > 0) {
|
|
1193
|
+
console.log(kolor.bold("š§© Components (") + kolor.cyan(components.length.toString()) + kolor.bold(")"));
|
|
1194
|
+
components.forEach(component => {
|
|
1195
|
+
console.log(" " + kolor.dim("⢠") + component);
|
|
1196
|
+
});
|
|
1197
|
+
console.log();
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
// List partials
|
|
1202
|
+
if (existsSync(partialsDir)) {
|
|
1203
|
+
const partials = await readdir(partialsDir).catch(() => []);
|
|
1204
|
+
if (partials.length > 0) {
|
|
1205
|
+
console.log(kolor.bold("š§° Partials (") + kolor.cyan(partials.length.toString()) + kolor.bold(")"));
|
|
1206
|
+
partials.forEach(partial => {
|
|
1207
|
+
console.log(" " + kolor.dim("⢠") + partial);
|
|
1208
|
+
});
|
|
1209
|
+
console.log();
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
// List collections
|
|
1214
|
+
if (existsSync(collectionsDir)) {
|
|
1215
|
+
const collections = await readdir(collectionsDir).catch(() => []);
|
|
1216
|
+
const jsonFiles = collections.filter(f => f.endsWith(".json"));
|
|
1217
|
+
if (jsonFiles.length > 0) {
|
|
1218
|
+
console.log(kolor.bold("š Collections (") + kolor.cyan(jsonFiles.length.toString()) + kolor.bold(")"));
|
|
1219
|
+
jsonFiles.forEach(collection => {
|
|
1220
|
+
console.log(" " + kolor.dim("⢠") + collection);
|
|
1221
|
+
});
|
|
1222
|
+
console.log();
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
// List data files
|
|
1227
|
+
if (existsSync(dataDir)) {
|
|
1228
|
+
const dataFiles = await readdir(dataDir).catch(() => []);
|
|
1229
|
+
const jsonFiles = dataFiles.filter(f => f.endsWith(".json"));
|
|
1230
|
+
if (jsonFiles.length > 0) {
|
|
1231
|
+
console.log(kolor.bold("š Data (") + kolor.cyan(jsonFiles.length.toString()) + kolor.bold(")"));
|
|
1232
|
+
jsonFiles.forEach(dataFile => {
|
|
1233
|
+
console.log(" " + kolor.dim("⢠") + dataFile);
|
|
1234
|
+
});
|
|
1235
|
+
console.log();
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
console.log(kolor.dim("š² Tip: Use 'campsite make:<type> <name>' to create new content\n"));
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
async function preview() {
|
|
1243
|
+
console.log(kolor.cyan(kolor.bold("šļø Building for production preview...\n")));
|
|
1244
|
+
await build();
|
|
1245
|
+
console.log();
|
|
1246
|
+
const config = await loadConfig(cwd);
|
|
1247
|
+
const outDir = resolve(cwd, config.outDir || "dist");
|
|
1248
|
+
console.log(kolor.cyan(kolor.bold("š„ Starting preview server...\n")));
|
|
1249
|
+
serve(outDir);
|
|
1250
|
+
}
|
|
1251
|
+
|
|
536
1252
|
async function main() {
|
|
537
1253
|
const command = argv[2] || "help";
|
|
538
1254
|
|
|
1255
|
+
// Handle flags
|
|
1256
|
+
if (command === "-h" || command === "--help" || command === "help") {
|
|
1257
|
+
showHelp();
|
|
1258
|
+
exit(0);
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
if (command === "-v" || command === "--version") {
|
|
1262
|
+
const version = await getVersion();
|
|
1263
|
+
console.log(`v${version}`);
|
|
1264
|
+
exit(0);
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
// Handle make:type commands
|
|
1268
|
+
if (command.startsWith("make:")) {
|
|
1269
|
+
const type = command.substring(5); // Remove 'make:' prefix
|
|
1270
|
+
if (!type) {
|
|
1271
|
+
console.log(kolor.red("ā No type specified"));
|
|
1272
|
+
console.log(kolor.dim("Run 'campsite --help' for available make commands.\n"));
|
|
1273
|
+
exit(1);
|
|
1274
|
+
}
|
|
1275
|
+
await makeContent(type);
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
539
1279
|
switch (command) {
|
|
1280
|
+
case "init":
|
|
1281
|
+
await init();
|
|
1282
|
+
break;
|
|
540
1283
|
case "dev":
|
|
541
1284
|
await dev();
|
|
542
1285
|
break;
|
|
@@ -552,9 +1295,25 @@ async function main() {
|
|
|
552
1295
|
serve(outDir);
|
|
553
1296
|
break;
|
|
554
1297
|
}
|
|
1298
|
+
case "preview":
|
|
1299
|
+
await preview();
|
|
1300
|
+
break;
|
|
1301
|
+
case "clean":
|
|
1302
|
+
await clean();
|
|
1303
|
+
break;
|
|
1304
|
+
case "check":
|
|
1305
|
+
await check();
|
|
1306
|
+
break;
|
|
1307
|
+
case "list":
|
|
1308
|
+
await list();
|
|
1309
|
+
break;
|
|
1310
|
+
case "upgrade":
|
|
1311
|
+
await upgrade();
|
|
1312
|
+
break;
|
|
555
1313
|
default:
|
|
556
|
-
console.log(
|
|
557
|
-
|
|
1314
|
+
console.log(kolor.yellow(`Unknown command: ${command}`));
|
|
1315
|
+
console.log(kolor.dim("Run 'campsite --help' for usage information."));
|
|
1316
|
+
exit(1);
|
|
558
1317
|
}
|
|
559
1318
|
}
|
|
560
1319
|
|