Skip to content

Commit

Permalink
feat(bin): add zx based generators
Browse files Browse the repository at this point in the history
  • Loading branch information
joshuayoes committed Nov 19, 2022
1 parent 5b52496 commit d9b102b
Show file tree
Hide file tree
Showing 8 changed files with 297 additions and 2 deletions.
53 changes: 53 additions & 0 deletions boilerplate/bin/generate/component.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/usr/bin/env zx
import "zx/globals"
import { prettier } from "../tools/string.mjs"
import { dir } from "../tools/path.mjs"
import { update } from "../tools/patch.mjs"

const name = await question("Component name? ")

const file = prettier(
/*ts*/ `
import * as React from "react"
import { StyleProp, TextStyle, View, ViewStyle } from "react-native"
import { observer } from "mobx-react-lite"
import { colors, typography } from "../theme"
import { Text } from "./Text"
export interface NameProps {
/**
* An optional style override useful for padding & margin.
*/
style?: StyleProp<ViewStyle>
}
/**
* Describe your Name here
*/
export const Name = observer(function Name(props: NameProps) {
const { style } = props
const $styles = [$container, style]
return (
<View style={$styles}>
<Text style={$text}>Hello</Text>
</View>
)
})
const $container: ViewStyle = {
justifyContent: "center",
}
const $text: TextStyle = {
fontFamily: typography.primary.normal,
fontSize: 14,
color: colors.palette.primary500,
}
`,
{ Name: name },
)

await fs.writeFile(dir.components(`${name}.tsx`), file)

await update(dir.components("index.ts"), (file) => file + `export * from "./${name}"` + "\n")
49 changes: 49 additions & 0 deletions boilerplate/bin/generate/model.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env zx
import "zx/globals"
import { insert, update } from "../tools/patch.mjs"
import { camelCase, prettier } from "../tools/string.mjs"
import { dir } from "../tools/path.mjs"

const name = await question("Model name? ")

const file = prettier(
/*ts*/ `
import { Instance, SnapshotIn, SnapshotOut, types } from "mobx-state-tree"
import { withSetPropAction } from "./helpers/withSetPropAction"
/**
* Model description here for TypeScript hints.
*/
export const NameModel = types
.model("Name")
.props({})
.actions(withSetPropAction)
.views((self) => ({})) // eslint-disable-line @typescript-eslint/no-unused-vars
.actions((self) => ({})) // eslint-disable-line @typescript-eslint/no-unused-vars
export interface Name extends Instance<typeof NameModel> {}
export interface NameSnapshotOut extends SnapshotOut<typeof NameModel> {}
export interface NameSnapshotIn extends SnapshotIn<typeof NameModel> {}
export const createNameDefaultModel = () => types.optional(NameModel, {})
`,
{ Name: name },
)
await fs.writeFile(dir.models(`${name}.ts`), file)

await update(dir.models("index.ts"), (file) => file + `export * from "./${name}"` + "\n")

if (!name.endsWith("Store")) {
process.exit(0)
}

await update(
dir.models("RootStore.ts"),
(file) =>
insert(file, `"mobx-state-tree"`, (s) => s + `\n` + `import { ${name}Model } from "./${name}"`),
(file) =>
insert(
file,
`types.model("RootStore").props({`,
(s) => s + `\n` + ` ${camelCase(name)}: types.optional(${name}Model, {}),`,
),
)
53 changes: 53 additions & 0 deletions boilerplate/bin/generate/navigator.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/usr/bin/env zx
import "zx/globals"
import { prettier } from "../tools/string.mjs"
import { dir } from "../tools/path.mjs"
import { update } from "../tools/patch.mjs"

const name = await question("Navigator name? ")
const fileName = `${name}Navigator`
const ext = "tsx"

const file = prettier(
/*ts*/ `
import React, { FC } from "react"
import { observer } from "mobx-react-lite"
import { ViewStyle } from "react-native"
import { StackScreenProps } from "@react-navigation/stack"
import { AppStackScreenProps } from "../navigators"
import { Screen, Text } from "../components"
// import { useNavigation } from "@react-navigation/native"
// import { useStores } from "../models"
// STOP! READ ME FIRST!
// To fix the TS error below, you'll need to add the following things in your navigation config:
// - Add Name: undefined to AppStackParamList
// - Import your screen, and add it to the stack:
// <Stack.Screen name="Name" component={<%= props.pascalCaseName%>Screen} />
// Hint: Look for the 🔥!
// REMOVE ME! ⬇️ This TS ignore will not be necessary after you've added the correct navigator param type
// @ts-ignore
export const NameScreen: FC<StackScreenProps<AppStackScreenProps, "Name">> = observer(function NameScreen() {
// Pull in one of our MST stores
// const { someStore, anotherStore } = useStores()
// Pull in navigation via hook
// const navigation = useNavigation()
return (
<Screen style={$root} preset="scroll">
<Text text="Name" />
</Screen>
)
})
const $root: ViewStyle = {
flex: 1,
}
`,
{ Name: name },
)

await fs.writeFile(dir.navigators(`${fileName}.${ext}`), file)

await update(dir.navigators("index.ts"), (file) => file + `export * from "./${fileName}"` + "\n")
35 changes: 35 additions & 0 deletions boilerplate/bin/generate/screen.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/usr/bin/env zx
import "zx/globals"
import { prettier } from "../tools/string.mjs"
import { dir } from "../tools/path.mjs"
import { update } from "../tools/patch.mjs"

