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.
Files changed (67) hide show
  1. package/app/app.config.ts +9 -0
  2. package/app/assets/css/main.css +90 -0
  3. package/app/components/AppHeader.vue +429 -0
  4. package/app/components/CreatePresentationDialog.vue +236 -0
  5. package/app/components/EmptyState.vue +37 -0
  6. package/app/components/ImportPresentationDialog.vue +865 -0
  7. package/app/components/PresentationCard.vue +343 -0
  8. package/app/components/PresentationListItem.vue +242 -0
  9. package/app/composables/useServers.ts +148 -0
  10. package/app/layouts/default.vue +49 -0
  11. package/app/pages/index.vue +542 -0
  12. package/dist/cli/index.js +183751 -137
  13. package/dist/config.d.ts +8 -0
  14. package/dist/config.js +16 -0
  15. package/dist/index.d.ts +21 -0
  16. package/dist/index.js +3 -0
  17. package/dist/module.d.ts +6 -0
  18. package/dist/module.js +9168 -0
  19. package/dist/prompt.js +847 -0
  20. package/nuxt.config.ts +53 -0
  21. package/package.json +26 -19
  22. package/server/api/export/[id].post.ts +67 -0
  23. package/server/api/open-editor/[id].post.ts +28 -0
  24. package/server/api/presentations/import.post.ts +139 -0
  25. package/server/api/presentations/index.get.ts +18 -0
  26. package/server/api/presentations/index.post.ts +175 -0
  27. package/server/api/presentations/upload.post.ts +174 -0
  28. package/server/api/presentations/validate.post.ts +14 -0
  29. package/server/api/servers/[id].delete.ts +15 -0
  30. package/server/api/servers/[id].post.ts +17 -0
  31. package/server/api/servers/index.delete.ts +5 -0
  32. package/server/api/servers/index.get.ts +5 -0
  33. package/server/api/servers/stop-all.post.ts +5 -0
  34. package/server/plugins/generate.ts +12 -0
  35. package/server/plugins/shutdown.ts +16 -0
  36. package/server/routes/exports/[...path].get.ts +25 -0
  37. package/server/utils/config.ts +13 -0
  38. package/server/utils/process-manager.ts +119 -0
  39. package/src/cli/commands/create.ts +125 -0
  40. package/src/cli/commands/deploy.ts +90 -0
  41. package/src/cli/commands/dev.ts +116 -0
  42. package/src/cli/commands/export.ts +63 -0
  43. package/src/cli/commands/import.ts +178 -0
  44. package/src/cli/commands/present.ts +111 -0
  45. package/src/cli/index.ts +87 -0
  46. package/src/cli/utils.ts +94 -0
  47. package/src/config.ts +21 -0
  48. package/src/index.ts +2 -0
  49. package/src/module.ts +12 -0
  50. package/src/shared/catalog.ts +94 -0
  51. package/src/shared/copy.ts +28 -0
  52. package/src/shared/index.ts +29 -0
  53. package/{scripts/generate-presentations.mjs → src/shared/presentations.ts} +23 -46
  54. package/src/shared/types.ts +29 -0
  55. package/src/shared/validation.ts +111 -0
  56. package/dist/assets/index-BerY9FcI.js +0 -49
  57. package/dist/assets/index-CVzsY-on.css +0 -1
  58. package/dist/index.html +0 -24
  59. package/server/api.js +0 -1225
  60. /package/{dist → public}/apple-touch-icon.png +0 -0
  61. /package/{dist → public}/favicon-96x96.png +0 -0
  62. /package/{dist → public}/favicon.ico +0 -0
  63. /package/{dist → public}/favicon.svg +0 -0
  64. /package/{dist → public}/site.webmanifest +0 -0
  65. /package/{dist → public}/ssl-logo.png +0 -0
  66. /package/{dist → public}/web-app-manifest-192x192.png +0 -0
  67. /package/{dist → public}/web-app-manifest-512x512.png +0 -0
