supaslidev 0.1.4 → 0.2.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/app/app.config.ts +9 -0
- package/app/assets/css/main.css +90 -0
- package/app/components/AppHeader.vue +429 -0
- package/app/components/CreatePresentationDialog.vue +236 -0
- package/app/components/EmptyState.vue +37 -0
- package/app/components/ImportPresentationDialog.vue +865 -0
- package/app/components/PresentationCard.vue +343 -0
- package/app/components/PresentationListItem.vue +242 -0
- package/app/composables/useServers.ts +148 -0
- package/app/layouts/default.vue +49 -0
- package/app/pages/index.vue +542 -0
- package/dist/cli/index.js +183751 -137
- package/dist/config.d.ts +8 -0
- package/dist/config.js +16 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +3 -0
- package/dist/module.d.ts +6 -0
- package/dist/module.js +9168 -0
- package/dist/prompt.js +847 -0
- package/nuxt.config.ts +53 -0
- package/package.json +26 -19
- package/server/api/export/[id].post.ts +67 -0
- package/server/api/open-editor/[id].post.ts +28 -0
- package/server/api/presentations/import.post.ts +139 -0
- package/server/api/presentations/index.get.ts +18 -0
- package/server/api/presentations/index.post.ts +175 -0
- package/server/api/presentations/upload.post.ts +174 -0
- package/server/api/presentations/validate.post.ts +14 -0
- package/server/api/servers/[id].delete.ts +15 -0
- package/server/api/servers/[id].post.ts +17 -0
- package/server/api/servers/index.delete.ts +5 -0
- package/server/api/servers/index.get.ts +5 -0
- package/server/api/servers/stop-all.post.ts +5 -0
- package/server/plugins/generate.ts +12 -0
- package/server/plugins/shutdown.ts +16 -0
- package/server/routes/exports/[...path].get.ts +25 -0
- package/server/utils/config.ts +13 -0
- package/server/utils/process-manager.ts +119 -0
- package/src/cli/commands/create.ts +125 -0
- package/src/cli/commands/deploy.ts +90 -0
- package/src/cli/commands/dev.ts +116 -0
- package/src/cli/commands/export.ts +63 -0
- package/src/cli/commands/import.ts +178 -0
- package/src/cli/commands/present.ts +111 -0
- package/src/cli/index.ts +87 -0
- package/src/cli/utils.ts +94 -0
- package/src/config.ts +21 -0
- package/src/index.ts +2 -0
- package/src/module.ts +12 -0
- package/src/shared/catalog.ts +94 -0
- package/src/shared/copy.ts +28 -0
- package/src/shared/index.ts +29 -0
- package/{scripts/generate-presentations.mjs → src/shared/presentations.ts} +23 -46
- package/src/shared/types.ts +29 -0
- package/src/shared/validation.ts +111 -0
- package/dist/assets/index-BerY9FcI.js +0 -49
- package/dist/assets/index-CVzsY-on.css +0 -1
- package/dist/index.html +0 -24
- package/server/api.js +0 -1225
- /package/{dist → public}/apple-touch-icon.png +0 -0
- /package/{dist → public}/favicon-96x96.png +0 -0
- /package/{dist → public}/favicon.ico +0 -0
- /package/{dist → public}/favicon.svg +0 -0
- /package/{dist → public}/site.webmanifest +0 -0
- /package/{dist → public}/ssl-logo.png +0 -0
- /package/{dist → public}/web-app-manifest-192x192.png +0 -0
- /package/{dist → public}/web-app-manifest-512x512.png +0 -0
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { spawn, ChildProcess } from 'node:child_process';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { findProjectRoot } from '../utils.js';
|
|
6
|
+
|
|
7
|
+
export function findSupaslidevPackageRoot(): string {
|
|
8
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
|
|
10
|
+
while (dir !== dirname(dir)) {
|
|
11
|
+
const packageJsonPath = join(dir, 'package.json');
|
|
12
|
+
if (existsSync(packageJsonPath)) {
|
|
13
|
+
try {
|
|
14
|
+
const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
15
|
+
if (pkg.name === 'supaslidev') {
|
|
16
|
+
return dir;
|
|
17
|
+
}
|
|
18
|
+
} catch {
|
|
19
|
+
// Continue searching
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
dir = dirname(dir);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
throw new Error('Could not find supaslidev package root');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function dev(): Promise<void> {
|
|
29
|
+
const projectRoot = findProjectRoot();
|
|
30
|
+
|
|
31
|
+
if (!projectRoot) {
|
|
32
|
+
console.error('Error: Could not find a Supaslidev project.');
|
|
33
|
+
console.error('Make sure you are in a directory with a "presentations" folder.');
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const presentationsDir = join(projectRoot, 'presentations');
|
|
38
|
+
|
|
39
|
+
if (!existsSync(presentationsDir)) {
|
|
40
|
+
console.error(`Error: No "presentations" folder found at ${presentationsDir}`);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
console.log(`Starting Supaslidev for project: ${projectRoot}`);
|
|
45
|
+
console.log(`Presentations directory: ${presentationsDir}`);
|
|
46
|
+
|
|
47
|
+
process.env.SUPASLIDEV_PROJECT_ROOT = projectRoot;
|
|
48
|
+
process.env.SUPASLIDEV_PRESENTATIONS_DIR = presentationsDir;
|
|
49
|
+
|
|
50
|
+
// Run Nuxt from the supaslidev package root so its nuxt.config.ts,
|
|
51
|
+
// app/, server/, and public/ directories are used directly — this avoids
|
|
52
|
+
// the Nuxt layer `extends` mechanism which can trigger Vite module
|
|
53
|
+
// resolution conflicts when the package is installed from a tarball.
|
|
54
|
+
const supaslidevRoot = findSupaslidevPackageRoot();
|
|
55
|
+
|
|
56
|
+
// Find the nuxt binary — prefer the project's node_modules, then
|
|
57
|
+
// the supaslidev package's own node_modules, then fall back to npx.
|
|
58
|
+
const projectNuxtBin = join(projectRoot, 'node_modules', '.bin', 'nuxt');
|
|
59
|
+
const packageNuxtBin = join(supaslidevRoot, 'node_modules', '.bin', 'nuxt');
|
|
60
|
+
|
|
61
|
+
let command: string;
|
|
62
|
+
let args: string[];
|
|
63
|
+
|
|
64
|
+
if (existsSync(projectNuxtBin)) {
|
|
65
|
+
command = projectNuxtBin;
|
|
66
|
+
args = ['dev'];
|
|
67
|
+
} else if (existsSync(packageNuxtBin)) {
|
|
68
|
+
command = packageNuxtBin;
|
|
69
|
+
args = ['dev'];
|
|
70
|
+
} else {
|
|
71
|
+
command = 'npx';
|
|
72
|
+
args = ['nuxt', 'dev'];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Build a clean env for nuxt dev: always run in development mode
|
|
76
|
+
// and strip test runner env vars (VITEST, etc.) that cause Nuxt to
|
|
77
|
+
// skip the dev server startup.
|
|
78
|
+
const nuxtEnv: Record<string, string | undefined> = { ...process.env, NODE_ENV: 'development' };
|
|
79
|
+
for (const key of Object.keys(nuxtEnv)) {
|
|
80
|
+
if (key === 'VITEST' || key.startsWith('VITEST_') || key === 'TEST') {
|
|
81
|
+
delete nuxtEnv[key];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const nuxt = spawn(command, args, {
|
|
86
|
+
cwd: supaslidevRoot,
|
|
87
|
+
stdio: 'inherit',
|
|
88
|
+
env: nuxtEnv,
|
|
89
|
+
shell: process.platform === 'win32',
|
|
90
|
+
detached: false,
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const processes: ChildProcess[] = [nuxt];
|
|
94
|
+
|
|
95
|
+
nuxt.on('error', (err) => {
|
|
96
|
+
console.error(`Failed to start Nuxt: ${err.message}`);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
nuxt.on('close', (code, signal) => {
|
|
101
|
+
if (signal) {
|
|
102
|
+
process.kill(process.pid, signal);
|
|
103
|
+
} else {
|
|
104
|
+
process.exit(code ?? 1);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
const cleanup = () => {
|
|
109
|
+
for (const proc of processes) {
|
|
110
|
+
proc.kill('SIGTERM');
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
process.on('SIGINT', cleanup);
|
|
115
|
+
process.on('SIGTERM', cleanup);
|
|
116
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { existsSync, mkdirSync } from 'node:fs';
|
|
4
|
+
import { findProjectRoot, getPresentations, printAvailablePresentations } from '../utils.js';
|
|
5
|
+
|
|
6
|
+
export interface ExportOptions {
|
|
7
|
+
output?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function exportPdf(name: string, options: ExportOptions = {}): Promise<void> {
|
|
11
|
+
const projectRoot = findProjectRoot();
|
|
12
|
+
|
|
13
|
+
if (!projectRoot) {
|
|
14
|
+
console.error('Error: Could not find a Supaslidev project.');
|
|
15
|
+
console.error('Make sure you are in a directory with a "presentations" folder.');
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const presentationsDir = join(projectRoot, 'presentations');
|
|
20
|
+
const distDir = join(projectRoot, 'dist');
|
|
21
|
+
const presentations = getPresentations(presentationsDir);
|
|
22
|
+
|
|
23
|
+
if (!presentations.includes(name)) {
|
|
24
|
+
console.error(`Error: Presentation "${name}" not found`);
|
|
25
|
+
printAvailablePresentations(presentations);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const presentationDir = join(presentationsDir, name);
|
|
30
|
+
const outputPath = options.output ?? join(distDir, `${name}.pdf`);
|
|
31
|
+
|
|
32
|
+
if (!existsSync(dirname(outputPath))) {
|
|
33
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
console.log('\n' + '='.repeat(50));
|
|
37
|
+
console.log(` Exporting PDF: ${name}`);
|
|
38
|
+
console.log(` Output: ${outputPath}`);
|
|
39
|
+
console.log('='.repeat(50) + '\n');
|
|
40
|
+
|
|
41
|
+
const slidev = spawn('npx', ['slidev', 'export', '--output', outputPath], {
|
|
42
|
+
cwd: presentationDir,
|
|
43
|
+
stdio: 'inherit',
|
|
44
|
+
shell: true,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
slidev.on('error', (err) => {
|
|
48
|
+
console.error(`Failed to export presentation: ${err.message}`);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
slidev.on('close', (code) => {
|
|
53
|
+
if (code !== 0) {
|
|
54
|
+
console.error(`\nExport failed with exit code ${code}`);
|
|
55
|
+
process.exit(code ?? 1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
console.log('\n' + '='.repeat(50));
|
|
59
|
+
console.log(` Export complete!`);
|
|
60
|
+
console.log(` Output: ${outputPath}`);
|
|
61
|
+
console.log('='.repeat(50) + '\n');
|
|
62
|
+
});
|
|
63
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { join, basename, resolve, dirname } from 'node:path';
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { addImportedPresentation, findWorkspaceRoot } from 'create-supaslidev';
|
|
5
|
+
import { findProjectRoot, getPresentations } from '../utils.js';
|
|
6
|
+
import {
|
|
7
|
+
IGNORE_PATTERNS,
|
|
8
|
+
validateName,
|
|
9
|
+
validateSourceDirectory,
|
|
10
|
+
copyDirectorySelective,
|
|
11
|
+
hasSharedPackage,
|
|
12
|
+
addSharedAddonToSlides,
|
|
13
|
+
addSharedDependencyToPackageJson,
|
|
14
|
+
} from '../../shared/index.js';
|
|
15
|
+
import type { PackageJson } from '../../shared/types.js';
|
|
16
|
+
|
|
17
|
+
// Re-export for tests and other consumers
|
|
18
|
+
export {
|
|
19
|
+
IGNORE_PATTERNS,
|
|
20
|
+
validateName,
|
|
21
|
+
shouldIgnore,
|
|
22
|
+
validateSourceDirectory,
|
|
23
|
+
copyDirectorySelective,
|
|
24
|
+
} from '../../shared/index.js';
|
|
25
|
+
|
|
26
|
+
export function findPnpmWorkspaceRoot(startDir: string): string | null {
|
|
27
|
+
let currentDir = startDir;
|
|
28
|
+
while (currentDir !== dirname(currentDir)) {
|
|
29
|
+
if (existsSync(join(currentDir, 'pnpm-workspace.yaml'))) {
|
|
30
|
+
return currentDir;
|
|
31
|
+
}
|
|
32
|
+
currentDir = dirname(currentDir);
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function transformPackageJson(
|
|
38
|
+
sourcePath: string,
|
|
39
|
+
name: string,
|
|
40
|
+
projectRoot: string,
|
|
41
|
+
): string {
|
|
42
|
+
const packageJsonPath = join(sourcePath, 'package.json');
|
|
43
|
+
const content = readFileSync(packageJsonPath, 'utf-8');
|
|
44
|
+
const packageJson = JSON.parse(content) as PackageJson;
|
|
45
|
+
|
|
46
|
+
packageJson.name = `@supaslidev/${name}`;
|
|
47
|
+
packageJson.private = true;
|
|
48
|
+
|
|
49
|
+
packageJson.scripts = {
|
|
50
|
+
dev: 'slidev --open',
|
|
51
|
+
build: 'slidev build',
|
|
52
|
+
export: 'slidev export',
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
if (hasSharedPackage(projectRoot)) {
|
|
56
|
+
addSharedDependencyToPackageJson(packageJson);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return JSON.stringify(packageJson, null, 2) + '\n';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function runPnpmInstall(projectRoot: string): Promise<void> {
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
console.log('\nRunning pnpm install...');
|
|
65
|
+
|
|
66
|
+
const child = spawn('pnpm', ['install'], {
|
|
67
|
+
cwd: projectRoot,
|
|
68
|
+
stdio: 'inherit',
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
child.on('close', (code) => {
|
|
72
|
+
if (code !== 0) {
|
|
73
|
+
reject(new Error(`pnpm install failed with exit code ${code}`));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
resolve();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
child.on('error', (err) => {
|
|
80
|
+
reject(err);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface ImportOptions {
|
|
86
|
+
name?: string;
|
|
87
|
+
install?: boolean;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function importPresentation(
|
|
91
|
+
source: string,
|
|
92
|
+
options: ImportOptions = {},
|
|
93
|
+
): Promise<void> {
|
|
94
|
+
const { name, install = true } = options;
|
|
95
|
+
const projectRoot = findProjectRoot();
|
|
96
|
+
|
|
97
|
+
if (!projectRoot) {
|
|
98
|
+
console.error('Error: Could not find a Supaslidev project.');
|
|
99
|
+
console.error('Make sure you are in a directory with a "presentations" folder.');
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const presentationsDir = join(projectRoot, 'presentations');
|
|
104
|
+
|
|
105
|
+
if (!existsSync(presentationsDir)) {
|
|
106
|
+
console.error(`Error: No "presentations" folder found at ${presentationsDir}`);
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const sourcePath = resolve(source);
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
validateSourceDirectory(sourcePath);
|
|
114
|
+
} catch (err) {
|
|
115
|
+
console.error(`Error: ${err instanceof Error ? err.message : 'Invalid source'}`);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const presentationName =
|
|
120
|
+
name ??
|
|
121
|
+
basename(sourcePath)
|
|
122
|
+
.toLowerCase()
|
|
123
|
+
.replace(/[^a-z0-9-]/g, '-');
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
validateName(presentationName);
|
|
127
|
+
} catch (err) {
|
|
128
|
+
console.error(`Error: ${err instanceof Error ? err.message : 'Invalid name'}`);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const existingPresentations = getPresentations(presentationsDir);
|
|
133
|
+
if (existingPresentations.includes(presentationName)) {
|
|
134
|
+
console.error(`Error: Presentation "${presentationName}" already exists`);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const destinationPath = join(presentationsDir, presentationName);
|
|
139
|
+
|
|
140
|
+
console.log(`Importing presentation from: ${sourcePath}`);
|
|
141
|
+
console.log(`Destination: ${destinationPath}`);
|
|
142
|
+
|
|
143
|
+
copyDirectorySelective(sourcePath, destinationPath);
|
|
144
|
+
|
|
145
|
+
const transformedPackageJson = transformPackageJson(sourcePath, presentationName, projectRoot);
|
|
146
|
+
writeFileSync(join(destinationPath, 'package.json'), transformedPackageJson);
|
|
147
|
+
|
|
148
|
+
if (hasSharedPackage(projectRoot)) {
|
|
149
|
+
const slidesPath = join(destinationPath, 'slides.md');
|
|
150
|
+
if (existsSync(slidesPath)) {
|
|
151
|
+
addSharedAddonToSlides(slidesPath);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
console.log('\nFiles copied successfully!');
|
|
156
|
+
console.log('Ignored: ' + IGNORE_PATTERNS.join(', '));
|
|
157
|
+
|
|
158
|
+
const workspaceRoot = findWorkspaceRoot(projectRoot);
|
|
159
|
+
const pnpmRoot = findPnpmWorkspaceRoot(projectRoot);
|
|
160
|
+
|
|
161
|
+
if (install) {
|
|
162
|
+
await runPnpmInstall(pnpmRoot ?? projectRoot);
|
|
163
|
+
} else {
|
|
164
|
+
console.log(
|
|
165
|
+
'\nSkipped pnpm install. Run "pnpm install" manually before using the presentation.',
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
if (workspaceRoot) {
|
|
169
|
+
addImportedPresentation(workspaceRoot, {
|
|
170
|
+
name: presentationName,
|
|
171
|
+
importedAt: new Date().toISOString(),
|
|
172
|
+
sourcePath,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
console.log('\nPresentation imported successfully!');
|
|
177
|
+
console.log(`Run "supaslidev present ${presentationName}" to start a dev server.`);
|
|
178
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
4
|
+
import { findProjectRoot } from '../utils.js';
|
|
5
|
+
|
|
6
|
+
function tryOpenBrowser(url: string): void {
|
|
7
|
+
const cmd =
|
|
8
|
+
process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'cmd' : 'xdg-open';
|
|
9
|
+
const args = process.platform === 'win32' ? ['/c', 'start', '', url] : [url];
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const child = spawn(cmd, args, { stdio: 'ignore', detached: true });
|
|
13
|
+
child.on('error', () => {
|
|
14
|
+
console.log(`\n Open ${url} in your browser\n`);
|
|
15
|
+
});
|
|
16
|
+
child.unref();
|
|
17
|
+
} catch {
|
|
18
|
+
console.log(`\n Open ${url} in your browser\n`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function getPresentations(presentationsDir: string): string[] {
|
|
23
|
+
if (!existsSync(presentationsDir)) {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return readdirSync(presentationsDir)
|
|
28
|
+
.filter((name) => {
|
|
29
|
+
const fullPath = join(presentationsDir, name);
|
|
30
|
+
return statSync(fullPath).isDirectory() && existsSync(join(fullPath, 'slides.md'));
|
|
31
|
+
})
|
|
32
|
+
.sort();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function printAvailable(presentations: string[]): void {
|
|
36
|
+
console.error('\nAvailable presentations:');
|
|
37
|
+
|
|
38
|
+
if (presentations.length === 0) {
|
|
39
|
+
console.error(' No presentations found');
|
|
40
|
+
} else {
|
|
41
|
+
presentations.forEach((name) => {
|
|
42
|
+
console.error(` ${name}`);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function present(name: string): Promise<void> {
|
|
48
|
+
const projectRoot = findProjectRoot();
|
|
49
|
+
|
|
50
|
+
if (!projectRoot) {
|
|
51
|
+
console.error('Error: Could not find a Supaslidev project.');
|
|
52
|
+
console.error('Make sure you are in a directory with a "presentations" folder.');
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const presentationsDir = join(projectRoot, 'presentations');
|
|
57
|
+
|
|
58
|
+
if (!existsSync(presentationsDir)) {
|
|
59
|
+
console.error(`Error: No "presentations" folder found at ${presentationsDir}`);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const presentations = getPresentations(presentationsDir);
|
|
64
|
+
|
|
65
|
+
if (!presentations.includes(name)) {
|
|
66
|
+
console.error(`Error: Presentation "${name}" not found`);
|
|
67
|
+
printAvailable(presentations);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const presentationPath = join(projectRoot, 'presentations', name);
|
|
72
|
+
const slidevBin = join(presentationPath, 'node_modules', '.bin', 'slidev');
|
|
73
|
+
|
|
74
|
+
console.log(`\nStarting dev server for ${name}...\n`);
|
|
75
|
+
|
|
76
|
+
return new Promise((resolve, reject) => {
|
|
77
|
+
const slidev = spawn(slidevBin, ['--open', 'false'], {
|
|
78
|
+
cwd: presentationPath,
|
|
79
|
+
stdio: ['inherit', 'pipe', 'inherit'],
|
|
80
|
+
shell: true,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
let browserOpened = false;
|
|
84
|
+
|
|
85
|
+
slidev.stdout?.on('data', (data: Buffer) => {
|
|
86
|
+
const text = data.toString();
|
|
87
|
+
process.stdout.write(text);
|
|
88
|
+
|
|
89
|
+
if (!browserOpened) {
|
|
90
|
+
const match = text.match(/https?:\/\/localhost:\d+/);
|
|
91
|
+
if (match) {
|
|
92
|
+
browserOpened = true;
|
|
93
|
+
tryOpenBrowser(match[0]);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
slidev.on('error', (err) => {
|
|
99
|
+
console.error(`Failed to start dev server: ${err.message}`);
|
|
100
|
+
reject(err);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
slidev.on('close', (code) => {
|
|
104
|
+
if (code !== 0) {
|
|
105
|
+
reject(new Error(`Process exited with code ${code}`));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
resolve();
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
}
|
package/src/cli/index.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { realpathSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { dev } from './commands/dev.js';
|
|
5
|
+
import { create } from './commands/create.js';
|
|
6
|
+
import { present } from './commands/present.js';
|
|
7
|
+
import { exportPdf } from './commands/export.js';
|
|
8
|
+
import { deploy } from './commands/deploy.js';
|
|
9
|
+
import { importPresentation } from './commands/import.js';
|
|
10
|
+
|
|
11
|
+
const program = new Command();
|
|
12
|
+
|
|
13
|
+
program.name('supaslidev').description('Supaslidev presentation management CLI').version('0.1.0');
|
|
14
|
+
|
|
15
|
+
program
|
|
16
|
+
.command('dev', { isDefault: true })
|
|
17
|
+
.description('Start the Supaslidev UI and development server')
|
|
18
|
+
.action(async () => {
|
|
19
|
+
await dev();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
program
|
|
23
|
+
.command('new')
|
|
24
|
+
.description('Create a new presentation')
|
|
25
|
+
.argument('[name]', 'Name of the presentation')
|
|
26
|
+
.action(async (name?: string) => {
|
|
27
|
+
await create(name);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
program
|
|
31
|
+
.command('present')
|
|
32
|
+
.description('Start a presentation dev server')
|
|
33
|
+
.argument('<name>', 'Name of the presentation to start')
|
|
34
|
+
.action(async (name: string) => {
|
|
35
|
+
await present(name);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
program
|
|
39
|
+
.command('export')
|
|
40
|
+
.description('Export a presentation to PDF')
|
|
41
|
+
.argument('<name>', 'Name of the presentation to export')
|
|
42
|
+
.option('-o, --output <path>', 'Output path for the PDF')
|
|
43
|
+
.action(async (name: string, options: { output?: string }) => {
|
|
44
|
+
await exportPdf(name, options);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
program
|
|
48
|
+
.command('deploy')
|
|
49
|
+
.description('Build and prepare a presentation for deployment')
|
|
50
|
+
.argument('<name>', 'Name of the presentation to deploy')
|
|
51
|
+
.option('-o, --output <path>', 'Output directory for deployment files')
|
|
52
|
+
.action(async (name: string, options: { output?: string }) => {
|
|
53
|
+
await deploy(name, options);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
program
|
|
57
|
+
.command('import')
|
|
58
|
+
.description('Import existing Sli.dev presentation(s)')
|
|
59
|
+
.argument('<source>', 'Path to existing Slidev presentation directory')
|
|
60
|
+
.option(
|
|
61
|
+
'-n, --name <name>',
|
|
62
|
+
'Name for the imported presentation (defaults to source directory name)',
|
|
63
|
+
)
|
|
64
|
+
.option('--no-install', 'Skip pnpm install after import')
|
|
65
|
+
.action(async (source: string, options: { name?: string; install?: boolean }) => {
|
|
66
|
+
await importPresentation(source, { name: options.name, install: options.install ?? true });
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
export async function run(): Promise<void> {
|
|
70
|
+
await program.parseAsync();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function isMainModule(): boolean {
|
|
74
|
+
if (!process.argv[1]) return false;
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
const scriptPath = realpathSync(process.argv[1]);
|
|
78
|
+
const modulePath = realpathSync(fileURLToPath(import.meta.url));
|
|
79
|
+
return scriptPath === modulePath;
|
|
80
|
+
} catch {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (isMainModule()) {
|
|
86
|
+
run();
|
|
87
|
+
}
|
package/src/cli/utils.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { dirname, join } from 'node:path';
|
|
2
|
+
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
3
|
+
|
|
4
|
+
export function findProjectRoot(cwd: string = process.cwd()): string | null {
|
|
5
|
+
let dir = cwd;
|
|
6
|
+
|
|
7
|
+
while (dir !== dirname(dir)) {
|
|
8
|
+
if (existsSync(join(dir, 'presentations')) && existsSync(join(dir, 'package.json'))) {
|
|
9
|
+
return dir;
|
|
10
|
+
}
|
|
11
|
+
if (existsSync(join(dir, 'pnpm-workspace.yaml'))) {
|
|
12
|
+
return dir;
|
|
13
|
+
}
|
|
14
|
+
dir = dirname(dir);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (existsSync(join(cwd, 'presentations'))) {
|
|
18
|
+
return cwd;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function getPresentations(presentationsDir: string): string[] {
|
|
25
|
+
if (!existsSync(presentationsDir)) {
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return readdirSync(presentationsDir)
|
|
30
|
+
.filter((name) => {
|
|
31
|
+
const fullPath = join(presentationsDir, name);
|
|
32
|
+
return statSync(fullPath).isDirectory() && existsSync(join(fullPath, 'slides.md'));
|
|
33
|
+
})
|
|
34
|
+
.sort();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function printAvailablePresentations(presentations: string[]): void {
|
|
38
|
+
console.error('\nAvailable presentations:');
|
|
39
|
+
|
|
40
|
+
if (presentations.length === 0) {
|
|
41
|
+
console.error(' No presentations found');
|
|
42
|
+
} else {
|
|
43
|
+
for (const name of presentations) {
|
|
44
|
+
console.error(` ${name}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function createVercelConfig(): string {
|
|
50
|
+
return (
|
|
51
|
+
JSON.stringify(
|
|
52
|
+
{
|
|
53
|
+
buildCommand: 'npm run build',
|
|
54
|
+
outputDirectory: 'dist',
|
|
55
|
+
rewrites: [{ source: '/(.*)', destination: '/index.html' }],
|
|
56
|
+
},
|
|
57
|
+
null,
|
|
58
|
+
2,
|
|
59
|
+
) + '\n'
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function createNetlifyConfig(): string {
|
|
64
|
+
return `[build]
|
|
65
|
+
publish = "dist"
|
|
66
|
+
command = "npm run build"
|
|
67
|
+
|
|
68
|
+
[build.environment]
|
|
69
|
+
NODE_VERSION = "20"
|
|
70
|
+
|
|
71
|
+
[[redirects]]
|
|
72
|
+
from = "/*"
|
|
73
|
+
to = "/index.html"
|
|
74
|
+
status = 200
|
|
75
|
+
`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function createDeployPackageJson(name: string): string {
|
|
79
|
+
return (
|
|
80
|
+
JSON.stringify(
|
|
81
|
+
{
|
|
82
|
+
name: `${name}-deploy`,
|
|
83
|
+
version: '1.0.0',
|
|
84
|
+
private: true,
|
|
85
|
+
scripts: {
|
|
86
|
+
build: 'echo "Already built - static files ready in dist/"',
|
|
87
|
+
start: 'npx serve dist',
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
null,
|
|
91
|
+
2,
|
|
92
|
+
) + '\n'
|
|
93
|
+
);
|
|
94
|
+
}
|
package/src/config.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
type DateString = `${number}-${number}-${number}`;
|
|
2
|
+
|
|
3
|
+
const DEFAULT_COMPAT_DATE: DateString = '2025-05-01';
|
|
4
|
+
|
|
5
|
+
export function defineSupaslidevConfig<T extends Record<string, unknown>>(
|
|
6
|
+
userConfig: T = {} as T,
|
|
7
|
+
): T & { extends: string[]; compatibilityDate: DateString } {
|
|
8
|
+
const configExtends = (userConfig as Record<string, unknown>).extends;
|
|
9
|
+
let userExtends: unknown[] = [];
|
|
10
|
+
if (Array.isArray(configExtends)) {
|
|
11
|
+
userExtends = configExtends;
|
|
12
|
+
} else if (configExtends) {
|
|
13
|
+
userExtends = [configExtends];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
...userConfig,
|
|
18
|
+
extends: ['supaslidev/layer', ...userExtends],
|
|
19
|
+
compatibilityDate: (userConfig.compatibilityDate as DateString) ?? DEFAULT_COMPAT_DATE,
|
|
20
|
+
} as T & { extends: string[]; compatibilityDate: DateString };
|
|
21
|
+
}
|
package/src/index.ts
ADDED
package/src/module.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { defineNuxtModule } from '@nuxt/kit';
|
|
2
|
+
|
|
3
|
+
export default defineNuxtModule({
|
|
4
|
+
meta: {
|
|
5
|
+
name: 'supaslidev',
|
|
6
|
+
configKey: 'supaslidev',
|
|
7
|
+
},
|
|
8
|
+
setup() {
|
|
9
|
+
// Runtime config is handled via nuxt.config.ts and env vars
|
|
10
|
+
// This module is a placeholder for future supaslidev-specific setup
|
|
11
|
+
},
|
|
12
|
+
});
|