smippo 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/package.json +1 -1
- package/src/server.js +100 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "smippo",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"description": "S.M.I.P.P.O. — Structured Mirroring of Internet Pages and Public Objects. Modern website copier that captures sites exactly as they appear in your browser.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
package/src/server.js
CHANGED
|
@@ -4,6 +4,8 @@ import fs from 'fs-extra';
|
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import chalk from 'chalk';
|
|
6
6
|
import {exec} from 'child_process';
|
|
7
|
+
import * as p from '@clack/prompts';
|
|
8
|
+
import {readManifest} from './manifest.js';
|
|
7
9
|
|
|
8
10
|
// MIME type mapping
|
|
9
11
|
const MIME_TYPES = {
|
|
@@ -335,6 +337,7 @@ export async function createServer(options = {}) {
|
|
|
335
337
|
port: requestedPort = 8080,
|
|
336
338
|
host = '127.0.0.1',
|
|
337
339
|
open = false,
|
|
340
|
+
openPath = null, // Specific path to open (e.g., domain directory)
|
|
338
341
|
cors = true,
|
|
339
342
|
verbose = false,
|
|
340
343
|
quiet = false,
|
|
@@ -470,7 +473,10 @@ export async function createServer(options = {}) {
|
|
|
470
473
|
// Start listening
|
|
471
474
|
return new Promise(resolve => {
|
|
472
475
|
server.listen(port, host, () => {
|
|
473
|
-
const
|
|
476
|
+
const baseUrl = `http://${host === '0.0.0.0' ? 'localhost' : host}:${port}`;
|
|
477
|
+
|
|
478
|
+
// Build the URL with optional path
|
|
479
|
+
const url = openPath ? `${baseUrl}/${openPath}/` : baseUrl;
|
|
474
480
|
|
|
475
481
|
if (!quiet) {
|
|
476
482
|
console.log('');
|
|
@@ -570,16 +576,108 @@ function truncatePath(p, maxLen) {
|
|
|
570
576
|
return '...' + p.slice(-(maxLen - 3));
|
|
571
577
|
}
|
|
572
578
|
|
|
579
|
+
/**
|
|
580
|
+
* Get captured sites from a smippo output directory
|
|
581
|
+
*/
|
|
582
|
+
async function getCapturedSites(directory) {
|
|
583
|
+
const sites = [];
|
|
584
|
+
const smippoDir = path.join(directory, '.smippo');
|
|
585
|
+
|
|
586
|
+
if (!(await fs.pathExists(smippoDir))) {
|
|
587
|
+
return sites;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Read manifest for site info
|
|
591
|
+
const manifest = await readManifest(directory);
|
|
592
|
+
|
|
593
|
+
// Find domain directories
|
|
594
|
+
const entries = await fs.readdir(directory);
|
|
595
|
+
for (const entry of entries) {
|
|
596
|
+
if (entry.startsWith('.')) continue;
|
|
597
|
+
const entryPath = path.join(directory, entry);
|
|
598
|
+
const stat = await fs.stat(entryPath);
|
|
599
|
+
if (stat.isDirectory()) {
|
|
600
|
+
// Check if this directory has an index.html
|
|
601
|
+
const indexPath = path.join(entryPath, 'index.html');
|
|
602
|
+
const hasIndex = await fs.pathExists(indexPath);
|
|
603
|
+
|
|
604
|
+
sites.push({
|
|
605
|
+
domain: entry,
|
|
606
|
+
path: entry,
|
|
607
|
+
hasIndex,
|
|
608
|
+
rootUrl: manifest?.rootUrl || null,
|
|
609
|
+
title: manifest?.pages?.[0]?.title || entry,
|
|
610
|
+
pagesCount: manifest?.stats?.pagesCapt || 0,
|
|
611
|
+
assetsCount: manifest?.stats?.assetsCapt || 0,
|
|
612
|
+
lastUpdated: manifest?.updated || null,
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
return sites;
|
|
618
|
+
}
|
|
619
|
+
|
|
573
620
|
/**
|
|
574
621
|
* Serve command for CLI
|
|
575
622
|
*/
|
|
576
623
|
export async function serve(options) {
|
|
577
624
|
try {
|
|
625
|
+
const directory = options.output || options.directory || './site';
|
|
626
|
+
const resolvedDir = path.resolve(directory);
|
|
627
|
+
|
|
628
|
+
// Check if this is a smippo capture directory
|
|
629
|
+
const sites = await getCapturedSites(resolvedDir);
|
|
630
|
+
let sitePath = null;
|
|
631
|
+
|
|
632
|
+
if (sites.length > 0 && process.stdin.isTTY && !options.quiet) {
|
|
633
|
+
// Show interactive site selection
|
|
634
|
+
console.log('');
|
|
635
|
+
p.intro(chalk.cyan('Smippo Server'));
|
|
636
|
+
|
|
637
|
+
if (sites.length === 1) {
|
|
638
|
+
// Single site - auto-select but show info
|
|
639
|
+
const site = sites[0];
|
|
640
|
+
console.log(
|
|
641
|
+
chalk.dim(' Found captured site: ') + chalk.bold(site.domain),
|
|
642
|
+
);
|
|
643
|
+
if (site.pagesCount > 0) {
|
|
644
|
+
console.log(
|
|
645
|
+
chalk.dim(` ${site.pagesCount} pages, ${site.assetsCount} assets`),
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
sitePath = site.path;
|
|
649
|
+
} else {
|
|
650
|
+
// Multiple sites - let user choose
|
|
651
|
+
const siteOptions = sites.map(site => ({
|
|
652
|
+
value: site.path,
|
|
653
|
+
label: site.domain,
|
|
654
|
+
hint: site.pagesCount > 0 ? `${site.pagesCount} pages` : undefined,
|
|
655
|
+
}));
|
|
656
|
+
|
|
657
|
+
const selected = await p.select({
|
|
658
|
+
message: 'Which site would you like to serve?',
|
|
659
|
+
options: siteOptions,
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
if (p.isCancel(selected)) {
|
|
663
|
+
p.cancel('Cancelled');
|
|
664
|
+
process.exit(0);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
sitePath = selected;
|
|
668
|
+
}
|
|
669
|
+
console.log('');
|
|
670
|
+
} else if (sites.length === 1) {
|
|
671
|
+
// Non-interactive mode with single site
|
|
672
|
+
sitePath = sites[0].path;
|
|
673
|
+
}
|
|
674
|
+
|
|
578
675
|
const serverInfo = await createServer({
|
|
579
|
-
directory:
|
|
676
|
+
directory: resolvedDir,
|
|
580
677
|
port: options.port || 8080,
|
|
581
678
|
host: options.host || '127.0.0.1',
|
|
582
679
|
open: options.open,
|
|
680
|
+
openPath: sitePath, // Pass the site path to open
|
|
583
681
|
cors: options.cors !== false,
|
|
584
682
|
verbose: options.verbose,
|
|
585
683
|
quiet: options.quiet,
|