@@ -0,0 +1,94 @@
1
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import type { PackageJson } from './types.js';
4
+
5
+ export const CATALOG_DEPENDENCIES = [
6
+ '@slidev/cli',
7
+ '@slidev/theme-default',
8
+ '@slidev/theme-seriph',
9
+ '@slidev/theme-apple-basic',
10
+ 'vue',
11
+ ];
12
+
13
+ export function hasSharedPackage(projectRoot: string): boolean {
14
+ const sharedPackagePath = join(projectRoot, 'packages', 'shared', 'package.json');
15
+ return existsSync(sharedPackagePath);
16
+ }
17
+
18
+ export function addSharedAddonToSlides(slidesPath: string): void {
19
+ const content = readFileSync(slidesPath, 'utf-8');
20
+ const frontmatterMatch = content.match(/^(---\n)([\s\S]*?)\n(---)/);
21
+ if (!frontmatterMatch) return;
22
+
23
+ const [fullMatch, openDelim, frontmatter, closeDelim] = frontmatterMatch;
24
+ const restOfFile = content.slice(fullMatch.length);
25
+ const sharedAddon = '@supaslidev/shared';
26
+
27
+ if (frontmatter.includes(sharedAddon)) return;
28
+
29
+ let updatedFrontmatter = frontmatter;
30
+
31
+ const addonsMatch = frontmatter.match(/^(addons:\s*)(\[.*?\])?$/m);
32
+ if (addonsMatch) {
33
+ if (addonsMatch[2]) {
34
+ const arrayContent = addonsMatch[2].slice(1, -1).trim();
35
+ if (arrayContent === '') {
36
+ updatedFrontmatter = frontmatter.replace(addonsMatch[0], `addons: ['${sharedAddon}']`);
37
+ } else {
38
+ updatedFrontmatter = frontmatter.replace(
39
+ addonsMatch[0],
40
+ `addons: [${arrayContent}, '${sharedAddon}']`,
41
+ );
42
+ }
43
+ } else {
44
+ const addonsBlockMatch = frontmatter.match(/^addons:\s*\n((?: - .+\n?)*)/m);
45
+ if (addonsBlockMatch) {
46
+ const existingBlock = addonsBlockMatch[0].trimEnd();
47
+ updatedFrontmatter = frontmatter.replace(
48
+ existingBlock,
49
+ `${existingBlock}\n - '${sharedAddon}'`,
50
+ );
51
+ } else {
52
+ updatedFrontmatter = frontmatter.replace(addonsMatch[0], `addons:\n - '${sharedAddon}'`);
53
+ }
54
+ }
55
+ } else {
56
+ const themeMatch = frontmatter.match(/^(theme:\s*.+)$/m);
57
+ if (themeMatch) {
58
+ updatedFrontmatter = frontmatter.replace(
59
+ themeMatch[1],
60
+ `${themeMatch[1]}\naddons:\n - '${sharedAddon}'`,
61
+ );
62
+ } else {
63
+ updatedFrontmatter = `${frontmatter}\naddons:\n - '${sharedAddon}'`;
64
+ }
65
+ }
66
+
67
+ if (updatedFrontmatter !== frontmatter) {
68
+ writeFileSync(slidesPath, `${openDelim}${updatedFrontmatter}\n${closeDelim}${restOfFile}`);
69
+ }
70
+ }
71
+
72
+ export function addSharedDependencyToPackageJson(packageJson: PackageJson): void {
73
+ if (!packageJson.dependencies) {
74
+ packageJson.dependencies = {};
75
+ }
76
+ if (!packageJson.dependencies['@supaslidev/shared']) {
77
+ packageJson.dependencies['@supaslidev/shared'] = 'workspace:*';
78
+ }
79
+ }
80
+
81
+ export function convertToCatalogDependencies(
82
+ dependencies: Record<string, string>,
83
+ ): Record<string, string> {
84
+ if (!dependencies || typeof dependencies !== 'object') {
85
+ return {};
86
+ }
87
+ const converted = { ...dependencies };
88
+ for (const dep of CATALOG_DEPENDENCIES) {
89
+ if (dep in converted) {
90
+ converted[dep] = 'catalog:';
91
+ }
92
+ }
93
+ return converted;
94
+ }
@@ -0,0 +1,28 @@
1
+ import { cpSync, mkdirSync, readdirSync, statSync } from 'node:fs';
2
+ import { join, basename } from 'node:path';
3
+ import { shouldIgnore } from './validation.js';
4
+
5
+ export function copyDirectorySelective(source: string, destination: string): void {
6
+ mkdirSync(destination, { recursive: true });
7
+
8
+ const entries = readdirSync(source);
9
+
10
+ for (const entry of entries) {
11
+ if (shouldIgnore(entry)) {
12
+ continue;
13
+ }
14
+
15
+ const sourcePath = join(source, entry);
16
+ const destPath = join(destination, entry);
17
+ const stat = statSync(sourcePath);
18
+
19
+ if (stat.isDirectory()) {
20
+ cpSync(sourcePath, destPath, {
21
+ recursive: true,
22
+ filter: (src) => !shouldIgnore(basename(src)),
23
+ });
24
+ } else {
25
+ cpSync(sourcePath, destPath);
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,29 @@
1
+ export type { Presentation, PackageJson, ValidationResult, PathValidationResult } from './types.js';
2
+
3
+ export {
4
+ SLUG_REGEX,
5
+ IGNORE_PATTERNS,
6
+ isValidPresentationId,
7
+ validateName,
8
+ shouldIgnore,
9
+ validateSourceDirectory,
10
+ validateSourceDirectoryResult,
11
+ validatePath,
12
+ validatePaths,
13
+ } from './validation.js';
14
+
15
+ export {
16
+ parseFrontmatter,
17
+ extractDescription,
18
+ regeneratePresentationsJson,
19
+ } from './presentations.js';
20
+
21
+ export {
22
+ CATALOG_DEPENDENCIES,
23
+ hasSharedPackage,
24
+ addSharedAddonToSlides,
25
+ addSharedDependencyToPackageJson,
26
+ convertToCatalogDependencies,
27
+ } from './catalog.js';
28
+
29
+ export { copyDirectorySelective } from './copy.js';
@@ -1,40 +1,16 @@
1
- #!/usr/bin/env node
1
+ import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import type { Presentation } from './types.js';
2
4
 
3
- import { existsSync, readdirSync, readFileSync, statSync, writeFileSync, mkdirSync } from 'node:fs';
4
- import { join, dirname } from 'node:path';
5
- import { fileURLToPath } from 'node:url';
6
-
7
- const __dirname = dirname(fileURLToPath(import.meta.url));
8
- const packageDir = join(__dirname, '..');
9
-
10
- function resolveProjectRoot() {
11
- if (process.env.SUPASLIDEV_PROJECT_ROOT) {
12
- return process.env.SUPASLIDEV_PROJECT_ROOT;
13
- }
14
- return join(packageDir, '..', '..', '..');
15
- }
16
-
17
- function resolvePresentationsDir() {
18
- if (process.env.SUPASLIDEV_PRESENTATIONS_DIR) {
19
- return process.env.SUPASLIDEV_PRESENTATIONS_DIR;
20
- }
21
- return join(resolveProjectRoot(), 'presentations');
22
- }
23
-
24
- const projectRoot = resolveProjectRoot();
25
- const presentationsDir = resolvePresentationsDir();
26
- const outputDir = join(projectRoot, '.supaslidev');
27
- const outputFile = join(outputDir, 'presentations.json');
28
-
29
- function parseFrontmatter(content) {
5
+ export function parseFrontmatter(content: string): Record<string, string> {
30
6
  const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
31
7
  if (!frontmatterMatch) return {};
32
8
 
33
9
  const frontmatter = frontmatterMatch[1];
34
- const result = {};
10
+ const result: Record<string, string> = {};
35
11
 
36
- let currentKey = null;
37
- let currentValue = [];
12
+ let currentKey: string | null = null;
13
+ let currentValue: string[] = [];
38
14
  let inMultiline = false;
39
15
 
40
16
  const lines = frontmatter.split('\n');
@@ -42,7 +18,7 @@ function parseFrontmatter(content) {
42
18
  for (const line of lines) {
43
19
  if (inMultiline) {
44
20
  if (line.match(/^[a-zA-Z]/)) {
45
- result[currentKey] = currentValue.join('\n').trim();
21
+ result[currentKey!] = currentValue.join('\n').trim();
46
22
  inMultiline = false;
47
23
  currentKey = null;
48
24
  currentValue = [];
@@ -77,7 +53,7 @@ function parseFrontmatter(content) {
77
53
  return result;
78
54
  }
79
55
 
80
- function extractDescription(info) {
56
+ export function extractDescription(info: string | undefined): string {
81
57
  if (!info) return '';
82
58
  return info
83
59
  .replace(/^##?\s+.*$/gm, '')
@@ -88,17 +64,24 @@ function extractDescription(info) {
88
64
  .join(' ');
89
65
  }
90
66
 
91
- function getPresentations() {
67
+ export function regeneratePresentationsJson(
68
+ presentationsDir: string,
69
+ presentationsJsonPath: string,
70
+ ): void {
92
71
  if (!existsSync(presentationsDir)) {
93
- return [];
72
+ return;
94
73
  }
95
74
 
96
- const dirs = readdirSync(presentationsDir).filter((name) => {
75
+ const allDirs = readdirSync(presentationsDir);
76
+
77
+ const dirs = allDirs.filter((name) => {
97
78
  const fullPath = join(presentationsDir, name);
98
- return statSync(fullPath).isDirectory() && existsSync(join(fullPath, 'slides.md'));
79
+ const isDir = statSync(fullPath).isDirectory();
80
+ const hasSlides = existsSync(join(fullPath, 'slides.md'));
81
+ return isDir && hasSlides;
99
82
  });
100
83
 
101
- return dirs
84
+ const presentations: Presentation[] = dirs
102
85
  .map((name) => {
103
86
  const slidesPath = join(presentationsDir, name, 'slides.md');
104
87
  const content = readFileSync(slidesPath, 'utf-8');
@@ -114,17 +97,11 @@ function getPresentations() {
114
97
  };
115
98
  })
116
99
  .sort((a, b) => a.title.localeCompare(b.title));
117
- }
118
-
119
- function main() {
120
- const presentations = getPresentations();
121
100
 
101
+ const outputDir = dirname(presentationsJsonPath);
122
102
  if (!existsSync(outputDir)) {
123
103
  mkdirSync(outputDir, { recursive: true });
124
104
  }
125
105
 
126
- writeFileSync(outputFile, JSON.stringify(presentations, null, 2));
127
- console.log(`Generated ${outputFile} with ${presentations.length} presentations`);
106
+ writeFileSync(presentationsJsonPath, JSON.stringify(presentations, null, 2));
128
107
  }
129
-
130
- main();
@@ -0,0 +1,29 @@
1
+ export interface Presentation {
2
+ id: string;
3
+ title: string;
4
+ description: string;
5
+ theme: string;
6
+ background: string;
7
+ duration: string;
8
+ }
9
+
10
+ export interface PackageJson {
11
+ name?: string;
12
+ private?: boolean;
13
+ scripts?: Record<string, string>;
14
+ dependencies?: Record<string, string>;
15
+ devDependencies?: Record<string, string>;
16
+ [key: string]: unknown;
17
+ }
18
+
19
+ export interface ValidationResult {
20
+ isValid: boolean;
21
+ error?: string;
22
+ }
23
+
24
+ export interface PathValidationResult {
25
+ path: string;
26
+ isValid: boolean;
27
+ suggestedName: string | null;
28
+ error: string | null;
29
+ }
@@ -0,0 +1,111 @@
1
+ import { existsSync, statSync } from 'node:fs';
2
+ import { join, basename, resolve } from 'node:path';
3
+ import type { ValidationResult, PathValidationResult } from './types.js';
4
+
5
+ export const SLUG_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
6
+
7
+ export const IGNORE_PATTERNS = [
8
+ 'node_modules',
9
+ '.git',
10
+ 'dist',
11
+ '.nuxt',
12
+ '.output',
13
+ 'pnpm-lock.yaml',
14
+ 'package-lock.json',
15
+ 'yarn.lock',
16
+ '.DS_Store',
17
+ ];
18
+
19
+ export function isValidPresentationId(id: string): boolean {
20
+ return typeof id === 'string' && id.length > 0 && id.length <= 100 && SLUG_REGEX.test(id);
21
+ }
22
+
23
+ export function validateName(name: string): void {
24
+ if (!SLUG_REGEX.test(name)) {
25
+ throw new Error(
26
+ 'Name must be lowercase alphanumeric with single hyphens only (no leading, trailing, or consecutive hyphens)',
27
+ );
28
+ }
29
+ }
30
+
31
+ export function shouldIgnore(name: string): boolean {
32
+ return IGNORE_PATTERNS.includes(name);
33
+ }
34
+
35
+ export function validateSourceDirectory(sourcePath: string): void {
36
+ if (!existsSync(sourcePath)) {
37
+ throw new Error(`Source directory does not exist: ${sourcePath}`);
38
+ }
39
+
40
+ if (!statSync(sourcePath).isDirectory()) {
41
+ throw new Error(`Source path is not a directory: ${sourcePath}`);
42
+ }
43
+
44
+ const slidesPath = join(sourcePath, 'slides.md');
45
+ if (!existsSync(slidesPath)) {
46
+ throw new Error(`No slides.md found in source directory: ${sourcePath}`);
47
+ }
48
+
49
+ const packageJsonPath = join(sourcePath, 'package.json');
50
+ if (!existsSync(packageJsonPath)) {
51
+ throw new Error(`No package.json found in source directory: ${sourcePath}`);
52
+ }
53
+ }
54
+
55
+ export function validateSourceDirectoryResult(sourcePath: string): ValidationResult {
56
+ try {
57
+ if (!existsSync(sourcePath)) {
58
+ return { isValid: false, error: 'Source directory does not exist' };
59
+ }
60
+
61
+ if (!statSync(sourcePath).isDirectory()) {
62
+ return { isValid: false, error: 'Source path is not a directory' };
63
+ }
64
+
65
+ const slidesPath = join(sourcePath, 'slides.md');
66
+ if (!existsSync(slidesPath)) {
67
+ return { isValid: false, error: 'No slides.md found in source directory' };
68
+ }
69
+
70
+ const packageJsonPath = join(sourcePath, 'package.json');
71
+ if (!existsSync(packageJsonPath)) {
72
+ return { isValid: false, error: 'No package.json found in source directory' };
73
+ }
74
+
75
+ return { isValid: true };
76
+ } catch (err) {
77
+ return { isValid: false, error: `Validation error: ${(err as Error).message}` };
78
+ }
79
+ }
80
+
81
+ export function validatePath(path: string): PathValidationResult {
82
+ const sourcePath = resolve(path);
83
+ const validation = validateSourceDirectoryResult(sourcePath);
84
+
85
+ if (!validation.isValid) {
86
+ return {
87
+ path,
88
+ isValid: false,
89
+ suggestedName: null,
90
+ error: validation.error ?? null,
91
+ };
92
+ }
93
+
94
+ const suggestedName =
95
+ basename(sourcePath)
96
+ .toLowerCase()
97
+ .replace(/[^a-z0-9-]/g, '-')
98
+ .replace(/-{2,}/g, '-')
99
+ .replace(/^-+|-+$/g, '') || 'untitled';
100
+
101
+ return {
102
+ path,
103
+ isValid: true,
104
+ suggestedName,
105
+ error: null,
106
+ };
107
+ }
108
+
109
+ export function validatePaths(paths: string[]): PathValidationResult[] {
110
+ return paths.map(validatePath);
111
+ }