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,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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
75
|
+
const allDirs = readdirSync(presentationsDir);
|
|
76
|
+
|
|
77
|
+
const dirs = allDirs.filter((name) => {
|
|
97
78
|
const fullPath = join(presentationsDir, name);
|
|
98
|
-
|
|
79
|
+
const isDir = statSync(fullPath).isDirectory();
|
|
80
|
+
const hasSlides = existsSync(join(fullPath, 'slides.md'));
|
|
81
|
+
return isDir && hasSlides;
|
|
99
82
|
});
|
|
100
83
|
|
|
101
|
-
|
|
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(
|
|
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
|
+
}
|