some cleanup
This commit is contained in:
@@ -1,21 +1,34 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
import * as p from "@clack/prompts";
|
||||
import { initRenderer } from '@gregorlohaus/tdir'
|
||||
import { initRenderer, TargetExistsError } from '@gregorlohaus/tdir'
|
||||
import { z } from 'zod'
|
||||
import path from "node:path";
|
||||
import { readdir, rename } from "node:fs/promises";
|
||||
import { existsSync, readdirSync, statSync } from "node:fs";
|
||||
|
||||
p.intro(`create-glstack ${process.env.GLSTACK_DEV && 'isDev'}`);
|
||||
p.intro(`create-glstack${process.env.GLSTACK_DEV === 'true' ? ' (dev)' : ''}`);
|
||||
|
||||
const defaultName = "glstack-test"
|
||||
// Java and Kotlin keywords can't be Android package segments.
|
||||
const reservedWords = new Set(["abstract", "as", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "default", "do", "double", "else", "enum", "extends", "false", "final", "finally", "float", "for", "fun", "goto", "if", "implements", "import", "in", "instanceof", "int", "interface", "is", "long", "native", "new", "null", "object", "package", "private", "protected", "public", "return", "short", "static", "strictfp", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "true", "try", "typealias", "typeof", "val", "var", "void", "volatile", "when", "while"])
|
||||
|
||||
const isNonEmptyPath = (target: string) =>
|
||||
existsSync(target) && (!statSync(target).isDirectory() || readdirSync(target).length > 0)
|
||||
|
||||
const project = await p.group(
|
||||
{
|
||||
name: () =>
|
||||
p.text({
|
||||
message: "What would you like to name your project?",
|
||||
placeholder: "glstack-test",
|
||||
defaultValue: "glstack-test",
|
||||
placeholder: defaultName,
|
||||
defaultValue: defaultName,
|
||||
validate: (value) => {
|
||||
const name = value || defaultName
|
||||
// Used as directory, npm scope, Go module path segment and database name.
|
||||
if (!/^[a-z][a-z0-9-]*$/.test(name)) {
|
||||
return "Use lowercase letters, digits and dashes, starting with a letter"
|
||||
}
|
||||
if (isNonEmptyPath(name)) return `./${name} already exists and is not empty`
|
||||
return undefined
|
||||
},
|
||||
}),
|
||||
@@ -41,15 +54,19 @@ const project = await p.group(
|
||||
bundleid: async ({ results }) => {
|
||||
if (results.mobile !== "expo") return ""
|
||||
// Valid as both an iOS bundle identifier and an Android package name.
|
||||
const fallback = `com.${String(results.name).toLowerCase().replace(/[^a-z0-9]/g, "").replace(/^[0-9]+/, "") || "app"}.mobile`
|
||||
const nameSegment = String(results.name).replace(/[^a-z0-9]/g, "")
|
||||
const fallback = `com.${reservedWords.has(nameSegment) ? `${nameSegment}app` : nameSegment}.mobile`
|
||||
return await p.text({
|
||||
message: "What bundle identifier should the mobile app use?",
|
||||
placeholder: fallback,
|
||||
defaultValue: fallback,
|
||||
validate: (value) => {
|
||||
if (value && !/^[a-zA-Z][a-zA-Z0-9]*(\.[a-zA-Z][a-zA-Z0-9]*)+$/.test(value)) {
|
||||
if (!value) return undefined
|
||||
if (!/^[a-zA-Z][a-zA-Z0-9]*(\.[a-zA-Z][a-zA-Z0-9]*)+$/.test(value)) {
|
||||
return "Use reverse-DNS segments of letters and digits, e.g. com.example.app"
|
||||
}
|
||||
const reserved = value.split(".").find((segment) => reservedWords.has(segment))
|
||||
if (reserved) return `"${reserved}" is a Java/Kotlin keyword and can't be part of an Android package`
|
||||
return undefined
|
||||
},
|
||||
})
|
||||
@@ -60,6 +77,9 @@ const project = await p.group(
|
||||
placeholder: "github.com/glstack-test",
|
||||
defaultValue: "github.com/glstack-test",
|
||||
validate: (value) => {
|
||||
if (value && !/^[a-zA-Z0-9._~-]+(\/[a-zA-Z0-9._~-]+)*$/.test(value)) {
|
||||
return "Use a module path without scheme or trailing slash, e.g. github.com/you"
|
||||
}
|
||||
return undefined
|
||||
},
|
||||
}),
|
||||
@@ -87,23 +107,18 @@ const render = createRenderer(z.object({
|
||||
})
|
||||
}))
|
||||
const destDir = path.join("./", project.name);
|
||||
// `results` in the bundleid prompt makes group() infer it as unknown.
|
||||
render(destDir, { project: { ...project, bundleid: project.bundleid as string } }, { reverseMap: true })
|
||||
|
||||
// npm never publishes .gitignore/.npmrc, so the template stores them as _gitignore/_npmrc.
|
||||
const dotfiles: Record<string, string> = { _gitignore: ".gitignore", _npmrc: ".npmrc" }
|
||||
for (const file of await readdir(destDir, { recursive: true })) {
|
||||
const dotname = dotfiles[path.basename(file)]
|
||||
if (dotname) await rename(path.join(destDir, file), path.join(destDir, path.dirname(file), dotname))
|
||||
}
|
||||
const mapFile = Bun.file(path.join(destDir, ".tdir-map.json"))
|
||||
if (await mapFile.exists()) {
|
||||
const map = await mapFile.json()
|
||||
for (const entry of map.files) {
|
||||
const dotname = dotfiles[path.basename(entry.outputPath)]
|
||||
if (dotname) entry.outputPath = path.join(path.dirname(entry.outputPath), dotname)
|
||||
}
|
||||
await Bun.write(mapFile, JSON.stringify(map, null, 2))
|
||||
try {
|
||||
// `results` in the bundleid prompt makes group() infer it as unknown.
|
||||
render(destDir, { project: { ...project, bundleid: project.bundleid as string } }, {
|
||||
reverseMap: true,
|
||||
overwrite: false,
|
||||
// npm never publishes .gitignore/.npmrc, so the template stores them as _gitignore/_npmrc.
|
||||
rename: { _gitignore: ".gitignore", _npmrc: ".npmrc" },
|
||||
})
|
||||
} catch (error) {
|
||||
if (!(error instanceof TargetExistsError)) throw error
|
||||
p.cancel(`./${project.name} already exists and is not empty.`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const s = p.spinner();
|
||||
|
||||
Reference in New Issue
Block a user