Skip to content

Commit 90dbafa

Browse files
committed
add typescript support included in the cli
1 parent 2715069 commit 90dbafa

35 files changed

+1249
-7761
lines changed

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,8 @@
2323
"thecodingmachine",
2424
"react-native",
2525
"starter-kit"
26-
]
26+
],
27+
"devDependencies": {
28+
"prompts": "^2.4.0"
29+
}
2730
}

post-init.script.js

Lines changed: 113 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,117 @@
11
#!/usr/bin/env node
2+
const fs = require('fs');
3+
const prompts = require('prompts');
4+
const { execSync } = require('child_process');
5+
const { green, blue } = require('kleur');
26

3-
const fs = require('fs')
4-
fs.copyFile('src/Config/index.example.js', 'src/Config/index.js', (err) => {
5-
if (err) throw err;
6-
console.log('src/Config/index.example.js was copied to src/Config/index.js');
7+
prompts({
8+
type: 'confirm',
9+
name: 'usingTs',
10+
message: 'Using typescript ?',
11+
initial: false
12+
}).then(async (response) => {
13+
if (response.usingTs) {
14+
await chooseTypescript();
15+
fs.copyFile('src/Config/index.example.js', 'src/Config/index.ts', (err) => {
16+
if (err) throw err;
17+
fs.rename('src/Config/index.example.js', 'src/Config/index.example.ts', function(err) {
18+
if (err) throw err;
19+
});
20+
});
21+
execSync('yarn add -D typescript @types/jest @types/react @types/react-native @types/react-test-renderer @types/fbemitter @types/react-redux\n ', {stdio : 'pipe' });
22+
} else {
23+
fs.copyFile('src/Config/index.example.js', 'src/Config/index.js', (err) => {
24+
if (err) throw err;
25+
});
26+
}
27+
28+
//Remove the ts-support
29+
fs.rmdir('ts-support-template', { recursive: true }, (err) => {
30+
if (err) throw err;
31+
});
32+
33+
printSuccess(response.usingTs);
734
});
35+
// To improve: needed because the "Executing post init script" is on top of the prompt
36+
console.log('\n');
37+
38+
// Function that apply the ts support
39+
function chooseTypescript() {
40+
return new Promise((resolve, reject) => {
41+
// add the tsconfig.json
42+
fs.copyFile('ts-support-template/tsconfig.json', 'tsconfig.json', (err) => {
43+
if (err) throw err;
44+
});
45+
overrideWithTypeScript();
46+
renameWithTypeScript();
47+
resolve();
48+
})
49+
}
50+
51+
function overrideWithTypeScript(accumulatedPath = '/', tsRoot = 'ts-support-template') {
52+
return fs.readdirSync(`${tsRoot}${accumulatedPath}`).forEach((item) => {
53+
if (!fs.lstatSync(`${tsRoot}${accumulatedPath}${item}`).isDirectory()) {
54+
const [name] = item.split('.');
55+
fs.copyFile(`${tsRoot}${accumulatedPath}${item}`, `.${accumulatedPath}${item}`, (err) => {
56+
if (err) throw err;
57+
});
58+
if (fs.existsSync(`.${accumulatedPath}${name}.js`)) {
59+
fs.unlink(`.${accumulatedPath}${name}.js`, (err) => {
60+
if (err) throw err;
61+
});
62+
}
63+
} else {
64+
overrideWithTypeScript(`${accumulatedPath}${item}/`);
65+
}
66+
});
67+
}
68+
69+
function renameWithTypeScript(accumulatedPath = '/', isTsx = true , jsRoot = 'src') {
70+
const EXCLUDED_DIRECTORIES = ['Assets', 'Config'];
71+
const TSX_DIRECTORIES = ['Components', 'Containers', 'Navigators'];
72+
return fs.readdirSync(`${jsRoot}${accumulatedPath}`).forEach((item) => {
73+
const [name, extension] = item.split('.');
74+
if (!fs.lstatSync(`${jsRoot}${accumulatedPath}${item}`).isDirectory()) {
75+
if (extension === 'js') {
76+
let fileExtension = '.ts';
77+
const firstCharIsUpperCase = name.charAt(0).toUpperCase() === name.charAt(0);
78+
if (isTsx && firstCharIsUpperCase) {
79+
fileExtension = '.tsx';
80+
}
81+
fs.rename(`${jsRoot}${accumulatedPath}${item}`, `${jsRoot}${accumulatedPath}${name}${fileExtension}`, function(err) {
82+
if (err) throw err;
83+
});
84+
}
85+
} else {
86+
const tsxCondition = accumulatedPath === '/' ? TSX_DIRECTORIES.includes(item) : isTsx || TSX_DIRECTORIES.includes(item);
87+
if (!EXCLUDED_DIRECTORIES.includes(item)) {
88+
renameWithTypeScript(`${accumulatedPath}${item}/`, tsxCondition);
89+
}
90+
}
91+
});
92+
}
93+
94+
function printSuccess(isTs) {
95+
console.log("\n");
96+
console.log("TheCodingMachine React-Native Boilerplate initialized with success !");
97+
console.log("" +
98+
green(" .-` `:: \n" +
99+
" `///////////// `/shhhy+- ://. /sy/ /ss/ :NMN: \n" +
100+
" `sssssyhhhyhhy:` `yMMMMNNMMMd. osss: `hMMMh- .-` `+ss: +MMm/. \n" +
101+
" :MMM+----. `mMMm+:-`.oo/` osssyo` `dMMMMh- ohhh- `oyy+- sMMd/. \n" +
102+
" :MMM+` oMMN/- osssyhs`.mMMMMMh- -hds` .syy+- `hMMh:` \n" +
103+
" :MMM+` hMMd: oss-ohhhMMMhdMMh- `. +NNm/. .dMMy:` \n" +
104+
" :MMM+` oMMN/ oss-`shdMMd:dMMh- :-` sMMd/. -NMMo- \n" +
105+
" :MMM+` `mMMm/` `/+:. oss- `sdMd:.hMMh- hMys- `hMMh:` -hdy-` \n" +
106+
" :MMM+` `yMMMMdssss+ oss. `oh/- hMMh- -hyo: dMMs:` -ss+` \n" +
107+
" .oys:` .+yhyo/- -::` `` -syo- ..` +mo- `// \n" +
108+
" `` ``` `` `. "));
109+
if (isTs) {
110+
console.log(blue("THE TYPESCRIPT VERSION"));
111+
}
112+
console.log("\n");
8113

9-
console.log("TheCodingMachine React-Native Boilerplate initialized with success !");
10-
console.log("" +
11-
" .-` `:: \n" +
12-
" `///////////// `/shhhy+- ://. /sy/ /ss/ :NMN: \n" +
13-
" `sssssyhhhyhhy:` `yMMMMNNMMMd. osss: `hMMMh- .-` `+ss: +MMm/. \n" +
14-
" :MMM+----. `mMMm+:-`.oo/` osssyo` `dMMMMh- ohhh- `oyy+- sMMd/. \n" +
15-
" :MMM+` oMMN/- osssyhs`.mMMMMMh- -hds` .syy+- `hMMh:` \n" +
16-
" :MMM+` hMMd: oss-ohhhMMMhdMMh- `. +NNm/. .dMMy:` \n" +
17-
" :MMM+` oMMN/ oss-`shdMMd:dMMh- :-` sMMd/. -NMMo- \n" +
18-
" :MMM+` `mMMm/` `/+:. oss- `sdMd:.hMMh- hMys- `hMMh:` -hdy-` \n" +
19-
" :MMM+` `yMMMMdssss+ oss. `oh/- hMMh- -hyo: dMMs:` -ss+` \n" +
20-
" .oys:` .+yhyo/- -::` `` -syo- ..` +mo- `// \n" +
21-
" `` ``` `` `. ")
22-
23-
console.log('- If you need to read more about this boilerplate : https://thecodingmachine.github.io/react-native-boilerplate/')
24-
console.log('- If you have some troubles : https://github.com/thecodingmachine/react-native-boilerplate/issues')
25-
console.log('- If you love this boilerplate, give us a star, you will be a ray of sunshine in our lives :) https://github.com/thecodingmachine/react-native-boilerplate')
114+
console.log('- If you need to read more about this boilerplate : https://thecodingmachine.github.io/react-native-boilerplate/');
115+
console.log('- If you have some troubles : https://github.com/thecodingmachine/react-native-boilerplate/issues');
116+
console.log('- If you love this boilerplate, give us a star, you will be a ray of sunshine in our lives :) https://github.com/thecodingmachine/react-native-boilerplate');
117+
}

template/_prettierrc.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ module.exports = {
33
singleQuote: true,
44
trailingComma: 'all',
55
semi: false,
6-
};
6+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import React from 'react'
2+
import { View, Image } from 'react-native'
3+
import { useTheme } from '@/Theme'
4+
5+
interface Props {
6+
height?: number | string
7+
width?: number | string
8+
mode?: 'contain' | 'cover' | 'stretch' | 'repeat' | 'center'
9+
}
10+
11+
const Brand = ({ height = 200, width = 200, mode = 'contain' }: Props) => {
12+
const { Layout, Images } = useTheme()
13+
14+
return (
15+
<View style={{ height, width }}>
16+
<Image style={Layout.fullSize} source={Images.logo} resizeMode={mode} />
17+
</View>
18+
)
19+
}
20+
21+
export default Brand
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import React, { useState } from 'react'
2+
import { useDispatch, useSelector } from 'react-redux'
3+
import {
4+
View,
5+
ActivityIndicator,
6+
Text,
7+
TextInput,
8+
TouchableOpacity,
9+
} from 'react-native'
10+
import { Brand } from '@/Components'
11+
import { useTheme } from '@/Theme'
12+
import FetchOne from '@/Store/User/FetchOne'
13+
import ChangeTheme from '@/Store/Theme/ChangeTheme'
14+
import { useTranslation } from 'react-i18next'
15+
import { UserState } from '@/Store/User'
16+
import { ThemeState } from '@/Store/Theme'
17+
18+
const IndexExampleContainer = () => {
19+
const { t } = useTranslation()
20+
const { Common, Fonts, Gutters, Layout } = useTheme()
21+
const dispatch = useDispatch()
22+
23+
const user = useSelector((state: { user: UserState }) => state.user.item)
24+
const fetchOneUserLoading = useSelector(
25+
(state: { user: UserState }) => state.user.fetchOne.loading,
26+
)
27+
const fetchOneUserError = useSelector(
28+
(state: { user: UserState }) => state.user.fetchOne.error,
29+
)
30+
31+
const [userId, setUserId] = useState('1')
32+
33+
const fetch = (id: string) => {
34+
setUserId(id)
35+
dispatch(FetchOne.action(id))
36+
}
37+
38+
const changeTheme = ({ theme, darkMode }: Partial<ThemeState>) => {
39+
dispatch(ChangeTheme.action({ theme, darkMode }))
40+
}
41+
42+
return (
43+
<View style={[Layout.fill, Layout.colCenter, Gutters.smallHPadding]}>
44+
<View style={[[Layout.colCenter, Gutters.smallHPadding]]}>
45+
<Brand />
46+
{fetchOneUserLoading && <ActivityIndicator />}
47+
{fetchOneUserError ? (
48+
<Text style={Fonts.textRegular}>{fetchOneUserError.message}</Text>
49+
) : (
50+
<Text style={Fonts.textRegular}>
51+
{t('example.helloUser', { name: user.name })}
52+
</Text>
53+
)}
54+
</View>
55+
<View
56+
style={[
57+
Layout.row,
58+
Layout.rowHCenter,
59+
Gutters.smallHPadding,
60+
Gutters.largeVMargin,
61+
Common.backgroundPrimary,
62+
]}
63+
>
64+
<Text style={[Layout.fill, Fonts.textCenter, Fonts.textSmall]}>
65+
{t('example.labels.userId')}
66+
</Text>
67+
<TextInput
68+
onChangeText={(text) => fetch(text)}
69+
editable={!fetchOneUserLoading}
70+
keyboardType={'number-pad'}
71+
maxLength={1}
72+
value={userId}
73+
selectTextOnFocus
74+
style={[Layout.fill, Common.textInput]}
75+
/>
76+
</View>
77+
<Text style={[Fonts.textRegular, Gutters.smallBMargin]}>DarkMode :</Text>
78+
79+
<TouchableOpacity
80+
style={[Common.button.rounded, Gutters.regularBMargin]}
81+
onPress={() => changeTheme({ darkMode: null })}
82+
>
83+
<Text style={Fonts.textRegular}>Auto</Text>
84+
</TouchableOpacity>
85+
<TouchableOpacity
86+
style={[Common.button.outlineRounded, Gutters.regularBMargin]}
87+
onPress={() => changeTheme({ darkMode: true })}
88+
>
89+
<Text style={Fonts.textRegular}>Dark</Text>
90+
</TouchableOpacity>
91+
<TouchableOpacity
92+
style={[Common.button.outline, Gutters.regularBMargin]}
93+
onPress={() => changeTheme({ darkMode: false })}
94+
>
95+
<Text style={Fonts.textRegular}>Light</Text>
96+
</TouchableOpacity>
97+
</View>
98+
)
99+
}
100+
101+
export default IndexExampleContainer
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import React, { useEffect } from 'react'
2+
import { ActivityIndicator, View, Text } from 'react-native'
3+
import { useTheme } from '@/Theme'
4+
import { useDispatch } from 'react-redux'
5+
import InitStartup from '@/Store/Startup/Init'
6+
import { useTranslation } from 'react-i18next'
7+
import { Brand } from '@/Components'
8+
9+
const IndexStartupContainer = () => {
10+
const { Layout, Gutters, Fonts } = useTheme()
11+
12+
const { t } = useTranslation()
13+
14+
const dispatch = useDispatch()
15+
16+
useEffect(() => {
17+
dispatch(InitStartup.action())
18+
}, [dispatch])
19+
20+
return (
21+
<View style={[Layout.fill, Layout.colCenter]}>
22+
<Brand />
23+
<ActivityIndicator size={'large'} style={[Gutters.largeVMargin]} />
24+
<Text style={Fonts.textCenter}>{t('welcome')}</Text>
25+
</View>
26+
)
27+
}
28+
29+
export default IndexStartupContainer
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import React, { useEffect, useState, FunctionComponent } from 'react'
2+
import { createStackNavigator } from '@react-navigation/stack'
3+
import { IndexStartupContainer } from '@/Containers'
4+
import { useSelector } from 'react-redux'
5+
import { NavigationContainer } from '@react-navigation/native'
6+
import { navigationRef } from '@/Navigators/Root'
7+
import { SafeAreaView, StatusBar } from 'react-native'
8+
import { useTheme } from '@/Theme'
9+
import { AppearanceProvider } from 'react-native-appearance'
10+
import { StartupState } from '@/Store/Startup'
11+
12+
const Stack = createStackNavigator()
13+
14+
let MainNavigator: FunctionComponent | null
15+
16+
// @refresh reset
17+
const ApplicationNavigator = () => {
18+
const { Layout, darkMode, NavigationTheme } = useTheme()
19+
const { colors } = NavigationTheme
20+
const [isApplicationLoaded, setIsApplicationLoaded] = useState(false)
21+
const applicationIsLoading = useSelector(
22+
(state: { startup: StartupState }) => state.startup.loading,
23+
)
24+
25+
useEffect(() => {
26+
if (MainNavigator == null && !applicationIsLoading) {
27+
MainNavigator = require('@/Navigators/Main').default
28+
setIsApplicationLoaded(true)
29+
}
30+
}, [applicationIsLoading])
31+
32+
// on destroy needed to be able to reset when app close in background (Android)
33+
useEffect(
34+
() => () => {
35+
setIsApplicationLoaded(false)
36+
MainNavigator = null
37+
},
38+
[],
39+
)
40+
41+
return (
42+
<AppearanceProvider>
43+
<SafeAreaView style={[Layout.fill, { backgroundColor: colors.card }]}>
44+
<NavigationContainer theme={NavigationTheme} ref={navigationRef}>
45+
<StatusBar barStyle={darkMode ? 'light-content' : 'dark-content'} />
46+
<Stack.Navigator headerMode={'none'}>
47+
<Stack.Screen name="Startup" component={IndexStartupContainer} />
48+
{isApplicationLoaded && MainNavigator != null && (
49+
<Stack.Screen
50+
name="Main"
51+
component={MainNavigator}
52+
options={{
53+
animationEnabled: false,
54+
}}
55+
/>
56+
)}
57+
</Stack.Navigator>
58+
</NavigationContainer>
59+
</SafeAreaView>
60+
</AppearanceProvider>
61+
)
62+
}
63+
64+
export default ApplicationNavigator

0 commit comments

Comments
 (0)