create-supaslidev 0.1.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 +91 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +1279 -0
- package/dist/index.d.ts +241 -0
- package/dist/index.js +1675 -0
- package/package.json +62 -0
- package/templates/default/.supaslidev/state.json.ejs +6 -0
- package/templates/default/gitignore.ejs +7 -0
- package/templates/default/npmrc.ejs +1 -0
- package/templates/default/package.json.ejs +21 -0
- package/templates/default/pnpm-workspace.yaml.ejs +13 -0
- package/templates/default/tsconfig.json.ejs +17 -0
- package/templates/default/turbo.json.ejs +24 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1675 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { basename, dirname, join, relative } from "node:path";
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
import * as p from "@clack/prompts";
|
|
7
|
+
import ejs from "ejs";
|
|
8
|
+
import pc from "picocolors";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { parse, parseDocument, stringify } from "yaml";
|
|
11
|
+
import { IndentationText, Node, Project, SyntaxKind } from "ts-morph";
|
|
12
|
+
|
|
13
|
+
//#region src/create.ts
|
|
14
|
+
const CLI_VERSION$2 = "0.1.0";
|
|
15
|
+
function createSafeSpinner() {
|
|
16
|
+
if (process.stdout.isTTY && process.stdin.isTTY) {
|
|
17
|
+
const spinner = p.spinner();
|
|
18
|
+
return {
|
|
19
|
+
start: (msg) => spinner.start(msg),
|
|
20
|
+
stop: (msg) => spinner.stop(msg),
|
|
21
|
+
message: (msg) => spinner.message(msg)
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
start: () => {},
|
|
26
|
+
stop: () => {},
|
|
27
|
+
message: () => {}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const templatesDir = join(dirname(fileURLToPath(import.meta.url)), "..", "templates");
|
|
31
|
+
const createdPaths = [];
|
|
32
|
+
function trackPath(path) {
|
|
33
|
+
createdPaths.push(path);
|
|
34
|
+
}
|
|
35
|
+
function cleanup() {
|
|
36
|
+
for (const path of createdPaths.reverse()) try {
|
|
37
|
+
if (existsSync(path)) rmSync(path, {
|
|
38
|
+
recursive: true,
|
|
39
|
+
force: true
|
|
40
|
+
});
|
|
41
|
+
} catch {}
|
|
42
|
+
}
|
|
43
|
+
async function renderTemplate(templatePath, data) {
|
|
44
|
+
return ejs.renderFile(templatePath, data);
|
|
45
|
+
}
|
|
46
|
+
function getOutputFileName(templateFileName) {
|
|
47
|
+
const name = templateFileName.replace(".ejs", "");
|
|
48
|
+
if (name === "gitignore") return ".gitignore";
|
|
49
|
+
if (name === "npmrc") return ".npmrc";
|
|
50
|
+
return name;
|
|
51
|
+
}
|
|
52
|
+
function runCommand(command, args, cwd) {
|
|
53
|
+
return new Promise((resolve, reject) => {
|
|
54
|
+
const child = spawn(command, args, {
|
|
55
|
+
cwd,
|
|
56
|
+
stdio: "inherit",
|
|
57
|
+
shell: true
|
|
58
|
+
});
|
|
59
|
+
child.on("error", reject);
|
|
60
|
+
child.on("close", (code) => {
|
|
61
|
+
if (code === 0) resolve();
|
|
62
|
+
else reject(/* @__PURE__ */ new Error(`Command "${command} ${args.join(" ")}" exited with code ${code}`));
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
async function renderWorkspaceTemplates(targetDir, templateName, data) {
|
|
67
|
+
const templateDir = join(templatesDir, templateName);
|
|
68
|
+
if (!existsSync(templateDir)) throw new Error(`Template "${templateName}" not found`);
|
|
69
|
+
await renderTemplatesRecursively(templateDir, targetDir, data);
|
|
70
|
+
}
|
|
71
|
+
async function renderTemplatesRecursively(sourceDir, targetDir, data) {
|
|
72
|
+
const entries = readdirSync(sourceDir);
|
|
73
|
+
for (const entry of entries) {
|
|
74
|
+
const sourcePath = join(sourceDir, entry);
|
|
75
|
+
if (statSync(sourcePath).isDirectory()) {
|
|
76
|
+
const subTargetDir = join(targetDir, entry);
|
|
77
|
+
mkdirSync(subTargetDir, { recursive: true });
|
|
78
|
+
await renderTemplatesRecursively(sourcePath, subTargetDir, data);
|
|
79
|
+
} else if (entry.endsWith(".ejs")) writeFileSync(join(targetDir, getOutputFileName(entry)), await renderTemplate(sourcePath, data), "utf-8");
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function createDirectoryStructure(targetDir) {
|
|
83
|
+
for (const dir of [
|
|
84
|
+
"presentations",
|
|
85
|
+
"packages",
|
|
86
|
+
"scripts"
|
|
87
|
+
]) {
|
|
88
|
+
const fullPath = join(targetDir, dir);
|
|
89
|
+
mkdirSync(fullPath, { recursive: true });
|
|
90
|
+
trackPath(fullPath);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async function createPresentation(targetDir, presentationName) {
|
|
94
|
+
const presentationDir = join(targetDir, "presentations", presentationName);
|
|
95
|
+
mkdirSync(presentationDir, { recursive: true });
|
|
96
|
+
trackPath(presentationDir);
|
|
97
|
+
const packageJson = {
|
|
98
|
+
name: `@supaslidev/${presentationName}`,
|
|
99
|
+
private: true,
|
|
100
|
+
type: "module",
|
|
101
|
+
scripts: {
|
|
102
|
+
build: "slidev build",
|
|
103
|
+
dev: "slidev --open",
|
|
104
|
+
export: "slidev export"
|
|
105
|
+
},
|
|
106
|
+
dependencies: {
|
|
107
|
+
"@slidev/cli": "catalog:",
|
|
108
|
+
"@slidev/theme-default": "catalog:",
|
|
109
|
+
"@slidev/theme-seriph": "catalog:",
|
|
110
|
+
"@slidev/theme-apple-basic": "catalog:",
|
|
111
|
+
"@supaslidev/shared": "workspace:*",
|
|
112
|
+
vue: "catalog:"
|
|
113
|
+
},
|
|
114
|
+
devDependencies: {}
|
|
115
|
+
};
|
|
116
|
+
writeFileSync(join(presentationDir, "package.json"), JSON.stringify(packageJson, null, 2) + "\n", "utf-8");
|
|
117
|
+
const slidesContent = `---
|
|
118
|
+
theme: default
|
|
119
|
+
title: ${presentationName}
|
|
120
|
+
addons:
|
|
121
|
+
- '@supaslidev/shared'
|
|
122
|
+
info: |
|
|
123
|
+
A new Slidev presentation
|
|
124
|
+
class: text-center
|
|
125
|
+
transition: slide-left
|
|
126
|
+
mdc: true
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
# ${presentationName}
|
|
130
|
+
|
|
131
|
+
Welcome to your new presentation
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
# Slide 2
|
|
136
|
+
|
|
137
|
+
Add your content here
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
# Learn More
|
|
142
|
+
|
|
143
|
+
[Slidev Documentation](https://sli.dev/)
|
|
144
|
+
`;
|
|
145
|
+
writeFileSync(join(presentationDir, "slides.md"), slidesContent, "utf-8");
|
|
146
|
+
writeFileSync(join(presentationDir, ".gitignore"), "node_modules\ndist\n.slidev\n", "utf-8");
|
|
147
|
+
writeFileSync(join(presentationDir, ".npmrc"), "shamefully-hoist=true\n", "utf-8");
|
|
148
|
+
}
|
|
149
|
+
function createScripts(targetDir) {
|
|
150
|
+
writeFileSync(join(join(targetDir, "scripts"), "dev-presentation.mjs"), `#!/usr/bin/env node
|
|
151
|
+
|
|
152
|
+
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
153
|
+
import { join, dirname } from 'node:path';
|
|
154
|
+
import { fileURLToPath } from 'node:url';
|
|
155
|
+
import { spawn } from 'node:child_process';
|
|
156
|
+
|
|
157
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
158
|
+
const rootDir = join(__dirname, '..');
|
|
159
|
+
const presentationsDir = join(rootDir, 'presentations');
|
|
160
|
+
|
|
161
|
+
function getPresentations() {
|
|
162
|
+
if (!existsSync(presentationsDir)) {
|
|
163
|
+
return [];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return readdirSync(presentationsDir)
|
|
167
|
+
.filter((name) => {
|
|
168
|
+
const fullPath = join(presentationsDir, name);
|
|
169
|
+
return statSync(fullPath).isDirectory() && existsSync(join(fullPath, 'slides.md'));
|
|
170
|
+
})
|
|
171
|
+
.sort();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function printUsage(presentations) {
|
|
175
|
+
console.error('Usage: pnpm dev <presentation-name>');
|
|
176
|
+
console.error('\\nAvailable presentations:');
|
|
177
|
+
|
|
178
|
+
if (presentations.length === 0) {
|
|
179
|
+
console.error(' No presentations found');
|
|
180
|
+
} else {
|
|
181
|
+
presentations.forEach((name) => {
|
|
182
|
+
console.error(\` \${name}\`);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function runDev(name) {
|
|
188
|
+
const packageName = \`@supaslidev/\${name}\`;
|
|
189
|
+
|
|
190
|
+
console.log(\`\\nStarting dev server for \${name}...\\n\`);
|
|
191
|
+
|
|
192
|
+
const pnpm = spawn('pnpm', ['--filter', packageName, 'dev'], {
|
|
193
|
+
cwd: rootDir,
|
|
194
|
+
stdio: 'inherit',
|
|
195
|
+
shell: true,
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
pnpm.on('error', (err) => {
|
|
199
|
+
console.error(\`Failed to start dev server: \${err.message}\`);
|
|
200
|
+
process.exit(1);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
pnpm.on('close', (code) => {
|
|
204
|
+
process.exit(code ?? 0);
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function main() {
|
|
209
|
+
const args = process.argv.slice(2);
|
|
210
|
+
const name = args[0];
|
|
211
|
+
const presentations = getPresentations();
|
|
212
|
+
|
|
213
|
+
if (!name) {
|
|
214
|
+
console.error('Error: Presentation name is required');
|
|
215
|
+
printUsage(presentations);
|
|
216
|
+
process.exit(1);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (!presentations.includes(name)) {
|
|
220
|
+
console.error(\`Error: Presentation "\${name}" not found\`);
|
|
221
|
+
printUsage(presentations);
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
runDev(name);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
main();
|
|
229
|
+
`, "utf-8");
|
|
230
|
+
}
|
|
231
|
+
function createSharedPackage(targetDir) {
|
|
232
|
+
const sharedDir = join(targetDir, "packages", "shared");
|
|
233
|
+
mkdirSync(sharedDir, { recursive: true });
|
|
234
|
+
trackPath(sharedDir);
|
|
235
|
+
for (const subdir of [
|
|
236
|
+
"components",
|
|
237
|
+
"layouts",
|
|
238
|
+
"styles"
|
|
239
|
+
]) {
|
|
240
|
+
const fullPath = join(sharedDir, subdir);
|
|
241
|
+
mkdirSync(fullPath, { recursive: true });
|
|
242
|
+
trackPath(fullPath);
|
|
243
|
+
}
|
|
244
|
+
writeFileSync(join(sharedDir, "package.json"), JSON.stringify({
|
|
245
|
+
name: "@supaslidev/shared",
|
|
246
|
+
private: true,
|
|
247
|
+
type: "module",
|
|
248
|
+
keywords: ["slidev-addon", "slidev"],
|
|
249
|
+
dependencies: { vue: "catalog:" }
|
|
250
|
+
}, null, 2) + "\n", "utf-8");
|
|
251
|
+
writeFileSync(join(sharedDir, "components", "SharedBadge.vue"), `<template>
|
|
252
|
+
<span class="shared-badge">
|
|
253
|
+
<slot />
|
|
254
|
+
</span>
|
|
255
|
+
</template>
|
|
256
|
+
|
|
257
|
+
<style scoped>
|
|
258
|
+
.shared-badge {
|
|
259
|
+
display: inline-block;
|
|
260
|
+
padding: 0.25rem 0.5rem;
|
|
261
|
+
border-radius: 0.25rem;
|
|
262
|
+
background-color: var(--slidev-theme-primary, #3b82f6);
|
|
263
|
+
color: white;
|
|
264
|
+
font-size: 0.875rem;
|
|
265
|
+
font-weight: 500;
|
|
266
|
+
}
|
|
267
|
+
</style>
|
|
268
|
+
`, "utf-8");
|
|
269
|
+
writeFileSync(join(sharedDir, "README.md"), `# @supaslidev/shared
|
|
270
|
+
|
|
271
|
+
Shared components, layouts, and styles for your Slidev presentations.
|
|
272
|
+
|
|
273
|
+
## Usage
|
|
274
|
+
|
|
275
|
+
This package is configured as a Slidev addon. Components in the \`components\` directory are automatically available in all presentations that include this addon.
|
|
276
|
+
|
|
277
|
+
## Structure
|
|
278
|
+
|
|
279
|
+
- \`components/\` - Shared Vue components
|
|
280
|
+
- \`layouts/\` - Custom slide layouts
|
|
281
|
+
- \`styles/\` - Global styles
|
|
282
|
+
`, "utf-8");
|
|
283
|
+
writeFileSync(join(sharedDir, "tsconfig.json"), JSON.stringify({
|
|
284
|
+
compilerOptions: {
|
|
285
|
+
target: "ESNext",
|
|
286
|
+
module: "ESNext",
|
|
287
|
+
moduleResolution: "bundler",
|
|
288
|
+
strict: true,
|
|
289
|
+
jsx: "preserve",
|
|
290
|
+
skipLibCheck: true
|
|
291
|
+
},
|
|
292
|
+
include: ["**/*.ts", "**/*.vue"]
|
|
293
|
+
}, null, 2) + "\n", "utf-8");
|
|
294
|
+
}
|
|
295
|
+
async function create(options = {}) {
|
|
296
|
+
const spinner = createSafeSpinner();
|
|
297
|
+
try {
|
|
298
|
+
let projectName;
|
|
299
|
+
let presentationName;
|
|
300
|
+
let initGit;
|
|
301
|
+
let runInstall;
|
|
302
|
+
if (options.name !== void 0 || options.presentation !== void 0) {
|
|
303
|
+
projectName = options.name ?? "my-presentations";
|
|
304
|
+
presentationName = options.presentation ?? "my-first-deck";
|
|
305
|
+
initGit = options.git ?? true;
|
|
306
|
+
runInstall = options.install ?? true;
|
|
307
|
+
if (!/^[a-z0-9-]+$/.test(projectName)) {
|
|
308
|
+
p.log.error("Project name must be lowercase alphanumeric with hyphens only");
|
|
309
|
+
process.exit(1);
|
|
310
|
+
}
|
|
311
|
+
if (projectName.startsWith("-") || projectName.endsWith("-")) {
|
|
312
|
+
p.log.error("Project name cannot start or end with a hyphen");
|
|
313
|
+
process.exit(1);
|
|
314
|
+
}
|
|
315
|
+
if (!/^[a-z0-9-]+$/.test(presentationName)) {
|
|
316
|
+
p.log.error("Presentation name must be lowercase alphanumeric with hyphens only");
|
|
317
|
+
process.exit(1);
|
|
318
|
+
}
|
|
319
|
+
if (presentationName.startsWith("-") || presentationName.endsWith("-")) {
|
|
320
|
+
p.log.error("Presentation name cannot start or end with a hyphen");
|
|
321
|
+
process.exit(1);
|
|
322
|
+
}
|
|
323
|
+
} else {
|
|
324
|
+
p.intro(pc.cyan("Create a new Supaslidev workspace"));
|
|
325
|
+
const projectNameResult = await p.text({
|
|
326
|
+
message: "What is your project name?",
|
|
327
|
+
placeholder: "my-presentations",
|
|
328
|
+
validate: (value) => {
|
|
329
|
+
if (!value.trim()) return "Project name is required";
|
|
330
|
+
if (!/^[a-z0-9-]+$/.test(value)) return "Project name must be lowercase alphanumeric with hyphens only";
|
|
331
|
+
if (value.startsWith("-") || value.endsWith("-")) return "Project name cannot start or end with a hyphen";
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
if (p.isCancel(projectNameResult)) {
|
|
335
|
+
p.cancel("Operation cancelled");
|
|
336
|
+
process.exit(0);
|
|
337
|
+
}
|
|
338
|
+
projectName = projectNameResult;
|
|
339
|
+
const presentationNameResult = await p.text({
|
|
340
|
+
message: "What is the name of your first presentation?",
|
|
341
|
+
placeholder: "my-first-deck",
|
|
342
|
+
initialValue: "my-first-deck",
|
|
343
|
+
validate: (value) => {
|
|
344
|
+
if (!value.trim()) return "Presentation name is required";
|
|
345
|
+
if (!/^[a-z0-9-]+$/.test(value)) return "Presentation name must be lowercase alphanumeric with hyphens only";
|
|
346
|
+
if (value.startsWith("-") || value.endsWith("-")) return "Presentation name cannot start or end with a hyphen";
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
if (p.isCancel(presentationNameResult)) {
|
|
350
|
+
p.cancel("Operation cancelled");
|
|
351
|
+
process.exit(0);
|
|
352
|
+
}
|
|
353
|
+
presentationName = presentationNameResult;
|
|
354
|
+
const initGitResult = await p.confirm({
|
|
355
|
+
message: "Initialize a git repository?",
|
|
356
|
+
initialValue: true
|
|
357
|
+
});
|
|
358
|
+
if (p.isCancel(initGitResult)) {
|
|
359
|
+
p.cancel("Operation cancelled");
|
|
360
|
+
process.exit(0);
|
|
361
|
+
}
|
|
362
|
+
initGit = initGitResult;
|
|
363
|
+
const runInstallResult = await p.confirm({
|
|
364
|
+
message: "Run pnpm install after scaffolding?",
|
|
365
|
+
initialValue: true
|
|
366
|
+
});
|
|
367
|
+
if (p.isCancel(runInstallResult)) {
|
|
368
|
+
p.cancel("Operation cancelled");
|
|
369
|
+
process.exit(0);
|
|
370
|
+
}
|
|
371
|
+
runInstall = runInstallResult;
|
|
372
|
+
}
|
|
373
|
+
const targetDir = join(process.cwd(), projectName);
|
|
374
|
+
if (existsSync(targetDir)) {
|
|
375
|
+
p.log.error(`Directory "${projectName}" already exists`);
|
|
376
|
+
process.exit(1);
|
|
377
|
+
}
|
|
378
|
+
mkdirSync(targetDir, { recursive: true });
|
|
379
|
+
trackPath(targetDir);
|
|
380
|
+
spinner.start("Creating workspace structure...");
|
|
381
|
+
createDirectoryStructure(targetDir);
|
|
382
|
+
const templateData = {
|
|
383
|
+
projectName,
|
|
384
|
+
presentationName,
|
|
385
|
+
description: `${projectName} - Slidev presentations monorepo`,
|
|
386
|
+
cliVersion: CLI_VERSION$2,
|
|
387
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
388
|
+
};
|
|
389
|
+
await renderWorkspaceTemplates(targetDir, options.template ?? "default", templateData);
|
|
390
|
+
spinner.message("Creating presentation...");
|
|
391
|
+
await createPresentation(targetDir, presentationName);
|
|
392
|
+
spinner.message("Creating shared package...");
|
|
393
|
+
createSharedPackage(targetDir);
|
|
394
|
+
spinner.message("Creating scripts...");
|
|
395
|
+
createScripts(targetDir);
|
|
396
|
+
spinner.stop("Workspace structure created");
|
|
397
|
+
if (initGit) {
|
|
398
|
+
spinner.start("Initializing git repository...");
|
|
399
|
+
try {
|
|
400
|
+
await runCommand("git", ["init"], targetDir);
|
|
401
|
+
spinner.stop("Git repository initialized");
|
|
402
|
+
} catch {
|
|
403
|
+
spinner.stop("Failed to initialize git repository");
|
|
404
|
+
p.log.warn("Git initialization failed. You can run \"git init\" manually.");
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
if (runInstall) {
|
|
408
|
+
spinner.start("Installing dependencies (this may take a while)...");
|
|
409
|
+
try {
|
|
410
|
+
await runCommand("pnpm", ["install"], targetDir);
|
|
411
|
+
spinner.stop("Dependencies installed");
|
|
412
|
+
} catch {
|
|
413
|
+
spinner.stop("Failed to install dependencies");
|
|
414
|
+
p.log.warn("Dependency installation failed. You can run \"pnpm install\" manually.");
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
createdPaths.length = 0;
|
|
418
|
+
p.outro(pc.green("Workspace created successfully!"));
|
|
419
|
+
console.log("");
|
|
420
|
+
console.log(pc.cyan("Next steps:"));
|
|
421
|
+
console.log(` ${pc.dim("$")} cd ${projectName}`);
|
|
422
|
+
if (!runInstall) console.log(` ${pc.dim("$")} pnpm install`);
|
|
423
|
+
console.log(` ${pc.dim("$")} pnpm dev ${presentationName}`);
|
|
424
|
+
console.log("");
|
|
425
|
+
} catch (error) {
|
|
426
|
+
spinner.stop("Failed");
|
|
427
|
+
if (createdPaths.length > 0) {
|
|
428
|
+
p.log.warn("Cleaning up partial files...");
|
|
429
|
+
cleanup();
|
|
430
|
+
p.log.info("Cleanup complete");
|
|
431
|
+
}
|
|
432
|
+
const message = error instanceof Error ? error.message : "Unknown error occurred";
|
|
433
|
+
p.log.error(message);
|
|
434
|
+
process.exit(1);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
//#endregion
|
|
439
|
+
//#region src/state.ts
|
|
440
|
+
const CLI_VERSION$1 = "0.1.0";
|
|
441
|
+
const STATE_DIR = ".supaslidev";
|
|
442
|
+
const STATE_FILE = "state.json";
|
|
443
|
+
function getStatePath(workspaceDir) {
|
|
444
|
+
return join(workspaceDir, STATE_DIR, STATE_FILE);
|
|
445
|
+
}
|
|
446
|
+
function getStateDir(workspaceDir) {
|
|
447
|
+
return join(workspaceDir, STATE_DIR);
|
|
448
|
+
}
|
|
449
|
+
function createInitialState() {
|
|
450
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
451
|
+
return {
|
|
452
|
+
cliVersion: CLI_VERSION$1,
|
|
453
|
+
createdAt: now,
|
|
454
|
+
lastUpdatedAt: now,
|
|
455
|
+
appliedMigrations: []
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
function readState(workspaceDir) {
|
|
459
|
+
const statePath = getStatePath(workspaceDir);
|
|
460
|
+
if (!existsSync(statePath)) return null;
|
|
461
|
+
try {
|
|
462
|
+
const content = readFileSync(statePath, "utf-8");
|
|
463
|
+
return JSON.parse(content);
|
|
464
|
+
} catch {
|
|
465
|
+
return null;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
function writeState(workspaceDir, state) {
|
|
469
|
+
const stateDir = getStateDir(workspaceDir);
|
|
470
|
+
const statePath = getStatePath(workspaceDir);
|
|
471
|
+
if (!existsSync(stateDir)) mkdirSync(stateDir, { recursive: true });
|
|
472
|
+
const updatedState = {
|
|
473
|
+
...state,
|
|
474
|
+
lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
475
|
+
};
|
|
476
|
+
writeFileSync(statePath, JSON.stringify(updatedState, null, 2) + "\n", "utf-8");
|
|
477
|
+
}
|
|
478
|
+
function initializeState(workspaceDir) {
|
|
479
|
+
const state = createInitialState();
|
|
480
|
+
writeState(workspaceDir, state);
|
|
481
|
+
return state;
|
|
482
|
+
}
|
|
483
|
+
function stateExists(workspaceDir) {
|
|
484
|
+
return existsSync(getStatePath(workspaceDir));
|
|
485
|
+
}
|
|
486
|
+
function addMigration(workspaceDir, migrationId) {
|
|
487
|
+
const state = readState(workspaceDir);
|
|
488
|
+
if (!state) throw new Error("State file not found. Is this a Supaslidev workspace?");
|
|
489
|
+
if (state.appliedMigrations.some((m) => m.id === migrationId)) return;
|
|
490
|
+
state.appliedMigrations.push({
|
|
491
|
+
id: migrationId,
|
|
492
|
+
appliedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
493
|
+
});
|
|
494
|
+
writeState(workspaceDir, state);
|
|
495
|
+
}
|
|
496
|
+
function hasMigration(workspaceDir, migrationId) {
|
|
497
|
+
const state = readState(workspaceDir);
|
|
498
|
+
if (!state) return false;
|
|
499
|
+
return state.appliedMigrations.some((m) => m.id === migrationId);
|
|
500
|
+
}
|
|
501
|
+
function updateCliVersion(workspaceDir) {
|
|
502
|
+
const state = readState(workspaceDir);
|
|
503
|
+
if (!state) throw new Error("State file not found. Is this a Supaslidev workspace?");
|
|
504
|
+
state.cliVersion = CLI_VERSION$1;
|
|
505
|
+
writeState(workspaceDir, state);
|
|
506
|
+
}
|
|
507
|
+
function findWorkspaceRoot(startDir = process.cwd()) {
|
|
508
|
+
let currentDir = startDir;
|
|
509
|
+
while (currentDir !== dirname(currentDir)) {
|
|
510
|
+
if (stateExists(currentDir)) return currentDir;
|
|
511
|
+
currentDir = dirname(currentDir);
|
|
512
|
+
}
|
|
513
|
+
return null;
|
|
514
|
+
}
|
|
515
|
+
function addImportedPresentation(workspaceDir, presentation) {
|
|
516
|
+
const state = readState(workspaceDir);
|
|
517
|
+
if (!state) throw new Error("State file not found. Is this a Supaslidev workspace?");
|
|
518
|
+
if (!state.importedPresentations) state.importedPresentations = [];
|
|
519
|
+
const existingIndex = state.importedPresentations.findIndex((p) => p.name === presentation.name);
|
|
520
|
+
if (existingIndex >= 0) state.importedPresentations[existingIndex] = presentation;
|
|
521
|
+
else state.importedPresentations.push(presentation);
|
|
522
|
+
writeState(workspaceDir, state);
|
|
523
|
+
}
|
|
524
|
+
function getImportedPresentations(workspaceDir) {
|
|
525
|
+
return readState(workspaceDir)?.importedPresentations ?? [];
|
|
526
|
+
}
|
|
527
|
+
function removeImportedPresentation(workspaceDir, name) {
|
|
528
|
+
const state = readState(workspaceDir);
|
|
529
|
+
if (!state) throw new Error("State file not found. Is this a Supaslidev workspace?");
|
|
530
|
+
if (!state.importedPresentations) return;
|
|
531
|
+
state.importedPresentations = state.importedPresentations.filter((p) => p.name !== name);
|
|
532
|
+
writeState(workspaceDir, state);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
//#endregion
|
|
536
|
+
//#region src/migrations/manifest.ts
|
|
537
|
+
const MIGRATIONS_MANIFEST_FILE = "migrations.json";
|
|
538
|
+
function readManifest(migrationsDir) {
|
|
539
|
+
const manifestPath = join(migrationsDir, MIGRATIONS_MANIFEST_FILE);
|
|
540
|
+
if (!existsSync(manifestPath)) return null;
|
|
541
|
+
const content = readFileSync(manifestPath, "utf-8");
|
|
542
|
+
return JSON.parse(content);
|
|
543
|
+
}
|
|
544
|
+
function writeManifest(migrationsDir, manifest) {
|
|
545
|
+
writeFileSync(join(migrationsDir, MIGRATIONS_MANIFEST_FILE), JSON.stringify(manifest, null, 2) + "\n", "utf-8");
|
|
546
|
+
}
|
|
547
|
+
function createEmptyManifest() {
|
|
548
|
+
return {
|
|
549
|
+
version: "1.0.0",
|
|
550
|
+
migrations: []
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
function validateManifest(manifest) {
|
|
554
|
+
const errors = [];
|
|
555
|
+
if (!manifest.version) errors.push("Manifest missing version field");
|
|
556
|
+
if (!Array.isArray(manifest.migrations)) {
|
|
557
|
+
errors.push("Manifest migrations field must be an array");
|
|
558
|
+
return errors;
|
|
559
|
+
}
|
|
560
|
+
const ids = /* @__PURE__ */ new Set();
|
|
561
|
+
for (const migration of manifest.migrations) {
|
|
562
|
+
if (!migration.id) {
|
|
563
|
+
errors.push("Migration entry missing id field");
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
if (ids.has(migration.id)) errors.push(`Duplicate migration id: ${migration.id}`);
|
|
567
|
+
ids.add(migration.id);
|
|
568
|
+
if (!migration.description) errors.push(`Migration ${migration.id} missing description field`);
|
|
569
|
+
if (!migration.version) errors.push(`Migration ${migration.id} missing version field`);
|
|
570
|
+
if (migration.dependencies) {
|
|
571
|
+
for (const dep of migration.dependencies) if (!ids.has(dep)) {
|
|
572
|
+
if (!manifest.migrations.some((m) => m.id === dep)) errors.push(`Migration ${migration.id} has unknown dependency: ${dep}`);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
return errors;
|
|
577
|
+
}
|
|
578
|
+
function getMigrationOrder(manifest) {
|
|
579
|
+
const visited = /* @__PURE__ */ new Set();
|
|
580
|
+
const order = [];
|
|
581
|
+
const migrationMap = new Map(manifest.migrations.map((m) => [m.id, m]));
|
|
582
|
+
function visit(id, path) {
|
|
583
|
+
if (visited.has(id)) return;
|
|
584
|
+
if (path.has(id)) throw new Error(`Circular dependency detected involving migration: ${id}`);
|
|
585
|
+
const migration = migrationMap.get(id);
|
|
586
|
+
if (!migration) return;
|
|
587
|
+
path.add(id);
|
|
588
|
+
for (const dep of migration.dependencies ?? []) visit(dep, path);
|
|
589
|
+
path.delete(id);
|
|
590
|
+
visited.add(id);
|
|
591
|
+
order.push(id);
|
|
592
|
+
}
|
|
593
|
+
for (const migration of manifest.migrations) visit(migration.id, /* @__PURE__ */ new Set());
|
|
594
|
+
return order;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
//#endregion
|
|
598
|
+
//#region src/version.ts
|
|
599
|
+
const CLI_VERSION = "0.1.0";
|
|
600
|
+
const PACKAGE_NAME = "@supaslidev/cli";
|
|
601
|
+
const CACHE_DIR = join(tmpdir(), "supaslidev-cli");
|
|
602
|
+
const CACHE_FILE = join(CACHE_DIR, "version-cache.json");
|
|
603
|
+
const CACHE_TTL_MS = 1440 * 60 * 1e3;
|
|
604
|
+
function compareVersions(current, latest) {
|
|
605
|
+
const parseVersion = (v) => v.replace(/^v/, "").split(".").map((n) => parseInt(n, 10) || 0);
|
|
606
|
+
const currentParts = parseVersion(current);
|
|
607
|
+
const latestParts = parseVersion(latest);
|
|
608
|
+
for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) {
|
|
609
|
+
const curr = currentParts[i] ?? 0;
|
|
610
|
+
const lat = latestParts[i] ?? 0;
|
|
611
|
+
if (lat > curr) return true;
|
|
612
|
+
if (lat < curr) return false;
|
|
613
|
+
}
|
|
614
|
+
return false;
|
|
615
|
+
}
|
|
616
|
+
function readCache() {
|
|
617
|
+
try {
|
|
618
|
+
if (!existsSync(CACHE_FILE)) return null;
|
|
619
|
+
const data = JSON.parse(readFileSync(CACHE_FILE, "utf-8"));
|
|
620
|
+
if (Date.now() - data.checkedAt > CACHE_TTL_MS) return null;
|
|
621
|
+
return data;
|
|
622
|
+
} catch {
|
|
623
|
+
return null;
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
function writeCache(latestVersion) {
|
|
627
|
+
try {
|
|
628
|
+
if (!existsSync(CACHE_DIR)) mkdirSync(CACHE_DIR, { recursive: true });
|
|
629
|
+
const cache = {
|
|
630
|
+
latestVersion,
|
|
631
|
+
checkedAt: Date.now()
|
|
632
|
+
};
|
|
633
|
+
writeFileSync(CACHE_FILE, JSON.stringify(cache));
|
|
634
|
+
} catch {}
|
|
635
|
+
}
|
|
636
|
+
async function fetchLatestVersion() {
|
|
637
|
+
try {
|
|
638
|
+
const controller = new AbortController();
|
|
639
|
+
const timeoutId = setTimeout(() => controller.abort(), 5e3);
|
|
640
|
+
const response = await fetch(`https://registry.npmjs.org/${PACKAGE_NAME}`, { signal: controller.signal });
|
|
641
|
+
clearTimeout(timeoutId);
|
|
642
|
+
if (!response.ok) return null;
|
|
643
|
+
const version = (await response.json())["dist-tags"].latest;
|
|
644
|
+
writeCache(version);
|
|
645
|
+
return version;
|
|
646
|
+
} catch {
|
|
647
|
+
return null;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
function getCachedLatestVersion() {
|
|
651
|
+
return readCache()?.latestVersion ?? null;
|
|
652
|
+
}
|
|
653
|
+
async function checkForUpdates() {
|
|
654
|
+
const latestVersion = await fetchLatestVersion();
|
|
655
|
+
return {
|
|
656
|
+
currentVersion: CLI_VERSION,
|
|
657
|
+
latestVersion,
|
|
658
|
+
updateAvailable: latestVersion ? compareVersions(CLI_VERSION, latestVersion) : false
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
function checkForUpdatesCached() {
|
|
662
|
+
const latestVersion = getCachedLatestVersion();
|
|
663
|
+
return {
|
|
664
|
+
currentVersion: CLI_VERSION,
|
|
665
|
+
latestVersion,
|
|
666
|
+
updateAvailable: latestVersion ? compareVersions(CLI_VERSION, latestVersion) : false
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
//#endregion
|
|
671
|
+
//#region src/commands/status.ts
|
|
672
|
+
function getPendingMigrationsCount(workspaceDir) {
|
|
673
|
+
const manifest = readManifest(join(workspaceDir, ".supaslidev", "migrations"));
|
|
674
|
+
if (!manifest) return 0;
|
|
675
|
+
return manifest.migrations.filter((m) => !hasMigration(workspaceDir, m.id)).length;
|
|
676
|
+
}
|
|
677
|
+
function getAllPresentations(workspaceDir) {
|
|
678
|
+
const presentationsDir = join(workspaceDir, "presentations");
|
|
679
|
+
if (!existsSync(presentationsDir)) return [];
|
|
680
|
+
return readdirSync(presentationsDir).filter((name) => {
|
|
681
|
+
const fullPath = join(presentationsDir, name);
|
|
682
|
+
return statSync(fullPath).isDirectory() && existsSync(join(fullPath, "slides.md"));
|
|
683
|
+
}).sort();
|
|
684
|
+
}
|
|
685
|
+
async function getStatus(workspaceDir) {
|
|
686
|
+
const resolvedDir = workspaceDir ?? findWorkspaceRoot() ?? process.cwd();
|
|
687
|
+
const state = readState(resolvedDir);
|
|
688
|
+
const latestVersion = await fetchLatestVersion();
|
|
689
|
+
const updateAvailable = latestVersion ? compareVersions(CLI_VERSION, latestVersion) : false;
|
|
690
|
+
const importedPresentations = getImportedPresentations(resolvedDir);
|
|
691
|
+
const importedNames = new Set(importedPresentations.map((p) => p.name));
|
|
692
|
+
const nativePresentations = getAllPresentations(resolvedDir).filter((name) => !importedNames.has(name));
|
|
693
|
+
return {
|
|
694
|
+
cliVersion: CLI_VERSION,
|
|
695
|
+
stateVersion: state?.cliVersion ?? null,
|
|
696
|
+
createdAt: state?.createdAt ?? null,
|
|
697
|
+
lastUpdatedAt: state?.lastUpdatedAt ?? null,
|
|
698
|
+
pendingMigrations: state ? getPendingMigrationsCount(resolvedDir) : 0,
|
|
699
|
+
latestVersion,
|
|
700
|
+
updateAvailable,
|
|
701
|
+
nativePresentations,
|
|
702
|
+
importedPresentations
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
function formatDate(isoDate) {
|
|
706
|
+
return new Date(isoDate).toLocaleDateString("en-US", {
|
|
707
|
+
year: "numeric",
|
|
708
|
+
month: "short",
|
|
709
|
+
day: "numeric",
|
|
710
|
+
hour: "2-digit",
|
|
711
|
+
minute: "2-digit"
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
function formatStatus(status) {
|
|
715
|
+
const lines = [];
|
|
716
|
+
lines.push(pc.bold("Supaslidev Status"));
|
|
717
|
+
lines.push("─".repeat(40));
|
|
718
|
+
lines.push("");
|
|
719
|
+
lines.push(`${pc.dim("CLI Version:")} ${status.cliVersion}`);
|
|
720
|
+
if (status.stateVersion) lines.push(`${pc.dim("State Version:")} ${status.stateVersion}`);
|
|
721
|
+
else lines.push(`${pc.dim("State Version:")} ${pc.yellow("Not initialized")}`);
|
|
722
|
+
lines.push("");
|
|
723
|
+
if (status.createdAt) lines.push(`${pc.dim("Created:")} ${formatDate(status.createdAt)}`);
|
|
724
|
+
if (status.lastUpdatedAt) lines.push(`${pc.dim("Last Updated:")} ${formatDate(status.lastUpdatedAt)}`);
|
|
725
|
+
lines.push("");
|
|
726
|
+
if (status.pendingMigrations > 0) lines.push(`${pc.dim("Pending Migrations:")} ${pc.yellow(String(status.pendingMigrations))}`);
|
|
727
|
+
else lines.push(`${pc.dim("Pending Migrations:")} ${pc.green("0")}`);
|
|
728
|
+
lines.push("");
|
|
729
|
+
lines.push(pc.bold("Presentations"));
|
|
730
|
+
lines.push("─".repeat(40));
|
|
731
|
+
lines.push("");
|
|
732
|
+
lines.push(pc.dim("Native:"));
|
|
733
|
+
if (status.nativePresentations.length === 0) lines.push(" No native presentations");
|
|
734
|
+
else for (const name of status.nativePresentations) lines.push(` ${name}`);
|
|
735
|
+
lines.push("");
|
|
736
|
+
lines.push(pc.dim("Imported:"));
|
|
737
|
+
if (status.importedPresentations.length === 0) lines.push(" No imported presentations");
|
|
738
|
+
else for (const presentation of status.importedPresentations) {
|
|
739
|
+
lines.push(` ${pc.bold(presentation.name)}`);
|
|
740
|
+
lines.push(` ${pc.dim("Source:")} ${presentation.sourcePath}`);
|
|
741
|
+
lines.push(` ${pc.dim("Imported:")} ${formatDate(presentation.importedAt)}`);
|
|
742
|
+
}
|
|
743
|
+
lines.push("");
|
|
744
|
+
if (status.updateAvailable && status.latestVersion) {
|
|
745
|
+
lines.push(pc.yellow(`Update available: ${status.cliVersion} → ${status.latestVersion}`));
|
|
746
|
+
lines.push(pc.dim(" Run `pnpm add -g @supaslidev/cli` to update"));
|
|
747
|
+
} else if (status.latestVersion) lines.push(pc.green("✓ CLI is up to date"));
|
|
748
|
+
else lines.push(pc.dim("Could not check for updates"));
|
|
749
|
+
return lines.join("\n");
|
|
750
|
+
}
|
|
751
|
+
async function status(workspaceDir) {
|
|
752
|
+
const result = await getStatus(workspaceDir);
|
|
753
|
+
console.log(formatStatus(result));
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
//#endregion
|
|
757
|
+
//#region src/migrations/backup.ts
|
|
758
|
+
const BACKUP_DIR = ".supaslidev/backups";
|
|
759
|
+
const BACKUP_METADATA_FILE = "backup.json";
|
|
760
|
+
const EXCLUDED_PATTERNS = [
|
|
761
|
+
"node_modules",
|
|
762
|
+
".git",
|
|
763
|
+
".supaslidev/backups",
|
|
764
|
+
"dist",
|
|
765
|
+
".turbo",
|
|
766
|
+
".cache"
|
|
767
|
+
];
|
|
768
|
+
function getBackupDir(workspaceDir) {
|
|
769
|
+
return join(workspaceDir, BACKUP_DIR);
|
|
770
|
+
}
|
|
771
|
+
function getBackupPath(workspaceDir, backupId) {
|
|
772
|
+
return join(getBackupDir(workspaceDir), backupId);
|
|
773
|
+
}
|
|
774
|
+
function generateBackupId() {
|
|
775
|
+
return `backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").replace("Z", "")}`;
|
|
776
|
+
}
|
|
777
|
+
function shouldExclude(relativePath) {
|
|
778
|
+
return EXCLUDED_PATTERNS.some((pattern) => relativePath === pattern || relativePath.startsWith(`${pattern}/`));
|
|
779
|
+
}
|
|
780
|
+
function collectFiles(dir, baseDir) {
|
|
781
|
+
const files = [];
|
|
782
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
783
|
+
for (const entry of entries) {
|
|
784
|
+
const fullPath = join(dir, entry.name);
|
|
785
|
+
const relativePath = relative(baseDir, fullPath);
|
|
786
|
+
if (shouldExclude(relativePath)) continue;
|
|
787
|
+
if (entry.isDirectory()) files.push(...collectFiles(fullPath, baseDir));
|
|
788
|
+
else if (entry.isFile()) files.push(relativePath);
|
|
789
|
+
}
|
|
790
|
+
return files;
|
|
791
|
+
}
|
|
792
|
+
function createBackup(workspaceDir) {
|
|
793
|
+
const state = readState(workspaceDir);
|
|
794
|
+
if (!state) throw new Error("State file not found. Is this a Supaslidev workspace?");
|
|
795
|
+
const backupId = generateBackupId();
|
|
796
|
+
const backupPath = getBackupPath(workspaceDir, backupId);
|
|
797
|
+
mkdirSync(backupPath, { recursive: true });
|
|
798
|
+
const files = collectFiles(workspaceDir, workspaceDir);
|
|
799
|
+
for (const file of files) {
|
|
800
|
+
const sourcePath = join(workspaceDir, file);
|
|
801
|
+
const destPath = join(backupPath, "files", file);
|
|
802
|
+
const destDir = join(destPath, "..");
|
|
803
|
+
if (!existsSync(destDir)) mkdirSync(destDir, { recursive: true });
|
|
804
|
+
cpSync(sourcePath, destPath);
|
|
805
|
+
}
|
|
806
|
+
const metadata = {
|
|
807
|
+
backupId,
|
|
808
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
809
|
+
workspaceDir,
|
|
810
|
+
cliVersion: state.cliVersion,
|
|
811
|
+
files
|
|
812
|
+
};
|
|
813
|
+
writeFileSync(join(backupPath, BACKUP_METADATA_FILE), JSON.stringify(metadata, null, 2) + "\n", "utf-8");
|
|
814
|
+
return backupId;
|
|
815
|
+
}
|
|
816
|
+
function restoreBackup(workspaceDir, backupId) {
|
|
817
|
+
const backupPath = getBackupPath(workspaceDir, backupId);
|
|
818
|
+
const metadataPath = join(backupPath, BACKUP_METADATA_FILE);
|
|
819
|
+
if (!existsSync(metadataPath)) throw new Error(`Backup not found: ${backupId}`);
|
|
820
|
+
const metadata = JSON.parse(readFileSync(metadataPath, "utf-8"));
|
|
821
|
+
for (const file of metadata.files) {
|
|
822
|
+
const sourcePath = join(backupPath, "files", file);
|
|
823
|
+
const destPath = join(workspaceDir, file);
|
|
824
|
+
if (existsSync(sourcePath)) {
|
|
825
|
+
const destDir = join(destPath, "..");
|
|
826
|
+
if (!existsSync(destDir)) mkdirSync(destDir, { recursive: true });
|
|
827
|
+
cpSync(sourcePath, destPath);
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
function deleteBackup(workspaceDir, backupId) {
|
|
832
|
+
const backupPath = getBackupPath(workspaceDir, backupId);
|
|
833
|
+
if (existsSync(backupPath)) rmSync(backupPath, {
|
|
834
|
+
recursive: true,
|
|
835
|
+
force: true
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
function listBackups(workspaceDir) {
|
|
839
|
+
const backupDir = getBackupDir(workspaceDir);
|
|
840
|
+
if (!existsSync(backupDir)) return [];
|
|
841
|
+
const entries = readdirSync(backupDir, { withFileTypes: true });
|
|
842
|
+
const backups = [];
|
|
843
|
+
for (const entry of entries) {
|
|
844
|
+
if (!entry.isDirectory()) continue;
|
|
845
|
+
const metadataPath = join(backupDir, entry.name, BACKUP_METADATA_FILE);
|
|
846
|
+
if (existsSync(metadataPath)) {
|
|
847
|
+
const metadata = JSON.parse(readFileSync(metadataPath, "utf-8"));
|
|
848
|
+
backups.push(metadata);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
return backups.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
852
|
+
}
|
|
853
|
+
function getBackupMetadata(workspaceDir, backupId) {
|
|
854
|
+
const metadataPath = join(getBackupPath(workspaceDir, backupId), BACKUP_METADATA_FILE);
|
|
855
|
+
if (!existsSync(metadataPath)) return null;
|
|
856
|
+
return JSON.parse(readFileSync(metadataPath, "utf-8"));
|
|
857
|
+
}
|
|
858
|
+
function backupExists(workspaceDir, backupId) {
|
|
859
|
+
return existsSync(join(getBackupPath(workspaceDir, backupId), BACKUP_METADATA_FILE));
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
//#endregion
|
|
863
|
+
//#region src/migrations/journal.ts
|
|
864
|
+
const JOURNAL_DIR = ".supaslidev";
|
|
865
|
+
const JOURNAL_FILE = "migration-journal.json";
|
|
866
|
+
function getJournalPath(workspaceDir) {
|
|
867
|
+
return join(workspaceDir, JOURNAL_DIR, JOURNAL_FILE);
|
|
868
|
+
}
|
|
869
|
+
function getJournalDir(workspaceDir) {
|
|
870
|
+
return join(workspaceDir, JOURNAL_DIR);
|
|
871
|
+
}
|
|
872
|
+
function ensureJournalDir(workspaceDir) {
|
|
873
|
+
const journalDir = getJournalDir(workspaceDir);
|
|
874
|
+
if (!existsSync(journalDir)) mkdirSync(journalDir, { recursive: true });
|
|
875
|
+
}
|
|
876
|
+
function readJournal(workspaceDir) {
|
|
877
|
+
const journalPath = getJournalPath(workspaceDir);
|
|
878
|
+
if (!existsSync(journalPath)) return { entries: [] };
|
|
879
|
+
const content = readFileSync(journalPath, "utf-8");
|
|
880
|
+
return JSON.parse(content);
|
|
881
|
+
}
|
|
882
|
+
function writeJournal(workspaceDir, journal) {
|
|
883
|
+
ensureJournalDir(workspaceDir);
|
|
884
|
+
writeFileSync(getJournalPath(workspaceDir), JSON.stringify(journal, null, 2) + "\n", "utf-8");
|
|
885
|
+
}
|
|
886
|
+
function addJournalEntry(workspaceDir, entry) {
|
|
887
|
+
const journal = readJournal(workspaceDir);
|
|
888
|
+
journal.entries.push(entry);
|
|
889
|
+
writeJournal(workspaceDir, journal);
|
|
890
|
+
}
|
|
891
|
+
function createJournalEntry(migrationId, backupId, success, rolledBack = false, error) {
|
|
892
|
+
return {
|
|
893
|
+
migrationId,
|
|
894
|
+
appliedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
895
|
+
backupId,
|
|
896
|
+
success,
|
|
897
|
+
rolledBack,
|
|
898
|
+
error: error?.message
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
function getJournalEntries(workspaceDir) {
|
|
902
|
+
return readJournal(workspaceDir).entries;
|
|
903
|
+
}
|
|
904
|
+
function getLastSuccessfulEntry(workspaceDir) {
|
|
905
|
+
const successful = getJournalEntries(workspaceDir).filter((e) => e.success && !e.rolledBack);
|
|
906
|
+
return successful.length > 0 ? successful[successful.length - 1] : null;
|
|
907
|
+
}
|
|
908
|
+
function getMigrationHistory(workspaceDir, migrationId) {
|
|
909
|
+
return getJournalEntries(workspaceDir).filter((e) => e.migrationId === migrationId);
|
|
910
|
+
}
|
|
911
|
+
function wasMigrationSuccessful(workspaceDir, migrationId) {
|
|
912
|
+
const history = getMigrationHistory(workspaceDir, migrationId);
|
|
913
|
+
const lastEntry = history[history.length - 1];
|
|
914
|
+
return lastEntry?.success === true && !lastEntry.rolledBack;
|
|
915
|
+
}
|
|
916
|
+
function getFailedMigrations(workspaceDir) {
|
|
917
|
+
return getJournalEntries(workspaceDir).filter((e) => !e.success || e.rolledBack);
|
|
918
|
+
}
|
|
919
|
+
function clearJournal(workspaceDir) {
|
|
920
|
+
writeJournal(workspaceDir, { entries: [] });
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
//#endregion
|
|
924
|
+
//#region src/migrations/runner.ts
|
|
925
|
+
function loadMigration(migrations, id) {
|
|
926
|
+
return migrations.find((m) => m.id === id) ?? null;
|
|
927
|
+
}
|
|
928
|
+
function dryRun(options) {
|
|
929
|
+
const { workspaceDir, migrationsDir } = options;
|
|
930
|
+
const results = [];
|
|
931
|
+
const manifest = readManifest(migrationsDir);
|
|
932
|
+
if (!manifest) throw new Error(`No migrations.json found in ${migrationsDir}`);
|
|
933
|
+
const errors = validateManifest(manifest);
|
|
934
|
+
if (errors.length > 0) throw new Error(`Invalid migrations.json:\n${errors.join("\n")}`);
|
|
935
|
+
const order = getMigrationOrder(manifest);
|
|
936
|
+
const manifestMap = new Map(manifest.migrations.map((m) => [m.id, m]));
|
|
937
|
+
for (const id of order) {
|
|
938
|
+
const entry = manifestMap.get(id);
|
|
939
|
+
if (!entry) continue;
|
|
940
|
+
const alreadyApplied = hasMigration(workspaceDir, id);
|
|
941
|
+
results.push({
|
|
942
|
+
migrationId: id,
|
|
943
|
+
description: entry.description,
|
|
944
|
+
wouldApply: !alreadyApplied,
|
|
945
|
+
alreadyApplied,
|
|
946
|
+
breaking: entry.breaking
|
|
947
|
+
});
|
|
948
|
+
}
|
|
949
|
+
return results;
|
|
950
|
+
}
|
|
951
|
+
function formatDryRunOutput(results) {
|
|
952
|
+
const lines = [];
|
|
953
|
+
lines.push("Migration Preview (Dry Run)");
|
|
954
|
+
lines.push("=".repeat(50));
|
|
955
|
+
lines.push("");
|
|
956
|
+
const toApply = results.filter((r) => r.wouldApply);
|
|
957
|
+
const alreadyApplied = results.filter((r) => r.alreadyApplied);
|
|
958
|
+
if (toApply.length === 0) lines.push("No migrations to apply. Workspace is up to date.");
|
|
959
|
+
else {
|
|
960
|
+
lines.push(`Migrations to apply: ${toApply.length}`);
|
|
961
|
+
lines.push("");
|
|
962
|
+
for (const result of toApply) {
|
|
963
|
+
const breakingTag = result.breaking ? " [BREAKING]" : "";
|
|
964
|
+
lines.push(` → ${result.migrationId}${breakingTag}`);
|
|
965
|
+
lines.push(` ${result.description}`);
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
if (alreadyApplied.length > 0) {
|
|
969
|
+
lines.push("");
|
|
970
|
+
lines.push(`Already applied: ${alreadyApplied.length}`);
|
|
971
|
+
for (const result of alreadyApplied) lines.push(` ✓ ${result.migrationId}`);
|
|
972
|
+
}
|
|
973
|
+
lines.push("");
|
|
974
|
+
lines.push("Run with --apply to execute migrations.");
|
|
975
|
+
return lines.join("\n");
|
|
976
|
+
}
|
|
977
|
+
async function executeMigration(migration, context) {
|
|
978
|
+
try {
|
|
979
|
+
await migration.up(context);
|
|
980
|
+
return {
|
|
981
|
+
migrationId: migration.id,
|
|
982
|
+
success: true,
|
|
983
|
+
rolledBack: false
|
|
984
|
+
};
|
|
985
|
+
} catch (error) {
|
|
986
|
+
return {
|
|
987
|
+
migrationId: migration.id,
|
|
988
|
+
success: false,
|
|
989
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
990
|
+
rolledBack: false
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
async function run(options) {
|
|
995
|
+
const { workspaceDir, migrationsDir, apply = false, migrations = [], migrationOptions = {} } = options;
|
|
996
|
+
if (!apply) return {
|
|
997
|
+
success: true,
|
|
998
|
+
applied: [],
|
|
999
|
+
skipped: dryRun(options).filter((r) => r.alreadyApplied).map((r) => r.migrationId),
|
|
1000
|
+
failed: null,
|
|
1001
|
+
backupId: null,
|
|
1002
|
+
rolledBack: false
|
|
1003
|
+
};
|
|
1004
|
+
const manifest = readManifest(migrationsDir);
|
|
1005
|
+
if (!manifest) throw new Error(`No migrations.json found in ${migrationsDir}`);
|
|
1006
|
+
const errors = validateManifest(manifest);
|
|
1007
|
+
if (errors.length > 0) throw new Error(`Invalid migrations.json:\n${errors.join("\n")}`);
|
|
1008
|
+
const order = getMigrationOrder(manifest);
|
|
1009
|
+
const toApply = order.filter((id) => !hasMigration(workspaceDir, id));
|
|
1010
|
+
if (toApply.length === 0) return {
|
|
1011
|
+
success: true,
|
|
1012
|
+
applied: [],
|
|
1013
|
+
skipped: order,
|
|
1014
|
+
failed: null,
|
|
1015
|
+
backupId: null,
|
|
1016
|
+
rolledBack: false
|
|
1017
|
+
};
|
|
1018
|
+
const backupId = createBackup(workspaceDir);
|
|
1019
|
+
const state = readState(workspaceDir);
|
|
1020
|
+
if (!state) throw new Error("State file not found. Is this a Supaslidev workspace?");
|
|
1021
|
+
const baseContext = {
|
|
1022
|
+
workspaceDir,
|
|
1023
|
+
state,
|
|
1024
|
+
backupPath: backupId ? join(workspaceDir, ".supaslidev", "backups", backupId) : null
|
|
1025
|
+
};
|
|
1026
|
+
const applied = [];
|
|
1027
|
+
let failed = null;
|
|
1028
|
+
let rolledBack = false;
|
|
1029
|
+
for (const id of toApply) {
|
|
1030
|
+
const migration = loadMigration(migrations, id);
|
|
1031
|
+
if (!migration) {
|
|
1032
|
+
failed = {
|
|
1033
|
+
migrationId: id,
|
|
1034
|
+
success: false,
|
|
1035
|
+
error: /* @__PURE__ */ new Error(`Migration implementation not found: ${id}`),
|
|
1036
|
+
rolledBack: false
|
|
1037
|
+
};
|
|
1038
|
+
break;
|
|
1039
|
+
}
|
|
1040
|
+
const result = await executeMigration(migration, {
|
|
1041
|
+
...baseContext,
|
|
1042
|
+
options: migrationOptions[id]
|
|
1043
|
+
});
|
|
1044
|
+
if (result.success) {
|
|
1045
|
+
applied.push(result);
|
|
1046
|
+
addMigration(workspaceDir, id);
|
|
1047
|
+
addJournalEntry(workspaceDir, createJournalEntry(id, backupId, true, false));
|
|
1048
|
+
} else {
|
|
1049
|
+
failed = result;
|
|
1050
|
+
addJournalEntry(workspaceDir, createJournalEntry(id, backupId, false, false, result.error));
|
|
1051
|
+
break;
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
if (failed) {
|
|
1055
|
+
restoreBackup(workspaceDir, backupId);
|
|
1056
|
+
rolledBack = true;
|
|
1057
|
+
addJournalEntry(workspaceDir, createJournalEntry(failed.migrationId, backupId, false, true, failed.error));
|
|
1058
|
+
failed.rolledBack = true;
|
|
1059
|
+
} else deleteBackup(workspaceDir, backupId);
|
|
1060
|
+
const skipped = order.filter((id) => !toApply.includes(id) || !applied.some((a) => a.migrationId === id) && failed?.migrationId !== id);
|
|
1061
|
+
return {
|
|
1062
|
+
success: !failed,
|
|
1063
|
+
applied,
|
|
1064
|
+
skipped,
|
|
1065
|
+
failed,
|
|
1066
|
+
backupId: failed ? backupId : null,
|
|
1067
|
+
rolledBack
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
function formatRunOutput(result) {
|
|
1071
|
+
const lines = [];
|
|
1072
|
+
if (result.success) {
|
|
1073
|
+
lines.push("Migration Complete");
|
|
1074
|
+
lines.push("=".repeat(50));
|
|
1075
|
+
lines.push("");
|
|
1076
|
+
if (result.applied.length === 0) lines.push("No migrations were applied. Workspace is up to date.");
|
|
1077
|
+
else {
|
|
1078
|
+
lines.push(`Applied ${result.applied.length} migration(s):`);
|
|
1079
|
+
for (const migration of result.applied) lines.push(` ✓ ${migration.migrationId}`);
|
|
1080
|
+
}
|
|
1081
|
+
if (result.skipped.length > 0) {
|
|
1082
|
+
lines.push("");
|
|
1083
|
+
lines.push(`Skipped ${result.skipped.length} (already applied):`);
|
|
1084
|
+
for (const id of result.skipped) lines.push(` - ${id}`);
|
|
1085
|
+
}
|
|
1086
|
+
} else {
|
|
1087
|
+
lines.push("Migration Failed");
|
|
1088
|
+
lines.push("=".repeat(50));
|
|
1089
|
+
lines.push("");
|
|
1090
|
+
if (result.failed) {
|
|
1091
|
+
lines.push(`Failed migration: ${result.failed.migrationId}`);
|
|
1092
|
+
if (result.failed.error) lines.push(`Error: ${result.failed.error.message}`);
|
|
1093
|
+
}
|
|
1094
|
+
if (result.rolledBack) {
|
|
1095
|
+
lines.push("");
|
|
1096
|
+
lines.push("Workspace has been restored from backup.");
|
|
1097
|
+
if (result.backupId) lines.push(`Backup preserved: ${result.backupId}`);
|
|
1098
|
+
}
|
|
1099
|
+
if (result.applied.length > 0) {
|
|
1100
|
+
lines.push("");
|
|
1101
|
+
lines.push(`Rolled back ${result.applied.length} migration(s):`);
|
|
1102
|
+
for (const migration of result.applied) lines.push(` ↩ ${migration.migrationId}`);
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
return lines.join("\n");
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
//#endregion
|
|
1109
|
+
//#region src/migrations/loader.ts
|
|
1110
|
+
const MIGRATIONS = [];
|
|
1111
|
+
async function loadMigrations() {
|
|
1112
|
+
return {
|
|
1113
|
+
migrations: MIGRATIONS.map((entry) => ({
|
|
1114
|
+
id: entry.id,
|
|
1115
|
+
description: entry.description,
|
|
1116
|
+
up: entry.module.up,
|
|
1117
|
+
down: entry.module.down
|
|
1118
|
+
})),
|
|
1119
|
+
order: MIGRATIONS.map((entry) => entry.id)
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
async function getMigrationById(id) {
|
|
1123
|
+
const { migrations } = await loadMigrations();
|
|
1124
|
+
return migrations.find((m) => m.id === id) ?? null;
|
|
1125
|
+
}
|
|
1126
|
+
async function loadInteractiveMigration(id) {
|
|
1127
|
+
const entry = MIGRATIONS.find((m) => m.id === id);
|
|
1128
|
+
if (!entry) return null;
|
|
1129
|
+
return {
|
|
1130
|
+
migration: {
|
|
1131
|
+
id: entry.id,
|
|
1132
|
+
description: entry.description,
|
|
1133
|
+
up: entry.module.up,
|
|
1134
|
+
down: entry.module.down
|
|
1135
|
+
},
|
|
1136
|
+
getAffectedPresentations: entry.module.getAffectedPresentations
|
|
1137
|
+
};
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
//#endregion
|
|
1141
|
+
//#region src/prompts.ts
|
|
1142
|
+
async function promptForCatalogSelection(presentations) {
|
|
1143
|
+
p.intro(pc.cyan("Catalog Conversion Selection"));
|
|
1144
|
+
p.note(`${pc.bold("Select presentations to convert to catalog: references")}\n\nSelected presentations will use ${pc.green("catalog:")} (managed versions)\nUnselected presentations will use ${pc.yellow("^52.11.3")} (pinned version)\n\n${pc.dim("Controls:")} ${pc.bold("Space")} ${pc.dim("toggle |")} ${pc.bold("a")} ${pc.dim("toggle all |")} ${pc.bold("Enter")} ${pc.dim("confirm")}`, "Migration Options");
|
|
1145
|
+
const options = presentations.map((pres) => ({
|
|
1146
|
+
value: pres.name,
|
|
1147
|
+
label: pres.name,
|
|
1148
|
+
hint: `currently ${pres.currentVersion}`
|
|
1149
|
+
}));
|
|
1150
|
+
const selected = await p.multiselect({
|
|
1151
|
+
message: "Which presentations should use catalog: references?",
|
|
1152
|
+
options,
|
|
1153
|
+
initialValues: presentations.map((p) => p.name),
|
|
1154
|
+
required: false
|
|
1155
|
+
});
|
|
1156
|
+
if (p.isCancel(selected)) {
|
|
1157
|
+
p.cancel("Migration cancelled");
|
|
1158
|
+
return {
|
|
1159
|
+
selectedForCatalog: [],
|
|
1160
|
+
cancelled: true
|
|
1161
|
+
};
|
|
1162
|
+
}
|
|
1163
|
+
const selectedCount = selected.length;
|
|
1164
|
+
const pinnedCount = presentations.length - selectedCount;
|
|
1165
|
+
if (selectedCount > 0) p.log.success(`${selectedCount} presentation(s) will use catalog: references`);
|
|
1166
|
+
if (pinnedCount > 0) p.log.info(`${pinnedCount} presentation(s) will use pinned ^52.11.3`);
|
|
1167
|
+
return {
|
|
1168
|
+
selectedForCatalog: selected,
|
|
1169
|
+
cancelled: false
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
//#endregion
|
|
1174
|
+
//#region src/commands/migrate.ts
|
|
1175
|
+
async function getMigrateResult(options = {}) {
|
|
1176
|
+
const workspaceDir = findWorkspaceRoot();
|
|
1177
|
+
if (!workspaceDir) return {
|
|
1178
|
+
success: false,
|
|
1179
|
+
dryRun: !options.apply,
|
|
1180
|
+
migrationsToApply: 0,
|
|
1181
|
+
migrationsApplied: 0,
|
|
1182
|
+
output: "Not a Supaslidev workspace. No .supaslidev/state.json found."
|
|
1183
|
+
};
|
|
1184
|
+
const migrationsDir = join(workspaceDir, ".supaslidev", "migrations");
|
|
1185
|
+
if (options.apply) {
|
|
1186
|
+
const { migrations } = await loadMigrations();
|
|
1187
|
+
const migrationOptions = {};
|
|
1188
|
+
const slidev51to52 = await loadInteractiveMigration("slidev-51-to-52");
|
|
1189
|
+
if (slidev51to52?.getAffectedPresentations) {
|
|
1190
|
+
const affected = slidev51to52.getAffectedPresentations(workspaceDir);
|
|
1191
|
+
if (affected.length > 0) {
|
|
1192
|
+
const selectionResult = await promptForCatalogSelection(affected);
|
|
1193
|
+
if (selectionResult.cancelled) return {
|
|
1194
|
+
success: false,
|
|
1195
|
+
dryRun: false,
|
|
1196
|
+
migrationsToApply: 0,
|
|
1197
|
+
migrationsApplied: 0,
|
|
1198
|
+
output: "Migration cancelled by user."
|
|
1199
|
+
};
|
|
1200
|
+
migrationOptions["slidev-51-to-52"] = {
|
|
1201
|
+
interactive: true,
|
|
1202
|
+
selectedForCatalog: selectionResult.selectedForCatalog
|
|
1203
|
+
};
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
const result = await run({
|
|
1207
|
+
workspaceDir,
|
|
1208
|
+
migrationsDir,
|
|
1209
|
+
apply: true,
|
|
1210
|
+
migrations,
|
|
1211
|
+
migrationOptions
|
|
1212
|
+
});
|
|
1213
|
+
return {
|
|
1214
|
+
success: result.success,
|
|
1215
|
+
dryRun: false,
|
|
1216
|
+
migrationsToApply: result.applied.length + (result.failed ? 1 : 0),
|
|
1217
|
+
migrationsApplied: result.applied.length,
|
|
1218
|
+
output: formatRunOutput(result)
|
|
1219
|
+
};
|
|
1220
|
+
}
|
|
1221
|
+
try {
|
|
1222
|
+
const results = dryRun({
|
|
1223
|
+
workspaceDir,
|
|
1224
|
+
migrationsDir
|
|
1225
|
+
});
|
|
1226
|
+
return {
|
|
1227
|
+
success: true,
|
|
1228
|
+
dryRun: true,
|
|
1229
|
+
migrationsToApply: results.filter((r) => r.wouldApply).length,
|
|
1230
|
+
migrationsApplied: 0,
|
|
1231
|
+
output: formatDryRunOutput(results)
|
|
1232
|
+
};
|
|
1233
|
+
} catch (error) {
|
|
1234
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1235
|
+
if (message.includes("No migrations.json found")) return {
|
|
1236
|
+
success: true,
|
|
1237
|
+
dryRun: true,
|
|
1238
|
+
migrationsToApply: 0,
|
|
1239
|
+
migrationsApplied: 0,
|
|
1240
|
+
output: "No migrations available. Workspace is up to date."
|
|
1241
|
+
};
|
|
1242
|
+
return {
|
|
1243
|
+
success: false,
|
|
1244
|
+
dryRun: true,
|
|
1245
|
+
migrationsToApply: 0,
|
|
1246
|
+
migrationsApplied: 0,
|
|
1247
|
+
output: `Error: ${message}`
|
|
1248
|
+
};
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
function formatMigrateOutput(result) {
|
|
1252
|
+
const lines = [];
|
|
1253
|
+
lines.push(pc.bold("Supaslidev Migrate"));
|
|
1254
|
+
lines.push("─".repeat(40));
|
|
1255
|
+
lines.push("");
|
|
1256
|
+
if (result.dryRun) {
|
|
1257
|
+
lines.push(pc.cyan("Mode: Dry Run (no changes will be made)"));
|
|
1258
|
+
lines.push("");
|
|
1259
|
+
}
|
|
1260
|
+
lines.push(result.output);
|
|
1261
|
+
if (!result.success) {
|
|
1262
|
+
lines.push("");
|
|
1263
|
+
lines.push(pc.red("✗ Migration failed"));
|
|
1264
|
+
} else if (result.dryRun && result.migrationsToApply > 0) {
|
|
1265
|
+
lines.push("");
|
|
1266
|
+
lines.push(pc.yellow(`Run ${pc.bold("supaslidev migrate --apply")} to execute migrations.`));
|
|
1267
|
+
} else if (!result.dryRun && result.migrationsApplied > 0) {
|
|
1268
|
+
lines.push("");
|
|
1269
|
+
lines.push(pc.green("✓ All migrations applied successfully"));
|
|
1270
|
+
}
|
|
1271
|
+
return lines.join("\n");
|
|
1272
|
+
}
|
|
1273
|
+
async function migrate(options = {}) {
|
|
1274
|
+
const result = await getMigrateResult(options);
|
|
1275
|
+
console.log(formatMigrateOutput(result));
|
|
1276
|
+
if (!result.success) process.exit(1);
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
//#endregion
|
|
1280
|
+
//#region src/commands/update.ts
|
|
1281
|
+
async function getUpdateResult() {
|
|
1282
|
+
const check = await checkForUpdates();
|
|
1283
|
+
return {
|
|
1284
|
+
currentVersion: check.currentVersion,
|
|
1285
|
+
latestVersion: check.latestVersion,
|
|
1286
|
+
updateAvailable: check.updateAvailable,
|
|
1287
|
+
error: check.latestVersion === null ? "Could not reach npm registry" : null
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1290
|
+
function formatUpdateResult(result) {
|
|
1291
|
+
const lines = [];
|
|
1292
|
+
lines.push(pc.bold("Supaslidev Update Check"));
|
|
1293
|
+
lines.push("─".repeat(40));
|
|
1294
|
+
lines.push("");
|
|
1295
|
+
lines.push(`${pc.dim("Current Version:")} ${result.currentVersion}`);
|
|
1296
|
+
if (result.error) {
|
|
1297
|
+
lines.push("");
|
|
1298
|
+
lines.push(pc.yellow(`⚠ ${result.error}`));
|
|
1299
|
+
return lines.join("\n");
|
|
1300
|
+
}
|
|
1301
|
+
lines.push(`${pc.dim("Latest Version:")} ${result.latestVersion}`);
|
|
1302
|
+
lines.push("");
|
|
1303
|
+
if (result.updateAvailable) {
|
|
1304
|
+
lines.push(pc.yellow(`Update available: ${result.currentVersion} → ${result.latestVersion}`));
|
|
1305
|
+
lines.push("");
|
|
1306
|
+
lines.push(pc.bold("To update, run:"));
|
|
1307
|
+
lines.push(` ${pc.cyan(`npm install -g ${PACKAGE_NAME}`)}`);
|
|
1308
|
+
lines.push("");
|
|
1309
|
+
lines.push(pc.dim("or with pnpm:"));
|
|
1310
|
+
lines.push(` ${pc.cyan(`pnpm add -g ${PACKAGE_NAME}`)}`);
|
|
1311
|
+
} else lines.push(pc.green("✓ You are using the latest version"));
|
|
1312
|
+
return lines.join("\n");
|
|
1313
|
+
}
|
|
1314
|
+
async function update() {
|
|
1315
|
+
const result = await getUpdateResult();
|
|
1316
|
+
console.log(formatUpdateResult(result));
|
|
1317
|
+
if (result.error) process.exit(1);
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
//#endregion
|
|
1321
|
+
//#region src/background-update.ts
|
|
1322
|
+
function startBackgroundUpdateCheck() {
|
|
1323
|
+
const cached = checkForUpdatesCached();
|
|
1324
|
+
if (cached.updateAvailable && cached.latestVersion) {
|
|
1325
|
+
scheduleNotification(cached.latestVersion);
|
|
1326
|
+
return;
|
|
1327
|
+
}
|
|
1328
|
+
fetchLatestVersion().then((latestVersion) => {
|
|
1329
|
+
if (latestVersion && compareVersions(CLI_VERSION, latestVersion)) scheduleNotification(latestVersion);
|
|
1330
|
+
}).catch(() => {});
|
|
1331
|
+
}
|
|
1332
|
+
function scheduleNotification(latestVersion) {
|
|
1333
|
+
process.on("beforeExit", () => {
|
|
1334
|
+
printUpdateNotification(latestVersion);
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
function printUpdateNotification(latestVersion) {
|
|
1338
|
+
const border = "─".repeat(50);
|
|
1339
|
+
console.log("");
|
|
1340
|
+
console.log(pc.yellow(border));
|
|
1341
|
+
console.log(pc.yellow(` Update available: ${pc.dim(CLI_VERSION)} → ${pc.green(latestVersion)}`));
|
|
1342
|
+
console.log(pc.yellow(` Run ${pc.cyan(`npm install -g ${PACKAGE_NAME}`)} to update`));
|
|
1343
|
+
console.log(pc.yellow(border));
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
//#endregion
|
|
1347
|
+
//#region src/cli.ts
|
|
1348
|
+
const program = new Command();
|
|
1349
|
+
program.name("create-supaslidev").description("CLI tool for scaffolding Supaslidev presentations").version("0.1.0");
|
|
1350
|
+
program.command("create", { isDefault: true }).description("Create a new Supaslidev workspace").option("-n, --name <name>", "Name of the workspace").option("-p, --presentation <name>", "Name of the first presentation").option("-t, --template <template>", "Template to use", "default").option("--git", "Initialize a git repository").option("--no-git", "Skip git initialization").option("--install", "Run pnpm install after scaffolding").option("--no-install", "Skip pnpm install").action(async (options) => {
|
|
1351
|
+
await create(options);
|
|
1352
|
+
});
|
|
1353
|
+
program.command("status").description("Show project status and check for updates").action(async () => {
|
|
1354
|
+
await status();
|
|
1355
|
+
});
|
|
1356
|
+
program.command("migrate").description("Run migrations to update the workspace").option("--apply", "Execute migrations (default is dry-run mode)").action(async (options) => {
|
|
1357
|
+
await migrate(options);
|
|
1358
|
+
});
|
|
1359
|
+
program.command("update").description("Check for CLI updates").action(async () => {
|
|
1360
|
+
await update();
|
|
1361
|
+
});
|
|
1362
|
+
async function run$1() {
|
|
1363
|
+
startBackgroundUpdateCheck();
|
|
1364
|
+
await program.parseAsync();
|
|
1365
|
+
}
|
|
1366
|
+
const scriptName = process.argv[1] ? basename(process.argv[1]) : "";
|
|
1367
|
+
if (import.meta.url === pathToFileURL(process.argv[1] ?? "").href || scriptName === "cli.js") run$1().catch((err) => {
|
|
1368
|
+
console.error(err);
|
|
1369
|
+
process.exit(1);
|
|
1370
|
+
});
|
|
1371
|
+
|
|
1372
|
+
//#endregion
|
|
1373
|
+
//#region src/transformers/json.ts
|
|
1374
|
+
function readJsonFile(filePath) {
|
|
1375
|
+
const content = readFileSync(filePath, "utf-8");
|
|
1376
|
+
return JSON.parse(content);
|
|
1377
|
+
}
|
|
1378
|
+
function writeJsonFile(filePath, data, indent = 2) {
|
|
1379
|
+
writeFileSync(filePath, JSON.stringify(data, null, indent) + "\n", "utf-8");
|
|
1380
|
+
}
|
|
1381
|
+
function transformJson(filePath, transformer) {
|
|
1382
|
+
const original = readJsonFile(filePath);
|
|
1383
|
+
const transformed = transformer(structuredClone(original));
|
|
1384
|
+
const changed = JSON.stringify(original) !== JSON.stringify(transformed);
|
|
1385
|
+
if (changed) writeJsonFile(filePath, transformed);
|
|
1386
|
+
return {
|
|
1387
|
+
original,
|
|
1388
|
+
transformed,
|
|
1389
|
+
changed
|
|
1390
|
+
};
|
|
1391
|
+
}
|
|
1392
|
+
function getJsonValue(data, path) {
|
|
1393
|
+
if (path === "") return data;
|
|
1394
|
+
const keys = path.split(".");
|
|
1395
|
+
let current = data;
|
|
1396
|
+
for (const key of keys) {
|
|
1397
|
+
if (current === null || typeof current !== "object") return;
|
|
1398
|
+
current = current[key];
|
|
1399
|
+
}
|
|
1400
|
+
return current;
|
|
1401
|
+
}
|
|
1402
|
+
function setJsonValue(data, path, value) {
|
|
1403
|
+
const keys = path.split(".");
|
|
1404
|
+
const result = structuredClone(data);
|
|
1405
|
+
let current = result;
|
|
1406
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
1407
|
+
const key = keys[i];
|
|
1408
|
+
const nextKey = keys[i + 1];
|
|
1409
|
+
const isNextKeyNumeric = /^\d+$/.test(nextKey);
|
|
1410
|
+
if (Array.isArray(current)) {
|
|
1411
|
+
const index = parseInt(key, 10);
|
|
1412
|
+
if (isNaN(index)) throw new Error(`Cannot use non-numeric key "${key}" on array at path "${keys.slice(0, i).join(".")}"`);
|
|
1413
|
+
if (current[index] === void 0 || current[index] === null || typeof current[index] !== "object") current[index] = isNextKeyNumeric ? [] : {};
|
|
1414
|
+
current = current[index];
|
|
1415
|
+
} else {
|
|
1416
|
+
if (!(key in current) || current[key] === null || typeof current[key] !== "object") current[key] = isNextKeyNumeric ? [] : {};
|
|
1417
|
+
else if (Array.isArray(current[key]) && !isNextKeyNumeric) throw new Error(`Cannot use non-numeric key "${nextKey}" on array at path "${keys.slice(0, i + 1).join(".")}"`);
|
|
1418
|
+
current = current[key];
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
const finalKey = keys[keys.length - 1];
|
|
1422
|
+
if (Array.isArray(current)) {
|
|
1423
|
+
const index = parseInt(finalKey, 10);
|
|
1424
|
+
if (isNaN(index)) throw new Error(`Cannot use non-numeric key "${finalKey}" on array`);
|
|
1425
|
+
current[index] = value;
|
|
1426
|
+
} else current[finalKey] = value;
|
|
1427
|
+
return result;
|
|
1428
|
+
}
|
|
1429
|
+
function deleteJsonValue(data, path) {
|
|
1430
|
+
const keys = path.split(".");
|
|
1431
|
+
const result = structuredClone(data);
|
|
1432
|
+
let current = result;
|
|
1433
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
1434
|
+
const key = keys[i];
|
|
1435
|
+
if (Array.isArray(current)) {
|
|
1436
|
+
const index = parseInt(key, 10);
|
|
1437
|
+
if (isNaN(index) || current[index] === void 0 || current[index] === null || typeof current[index] !== "object") return result;
|
|
1438
|
+
current = current[index];
|
|
1439
|
+
} else {
|
|
1440
|
+
if (!(key in current) || current[key] === null || typeof current[key] !== "object") return result;
|
|
1441
|
+
current = current[key];
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
const finalKey = keys[keys.length - 1];
|
|
1445
|
+
if (Array.isArray(current)) {
|
|
1446
|
+
const index = parseInt(finalKey, 10);
|
|
1447
|
+
if (!isNaN(index) && index < current.length) current.splice(index, 1);
|
|
1448
|
+
} else delete current[finalKey];
|
|
1449
|
+
return result;
|
|
1450
|
+
}
|
|
1451
|
+
function mergeJson(target, source) {
|
|
1452
|
+
const result = structuredClone(target);
|
|
1453
|
+
for (const key of Object.keys(source)) {
|
|
1454
|
+
const sourceValue = source[key];
|
|
1455
|
+
const targetValue = result[key];
|
|
1456
|
+
if (sourceValue !== null && typeof sourceValue === "object" && !Array.isArray(sourceValue) && targetValue !== null && typeof targetValue === "object" && !Array.isArray(targetValue)) result[key] = mergeJson(targetValue, sourceValue);
|
|
1457
|
+
else result[key] = structuredClone(sourceValue);
|
|
1458
|
+
}
|
|
1459
|
+
return result;
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
//#endregion
|
|
1463
|
+
//#region src/transformers/yaml.ts
|
|
1464
|
+
const defaultParseOptions = { keepSourceTokens: true };
|
|
1465
|
+
function readYamlFile(filePath) {
|
|
1466
|
+
return parse(readFileSync(filePath, "utf-8"));
|
|
1467
|
+
}
|
|
1468
|
+
function writeYamlFile(filePath, data) {
|
|
1469
|
+
writeFileSync(filePath, stringify(data), "utf-8");
|
|
1470
|
+
}
|
|
1471
|
+
function readYamlDocument(filePath) {
|
|
1472
|
+
return parseDocument(readFileSync(filePath, "utf-8"), defaultParseOptions);
|
|
1473
|
+
}
|
|
1474
|
+
function writeYamlDocument(filePath, doc) {
|
|
1475
|
+
writeFileSync(filePath, doc.toString(), "utf-8");
|
|
1476
|
+
}
|
|
1477
|
+
function transformYaml(filePath, transformer) {
|
|
1478
|
+
const original = readYamlFile(filePath);
|
|
1479
|
+
const transformed = transformer(structuredClone(original));
|
|
1480
|
+
const changed = stringify(original) !== stringify(transformed);
|
|
1481
|
+
if (changed) writeYamlFile(filePath, transformed);
|
|
1482
|
+
return {
|
|
1483
|
+
original,
|
|
1484
|
+
transformed,
|
|
1485
|
+
changed
|
|
1486
|
+
};
|
|
1487
|
+
}
|
|
1488
|
+
function transformYamlDocument(filePath, transformer) {
|
|
1489
|
+
const doc = readYamlDocument(filePath);
|
|
1490
|
+
const originalContent = doc.toString();
|
|
1491
|
+
transformer(doc);
|
|
1492
|
+
const newContent = doc.toString();
|
|
1493
|
+
const changed = originalContent !== newContent;
|
|
1494
|
+
if (changed) writeFileSync(filePath, newContent, "utf-8");
|
|
1495
|
+
return { changed };
|
|
1496
|
+
}
|
|
1497
|
+
function getYamlValue(data, path) {
|
|
1498
|
+
const keys = path.split(".");
|
|
1499
|
+
let current = data;
|
|
1500
|
+
for (const key of keys) {
|
|
1501
|
+
if (current === null || typeof current !== "object") return;
|
|
1502
|
+
current = current[key];
|
|
1503
|
+
}
|
|
1504
|
+
return current;
|
|
1505
|
+
}
|
|
1506
|
+
function setYamlValue(data, path, value) {
|
|
1507
|
+
const keys = path.split(".");
|
|
1508
|
+
const result = structuredClone(data);
|
|
1509
|
+
let current = result;
|
|
1510
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
1511
|
+
const key = keys[i];
|
|
1512
|
+
if (!(key in current) || typeof current[key] !== "object" || current[key] === null) current[key] = {};
|
|
1513
|
+
current = current[key];
|
|
1514
|
+
}
|
|
1515
|
+
current[keys[keys.length - 1]] = value;
|
|
1516
|
+
return result;
|
|
1517
|
+
}
|
|
1518
|
+
function deleteYamlValue(data, path) {
|
|
1519
|
+
const keys = path.split(".");
|
|
1520
|
+
const result = structuredClone(data);
|
|
1521
|
+
let current = result;
|
|
1522
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
1523
|
+
const key = keys[i];
|
|
1524
|
+
if (!(key in current) || typeof current[key] !== "object" || current[key] === null) return result;
|
|
1525
|
+
current = current[key];
|
|
1526
|
+
}
|
|
1527
|
+
delete current[keys[keys.length - 1]];
|
|
1528
|
+
return result;
|
|
1529
|
+
}
|
|
1530
|
+
function mergeYaml(target, source) {
|
|
1531
|
+
const result = structuredClone(target);
|
|
1532
|
+
for (const key of Object.keys(source)) {
|
|
1533
|
+
const sourceValue = source[key];
|
|
1534
|
+
const targetValue = result[key];
|
|
1535
|
+
if (sourceValue !== null && typeof sourceValue === "object" && !Array.isArray(sourceValue) && targetValue !== null && typeof targetValue === "object" && !Array.isArray(targetValue)) result[key] = mergeYaml(targetValue, sourceValue);
|
|
1536
|
+
else result[key] = structuredClone(sourceValue);
|
|
1537
|
+
}
|
|
1538
|
+
return result;
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
//#endregion
|
|
1542
|
+
//#region src/transformers/typescript.ts
|
|
1543
|
+
function createTransformer(filePath) {
|
|
1544
|
+
const project = new Project({
|
|
1545
|
+
useInMemoryFileSystem: false,
|
|
1546
|
+
manipulationSettings: {
|
|
1547
|
+
indentationText: IndentationText.TwoSpaces,
|
|
1548
|
+
useTrailingCommas: true
|
|
1549
|
+
}
|
|
1550
|
+
});
|
|
1551
|
+
const sourceFile = project.addSourceFileAtPath(filePath);
|
|
1552
|
+
const originalContent = sourceFile.getFullText();
|
|
1553
|
+
return {
|
|
1554
|
+
project,
|
|
1555
|
+
sourceFile,
|
|
1556
|
+
save: () => {
|
|
1557
|
+
const newContent = sourceFile.getFullText();
|
|
1558
|
+
const changed = originalContent !== newContent;
|
|
1559
|
+
if (changed) sourceFile.saveSync();
|
|
1560
|
+
return {
|
|
1561
|
+
changed,
|
|
1562
|
+
originalContent,
|
|
1563
|
+
newContent
|
|
1564
|
+
};
|
|
1565
|
+
}
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
function transformTypeScript(filePath, transformer) {
|
|
1569
|
+
const { sourceFile, save } = createTransformer(filePath);
|
|
1570
|
+
transformer(sourceFile);
|
|
1571
|
+
return save();
|
|
1572
|
+
}
|
|
1573
|
+
function addImport(sourceFile, moduleSpecifier, namedImports) {
|
|
1574
|
+
const existingImport = sourceFile.getImportDeclaration(moduleSpecifier);
|
|
1575
|
+
if (existingImport) {
|
|
1576
|
+
const existingNamedImports = existingImport.getNamedImports().map((n) => n.getName());
|
|
1577
|
+
const newImports = namedImports.filter((n) => !existingNamedImports.includes(n));
|
|
1578
|
+
if (newImports.length > 0) existingImport.addNamedImports(newImports);
|
|
1579
|
+
return existingImport;
|
|
1580
|
+
}
|
|
1581
|
+
return sourceFile.addImportDeclaration({
|
|
1582
|
+
moduleSpecifier,
|
|
1583
|
+
namedImports
|
|
1584
|
+
});
|
|
1585
|
+
}
|
|
1586
|
+
function removeImport(sourceFile, moduleSpecifier, namedImports) {
|
|
1587
|
+
const importDecl = sourceFile.getImportDeclaration(moduleSpecifier);
|
|
1588
|
+
if (!importDecl) return false;
|
|
1589
|
+
if (!namedImports) {
|
|
1590
|
+
importDecl.remove();
|
|
1591
|
+
return true;
|
|
1592
|
+
}
|
|
1593
|
+
const existingNamedImports = importDecl.getNamedImports();
|
|
1594
|
+
for (const namedImport of existingNamedImports) if (namedImports.includes(namedImport.getName())) namedImport.remove();
|
|
1595
|
+
const hasNamedImports = importDecl.getNamedImports().length > 0;
|
|
1596
|
+
const hasDefaultImport = !!importDecl.getDefaultImport();
|
|
1597
|
+
const hasNamespaceImport = !!importDecl.getNamespaceImport();
|
|
1598
|
+
if (!hasNamedImports && !hasDefaultImport && !hasNamespaceImport) importDecl.remove();
|
|
1599
|
+
return true;
|
|
1600
|
+
}
|
|
1601
|
+
function addExport(sourceFile, moduleSpecifier, namedExports) {
|
|
1602
|
+
const existingExport = sourceFile.getExportDeclarations().find((e) => e.getModuleSpecifierValue() === moduleSpecifier);
|
|
1603
|
+
if (existingExport) {
|
|
1604
|
+
const existingNamedExports = existingExport.getNamedExports().map((e) => e.getName());
|
|
1605
|
+
const newExports = namedExports.filter((e) => !existingNamedExports.includes(e));
|
|
1606
|
+
if (newExports.length > 0) existingExport.addNamedExports(newExports);
|
|
1607
|
+
return existingExport;
|
|
1608
|
+
}
|
|
1609
|
+
return sourceFile.addExportDeclaration({
|
|
1610
|
+
moduleSpecifier,
|
|
1611
|
+
namedExports
|
|
1612
|
+
});
|
|
1613
|
+
}
|
|
1614
|
+
function findObjectLiteral(sourceFile, variableName) {
|
|
1615
|
+
const variable = sourceFile.getVariableDeclaration(variableName);
|
|
1616
|
+
if (!variable) return;
|
|
1617
|
+
const initializer = variable.getInitializer();
|
|
1618
|
+
if (initializer?.getKind() === SyntaxKind.ObjectLiteralExpression) return initializer;
|
|
1619
|
+
}
|
|
1620
|
+
function findArrayLiteral(sourceFile, variableName) {
|
|
1621
|
+
const variable = sourceFile.getVariableDeclaration(variableName);
|
|
1622
|
+
if (!variable) return;
|
|
1623
|
+
const initializer = variable.getInitializer();
|
|
1624
|
+
if (initializer?.getKind() === SyntaxKind.ArrayLiteralExpression) return initializer;
|
|
1625
|
+
}
|
|
1626
|
+
function getObjectProperty(obj, propertyName) {
|
|
1627
|
+
const property = obj.getProperty(propertyName);
|
|
1628
|
+
if (property && Node.isPropertyAssignment(property)) return property;
|
|
1629
|
+
}
|
|
1630
|
+
function setObjectProperty(obj, propertyName, value) {
|
|
1631
|
+
const existing = getObjectProperty(obj, propertyName);
|
|
1632
|
+
if (existing) existing.setInitializer(value);
|
|
1633
|
+
else obj.addPropertyAssignment({
|
|
1634
|
+
name: propertyName,
|
|
1635
|
+
initializer: value
|
|
1636
|
+
});
|
|
1637
|
+
}
|
|
1638
|
+
function removeObjectProperty(obj, propertyName) {
|
|
1639
|
+
const property = obj.getProperty(propertyName);
|
|
1640
|
+
if (property) {
|
|
1641
|
+
property.remove();
|
|
1642
|
+
return true;
|
|
1643
|
+
}
|
|
1644
|
+
return false;
|
|
1645
|
+
}
|
|
1646
|
+
function addArrayElement(arr, element) {
|
|
1647
|
+
arr.addElement(element);
|
|
1648
|
+
}
|
|
1649
|
+
function removeArrayElement(arr, element) {
|
|
1650
|
+
const elements = arr.getElements();
|
|
1651
|
+
for (const el of elements) if (el.getText() === element) {
|
|
1652
|
+
arr.removeElement(el);
|
|
1653
|
+
return true;
|
|
1654
|
+
}
|
|
1655
|
+
return false;
|
|
1656
|
+
}
|
|
1657
|
+
function findDefaultExportObject(sourceFile) {
|
|
1658
|
+
const defaultExport = sourceFile.getDefaultExportSymbol();
|
|
1659
|
+
if (!defaultExport) return;
|
|
1660
|
+
const declarations = defaultExport.getDeclarations();
|
|
1661
|
+
for (const decl of declarations) if (Node.isExportAssignment(decl)) {
|
|
1662
|
+
const expression = decl.getExpression();
|
|
1663
|
+
if (expression.getKind() === SyntaxKind.ObjectLiteralExpression) return expression;
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
function findCallExpression(sourceFile, functionName) {
|
|
1667
|
+
const callExpressions = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression);
|
|
1668
|
+
for (const call of callExpressions) if (call.getExpression().getText() === functionName) {
|
|
1669
|
+
const args = call.getArguments();
|
|
1670
|
+
if (args.length > 0 && args[0].getKind() === SyntaxKind.ObjectLiteralExpression) return args[0];
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1674
|
+
//#endregion
|
|
1675
|
+
export { addArrayElement, addExport, addImport, addImportedPresentation, addJournalEntry, addMigration, backupExists, clearJournal, createBackup, createEmptyManifest, createInitialState, createJournalEntry, createTransformer, deleteBackup, deleteJsonValue, deleteYamlValue, dryRun, findArrayLiteral, findCallExpression, findDefaultExportObject, findObjectLiteral, findWorkspaceRoot, formatDryRunOutput, formatRunOutput, formatStatus, getBackupMetadata, getFailedMigrations, getImportedPresentations, getJournalEntries, getJsonValue, getLastSuccessfulEntry, getMigrationById, getMigrationHistory, getMigrationOrder, getObjectProperty, getStatus, getYamlValue, hasMigration, initializeState, listBackups, loadInteractiveMigration, loadMigrations, mergeJson, mergeYaml, readJournal, readJsonFile, readManifest, readState, readYamlDocument, readYamlFile, removeArrayElement, removeImport, removeImportedPresentation, removeObjectProperty, restoreBackup, run, run$1 as runCli, setJsonValue, setObjectProperty, setYamlValue, stateExists, status, transformJson, transformTypeScript, transformYaml, transformYamlDocument, updateCliVersion, validateManifest, wasMigrationSuccessful, writeJournal, writeJsonFile, writeManifest, writeState, writeYamlDocument, writeYamlFile };
|