Joscha Götzer

Joscha Götzer

// Name: JSON to TypeScript
// Description: Convert some JSON to TypeScript models
import "@johnlindquist/kit"
import jsonToTS from "json-to-ts"
import { submitShortcut } from "@johnlindquist/kit/core/utils"
import { refreshable } from "@josxa/kit-utils"
import { crudArg } from "@josxa/kit-utils"
import ModernError from "modern-errors"
let json = args[0]
if (!json) {
json = await editor({
language: "json",
validate(input: string): true | string {
try {
JSON.parse(input)
return true
} catch (err) {
return ModernError.normalize(err).message
}
},
shortcuts: [submitShortcut],
})
}
const rootName = await crudArg("Name of the root type?")
const options: Parameters<typeof jsonToTS>[1] = {
rootName,
useTypeAlias: true,
}
await refreshable(async ({ refresh }) => {
let types = ""
try {
types = `${jsonToTS(JSON.parse(json), options).join("\n\n")}\n`
} catch (error) {
const hint = ModernError.normalize(error).message
setHint(hint)
exit()
}
if (options.useTypeAlias) {
types = types.replaceAll(/^type /g, "export type ")
} else {
types = types.replaceAll(/^interface /g, "export interface ")
}
await editor({
value: types,
language: "ts",
shortcuts: [
{
key: `${cmd}+shift+t`,
name: `Use ${options.useTypeAlias ? "Interfaces" : "Types"}`,
onPress: () => {
options.useTypeAlias = !options.useTypeAlias
refresh()
},
visible: true,
bar: "right",
},
{
name: "Copy to Clipboard",
key: `${cmd}+shift+c`,
onPress: async (input) => {
await clipboard.writeText(input)
setHint("Copied to clipboard")
},
visible: true,
bar: "right",
},
],
})
})
// Name: Cron Expression Validator
// Description: Validates and helps you build Crontab expressions
// Shortcode: cron
// Author: @JosXa, loosely based on Ricardo Gonçalves Bassete's version
import "@johnlindquist/kit"
import { computed, effect, signal } from "@preact/signals-core"
import cronstrue from "cronstrue"
import { markdownTable } from "markdown-table"
const FONT_SIZE = "0.8em"
const allowedCharsTable = markdownTable(
[
["Character", "Meaning"],
["`*`", "any value"],
["`,`", "value list separator"],
["`-`", "range of values"],
["`/`", 'step values (e.g. `*/5 * * * *` for "every 5 minutes")'],
],
{ align: "l" },
)
const tableHtml = md(allowedCharsTable).replace('<th align="left">', '<th align="left" style="width: 17%">')
const input = signal("* * * * *")
const parts = computed(() => input.value.split(" "))
const parsedExpression = computed(() => {
try {
return cronstrue.toString(input.value)
} catch (err) {
return undefined
}
})
const isValid = computed(() => !!parsedExpression.value)
const asciiHint = computed(() => {
if (!input.value) {
return `
| | | | |
| | | | +----- day of the week (0 - 7) (Sunday = 0 or 7)
| | | +------- month (1 - 12)
| | +--------- day of the month (1 - 31)
| +----------- hour (0 - 23)
+------------- minute (0 - 59)
`.trim()
}
const hasSecond = parts.value.length >= 6
const names = [
"second (0 - 59)",
"minute (0 - 59)",
"hour (0 - 23)",
"day of the month (1 - 31)",
"month (1 - 12)",
"day of the week (0 - 7) (Sunday = 0 or 7)",
]
if (!hasSecond) {
names.splice(0, 1)
}
/*
0 1 2 3 4 5 👉 partIdx
0 | | | | | +----- day of the week (0 - 7) (Sunday = 0 or 7)
1 | | | | +------- month (1 - 12)
2 | | | +--------- day of the month (1 - 31)
3 | | +----------- hour (0 - 23)
4 | +------------- minute (0 - 59)
5 +--------------- second (0 - 59)
👇
lineIdx
*/
const columns = parts.value.reduce(
(agg, part, partIdx) => {
const prev = agg[partIdx - 1]
const startCol = prev ? prev.endCol + 1 : 0
const endCol = startCol + part.length
const name = names[partIdx]!
agg.push({
partIdx,
startCol,
endCol,
part,
gapToPrevious: Math.max(0, endCol - startCol),
name,
})
return agg
},
[] as Array<{
startCol: number
endCol: number
part: string
gapToPrevious: number
partIdx: number
name: string
}>,
)
const lines: string[] = []
const maxLen = columns.slice(-1)[0]!.endCol + 5
for (let lineIdx = -1; lineIdx < columns.length; lineIdx++) {
let line = ""
for (const { gapToPrevious, partIdx, name } of columns) {
if (lineIdx === -1) {
line += "|"
line += " ".repeat(gapToPrevious)
continue
}
if (partIdx + lineIdx === columns.length - 1) {
line += "+"
line = line.padEnd(maxLen, "-")
line += ` ${name}`
break
}
line += "|"
line += " ".repeat(gapToPrevious)
}
lines.push(line)
}
return lines.join("\n")
})
const asciiHintHtml = computed(() =>
`<div style="font-size: ${FONT_SIZE};" class="px-4"><pre>${asciiHint}</pre></div>`.trim(),
)
const resultMessage = computed(() => {
if (!input.value) {
return ""
}
return (
"<br>" +
(parsedExpression.value
? `<h3 class="px-4" style="color: rgba(var(--color-primary), var(--tw-text-opacity))">👉 ${parsedExpression.value}</h3>`
: `<h3 class="px-4" style="color: #f65671">❌ The expression "${input.value}" cannot be parsed.</h3>`)
)
})
const enter = computed(() => (isValid.value ? "Copy" : ""))
const panel = computed(() => `<div>${asciiHintHtml.value}${resultMessage.value}<br><hr>${tableHtml}</div>`)
const cleanup: Array<() => void> = []
await arg({
placeholder: "Type a Crontab expression",
input: input.value,
className: "p-0",
inputClassName: "font-mono",
css: `
#input {
min-width: 250px !important;
font-size: ${FONT_SIZE} !important;
}
`,
onInit() {
cleanup.push(effect(() => setEnter(enter.value)))
cleanup.push(effect(() => setPanel(panel.value)))
},
onInput(val) {
if (!val) {
input.value = ""
return
}
let sanitized = val
// Replace duplicate spaces
.replaceAll(/\s{2,}/g, " ")
// Leading whitespace
.replaceAll(/^\s+/g, "")
const s = sanitized.split(" ")
// Ensure maximum of 6 parts
if (s.length > 6) {
sanitized = s.slice(0, 6).join(" ")
}
if (val !== sanitized) {
setInput(sanitized)
}
input.value = sanitized
},
enter: enter.value,
alwaysOnTop: true,
})
cleanup.forEach((fn) => fn())
await clipboard.writeText(input.value)
// Name: Urban Dictionary
// Keyword: ud
import "@johnlindquist/kit"
import type { Choice } from "@johnlindquist/kit"
import { DateTime } from "luxon"
import { escapeHTML } from "../../.kit/core/utils"
await setInput("")
const debouncedOnInput = debounce(async (input) => {
if (!input) {
return
}
try {
const choices = await getResultsAsChoices(input)
await setChoices(choices)
} finally {
setHint("")
}
}, 500)
const link = await arg({
placeholder: "Search a word definition",
onInput: (input) => {
setHint("Searching...")
debouncedOnInput(input)
},
})
await clipboard.writeText(link)
notify({ title: "Urban Dictionary", message: "Definition URL copied to clipboard." })
await wait(200) // Somehow the notify function needs some time to complete...
type Definition = {
definition: string
permalink: string
thumbs_up: number
author: string
word: string
defid: number
current_vote: string
written_on: string
example: string
thumbs_down: number
}
async function getResultsAsChoices(query: string) {
const response = await get<{ list: Definition[] }>(
`https://api.urbandictionary.com/v0/define?term=${encodeURI(query)}`,
)
return response.data.list.map(
(e) =>
({
name: e.word,
description: e.definition.replaceAll(/[\[\]]/g, ""),
preview: formatMdDefinition(e),
value: e.permalink,
}) as Choice<string>,
)
}
function formatMdDefinition(def: Definition) {
const parts: string[] = []
parts.push(`# ${def.word}`)
parts.push(f(def.definition))
parts.push("<br><br>")
parts.push(`<i>${f(def.example)}</i>`)
parts.push("<br><br>")
const timestamp = DateTime.fromISO(def.written_on).toFormat("MMMM d, yyyy")
const author =
def.author && `by [${def.author}](https://www.urbandictionary.com/author.php?author=${encodeURI(def.author)})`
parts.push(`<b>${author ? `${author} ` : ""}${timestamp}</b>`)
parts.push("<br>")
parts.push(`👍 ${def.thumbs_up} | 👎 ${def.thumbs_down}`)
return md(parts.join("\n"))
}
function f(val: string) {
const result = val.replaceAll("\r\n", "\n")
return result.replaceAll(/\[(.*?)]/g, (substring: string, ...args: any[]) => {
const term = escapeHTML(args[0])
return `[${term}](https://www.urbandictionary.com/define.php?term=${encodeURI(term)})`
})
}
// Name: Generate scripts.d.ts
// Description: Enables autocompletion for the `run` command
import "@johnlindquist/kit"
import { writeFile } from "node:fs/promises"
const scripts = await getScripts()
const availableScripts = scripts.map((x) => ` | "${x.command}"`).join("\n")
const body = `// Do not edit. Autogenerated by generate-scripts-declarations.ts
import type { Run } from "../../.kit/types/kit"
type AvailableScript =
${availableScripts};
declare module "@johnlindquist/kit/types/kit" {
export interface Run {
// biome-ignore lint/style/useShorthandFunctionType: <explanation>
(command?: AvailableScript, ...args: string[]): Promise<any>
}
}
declare global {
var run: Run
}`
await ensureDir(kenvPath("@types"))
await writeFile(kenvPath("@types", "scripts.d.ts"), body, { encoding: "utf-8" })
**/.idea/GitLink.xml
**/.idea/deploymentTargetDropDown.xml
**/.idea/gbrowser_project.xml
**/.idea/highlightedFiles.xml
**/.idea/discord.xml
**/.idea/developer-tools.xml
**/.idea/CustomInspectionsConfig.xml
// Name: Edit global .gitignore
// Description: Opens an editor with the global .gitignore file and sets it up if it doesn't exist
import "@johnlindquist/kit"
import { writeFile } from "node:fs/promises"
import { startSpinner } from "@josxa/kit-utils"
const cache = await db({ defaultEntries: [] as string[] })
const DESIRED_IGNORE_PATH = home(".global.gitignore")
const ensureGlobalGitIgnorePathConfigured = async () => {
try {
const existing = await exec("git config --global core.excludesfile")
debugger
if (existing.stdout.toString() !== DESIRED_IGNORE_PATH) {
await exec(`git config --global core.excludesfile "${DESIRED_IGNORE_PATH}"`)
await div(`Global gitconfig file configured to be at ${DESIRED_IGNORE_PATH}`)
}
} catch (err) {
await exec(`git config --global core.excludesfile "${DESIRED_IGNORE_PATH}"`)
await div(`Global gitconfig file configured to be at ${DESIRED_IGNORE_PATH}`)
}
}
await ensureGlobalGitIgnorePathConfigured()
const content = await ensureReadFile(DESIRED_IGNORE_PATH, "", { encoding: "utf-8" })
const updated = ensureDefaultEntriesPresent(content).trim()
if (updated !== content) {
await writeFile(DESIRED_IGNORE_PATH, updated, { encoding: "utf-8" })
await div("Inserted default entries and wrote to file!")
}
const edited = (await editor({ value: updated })).trim()
if (edited !== updated) {
await writeFile(DESIRED_IGNORE_PATH, edited, { encoding: "utf-8" })
startSpinner("dots", { initialMessage: "Writing..." })
await wait(1200)
await submit("done")
}
cache.defaultEntries = edited.split("\n")
await cache.write()
function ensureDefaultEntriesPresent(content: string): string {
const lines = content.split("\n")
for (const x of cache.defaultEntries) {
if (!lines.includes(x)) {
lines.unshift(x)
}
}
return lines.join("\n")
}
// Name: Kill
// Description: Immediately terminates a Windows process using `taskkill /F /im <name.exe>`
// Keyword: kill
// Pass: true
import '@johnlindquist/kit'
import { Choice } from '@johnlindquist/kit'
const cache = await db({ recents: ["node.exe"] as string[] })
const exe = arg.pass ?? await arg('Type the name of a .exe to kill', cache.recents.map(x => {
if (x === 'node.exe') {
return {
name: x,
description: "CAUTION: This will stop all Kit Scripts (including this one, so there won't be an output)",
value: x
} as Choice
}
return x
}))
cache.recents = Array.from(new Set(cache.recents).add(exe))
cache.write().then()
try {
const res = await exec(`taskkill /F /im "${exe}"`)
await div(res.stdout + res.stderr)
} catch (err) {
if (err.exitCode === 128) {
await div('No process was active.')
}
console.error(err);
exit()
}