Skip to content

Commit 7076269

Browse files
authored
Fix creating and building example and cleanup code around it (#75)
1 parent 0b78730 commit 7076269

File tree

13 files changed

+277
-71
lines changed

13 files changed

+277
-71
lines changed

.gitignore

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,29 @@
1+
### macOS ###
2+
*.DS_Store
3+
.AppleDouble
4+
.LSOverride
5+
6+
# Icon must end with two \r
7+
Icon
8+
9+
# Thumbnails
10+
._*
11+
12+
# Files that might appear in the root of a volume
13+
.DocumentRevisions-V100
14+
.fseventsd
15+
.Spotlight-V100
16+
.TemporaryItems
17+
.Trashes
18+
.VolumeIcon.icns
19+
.com.apple.timemachine.donotpresent
20+
21+
# Directories potentially created on remote AFP share
22+
.AppleDB
23+
.AppleDesktop
24+
Network Trash Folder
25+
Temporary Items
26+
.apdisk
27+
128
node_modules
229
npm-debug.log
3-
4-
example

lib.js

Lines changed: 56 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const pascalCase = require('pascal-case');
44
const paramCase = require('param-case');
55

66
const templates = require('./templates');
7-
const { hasPrefix, createFile, createFolder } = require('./utils');
7+
const { hasPrefix, createFile, createFolder, npmAddScriptSync, exec } = require('./utils');
88
const { execSync } = require('child_process');
99

1010
const DEFAULT_NAME = 'Library';
@@ -13,12 +13,21 @@ const DEFAULT_MODULE_PREFIX = 'react-native';
1313
const DEFAULT_PACKAGE_IDENTIFIER = 'com.reactlibrary';
1414
const DEFAULT_PLATFORMS = ['android', 'ios', 'windows'];
1515
const DEFAULT_OVERRIDE_PREFIX = false;
16-
const DEFAULT_GITHUB_ACCOUNT = 'github_account'
17-
const DEFAULT_AUTHOR_NAME = 'Your Name'
18-
const DEFAULT_AUTHOR_EMAIL = 'yourname@email.com'
19-
const DEFAULT_LICENSE = 'Apache-2.0'
16+
const DEFAULT_GITHUB_ACCOUNT = 'github_account';
17+
const DEFAULT_AUTHOR_NAME = 'Your Name';
18+
const DEFAULT_AUTHOR_EMAIL = 'yourname@email.com';
19+
const DEFAULT_LICENSE = 'Apache-2.0';
2020
const DEFAULT_GENERATE_EXAMPLE = false;
2121

22+
const renderTemplate = (name, template, templateArgs) => {
23+
const filename = path.join(name, template.name(templateArgs));
24+
const baseDir = filename.split(path.basename(filename))[0];
25+
26+
return createFolder(baseDir).then(() =>
27+
createFile(filename, template.content(templateArgs))
28+
);
29+
}
30+
2231
module.exports = ({
2332
namespace,
2433
name = DEFAULT_NAME,
@@ -58,16 +67,9 @@ module.exports = ({
5867
identifier, it is recommended to customize the package identifier.`);
5968
}
6069

61-
const moduleName = rootFolderName = `${modulePrefix}-${paramCase(name)}`;
70+
const moduleName = `${modulePrefix}-${paramCase(name)}`;
71+
const rootFolderName = moduleName;
6272
return createFolder(rootFolderName)
63-
.then(() => {
64-
if (!generateExample) {
65-
return Promise.resolve()
66-
}
67-
// Note: The example has to be created first because it will fail if there
68-
// is already a package.json in the folder in which the command is executed.
69-
return execSync('react-native init example', { cwd: './' + rootFolderName, stdio:'inherit'});
70-
})
7173
.then(() => {
7274
return Promise.all(templates.filter((template) => {
7375
if (template.platform) {
@@ -79,7 +81,7 @@ module.exports = ({
7981
if (!template.name) {
8082
return Promise.resolve();
8183
}
82-
const args = {
84+
const templateArgs = {
8385
name: `${prefix}${pascalCase(name)}`,
8486
moduleName,
8587
packageIdentifier,
@@ -89,29 +91,52 @@ module.exports = ({
8991
authorName,
9092
authorEmail,
9193
license,
94+
generateExample,
9295
};
9396

94-
const filename = path.join(rootFolderName, template.name(args));
95-
var baseDir = filename.split(path.basename(filename))[0];
96-
97-
return createFolder(baseDir).then(() =>
98-
createFile(filename, template.content(args))
99-
);
97+
return renderTemplate(rootFolderName, template, templateArgs);
10098
}));
10199
})
102100
.then(() => {
101+
// Generate the example if necessary
103102
if (!generateExample) {
104103
return Promise.resolve();
105104
}
106-
// Adds and links the created library project
107-
const pathExampleApp = `./${name}/example`;
108-
const options = { cwd: pathExampleApp, stdio:'inherit'};
109-
try {
110-
execSync('yarn add file:../', options);
111-
} catch (e) {
112-
execSync('npm install ../', options);
113-
execSync('npm install', options);
114-
}
115-
execSync('react-native link', options);
105+
106+
const initExampleOptions = { cwd: `./${rootFolderName}`, stdio: 'inherit' };
107+
return exec('react-native init example', initExampleOptions)
108+
.then(() => {
109+
// Execute the example template
110+
const exampleTemplates = require('./templates/example');
111+
return Promise.all(
112+
exampleTemplates.map((template) => {
113+
return renderTemplate(rootFolderName, template);
114+
})
115+
);
116+
})
117+
.then(() => {
118+
// Adds and link the new library
119+
return new Promise((resolve, reject) => {
120+
// Add postinstall script to example package.json
121+
const pathExampleApp = `./${rootFolderName}/example`;
122+
const moduleName = `${modulePrefix}-${paramCase(name)}`;
123+
npmAddScriptSync(`${pathExampleApp}/package.json`, {
124+
key: 'postinstall',
125+
value: `node ../scripts/examples_postinstall.js node_modules/${moduleName}`
126+
});
127+
128+
// Add and link the new library
129+
const addLinkLibraryOptions = { cwd: pathExampleApp, stdio: 'inherit' };
130+
try {
131+
execSync('yarn add file:../', addLinkLibraryOptions);
132+
} catch (e) {
133+
execSync('npm install ../', addLinkLibraryOptions);
134+
execSync('npm install', addLinkLibraryOptions);
135+
}
136+
execSync('react-native link', addLinkLibraryOptions);
137+
138+
return resolve();
139+
});
140+
});
116141
});
117142
};

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"homepage": "https://github.com/frostney/react-native-create-library#readme",
4040
"dependencies": {
4141
"commander": "^2.9.0",
42+
"jsonfile": "^4.0.0",
4243
"mkdirp": "^0.5.1",
4344
"node-emoji": "^1.5.1",
4445
"param-case": "^2.1.0",

templates/android.js

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
module.exports = platform => [{
22
name: () => `${platform}/build.gradle`,
3-
content: ({ packageIdentifier }) => `
4-
buildscript {
3+
content: ({ packageIdentifier }) => `buildscript {
54
repositories {
65
jcenter()
76
}
@@ -120,20 +119,18 @@ afterEvaluate { project ->
120119
}
121120
}
122121
}
123-
`,
122+
`,
124123
}, {
125124
name: () => `${platform}/src/main/AndroidManifest.xml`,
126-
content: ({ packageIdentifier }) => `
127-
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
125+
content: ({ packageIdentifier }) => `<manifest xmlns:android="http://schemas.android.com/apk/res/android"
128126
package="${packageIdentifier}">
129127
130128
</manifest>
131-
`,
129+
`,
132130
}, {
133131
name: ({ packageIdentifier, name }) =>
134132
`${platform}/src/main/java/${packageIdentifier.split('.').join('/')}/${name}Module.java`,
135-
content: ({ packageIdentifier, name }) => `
136-
package ${packageIdentifier};
133+
content: ({ packageIdentifier, name }) => `package ${packageIdentifier};
137134
138135
import com.facebook.react.bridge.ReactApplicationContext;
139136
import com.facebook.react.bridge.ReactContextBaseJavaModule;
@@ -162,8 +159,7 @@ public class ${name}Module extends ReactContextBaseJavaModule {
162159
}, {
163160
name: ({ packageIdentifier, name }) =>
164161
`${platform}/src/main/java/${packageIdentifier.split('.').join('/')}/${name}Package.java`,
165-
content: ({ packageIdentifier, name }) => `
166-
package ${packageIdentifier};
162+
content: ({ packageIdentifier, name }) => `package ${packageIdentifier};
167163
168164
import java.util.Arrays;
169165
import java.util.Collections;
@@ -189,11 +185,11 @@ public class ${name}Package implements ReactPackage {
189185
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
190186
return Collections.emptyList();
191187
}
192-
}`,
188+
}
189+
`,
193190
}, {
194191
name: () => `${platform}/README.md`,
195-
content: () => `
196-
README
192+
content: () => `README
197193
======
198194
199195
If you want to publish the lib as a maven dependency, follow these steps before publishing a new version to npm:
@@ -207,4 +203,5 @@ sdk.dir=/Users/{username}/Library/Android/sdk
207203
3. Delete the \`maven\` folder
208204
4. Run \`sudo ./gradlew installArchives\`
209205
5. Verify that latest set of generated files is in the maven folder with the correct version number
210-
`}];
206+
`
207+
}];

templates/example.js

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/* eslint max-len: 0 */
2+
3+
module.exports = [{
4+
name: () => 'scripts/examples_postinstall.js',
5+
content: () =>
6+
`#!/usr/bin/env node
7+
8+
/*
9+
* Using libraries within examples and linking them within packages.json like:
10+
* "react-native-library-name": "file:../"
11+
* will cause problems with the metro bundler if the example will run via
12+
* \`react-native run-[ios|android]\`. This will result in an error as the metro
13+
* bundler will find multiple versions for the same module while resolving it.
14+
* The reason for that is that if the library is installed it also copies in the
15+
* example folder itself as well as the node_modules folder of the library
16+
* although their are defined in .npmignore and should be ignored in theory.
17+
*
18+
* This postinstall script removes the node_modules folder as well as all
19+
* entries from the libraries .npmignore file within the examples node_modules
20+
* folder after the library was installed. This should resolve the metro
21+
* bundler issue mentioned above.
22+
*
23+
* It is expected this scripts lives in the libraries root folder within a
24+
* scripts folder. As first parameter the relative path to the libraries
25+
* folder within the examples node_modules folder should be provided.
26+
* An example's package.json entry could look like:
27+
* "postinstall": "node ../scripts/examples_postinstall.js node_modules/react-native-library-name/"
28+
*/
29+
30+
'use strict';
31+
32+
const fs = require('fs');
33+
const path = require('path');
34+
35+
/// Delete all files and directories for the given path
36+
const removeFileDirectoryRecursively = fileDirPath => {
37+
// Remove file
38+
if (!fs.lstatSync(fileDirPath).isDirectory()) {
39+
fs.unlinkSync(fileDirPath);
40+
return;
41+
}
42+
43+
// Go down the directory an remove each file / directory recursively
44+
fs.readdirSync(fileDirPath).forEach(entry => {
45+
const entryPath = path.join(fileDirPath, entry);
46+
removeFileDirectoryRecursively(entryPath);
47+
});
48+
fs.rmdirSync(fileDirPath);
49+
};
50+
51+
/// Remove example/node_modules/react-native-library-name/node_modules directory
52+
const removeLibraryNodeModulesPath = (libraryNodeModulesPath) => {
53+
const nodeModulesPath = path.resolve(libraryNodeModulesPath, 'node_modules')
54+
55+
if (!fs.existsSync(nodeModulesPath)) {
56+
console.log(\`No node_modules path found at \${nodeModulesPath}. Skipping delete.\`)
57+
return;
58+
}
59+
60+
console.log(\`Deleting: \${nodeModulesPath}\`)
61+
try {
62+
removeFileDirectoryRecursively(nodeModulesPath);
63+
console.log(\`Successfully deleted: \${nodeModulesPath}\`)
64+
} catch (err) {
65+
console.log(\`Error deleting \${nodeModulesPath}: \${err.message}\`);
66+
}
67+
};
68+
69+
/// Remove all entries from the .npmignore within example/node_modules/react-native-library-name/
70+
const removeLibraryNpmIgnorePaths = (npmIgnorePath, libraryNodeModulesPath) => {
71+
if (!fs.existsSync(npmIgnorePath)) {
72+
console.log(\`No .npmignore path found at \${npmIgnorePath}. Skipping deleting content.\`);
73+
return;
74+
}
75+
76+
fs.readFileSync(npmIgnorePath, 'utf8').split(/\\r?\\n/).forEach(entry => {
77+
if (entry.length === 0) {
78+
return
79+
}
80+
81+
const npmIgnoreLibraryNodeModulesEntryPath = path.resolve(libraryNodeModulesPath, entry);
82+
if (!fs.existsSync(npmIgnoreLibraryNodeModulesEntryPath)) {
83+
return;
84+
}
85+
86+
console.log(\`Deleting: \${npmIgnoreLibraryNodeModulesEntryPath}\`)
87+
try {
88+
removeFileDirectoryRecursively(npmIgnoreLibraryNodeModulesEntryPath);
89+
console.log(\`Successfully deleted: \${npmIgnoreLibraryNodeModulesEntryPath}\`)
90+
} catch (err) {
91+
console.log(\`Error deleting \${npmIgnoreLibraryNodeModulesEntryPath}: \${err.message}\`);
92+
}
93+
});
94+
};
95+
96+
// Main start sweeping process
97+
(() => {
98+
// Read out dir of example project
99+
const exampleDir = process.cwd();
100+
101+
// Relative libraries path within the examples node_modules directory
102+
const relativeLibraryNodeModulesPath = process.argv[2];
103+
const libraryNodeModulesPath = path.resolve(exampleDir, relativeLibraryNodeModulesPath);
104+
105+
106+
removeLibraryNodeModulesPath(libraryNodeModulesPath);
107+
108+
const npmIgnorePath = path.resolve(__dirname, '../.npmignore');
109+
removeLibraryNpmIgnorePaths(npmIgnorePath, libraryNodeModulesPath);
110+
})();
111+
`
112+
}];

0 commit comments

Comments
 (0)