@daz4126/swifty 2.11.0 → 3.0.0
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/README.md +15 -4
- package/package.json +3 -2
- package/src/assets.js +116 -46
- package/src/build.js +57 -1
- package/src/cli.js +51 -7
- package/src/client/IDIOMORPH-LICENSE.txt +13 -0
- package/src/client/idiomorph.esm.js +1385 -0
- package/src/client/swifty-navigation.js +399 -0
- package/src/concurrency.js +25 -0
- package/src/config.js +36 -2
- package/src/data.js +70 -0
- package/src/init.js +39 -33
- package/src/layout.js +16 -11
- package/src/pages.js +231 -119
- package/src/pagination.js +58 -0
- package/src/partials.js +236 -42
- package/src/rss.js +6 -4
- package/src/sitemap.js +89 -0
- package/src/watcher.js +36 -9
package/README.md
CHANGED
|
@@ -15,13 +15,21 @@ Swifty uses convention over configuration to make it super simple to build blazi
|
|
|
15
15
|
- **RSS feed generation** for blogs and content folders
|
|
16
16
|
- **Draft mode** for work-in-progress pages (visible in dev, hidden in production)
|
|
17
17
|
- **Scheduled publishing** via future dates in front matter
|
|
18
|
-
- **
|
|
18
|
+
- **Contact forms** via third-party services (Formspree, Netlify Forms, etc.)
|
|
19
|
+
- **Pagination** for folders with many pages
|
|
20
|
+
- **Data files** - Load JSON/YAML data and use in templates
|
|
21
|
+
- **Open Graph tags** - Auto-generated social sharing meta tags
|
|
22
|
+
- **Word count & reading time** - Auto-calculated for blog posts
|
|
23
|
+
- **Previous/next navigation** - Auto-generated links between sibling pages
|
|
24
|
+
- **[Eta templating](https://eta.js.org/)** - Full JavaScript in templates with EJS syntax
|
|
25
|
+
- **Idiomorph navigation** with optional intent prefetching for SPA-like transitions
|
|
19
26
|
|
|
20
27
|
## Quickstart
|
|
21
28
|
|
|
22
29
|
```bash
|
|
23
|
-
npm install @daz4126/swifty
|
|
24
|
-
|
|
30
|
+
npm install -g @daz4126/swifty
|
|
31
|
+
swifty my-site
|
|
32
|
+
cd my-site
|
|
25
33
|
npx swifty start
|
|
26
34
|
```
|
|
27
35
|
|
|
@@ -34,6 +42,7 @@ your-site/
|
|
|
34
42
|
├── pages/ # Markdown content (folder structure = URLs)
|
|
35
43
|
├── layouts/ # HTML layout templates
|
|
36
44
|
├── partials/ # Reusable content snippets
|
|
45
|
+
├── data/ # JSON/YAML data files
|
|
37
46
|
├── css/ # Stylesheets (auto-injected)
|
|
38
47
|
├── js/ # JavaScript (auto-injected)
|
|
39
48
|
├── images/ # Images (auto-optimized to WebP)
|
|
@@ -44,16 +53,18 @@ your-site/
|
|
|
44
53
|
## Commands
|
|
45
54
|
|
|
46
55
|
```bash
|
|
47
|
-
npx swifty
|
|
56
|
+
npx swifty my-site # Create new site in my-site/ folder
|
|
48
57
|
npx swifty build # Build static site to dist/ (for production)
|
|
49
58
|
npx swifty start # Build, watch, and serve at localhost:3000 (for development)
|
|
50
59
|
npx swifty build --out dir # Build to custom output directory
|
|
60
|
+
npx swifty deploy "message" # Build, git add, commit, and push (message optional)
|
|
51
61
|
```
|
|
52
62
|
|
|
53
63
|
### Development vs Production
|
|
54
64
|
|
|
55
65
|
- **`swifty start`** - For development. Includes live reload (auto-refreshes browser on file changes) and file watching with incremental builds for CSS/JS/images.
|
|
56
66
|
- **`swifty build`** - For production deployment. Produces clean output without any development scripts.
|
|
67
|
+
- **`swifty deploy "message"`** - Builds the site and commits/pushes to git in one command.
|
|
57
68
|
|
|
58
69
|
## Documentation
|
|
59
70
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@daz4126/swifty",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"description": "Super Speedy Static Site Generator",
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"chokidar": "^4.0.0",
|
|
29
|
+
"eta": "^4.5.0",
|
|
29
30
|
"fs-extra": "^11.2.0",
|
|
30
31
|
"gray-matter": "^4.0.3",
|
|
31
32
|
"highlight.js": "^11.11.1",
|
|
@@ -33,7 +34,7 @@
|
|
|
33
34
|
"livereload": "^0.10.3",
|
|
34
35
|
"marked": "^14.1.3",
|
|
35
36
|
"marked-highlight": "^2.2.1",
|
|
36
|
-
"serve": "^14.2.
|
|
37
|
+
"serve": "^14.2.5",
|
|
37
38
|
"sharp": "^0.33.5"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
package/src/assets.js
CHANGED
|
@@ -2,7 +2,13 @@ import fs from "fs/promises";
|
|
|
2
2
|
import fsExtra from "fs-extra";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import sharp from "sharp";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
5
6
|
import { dirs, defaultConfig } from "./config.js";
|
|
7
|
+
import { mapLimit } from "./concurrency.js";
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
const clientAssetsDir = path.join(__dirname, "client");
|
|
6
12
|
|
|
7
13
|
// Get file modification timestamp for cache busting
|
|
8
14
|
const getFileMtime = async (filePath) => {
|
|
@@ -13,7 +19,54 @@ const getFileMtime = async (filePath) => {
|
|
|
13
19
|
const validExtensions = {
|
|
14
20
|
css: [".css"],
|
|
15
21
|
js: [".js"],
|
|
16
|
-
images: [".png", ".jpg", ".jpeg", ".gif", ".svg", "
|
|
22
|
+
images: [".png", ".jpg", ".jpeg", ".gif", ".svg", ".webp"],
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const optimizableImageExtensions = [".jpg", ".jpeg", ".png"];
|
|
26
|
+
|
|
27
|
+
const getBuildConcurrency = () => defaultConfig.build_concurrency || 16;
|
|
28
|
+
const navigationEnabled = () => defaultConfig.morphing !== false;
|
|
29
|
+
|
|
30
|
+
const isDestinationFresh = async (source, destination) => {
|
|
31
|
+
try {
|
|
32
|
+
const [sourceStats, destinationStats] = await Promise.all([
|
|
33
|
+
fs.stat(source),
|
|
34
|
+
fs.stat(destination),
|
|
35
|
+
]);
|
|
36
|
+
return destinationStats.mtimeMs >= sourceStats.mtimeMs;
|
|
37
|
+
} catch (err) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const copyIfStale = async (source, destination) => {
|
|
43
|
+
if (await isDestinationFresh(source, destination)) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
await fsExtra.copy(source, destination);
|
|
48
|
+
return true;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const optimizeImageToWebp = async (source, destination) => {
|
|
52
|
+
if (await isDestinationFresh(source, destination)) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const image = sharp(source);
|
|
57
|
+
const metadata = await image.metadata();
|
|
58
|
+
const originalWidth = metadata.width || 0;
|
|
59
|
+
const maxWidth = defaultConfig.max_image_width || 800;
|
|
60
|
+
const imageQuality = defaultConfig.image_quality || 80;
|
|
61
|
+
const resizeOptions =
|
|
62
|
+
originalWidth > 0 ? { width: Math.min(originalWidth, maxWidth) } : {};
|
|
63
|
+
|
|
64
|
+
await image
|
|
65
|
+
.resize(resizeOptions)
|
|
66
|
+
.toFormat("webp", { quality: imageQuality })
|
|
67
|
+
.toFile(destination);
|
|
68
|
+
|
|
69
|
+
return true;
|
|
17
70
|
};
|
|
18
71
|
|
|
19
72
|
const ensureAndCopy = async (source, destination, validExts) => {
|
|
@@ -33,6 +86,29 @@ const ensureAndCopy = async (source, destination, validExts) => {
|
|
|
33
86
|
console.log(`No ${path.basename(source)} found in ${source}`);
|
|
34
87
|
}
|
|
35
88
|
};
|
|
89
|
+
|
|
90
|
+
const copyNavigationAssets = async (outputDir = dirs.dist) => {
|
|
91
|
+
if (!navigationEnabled()) return;
|
|
92
|
+
if (!(await fsExtra.pathExists(clientAssetsDir))) return;
|
|
93
|
+
|
|
94
|
+
const destination = path.join(outputDir, "swifty");
|
|
95
|
+
await fsExtra.ensureDir(destination);
|
|
96
|
+
|
|
97
|
+
const files = await fs.readdir(clientAssetsDir);
|
|
98
|
+
await mapLimit(
|
|
99
|
+
files,
|
|
100
|
+
async (file) => {
|
|
101
|
+
const sourcePath = path.join(clientAssetsDir, file);
|
|
102
|
+
const destinationPath = path.join(destination, file);
|
|
103
|
+
const copied = await copyIfStale(sourcePath, destinationPath);
|
|
104
|
+
if (copied) {
|
|
105
|
+
console.log(`Copied Swifty navigation asset ${file}`);
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
getBuildConcurrency(),
|
|
109
|
+
);
|
|
110
|
+
};
|
|
111
|
+
|
|
36
112
|
const copyAssets = async (outputDir = dirs.dist) => {
|
|
37
113
|
await ensureAndCopy(
|
|
38
114
|
dirs.css,
|
|
@@ -40,47 +116,48 @@ const copyAssets = async (outputDir = dirs.dist) => {
|
|
|
40
116
|
validExtensions.css,
|
|
41
117
|
);
|
|
42
118
|
await ensureAndCopy(dirs.js, path.join(outputDir, "js"), validExtensions.js);
|
|
43
|
-
await
|
|
44
|
-
dirs.images,
|
|
45
|
-
path.join(outputDir, "images"),
|
|
46
|
-
validExtensions.images,
|
|
47
|
-
);
|
|
119
|
+
await copyNavigationAssets(outputDir);
|
|
48
120
|
};
|
|
49
121
|
async function optimizeImages(outputDir = dirs.dist) {
|
|
50
122
|
try {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
123
|
+
if (!(await fsExtra.pathExists(dirs.images))) {
|
|
124
|
+
console.log(`No ${path.basename(dirs.images)} found in ${dirs.images}`);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const imagesFolder = path.join(outputDir, "images");
|
|
129
|
+
await fsExtra.ensureDir(imagesFolder);
|
|
130
|
+
|
|
131
|
+
const files = await fs.readdir(dirs.images);
|
|
132
|
+
|
|
133
|
+
await mapLimit(
|
|
134
|
+
files.filter((file) =>
|
|
135
|
+
validExtensions.images.includes(path.extname(file).toLowerCase()),
|
|
136
|
+
),
|
|
137
|
+
async (file) => {
|
|
138
|
+
const filePath = path.join(dirs.images, file);
|
|
58
139
|
const ext = path.extname(file).toLowerCase();
|
|
59
140
|
|
|
60
|
-
if (!
|
|
141
|
+
if (!optimizableImageExtensions.includes(ext)) {
|
|
142
|
+
const destination = path.join(imagesFolder, file);
|
|
143
|
+
const copied = await copyIfStale(filePath, destination);
|
|
144
|
+
if (copied) {
|
|
145
|
+
console.log(`Copied ${file}`);
|
|
146
|
+
}
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
61
149
|
|
|
62
150
|
const optimizedPath = path.join(
|
|
63
|
-
|
|
151
|
+
imagesFolder,
|
|
64
152
|
`${path.basename(file, ext)}.webp`,
|
|
65
153
|
);
|
|
66
154
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const metadata = await image.metadata();
|
|
70
|
-
const originalWidth = metadata.width || 0;
|
|
71
|
-
const maxWidth = defaultConfig.max_image_size || 800;
|
|
72
|
-
const resizeWidth = Math.min(originalWidth, maxWidth);
|
|
73
|
-
|
|
74
|
-
await image
|
|
75
|
-
.resize({ width: resizeWidth })
|
|
76
|
-
.toFormat("webp", { quality: 80 })
|
|
77
|
-
.toFile(optimizedPath);
|
|
78
|
-
|
|
79
|
-
await fs.unlink(filePath);
|
|
80
|
-
|
|
155
|
+
const optimized = await optimizeImageToWebp(filePath, optimizedPath);
|
|
156
|
+
if (optimized) {
|
|
81
157
|
console.log(`Optimized ${file} -> ${optimizedPath}`);
|
|
82
158
|
}
|
|
83
|
-
}
|
|
159
|
+
},
|
|
160
|
+
getBuildConcurrency(),
|
|
84
161
|
);
|
|
85
162
|
} catch (error) {
|
|
86
163
|
console.error("Error optimizing images:", error);
|
|
@@ -148,7 +225,6 @@ const copySingleAsset = async (filePath, outputDir = dirs.dist) => {
|
|
|
148
225
|
|
|
149
226
|
// Process a single image
|
|
150
227
|
const optimizeSingleImage = async (filePath, outputDir = dirs.dist) => {
|
|
151
|
-
const IMAGE_EXTENSIONS = [".jpg", ".jpeg", ".png"];
|
|
152
228
|
const filename = path.basename(filePath);
|
|
153
229
|
const ext = path.extname(filePath).toLowerCase();
|
|
154
230
|
const imagesFolder = path.join(outputDir, "images");
|
|
@@ -156,26 +232,20 @@ const optimizeSingleImage = async (filePath, outputDir = dirs.dist) => {
|
|
|
156
232
|
await fsExtra.ensureDir(imagesFolder);
|
|
157
233
|
|
|
158
234
|
// For non-optimizable images (svg, webp, gif), just copy
|
|
159
|
-
if (!
|
|
160
|
-
await
|
|
161
|
-
|
|
235
|
+
if (!optimizableImageExtensions.includes(ext)) {
|
|
236
|
+
const copied = await copyIfStale(filePath, path.join(imagesFolder, filename));
|
|
237
|
+
if (copied) {
|
|
238
|
+
console.log(`Copied ${filename}`);
|
|
239
|
+
}
|
|
162
240
|
return true;
|
|
163
241
|
}
|
|
164
242
|
|
|
165
243
|
// Optimize jpg/jpeg/png to webp
|
|
166
244
|
const optimizedPath = path.join(imagesFolder, `${path.basename(filename, ext)}.webp`);
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const resizeWidth = Math.min(originalWidth, maxWidth);
|
|
172
|
-
|
|
173
|
-
await image
|
|
174
|
-
.resize({ width: resizeWidth })
|
|
175
|
-
.toFormat("webp", { quality: 80 })
|
|
176
|
-
.toFile(optimizedPath);
|
|
177
|
-
|
|
178
|
-
console.log(`Optimized ${filename} -> ${path.basename(optimizedPath)}`);
|
|
245
|
+
const optimized = await optimizeImageToWebp(filePath, optimizedPath);
|
|
246
|
+
if (optimized) {
|
|
247
|
+
console.log(`Optimized ${filename} -> ${path.basename(optimizedPath)}`);
|
|
248
|
+
}
|
|
179
249
|
return true;
|
|
180
250
|
};
|
|
181
251
|
|
package/src/build.js
CHANGED
|
@@ -1,13 +1,69 @@
|
|
|
1
1
|
import { copyAssets, optimizeImages } from "./assets.js";
|
|
2
2
|
import { generatePages, createPages, addLinks } from "./pages.js";
|
|
3
3
|
import { generateRssFeeds } from "./rss.js";
|
|
4
|
+
import { generateSeoFiles } from "./sitemap.js";
|
|
4
5
|
import { dirs } from "./config.js";
|
|
5
6
|
|
|
6
|
-
export default async function build(outputDir) {
|
|
7
|
+
export default async function build(outputDir = dirs.dist) {
|
|
8
|
+
const startTime = performance.now();
|
|
9
|
+
console.log("🚀 Starting build...");
|
|
10
|
+
|
|
11
|
+
const copyStart = performance.now();
|
|
7
12
|
await copyAssets(outputDir);
|
|
13
|
+
const copyTime = performance.now() - copyStart;
|
|
14
|
+
console.log(`📁 Assets copied in ${(copyTime / 1000).toFixed(2)}s`);
|
|
15
|
+
|
|
16
|
+
const imageStart = performance.now();
|
|
8
17
|
await optimizeImages(outputDir);
|
|
18
|
+
const imageTime = performance.now() - imageStart;
|
|
19
|
+
console.log(`🖼️ Images optimized in ${(imageTime / 1000).toFixed(2)}s`);
|
|
20
|
+
|
|
21
|
+
const pagesStart = performance.now();
|
|
9
22
|
const pages = await generatePages(dirs.pages);
|
|
23
|
+
const pagesTime = performance.now() - pagesStart;
|
|
24
|
+
console.log(`📄 Pages generated in ${(pagesTime / 1000).toFixed(2)}s`);
|
|
25
|
+
|
|
26
|
+
const linksStart = performance.now();
|
|
10
27
|
await addLinks(pages);
|
|
28
|
+
const linksTime = performance.now() - linksStart;
|
|
29
|
+
console.log(`🔗 Links added in ${(linksTime / 1000).toFixed(2)}s`);
|
|
30
|
+
|
|
31
|
+
const createStart = performance.now();
|
|
11
32
|
await createPages(pages, outputDir);
|
|
33
|
+
const createTime = performance.now() - createStart;
|
|
34
|
+
console.log(`✨ Pages created in ${(createTime / 1000).toFixed(2)}s`);
|
|
35
|
+
|
|
36
|
+
const rssStart = performance.now();
|
|
12
37
|
await generateRssFeeds(pages, outputDir);
|
|
38
|
+
const rssTime = performance.now() - rssStart;
|
|
39
|
+
console.log(`📡 RSS feeds generated in ${(rssTime / 1000).toFixed(2)}s`);
|
|
40
|
+
|
|
41
|
+
const seoStart = performance.now();
|
|
42
|
+
await generateSeoFiles(pages, outputDir);
|
|
43
|
+
const seoTime = performance.now() - seoStart;
|
|
44
|
+
console.log(`🧭 SEO files generated in ${(seoTime / 1000).toFixed(2)}s`);
|
|
45
|
+
|
|
46
|
+
const totalTime = performance.now() - startTime;
|
|
47
|
+
console.log(`\n✅ Build completed in ${(totalTime / 1000).toFixed(2)}s`);
|
|
48
|
+
|
|
49
|
+
// Performance summary
|
|
50
|
+
const stages = [
|
|
51
|
+
{ name: "Assets", time: copyTime },
|
|
52
|
+
{ name: "Images", time: imageTime },
|
|
53
|
+
{ name: "Pages", time: pagesTime },
|
|
54
|
+
{ name: "Links", time: linksTime },
|
|
55
|
+
{ name: "Create", time: createTime },
|
|
56
|
+
{ name: "RSS", time: rssTime },
|
|
57
|
+
{ name: "SEO", time: seoTime }
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
const slowest = stages.sort((a, b) => b.time - a.time)[0];
|
|
61
|
+
if (slowest.time > totalTime * 0.3) {
|
|
62
|
+
console.log(`⚠️ Slowest stage: ${slowest.name} (${(slowest.time / 1000).toFixed(2)}s)`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Force output to appear
|
|
66
|
+
if (process.stdout.isTTY) {
|
|
67
|
+
process.stdout.write('\n');
|
|
68
|
+
}
|
|
13
69
|
}
|
package/src/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { spawn } from "child_process";
|
|
3
|
+
import { spawn, execSync } from "child_process";
|
|
4
4
|
|
|
5
5
|
const args = process.argv.slice(2);
|
|
6
6
|
let outDir = "dist"; // default
|
|
@@ -14,13 +14,22 @@ if (outIndex !== -1 && args[outIndex + 1]) {
|
|
|
14
14
|
// Pass outDir as an environment variable too (optional, still useful)
|
|
15
15
|
process.env.OUT_DIR = outDir;
|
|
16
16
|
|
|
17
|
+
const reservedCommands = ["build", "start", "watch", "deploy"];
|
|
18
|
+
|
|
17
19
|
async function main() {
|
|
18
20
|
const command = args[0];
|
|
19
21
|
|
|
22
|
+
// No arguments - show usage
|
|
23
|
+
if (!command) {
|
|
24
|
+
console.log(`Usage:`);
|
|
25
|
+
console.log(` swifty <sitename> Create a new site in <sitename> folder`);
|
|
26
|
+
console.log(` swifty build [--out folder] Build the site`);
|
|
27
|
+
console.log(` swifty start [--out folder] Build and serve with live reload`);
|
|
28
|
+
console.log(` swifty deploy ["message"] Build, commit, and push to git`);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
20
32
|
switch (command) {
|
|
21
|
-
case "init":
|
|
22
|
-
await import("./init.js");
|
|
23
|
-
break;
|
|
24
33
|
case "build": {
|
|
25
34
|
const build = await import("./build.js");
|
|
26
35
|
if (typeof build.default === "function") {
|
|
@@ -54,9 +63,44 @@ async function main() {
|
|
|
54
63
|
}
|
|
55
64
|
break;
|
|
56
65
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
case "deploy": {
|
|
67
|
+
const commitMessage = args[1] || "Deploying latest build";
|
|
68
|
+
|
|
69
|
+
// Build the site
|
|
70
|
+
const build = await import("./build.js");
|
|
71
|
+
if (typeof build.default === "function") {
|
|
72
|
+
await build.default(outDir);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Git operations
|
|
76
|
+
try {
|
|
77
|
+
console.log("Adding files to git...");
|
|
78
|
+
execSync("git add .", { stdio: "inherit" });
|
|
79
|
+
|
|
80
|
+
console.log("Committing changes...");
|
|
81
|
+
execSync(`git commit -m "${commitMessage.replace(/"/g, '\\"')}"`, {
|
|
82
|
+
stdio: "inherit",
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
console.log("Pushing to remote...");
|
|
86
|
+
execSync("git push", { stdio: "inherit" });
|
|
87
|
+
|
|
88
|
+
console.log("Deployed successfully!");
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error("Deploy failed:", error.message);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
default: {
|
|
96
|
+
// Treat as sitename for new project creation
|
|
97
|
+
const sitename = command;
|
|
98
|
+
const init = await import("./init.js");
|
|
99
|
+
if (typeof init.default === "function") {
|
|
100
|
+
await init.default(sitename);
|
|
101
|
+
}
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
60
104
|
}
|
|
61
105
|
}
|
|
62
106
|
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Zero-Clause BSD
|
|
2
|
+
=============
|
|
3
|
+
|
|
4
|
+
Permission to use, copy, modify, and/or distribute this software for
|
|
5
|
+
any purpose with or without fee is hereby granted.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL
|
|
8
|
+
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
|
|
9
|
+
OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE
|
|
10
|
+
FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
|
|
11
|
+
DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
|
12
|
+
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
|
13
|
+
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|