const name = await question("Screen name? ")
const fileName = `${name}Screen`
const ext = "tsx"

const file = prettier(
/*ts*/ `
import React from "react"
import { createStackNavigator } from "@react-navigation/stack"
import { WelcomeScreen } from "../screens"
export type NameNavigatorParamList = {
Demo: undefined
}
const Stack = createStackNavigator<NameNavigatorParamList>()
export const NameNavigator = () => {
return (
<Stack.Navigator screenOptions={{ cardStyle: { backgroundColor: "transparent" }, headerShown: false, }}>
<Stack.Screen name="Demo" component={WelcomeScreen} />
</Stack.Navigator>
)
}
`,
{ Name: name },
)

await fs.writeFile(dir.screens(`${fileName}.${ext}`), file)

await update(dir.screens("index.ts"), (file) => file + `export * from "./${fileName}"` + "\n")
26 changes: 26 additions & 0 deletions boilerplate/bin/tools/patch.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/** @callback Replacement @param search {string} @returns {string} */
/**
* Insert a string into a file based on a search string.
* @param file {string} contents of the file
* @param search {string} string to search for in the file
* @param replacement {Replacement} callback to construct to replacement string
* @returns {string} string with the replacement made
*/
export const insert = (file, search, replacement) => file.replace(search, replacement(search))

/**
* @callback UpdateCallback
* @param file {string} contents of the file
* @returns {string} updated file contents
*/

/**
*
* @param filepath {string} path to the file to update
* @param {...UpdateCallback} callbacks functions to update the contents of the file
*/
export const update = async (filepath, ...callbacks) => {
const filecontent = await fs.readFile(filepath, "utf-8")
const newfilecontent = callbacks.reduce((file, callback) => callback(file), filecontent)
await fs.writeFile(filepath, newfilecontent)
}
52 changes: 52 additions & 0 deletions boilerplate/bin/tools/path.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
export const APP_DIR = path.join(__dirname, "..", "..", "app")

/** @param {...string} args */
export const app = (...args) => path.join(APP_DIR, ...args)

export const dir = {
/**
* `path.join` with `app/components` prefix
* @param {...string} args
*/
components: (...args) => app("components", ...args),
/**
* `path.join` with `app/config` prefix
* @param {...string} args
*/
config: (...args) => app("config", ...args),
/**
* `path.join` with `app/i18n` prefix
* @param {...string} args
*/
i18n: (...args) => app("i18n", ...args),
/**
* `path.join` with `app/models` prefix
* @param {...string} args
*/
models: (...args) => app("models", ...args),
/**
* `path.join` with `app/navigators` prefix
* @param {...string} args
*/
navigators: (...args) => app("navigators", ...args),
/**
* `path.join` with `app/screens` prefix
* @param {...string} args
*/
screens: (...args) => app("screens", ...args),
/**
* `path.join` with `app/services` prefix
* @param {...string} args
*/
services: (...args) => app("services", ...args),
/**
* `path.join` with `app/theme` prefix
* @param {...string} args
*/
theme: (...args) => app("theme", ...args),
/**
* `path.join` with `app/utils` prefix
* @param {...string} args
*/
utils: (...args) => app("utils", ...args),
}
21 changes: 21 additions & 0 deletions boilerplate/bin/tools/string.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* @param {string} str
* @return {string}
* @see https://stackoverflow.com/questions/2970525/converting-any-string-into-camel-case
*/
export const camelCase = (str) => {
return str
.replace(/(?:^\w|[A-Z]|\b\w)/g, function (word, index) {
return index == 0 ? word.toLowerCase() : word.toUpperCase()
})
.replace(/\s+/g, "")
}

/**
* @param file {string} contents of the file
* @param replaceValues {Object.<string,string>} key value pairs to replace in the file
*/
export const prettier = (file, replaceValues = {}) =>
Object.entries(replaceValues)
.reduce((file, [key, value]) => file.replace(new RegExp(key, "g"), value), file)
.trim() + "\n"
10 changes: 8 additions & 2 deletions boilerplate/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@
"expo:ios": "expo start --ios",
"expo:web": "expo start --web",
"expo:build:detox": "detox build -c ios.sim.expo",
"expo:test:detox": "./bin/downloadExpoApp.sh && detox test --configuration ios.sim.expo"
"expo:test:detox": "./bin/downloadExpoApp.sh && detox test --configuration ios.sim.expo",
"generate:component": "zx bin/generate/component.mjs",
"generate:model": "zx bin/generate/model.mjs",
"generate:navigator": "zx bin/generate/navigator.mjs",
"generate:screen": "zx bin/generate/screen.mjs"
},
"overrides": {
"react-error-overlay": "6.0.9"
Expand Down Expand Up @@ -68,7 +72,8 @@
"react-native-screens": "3.15.0",
"reactotron-mst": "3.1.4",
"reactotron-react-js": "^3.3.7",
"reactotron-react-native": "5.0.3"
"reactotron-react-native": "5.0.3",
"zx": "^7.1.1"
},
"devDependencies": {
"@babel/core": "^7.18.0",
Expand Down Expand Up @@ -166,6 +171,7 @@
"eslintConfig": {
"root": true,
"parser": "@typescript-eslint/parser",
"ignorePatterns": ["bin/*"],
"extends": [
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended",
Expand Down

0 comments on commit d9b102b

Please sign in to comment.