The .kenvs/snippets directory is now deprecated in favor of Scriptlets.

This script migrates your previous txt-based snippets to the new syntax.

USE AT YOUR OWN RISK! Best create a backup of the snippets directory before running it!

Open migrate-snippets-to-scriptlets in Script Kit

import "@johnlindquist/kit"
import { parseMetadata } from "@johnlindquist/kit"
metadata = {
name: "Migrate Snippets to Scriptlets",
description: "Reads your legacy snippets folder and migrates them to the new markdown-based scriptlets syntax",
author: "JosXa",
}
const snippetsDir = kenvPath("snippets")
const snippetFiles = await readdir(snippetsDir, { encoding: "utf8", recursive: false })
const scriptletParts = ["# Migrated Snippets"]
const skippedFiles = [] as { fileName: string; reason: string }[]
const filesToDelete = [] as string[]
for (const fileName of snippetFiles) {
try {
if (!fileName.endsWith(".txt")) {
skippedFiles.push({ fileName, reason: "it is not a .txt file" })
continue
}
const fileContent = await readFile(path.join(snippetsDir, fileName), { encoding: "utf8" })
const contentRegex = /(?:\/\/.*\r?\n)*([\s\S]+)$/
const text = fileContent.match(contentRegex)?.[1]?.trim()
if (!text) {
skippedFiles.push({ fileName, reason: "no expand text was found" })
continue
}
// @ts-expect-error Typings bug
const { name: metadataName, snippet, expand, type: _, ...rest } = parseMetadata(fileContent)
const name = metadataName ?? path.parse(fileName).name
const expandMetadata = expand ?? snippet
if (!expandMetadata) {
skippedFiles.push({ fileName, reason: `no "// Expand" or "// Snippet" metadata was found` })
continue
}
scriptletParts.push(`## ${name}`)
const metadataStr = [] as string[]
metadataStr.push(`Expand: ${expandMetadata}`)
for (const [key, value] of Object.entries(rest)) {
metadataStr.push(`${titleCase(key)}: ${value}`)
}
scriptletParts.push(`<!--\n${metadataStr.join("\n")}\n-->`)
const sanitizedText = text.replaceAll("~~~", "```")
scriptletParts.push(`~~~paste\n${sanitizedText}\n\~~~\n`)
filesToDelete.push(path.join(snippetsDir, fileName))
} catch (err) {
skippedFiles.push({ fileName, reason: `an error occurred: ${err}` })
}
}
const finalScriptlet = scriptletParts.join("\n\n")
const outFile = kenvPath("scriptlets", "migrated-snippets.md")
if (await pathExists(outFile)) {
const choice = await select<"Abort" | "Yes">(
{ hint: `File at ${outFile} already exists! Overwrite it?`, strict: true, multiple: false },
["[A]bort", "[Y]es"],
)
if (choice === "Abort") {
exit()
}
await unlink(outFile)
}
await writeFile(outFile, finalScriptlet)
await edit(outFile)
await rm(filesToDelete)
function titleCase(str: string) {
return str.slice(0, 1).toUpperCase() + str.slice(1)
}
if (skippedFiles.length > 0) {
await div(
md(
`Skipped ${skippedFiles.length} files:\n\n${skippedFiles.map((x) => `- <b>${x.fileName}</b> because ${x.reason}`).join("\n")}`,
),
)
} else if (skippedFiles.length === 0) {
await div(md("All files migrated successfully, yay!"))
try {
await rmdir(snippetsDir, { recursive: true })
} catch (err) {
console.error(err)
}
}