11import React , { Component , Fragment } from "react" ;
22import PropTypes from "prop-types" ;
3- import { Link } from "react-router-dom" ;
3+ import { Link , withRouter } from "react-router-dom" ;
4+ import { match as matchSemver } from "semver-match" ;
45
56import { formatPackageString } from "../../utils/string" ;
67import { getInstance as api } from "../../services/ApiManager" ;
78
8- export default class Package extends Component {
9+ class Package extends Component {
910 static propTypes = {
10- match : PropTypes . object . isRequired
11+ match : PropTypes . object . isRequired ,
12+ history : PropTypes . object . isRequired
1113 } ;
1214 constructor ( props ) {
1315 super ( props ) ;
@@ -17,9 +19,15 @@ export default class Package extends Component {
1719 state : "loading"
1820 } ;
1921 async componentDidMount ( ) {
20- const { scope, name } = this . props . match . params ;
21- this . loadInfos ( scope , name ) ;
22+ const { scope, name, version } = this . props . match . params ;
23+ this . loadInfos ( scope , name , version ) ;
2224 }
25+ /**
26+ * Once the container is mounted, we have to track for changes in the router
27+ * to relaunch xhr to npm-registry, since componentDidMount won't be re-triggered
28+ * That way, we trigger this.loadInfos with fresh props from the router
29+ * @param {Object } newProps
30+ */
2331 componentWillReceiveProps ( newProps ) {
2432 const { scope, name, version } = this . props . match . params ;
2533 const {
@@ -28,17 +36,60 @@ export default class Package extends Component {
2836 version : newVersion
2937 } = newProps . match . params ;
3038 if ( scope !== newScope || name !== newName || version !== newVersion ) {
31- this . loadInfos ( newScope , newName ) ;
39+ this . loadInfos ( newScope , newName , newVersion ) ;
40+ }
41+ }
42+ /**
43+ * Redirect a final version is matched
44+ * Returns the matched versions if matched (and redirects the router).
45+ * If no need to redirect, returns false.
46+ * @param {String } scope
47+ * @param {String } name
48+ * @param {String } range
49+ * @param {Array } versions
50+ * @param {Object } distTags
51+ * @return {Boolean|String }
52+ */
53+ redirectUntilMatchVersion ( scope , name , range , versions = [ ] , distTags = { } ) {
54+ if ( typeof range !== "undefined" ) {
55+ const matchedVersion = matchSemver ( range , versions , distTags ) || "latest" ;
56+ console . log ( { range, versions, distTags, matchedVersion } ) ;
57+ if (
58+ matchedVersion &&
59+ matchedVersion !== this . props . match . params . version
60+ ) {
61+ this . props . history . replace (
62+ `/package/${ formatPackageString ( { scope, name } ) } @${ matchedVersion } `
63+ ) ;
64+ }
65+ return matchedVersion ;
3266 }
67+ return false ;
3368 }
34- async loadInfos ( scope , name ) {
69+ /**
70+ * Loads infos from the npm registry.
71+ * @param {String } scope
72+ * @param {String } name
73+ * @param {String } range
74+ */
75+ async loadInfos ( scope , name , range ) {
3576 this . setState ( { state : "loading" } ) ;
3677 try {
3778 const { data : packageInfos } = await api ( ) . packageInfos (
3879 formatPackageString ( { scope, name } )
3980 ) ;
4081 console . log ( packageInfos . name , { packageInfos } ) ;
41- this . setState ( { packageInfos, state : "loaded" } ) ;
82+ const matched = this . redirectUntilMatchVersion (
83+ scope ,
84+ name ,
85+ range ,
86+ Object . keys ( packageInfos . versions ) ,
87+ packageInfos [ "dist-tags" ]
88+ ) ;
89+ if ( matched ) {
90+ console . log ( "matched" , matched ) ;
91+ this . setState ( { packageInfos, state : "loaded" } ) ;
92+ }
4293 } catch ( e ) {
4394 console . error ( e ) ;
4495 this . setState ( { packageInfos : null , state : "error" } ) ;
@@ -80,3 +131,8 @@ export default class Package extends Component {
80131 ) ;
81132 }
82133}
134+
135+ /**
136+ * Gives access to history/location/match in props from the router - https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/withRouter.md
137+ */
138+ export default withRouter ( Package ) ;
0 commit comments