├── tests ├── .eslintrc.yml ├── with-injection │ ├── cli │ │ └── command │ │ │ ├── object │ │ │ ├── lib-cli-command-object-text.test.js │ │ │ └── __snapshots__ │ │ │ │ └── lib-cli-command-object-text.test.js.snap │ │ │ └── action-error │ │ │ ├── cli-action-error-empty-args-missing-name.test.js │ │ │ ├── cli-action-error-too-many-args.test.js │ │ │ └── cli-action-error-undefined-args-missing-name.test.js │ ├── create │ │ ├── with-defaults │ │ │ ├── create-with-defaults.test.js │ │ │ ├── no-prefix-needed │ │ │ │ └── create-with-no-prefix-needed.test.js │ │ │ └── bogus-platforms │ │ │ │ ├── bogus-name │ │ │ │ ├── bogus-platforms-name.test.js │ │ │ │ └── __snapshots__ │ │ │ │ │ └── bogus-platforms-name.test.js.snap │ │ │ │ ├── empty-array │ │ │ │ ├── bogus-platforms-empty-array.test.js │ │ │ │ └── __snapshots__ │ │ │ │ │ └── bogus-platforms-empty-array.test.js.snap │ │ │ │ └── empty-string │ │ │ │ ├── bogus-platforms-empty-string.test.js │ │ │ │ └── __snapshots__ │ │ │ │ └── bogus-platforms-empty-string.test.js.snap │ │ ├── with-name-in-camel-case │ │ │ └── create-with-name-in-camel-case.test.js │ │ ├── view │ │ │ ├── with-defaults │ │ │ │ └── create-view-with-defaults.test.js │ │ │ ├── with-example │ │ │ │ ├── with-defaults │ │ │ │ │ └── create-view-with-example-with-defaults.test.js │ │ │ │ └── with-options │ │ │ │ │ └── create-view-with-example-with-options.test.js │ │ │ └── with-options │ │ │ │ ├── for-android │ │ │ │ ├── lib-view-android-config-options.test.js │ │ │ │ └── __snapshots__ │ │ │ │ │ └── lib-view-android-config-options.test.js.snap │ │ │ │ └── for-ios │ │ │ │ └── create-view-with-options-for-ios.test.js │ │ ├── with-example │ │ │ ├── with-defaults │ │ │ │ └── create-with-example-with-defaults.test.js │ │ │ ├── for-android-only │ │ │ │ ├── create-with-example-for-android-only.test.js │ │ │ │ └── __snapshots__ │ │ │ │ │ └── create-with-example-for-android-only.test.js.snap │ │ │ ├── with-error │ │ │ │ ├── example-yarn-add-failure.test.js │ │ │ │ ├── with-json-error │ │ │ │ │ ├── json-eacces-error.test.js │ │ │ │ │ └── json-enoent-error.test.js │ │ │ │ └── with-missing-tools │ │ │ │ │ └── create-with-example-with-missing-yarn.test.js │ │ │ ├── with-options │ │ │ │ └── create-with-example-with-options.test.js │ │ │ └── with-missing-package-scripts │ │ │ │ └── recover-from-missing-package-scripts.test.js │ │ ├── with-options │ │ │ ├── with-package-name │ │ │ │ └── create-with-package-name.test.js │ │ │ ├── platforms-array │ │ │ │ └── platforms-array.test.js │ │ │ ├── for-android │ │ │ │ ├── create-with-options-for-android.test.js │ │ │ │ └── __snapshots__ │ │ │ │ │ └── create-with-options-for-android.test.js.snap │ │ │ ├── for-ios │ │ │ │ └── create-with-options-for-ios.test.js │ │ │ └── platforms-comma-separated │ │ │ │ └── platforms-comma-separated.test.js │ │ └── with-bogus │ │ │ └── lib-bogus-null-name.test.js │ └── helpers │ │ └── io-inject.js ├── integration │ └── cli │ │ ├── noargs │ │ ├── cli-noargs.test.js │ │ └── __snapshots__ │ │ │ └── cli-noargs.test.js.snap │ │ ├── help │ │ ├── cli-help.test.js │ │ └── __snapshots__ │ │ │ └── cli-help.test.js.snap │ │ └── create │ │ ├── with-defaults │ │ └── cli-create-with-defaults.test.js │ │ └── view │ │ └── cli-create-with-view.test.js ├── unsupported-platforms │ └── windows-csharp │ │ ├── with-defaults │ │ └── windows-csharp-with-defaults.test.js │ │ └── with-options │ │ └── windows-csharp-with-options.test.js └── with-mocks │ ├── cli │ ├── command │ │ └── action-func │ │ │ ├── with-options │ │ │ └── cli-command-func-with-options.test.js │ │ │ └── with-logging │ │ │ ├── with-bogus-platforms │ │ │ ├── empty-string │ │ │ │ ├── cli-command-with-empty-platforms-string.test.js │ │ │ │ └── __snapshots__ │ │ │ │ │ └── cli-command-with-empty-platforms-string.test.js.snap │ │ │ └── bogus-name │ │ │ │ ├── cli-command-with-bogus-platforms-name.test.js │ │ │ │ └── __snapshots__ │ │ │ │ └── cli-command-with-bogus-platforms-name.test.js.snap │ │ │ └── with-error │ │ │ ├── __snapshots__ │ │ │ └── cli-command-with-logging-with-error.test.js.snap │ │ │ └── cli-command-with-logging-with-error.test.js │ └── program │ │ ├── with-missing-args │ │ ├── cli-program-with-missing-args.test.js │ │ └── __snapshots__ │ │ │ └── cli-program-with-missing-args.test.js.snap │ │ ├── with-defaults │ │ └── for-android │ │ │ └── cli-program-with-defaults-for-android.test.js │ │ └── with-example │ │ └── with-logging │ │ └── cli-program-with-example-with-logging.test.js │ └── lib │ └── create │ └── with-example │ └── with-logging │ ├── with-defaults │ └── create-with-example-with-defaults.test.js │ ├── with-error │ └── with-yarn-error-logging.test.js │ └── with-options │ └── create-with-example-with-options.test.js ├── templates ├── .eslintrc.yml ├── index.js ├── general.js ├── example.js ├── android.js └── ios.js ├── .eslintignore ├── unsupported-platforms └── .eslintrc.yml ├── bin └── cli.js ├── .editorconfig ├── .eslintrc.yml ├── renovate.json ├── stryker.conf.js ├── .gitignore ├── lib ├── cli-program.js ├── normalized-options.js ├── cli-command.js └── lib.js ├── .github └── workflows │ ├── stryker.yml │ └── nodejs.yml ├── LICENSE ├── package.json └── README.md /tests/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | jest: true 3 | -------------------------------------------------------------------------------- /templates/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | rules: 2 | no-tabs: 0 3 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Stryker mutator output 2 | reports 3 | -------------------------------------------------------------------------------- /unsupported-platforms/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | rules: 2 | no-tabs: 0 3 | -------------------------------------------------------------------------------- /bin/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../lib/cli-program.js'); 4 | -------------------------------------------------------------------------------- /tests/with-injection/cli/command/object/lib-cli-command-object-text.test.js: -------------------------------------------------------------------------------- 1 | const command = require('../../../../../lib/cli-command.js'); 2 | 3 | test('cli-command object', () => { 4 | expect(command).toMatchSnapshot(); 5 | }); 6 | -------------------------------------------------------------------------------- /tests/with-injection/cli/command/action-error/cli-action-error-empty-args-missing-name.test.js: -------------------------------------------------------------------------------- 1 | const action = require('../../../../../lib/cli-command.js').action; 2 | 3 | test('create lib module with empty args - missing name', () => { 4 | const args = []; 5 | 6 | expect(() => { action(args, {}); }).toThrow('missing lib module name'); 7 | }); 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*.md] 5 | trim_trailing_whitespace = false 6 | 7 | # Unix-style newlines with a newline ending every file 8 | [*] 9 | end_of_line = lf 10 | insert_final_newline = true 11 | 12 | [*.js] 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.json] 17 | indent_style = space 18 | indent_size = 2 19 | -------------------------------------------------------------------------------- /templates/index.js: -------------------------------------------------------------------------------- 1 | const android = require('./android')('android'); 2 | const ios = require('./ios')('ios'); 3 | 4 | const general = require('./general'); 5 | 6 | const updatePlatformInFile = platform => file => 7 | Object.assign({}, file, { platform }); 8 | 9 | module.exports = [].concat( 10 | general, 11 | android.map(updatePlatformInFile('android')), 12 | ios.map(updatePlatformInFile('ios')), 13 | ); 14 | -------------------------------------------------------------------------------- /tests/integration/cli/noargs/cli-noargs.test.js: -------------------------------------------------------------------------------- 1 | const execa = require('execa'); 2 | const path = require('path'); 3 | 4 | test('bin/cli.js with no arguments returns expected output', () => { 5 | // Test fix for issue #48 6 | return Promise.resolve( 7 | execa.command(`node ${path.resolve('bin/cli.js')}`) 8 | ).then( 9 | ({ stdout }) => { 10 | expect(stdout).toMatchSnapshot(); 11 | } 12 | ); 13 | }); 14 | -------------------------------------------------------------------------------- /tests/with-injection/cli/command/action-error/cli-action-error-too-many-args.test.js: -------------------------------------------------------------------------------- 1 | const action = require('../../../../../lib/cli-command.js').action; 2 | 3 | test('create module with too many args', () => { 4 | const args = ['name1', 'name2']; 5 | 6 | // ref: 7 | // https://github.com/brodybits/create-react-native-module/issues/119 8 | expect(() => { action(args, {}); }).toThrow('too many arguments'); 9 | }); 10 | -------------------------------------------------------------------------------- /tests/with-injection/cli/command/action-error/cli-action-error-undefined-args-missing-name.test.js: -------------------------------------------------------------------------------- 1 | const action = require('../../../../../lib/cli-command.js').action; 2 | 3 | test('create lib module with undefined args - missing name', () => { 4 | const args = undefined; 5 | 6 | // ref: 7 | // https://github.com/brodybits/create-react-native-module/issues/305 8 | expect(() => { action(args, {}); }).toThrow('missing lib module name'); 9 | }); 10 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | extends: 2 | - standard 3 | 4 | rules: 5 | # disable quotes for now, at least 6 | # FUTURE TBD decide whether to enforce single quotes 7 | # (as specified by "Standard JS") or 8 | # switch to using backticks instead 9 | quotes: off 10 | 11 | semi: 12 | - error 13 | - always 14 | 15 | comma-dangle: 16 | - error 17 | - arrays: only-multiline 18 | objects: only-multiline 19 | functions: only-multiline 20 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "commitMessageExtra": "-> {{#if isMajor}}{{{newMajor}}}{{else}}{{#if isSingleVersion}}{{{toVersion}}}{{else}}{{{newValue}}}{{/if}}{{/if}} - {{prettyDepType}}", 6 | "commitMessageTopic": "{{depName}}", 7 | "prHourlyLimit": 0, 8 | "rangeStrategy": "bump", 9 | "packageRules": [ 10 | { 11 | "matchPackageNames": ["node"], 12 | "enabled": false 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /tests/with-injection/create/with-defaults/create-with-defaults.test.js: -------------------------------------------------------------------------------- 1 | const lib = require('../../../../lib/lib.js'); 2 | 3 | const ioInject = require('../../helpers/io-inject.js'); 4 | 5 | test('create alice-bobbi module with defaults', () => { 6 | const mysnap = []; 7 | 8 | const inject = ioInject(mysnap); 9 | 10 | const options = { 11 | name: 'alice-bobbi', 12 | }; 13 | 14 | return lib(options, inject) 15 | .then(() => { expect(mysnap).toMatchSnapshot(); }); 16 | }); 17 | -------------------------------------------------------------------------------- /tests/with-injection/create/with-name-in-camel-case/create-with-name-in-camel-case.test.js: -------------------------------------------------------------------------------- 1 | const lib = require('../../../../lib/lib.js'); 2 | 3 | const ioInject = require('../../helpers/io-inject.js'); 4 | 5 | test('create alice-bobbi module with name in camel case', async () => { 6 | const mysnap = []; 7 | 8 | const inject = ioInject(mysnap); 9 | 10 | const options = { 11 | name: 'aliceBobbi', 12 | }; 13 | 14 | await lib(options, inject); 15 | 16 | expect(mysnap).toMatchSnapshot(); 17 | }); 18 | -------------------------------------------------------------------------------- /tests/with-injection/create/with-defaults/no-prefix-needed/create-with-no-prefix-needed.test.js: -------------------------------------------------------------------------------- 1 | const lib = require('../../../../../lib/lib.js'); 2 | 3 | const ioInject = require('../../../helpers/io-inject.js'); 4 | 5 | test('create with defaults, with no prefix needed', () => { 6 | const mysnap = []; 7 | 8 | const inject = ioInject(mysnap); 9 | 10 | const options = { 11 | name: 'react-native-alice', 12 | }; 13 | 14 | return lib(options, inject) 15 | .then(() => { expect(mysnap).toMatchSnapshot(); }); 16 | }); 17 | -------------------------------------------------------------------------------- /tests/with-injection/create/view/with-defaults/create-view-with-defaults.test.js: -------------------------------------------------------------------------------- 1 | const lib = require('../../../../../lib/lib.js'); 2 | 3 | const ioInject = require('../../../helpers/io-inject.js'); 4 | 5 | test('create alice-bobbi view module with defaults', () => { 6 | const mysnap = []; 7 | 8 | const inject = ioInject(mysnap); 9 | 10 | const options = { 11 | name: 'alice-bobbi', 12 | isView: true, 13 | }; 14 | 15 | return lib(options, inject) 16 | .then(() => { expect(mysnap).toMatchSnapshot(); }); 17 | }); 18 | -------------------------------------------------------------------------------- /tests/with-injection/create/with-example/with-defaults/create-with-example-with-defaults.test.js: -------------------------------------------------------------------------------- 1 | const lib = require('../../../../../lib/lib.js'); 2 | 3 | const ioInject = require('../../../helpers/io-inject.js'); 4 | 5 | test('create alice-bobbi module with defaults', () => { 6 | const mysnap = []; 7 | 8 | const inject = ioInject(mysnap); 9 | 10 | const options = { 11 | name: 'alice-bobbi', 12 | generateExample: true, 13 | }; 14 | 15 | return lib(options, inject) 16 | .then(() => { expect(mysnap).toMatchSnapshot(); }); 17 | }); 18 | -------------------------------------------------------------------------------- /tests/with-injection/create/with-defaults/bogus-platforms/bogus-name/bogus-platforms-name.test.js: -------------------------------------------------------------------------------- 1 | const lib = require('../../../../../../lib/lib.js'); 2 | 3 | const ioInject = require('../../../../helpers/io-inject.js'); 4 | 5 | test(`create alice-bobbi module with defaults, with platforms: 'bogus'`, async () => { 6 | const mysnap = []; 7 | 8 | const inject = ioInject(mysnap); 9 | 10 | const options = { 11 | name: 'alice-bobbi', 12 | platforms: 'bogus' 13 | }; 14 | 15 | await lib(options, inject); 16 | expect(mysnap).toMatchSnapshot(); 17 | }); 18 | -------------------------------------------------------------------------------- /tests/with-injection/create/with-defaults/bogus-platforms/empty-array/bogus-platforms-empty-array.test.js: -------------------------------------------------------------------------------- 1 | const lib = require('../../../../../../lib/lib.js'); 2 | 3 | const ioInject = require('../../../../helpers/io-inject.js'); 4 | 5 | test('create alice-bobbi module with defaults, with bogus platforms: []', async () => { 6 | const mysnap = []; 7 | 8 | const inject = ioInject(mysnap); 9 | 10 | const options = { 11 | name: 'alice-bobbi', 12 | platforms: [] 13 | }; 14 | 15 | await lib(options, inject); 16 | expect(mysnap).toMatchSnapshot(); 17 | }); 18 | -------------------------------------------------------------------------------- /tests/with-injection/create/with-defaults/bogus-platforms/empty-string/bogus-platforms-empty-string.test.js: -------------------------------------------------------------------------------- 1 | const lib = require('../../../../../../lib/lib.js'); 2 | 3 | const ioInject = require('../../../../helpers/io-inject.js'); 4 | 5 | test(`create alice-bobbi module with defaults, with bogus platforms: ''`, async () => { 6 | const mysnap = []; 7 | 8 | const inject = ioInject(mysnap); 9 | 10 | const options = { 11 | name: 'alice-bobbi', 12 | platforms: '' 13 | }; 14 | 15 | await lib(options, inject); 16 | expect(mysnap).toMatchSnapshot(); 17 | }); 18 | -------------------------------------------------------------------------------- /tests/with-injection/create/with-options/with-package-name/create-with-package-name.test.js: -------------------------------------------------------------------------------- 1 | const lib = require('../../../../../lib/lib.js'); 2 | 3 | const ioInject = require('../../../helpers/io-inject.js'); 4 | 5 | test(`create alice-bobbi module package with custom packageName`, async () => { 6 | const mysnap = []; 7 | 8 | const inject = ioInject(mysnap); 9 | 10 | const options = { 11 | name: 'alice-bobbi', 12 | packageName: 'custom-native-module-package-name' 13 | }; 14 | 15 | await lib(options, inject); 16 | 17 | expect(mysnap).toMatchSnapshot(); 18 | }); 19 | -------------------------------------------------------------------------------- /tests/integration/cli/help/cli-help.test.js: -------------------------------------------------------------------------------- 1 | const execa = require('execa'); 2 | const path = require('path'); 3 | 4 | test('bin/cli.js --help returns expected output', () => { 5 | // Thanks for iniitial guidance: 6 | // * https://medium.com/@ole.ersoy/unit-testing-commander-scripts-with-jest-bc32465709d6 7 | // * https://github.com/superflycss/cli/blob/master/index.spec.js 8 | return Promise.resolve( 9 | execa.command(`node ${path.resolve('bin/cli.js')} --help`) 10 | ).then( 11 | ({ stdout }) => { 12 | expect(stdout).toMatchSnapshot(); 13 | } 14 | ); 15 | }); 16 | -------------------------------------------------------------------------------- /tests/with-injection/create/with-example/for-android-only/create-with-example-for-android-only.test.js: -------------------------------------------------------------------------------- 1 | const lib = require('../../../../../lib/lib.js'); 2 | 3 | const ioInject = require('../../../helpers/io-inject.js'); 4 | 5 | test('create alice-bobbi module with example for Android only', () => { 6 | const mysnap = []; 7 | 8 | const inject = ioInject(mysnap); 9 | 10 | const options = { 11 | name: 'alice-bobbi', 12 | generateExample: true, 13 | platforms: 'android' 14 | }; 15 | 16 | return lib(options, inject) 17 | .then(() => { expect(mysnap).toMatchSnapshot(); }); 18 | }); 19 | -------------------------------------------------------------------------------- /tests/with-injection/create/view/with-example/with-defaults/create-view-with-example-with-defaults.test.js: -------------------------------------------------------------------------------- 1 | const lib = require('../../../../../../lib/lib.js'); 2 | 3 | const ioInject = require('../../../../helpers/io-inject.js'); 4 | 5 | test('create alice-bobbi view module with example, with defaults', () => { 6 | const mysnap = []; 7 | 8 | const inject = ioInject(mysnap); 9 | 10 | const options = { 11 | name: 'alice-bobbi', 12 | generateExample: true, 13 | isView: true, 14 | }; 15 | 16 | return lib(options, inject) 17 | .then(() => { expect(mysnap).toMatchSnapshot(); }); 18 | }); 19 | -------------------------------------------------------------------------------- /stryker.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = config => { 2 | config.set({ 3 | mutator: 'javascript', 4 | mutate: [ 5 | // TBD there seems to be an issue with 6 | // Stryker mutation testing on 7 | // bin/*.js (...) 8 | // 'bin/**/*.js', 9 | 'lib/**/*.js', 10 | 'templates/**/*.js', 11 | 'unsupported-platforms/**/*.js' 12 | ], 13 | packageManager: 'yarn', 14 | reporters: [ 15 | 'html', 16 | 'clear-text', 17 | 'progress', 18 | 'dashboard' 19 | ], 20 | testRunner: 'jest', 21 | transpilers: [], 22 | coverageAnalysis: 'off' 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /tests/with-injection/create/with-bogus/lib-bogus-null-name.test.js: -------------------------------------------------------------------------------- 1 | const lib = require('../../../../lib/lib.js'); 2 | 3 | const ioInject = require('../../helpers/io-inject.js'); 4 | 5 | test('create module with bogus null name', async () => { 6 | // with snapshot info ignored in this test 7 | const inject = ioInject([]); 8 | 9 | const options = { 10 | name: null 11 | }; 12 | 13 | let error; 14 | try { 15 | // expected to throw: 16 | await lib(options, inject); 17 | } catch (e) { 18 | error = e; 19 | } 20 | expect(error).toBeDefined(); 21 | expect(error.message).toMatch(`Please write your library's name`); 22 | }); 23 | -------------------------------------------------------------------------------- /tests/with-injection/create/view/with-options/for-android/lib-view-android-config-options.test.js: -------------------------------------------------------------------------------- 1 | const lib = require('../../../../../../lib/lib.js'); 2 | 3 | const ioInject = require('../../../../helpers/io-inject.js'); 4 | 5 | test('create alice-bobbi view module with config options for Android only', () => { 6 | const mysnap = []; 7 | 8 | const inject = ioInject(mysnap); 9 | 10 | const options = { 11 | platforms: ['android'], 12 | name: 'alice-bobbi', 13 | githubAccount: 'alicebits', 14 | authorName: 'Alice', 15 | authorEmail: 'contact@alice.me', 16 | license: 'ISC', 17 | isView: true, 18 | }; 19 | 20 | return lib(options, inject) 21 | .then(() => { expect(mysnap).toMatchSnapshot(); }); 22 | }); 23 | -------------------------------------------------------------------------------- /tests/with-injection/create/with-options/platforms-array/platforms-array.test.js: -------------------------------------------------------------------------------- 1 | const lib = require('../../../../../lib/lib.js'); 2 | 3 | const ioInject = require('../../../helpers/io-inject.js'); 4 | 5 | test('create alice-bobbi module with config options for android,ios comma separated', () => { 6 | const mysnap = []; 7 | 8 | const inject = ioInject(mysnap); 9 | 10 | const options = { 11 | platforms: ['android', 'ios'], 12 | tvosEnabled: true, 13 | name: 'alice-bobbi', 14 | githubAccount: 'alicebits', 15 | authorName: 'Alice', 16 | authorEmail: 'contact@alice.me', 17 | license: 'ISC', 18 | }; 19 | 20 | return lib(options, inject) 21 | .then(() => { expect(mysnap).toMatchSnapshot(); }); 22 | }); 23 | -------------------------------------------------------------------------------- /tests/with-injection/create/with-options/for-android/create-with-options-for-android.test.js: -------------------------------------------------------------------------------- 1 | const lib = require('../../../../../lib/lib.js'); 2 | 3 | const ioInject = require('../../../helpers/io-inject.js'); 4 | 5 | test('create alice-bobbi module with config options for Android only', () => { 6 | const mysnap = []; 7 | 8 | const inject = ioInject(mysnap); 9 | 10 | const options = { 11 | platforms: ['android'], 12 | name: 'alice-bobbi', 13 | nativePackageId: 'com.alicebits', 14 | githubAccount: 'alicebits', 15 | authorName: 'Alice', 16 | authorEmail: 'contact@alice.me', 17 | license: 'ISC', 18 | }; 19 | 20 | return lib(options, inject) 21 | .then(() => { expect(mysnap).toMatchSnapshot(); }); 22 | }); 23 | -------------------------------------------------------------------------------- /tests/with-injection/create/with-options/for-ios/create-with-options-for-ios.test.js: -------------------------------------------------------------------------------- 1 | const lib = require('../../../../../lib/lib.js'); 2 | 3 | const ioInject = require('../../../helpers/io-inject.js'); 4 | 5 | test('create alice-bobbi module with config options for iOS only', () => { 6 | const mysnap = []; 7 | 8 | const inject = ioInject(mysnap); 9 | 10 | const options = { 11 | platforms: ['ios'], 12 | name: 'alice-bobbi', 13 | tvosEnabled: true, 14 | githubAccount: 'alicebits', 15 | authorName: 'Alice', 16 | authorEmail: 'contact@alice.me', 17 | license: 'ISC', 18 | useAppleNetworking: true 19 | }; 20 | 21 | return lib(options, inject) 22 | .then(() => { expect(mysnap).toMatchSnapshot(); }); 23 | }); 24 | -------------------------------------------------------------------------------- /tests/with-injection/create/with-options/platforms-comma-separated/platforms-comma-separated.test.js: -------------------------------------------------------------------------------- 1 | const lib = require('../../../../../lib/lib.js'); 2 | 3 | const ioInject = require('../../../helpers/io-inject.js'); 4 | 5 | test('create alice-bobbi module with config options for android,ios comma separated', () => { 6 | const mysnap = []; 7 | 8 | const inject = ioInject(mysnap); 9 | 10 | const options = { 11 | platforms: 'android,ios', 12 | name: 'alice-bobbi', 13 | tvosEnabled: true, 14 | githubAccount: 'alicebits', 15 | authorName: 'Alice', 16 | authorEmail: 'contact@alice.me', 17 | license: 'ISC', 18 | }; 19 | 20 | return lib(options, inject) 21 | .then(() => { expect(mysnap).toMatchSnapshot(); }); 22 | }); 23 | -------------------------------------------------------------------------------- /tests/with-injection/create/view/with-options/for-ios/create-view-with-options-for-ios.test.js: -------------------------------------------------------------------------------- 1 | const lib = require('../../../../../../lib/lib.js'); 2 | 3 | const ioInject = require('../../../../helpers/io-inject.js'); 4 | 5 | test('create alice-bobbi view module with config options for iOS only', () => { 6 | const mysnap = []; 7 | 8 | const inject = ioInject(mysnap); 9 | 10 | const options = { 11 | platforms: ['ios'], 12 | name: 'alice-bobbi', 13 | tvosEnabled: true, 14 | githubAccount: 'alicebits', 15 | authorName: 'Alice', 16 | authorEmail: 'contact@alice.me', 17 | license: 'ISC', 18 | isView: true, 19 | }; 20 | 21 | return lib(options, inject) 22 | .then(() => { expect(mysnap).toMatchSnapshot(); }); 23 | }); 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # vim 2 | .*.swp 3 | 4 | ### macOS ### 5 | *.DS_Store 6 | .AppleDouble 7 | .LSOverride 8 | 9 | # Icon must end with two \r 10 | Icon 11 | 12 | # Thumbnails 13 | ._* 14 | 15 | # Files that might appear in the root of a volume 16 | .DocumentRevisions-V100 17 | .fseventsd 18 | .Spotlight-V100 19 | .TemporaryItems 20 | .Trashes 21 | .VolumeIcon.icns 22 | .com.apple.timemachine.donotpresent 23 | 24 | # Directories potentially created on remote AFP share 25 | .AppleDB 26 | .AppleDesktop 27 | Network Trash Folder 28 | Temporary Items 29 | .apdisk 30 | 31 | node_modules 32 | npm-debug.log 33 | yarn-error.log 34 | 35 | # Stryker mutator output 36 | .stryker-tmp 37 | reports 38 | stryker.log 39 | 40 | # CLI create integration test output to be ignored 41 | react-native-integration-* 42 | -------------------------------------------------------------------------------- /tests/unsupported-platforms/windows-csharp/with-defaults/windows-csharp-with-defaults.test.js: -------------------------------------------------------------------------------- 1 | const windowsTemplate = require('../../../../unsupported-platforms/windows-csharp'); 2 | 3 | // injected uuid: 4 | const uuid = 'E22606E0-B47F-11E9-A3F0-07F70A25DAFB'; 5 | 6 | test('unsupported Windows C# template with defaults', () => { 7 | const options = { 8 | // normalized for name: 'alice-bobbi': 9 | objectClassName: 'AliceBobbi', 10 | namespace: 'Alice.Bobbi', 11 | // injected uuid: 12 | uuid 13 | }; 14 | 15 | const mysnap = []; 16 | 17 | windowsTemplate('windows').forEach(({ name, content }) => { 18 | mysnap.push({ 19 | outputFileName: name(options), 20 | theContent: content(options) 21 | }); 22 | }); 23 | 24 | expect(mysnap).toMatchSnapshot(); 25 | }); 26 | -------------------------------------------------------------------------------- /lib/cli-program.js: -------------------------------------------------------------------------------- 1 | const pkg = require('./../package.json'); 2 | require('please-upgrade-node')(pkg); 3 | 4 | const program = require('commander'); 5 | 6 | const updateNotifier = require('update-notifier'); 7 | 8 | const command = require('./cli-command'); 9 | 10 | updateNotifier({ pkg }).notify(); 11 | 12 | program 13 | .version(pkg.version) 14 | .usage(command.usage) 15 | .description(command.description) 16 | .action(function programAction (_, args) { 17 | command.action(args, this.opts()); 18 | }); 19 | 20 | (command.options || []) 21 | .forEach(opt => program.option( 22 | opt.command, 23 | opt.description, 24 | opt.parse || (value => value), 25 | opt.default 26 | )); 27 | 28 | const args = process.argv; 29 | if (args.length === 2) { 30 | args.push('--help'); 31 | } 32 | program.parse(args); 33 | -------------------------------------------------------------------------------- /tests/with-injection/create/view/with-example/with-options/create-view-with-example-with-options.test.js: -------------------------------------------------------------------------------- 1 | const lib = require('../../../../../../lib/lib.js'); 2 | 3 | const ioInject = require('../../../../helpers/io-inject.js'); 4 | 5 | test('create alice-bobbi view module with example, with custom config options', () => { 6 | const mysnap = []; 7 | 8 | const inject = ioInject(mysnap); 9 | 10 | const options = { 11 | name: 'alice-bobbi', 12 | tvosEnabled: true, 13 | githubAccount: 'alicebits', 14 | authorName: 'Alice', 15 | authorEmail: 'contact@alice.me', 16 | license: 'ISC', 17 | isView: true, 18 | generateExample: true, 19 | exampleName: 'test-demo', 20 | exampleReactNativeTemplate: 'react-native@0.60', 21 | }; 22 | 23 | return lib(options, inject) 24 | .then(() => { expect(mysnap).toMatchSnapshot(); }); 25 | }); 26 | -------------------------------------------------------------------------------- /tests/unsupported-platforms/windows-csharp/with-options/windows-csharp-with-options.test.js: -------------------------------------------------------------------------------- 1 | const windowsTemplate = require('../../../../unsupported-platforms/windows-csharp'); 2 | 3 | // injected uuid: 4 | const uuid = 'E22606E0-B47F-11E9-A3F0-07F70A25DAFB'; 5 | 6 | test('unsupported Windows C# template with options', () => { 7 | const options = { 8 | // normalized for name: 'alice-bobbi': 9 | objectClassName: 'AliceBobbi', 10 | // options 11 | githubAccount: 'alicebits', 12 | authorName: 'Alice', 13 | authorEmail: 'contact@alice.me', 14 | license: 'ISC', 15 | namespace: 'Carol', 16 | // injected uuid: 17 | uuid 18 | }; 19 | 20 | const mysnap = []; 21 | 22 | windowsTemplate('windows').forEach(({ name, content }) => { 23 | mysnap.push({ 24 | outputFileName: name(options), 25 | theContent: content(options) 26 | }); 27 | }); 28 | 29 | expect(mysnap).toMatchSnapshot(); 30 | }); 31 | -------------------------------------------------------------------------------- /tests/with-injection/create/with-example/with-error/example-yarn-add-failure.test.js: -------------------------------------------------------------------------------- 1 | const lib = require('../../../../../lib/lib.js'); 2 | 3 | const ioInject = require('../../../helpers/io-inject.js'); 4 | 5 | test('create module with example, with `yarn add` failure', async () => { 6 | // with snapshot info ignored in this test 7 | const ioInject2 = ioInject([]); 8 | const inject = { 9 | ...ioInject2, 10 | execa: { 11 | commandSync: (command, _) => { 12 | if (/yarn add/.test(command)) { 13 | throw new Error('ENOPERM not permitted'); 14 | } 15 | } 16 | } 17 | }; 18 | 19 | const options = { 20 | name: 'alice-bettty', 21 | generateExample: true 22 | }; 23 | 24 | let error; 25 | try { 26 | // expected to throw: 27 | await lib(options, inject); 28 | } catch (e) { 29 | error = e; 30 | } 31 | expect(error).toBeDefined(); 32 | expect(error.message).toMatch(/ENOPERM not permitted/); 33 | }); 34 | -------------------------------------------------------------------------------- /tests/with-injection/create/with-example/with-options/create-with-example-with-options.test.js: -------------------------------------------------------------------------------- 1 | const lib = require('../../../../../lib/lib.js'); 2 | 3 | const ioInject = require('../../../helpers/io-inject.js'); 4 | 5 | test('create alice-bobbi module with example, with config options including `exampleFileLinkage: true`', () => { 6 | const mysnap = []; 7 | 8 | const inject = ioInject(mysnap); 9 | 10 | const options = { 11 | name: 'alice-bobbi', 12 | objectClassName: 'SuperAwesomeModule', 13 | tvosEnabled: true, 14 | githubAccount: 'alicebits', 15 | authorName: 'Alice', 16 | authorEmail: 'contact@alice.me', 17 | license: 'ISC', 18 | generateExample: true, 19 | exampleFileLinkage: true, 20 | exampleName: 'test-demo', 21 | exampleReactNativeTemplate: 'react-native@0.60', 22 | useAppleNetworking: true, 23 | writeExamplePodfile: true, 24 | }; 25 | 26 | return lib(options, inject) 27 | .then(() => { expect(mysnap).toMatchSnapshot(); }); 28 | }); 29 | -------------------------------------------------------------------------------- /tests/with-injection/create/with-example/with-error/with-json-error/json-eacces-error.test.js: -------------------------------------------------------------------------------- 1 | const lib = require('../../../../../../lib/lib.js'); 2 | 3 | const ioInject = require('../../../../helpers/io-inject.js'); 4 | 5 | test('create module with example with `EACCES permission denied` error, with `exampleFileLinkage: true`', async () => { 6 | // with snapshot info ignored in this test 7 | const ioInject2 = ioInject([]); 8 | const inject = { 9 | ...ioInject2, 10 | fs: { 11 | ...ioInject2.fs, 12 | readFileSync: (_) => { 13 | throw new Error('EACCES permission denied'); 14 | } 15 | } 16 | }; 17 | 18 | const options = { 19 | name: 'alice-bettty', 20 | generateExample: true, 21 | exampleFileLinkage: true 22 | }; 23 | 24 | let error; 25 | try { 26 | // expected to throw: 27 | await lib(options, inject); 28 | } catch (e) { 29 | error = e; 30 | } 31 | expect(error).toBeDefined(); 32 | expect(error.message).toMatch(/alice-bettty.example.package.json: EACCES permission denied/); 33 | }); 34 | -------------------------------------------------------------------------------- /tests/with-injection/create/with-example/with-error/with-missing-tools/create-with-example-with-missing-yarn.test.js: -------------------------------------------------------------------------------- 1 | const lib = require('../../../../../../lib/lib.js'); 2 | 3 | const ioInject = require('../../../../helpers/io-inject.js'); 4 | 5 | test('create module with example, with `yarn --version` not working', async () => { 6 | // with snapshot info ignored in this test 7 | const ioInject2 = ioInject([]); 8 | const inject = { 9 | ...ioInject2, 10 | execa: { 11 | commandSync: (command, _) => { 12 | if (/yarn/.test(command)) { 13 | throw new Error('command not found'); 14 | } 15 | } 16 | } 17 | }; 18 | 19 | const options = { 20 | name: 'alice-bettty', 21 | generateExample: true 22 | }; 23 | 24 | let error; 25 | try { 26 | // expected to throw: 27 | await lib(options, inject); 28 | } catch (e) { 29 | error = e; 30 | } 31 | expect(error).toBeDefined(); 32 | expect(error.message).toMatch( 33 | `yarn --version failed ... yarn CLI is needed to generate example project`); 34 | }); 35 | -------------------------------------------------------------------------------- /tests/with-injection/create/with-example/with-error/with-json-error/json-enoent-error.test.js: -------------------------------------------------------------------------------- 1 | const lib = require('../../../../../../lib/lib.js'); 2 | 3 | const ioInject = require('../../../../helpers/io-inject.js'); 4 | 5 | test('create module with example with `ENOENT` error, with `exampleFileLinkage: true`', async () => { 6 | // with snapshot info ignored in this test 7 | const ioInject2 = ioInject([]); 8 | const inject = { 9 | ...ioInject2, 10 | fs: { 11 | ...ioInject2.fs, 12 | readFileSync: (_) => { 13 | throw new Error('ENOENT file not found: package.json'); 14 | } 15 | } 16 | }; 17 | 18 | const options = { 19 | name: 'alice-bettty', 20 | generateExample: true, 21 | exampleFileLinkage: true, 22 | }; 23 | 24 | let error; 25 | try { 26 | // expected to throw: 27 | await lib(options, inject); 28 | } catch (e) { 29 | error = e; 30 | } 31 | expect(error).toBeDefined(); 32 | expect(error.message).toMatch( 33 | /The package.json at path: .*react-native-alice-bettty.example.package.json does not exist/); 34 | }); 35 | -------------------------------------------------------------------------------- /tests/with-injection/create/with-example/with-missing-package-scripts/recover-from-missing-package-scripts.test.js: -------------------------------------------------------------------------------- 1 | const lib = require('../../../../../lib/lib.js'); 2 | 3 | const ioInject = require('../../../helpers/io-inject.js'); 4 | 5 | const mycwd = require('process').cwd(); 6 | 7 | test('create alice-bobbi module with example, with `exampleFileLinkage: true` then recover from missing scripts in example package.json', async () => { 8 | const mysnap = []; 9 | 10 | const ioInject2 = ioInject(mysnap); 11 | const inject = { 12 | ...ioInject2, 13 | fs: { 14 | ...ioInject2.fs, 15 | readFileSync: (jsonFilePath) => { 16 | mysnap.push({ 17 | call: 'fs.readFileSync', 18 | jsonFilePath: jsonFilePath.replace(mycwd, '...').replace(/\\/g, '/'), 19 | }); 20 | return `{ "name": "example", "version": "0.0.1" }`; 21 | } 22 | } 23 | }; 24 | 25 | const options = { 26 | name: 'alice-bobbi', 27 | generateExample: true, 28 | exampleFileLinkage: true, 29 | }; 30 | 31 | await lib(options, inject); 32 | expect(mysnap).toMatchSnapshot(); 33 | }); 34 | -------------------------------------------------------------------------------- /tests/with-mocks/cli/command/action-func/with-options/cli-command-func-with-options.test.js: -------------------------------------------------------------------------------- 1 | const action = require('../../../../../../lib/cli-command.js').action; 2 | 3 | // special compact mocks for this test: 4 | const mysnap = []; 5 | const mockpushit = x => mysnap.push(x); 6 | jest.mock('fs-extra', () => ({ 7 | outputFile: (outputFileName, theContent) => { 8 | mockpushit({ 9 | outputFileName: outputFileName.replace(/\\/g, '/'), 10 | theContent 11 | }); 12 | return Promise.resolve(); 13 | }, 14 | ensureDir: (dir) => { 15 | mockpushit({ ensureDir: dir.replace(/\\/g, '/') }); 16 | return Promise.resolve(); 17 | }, 18 | })); 19 | 20 | test('create alice-bobbi module with explicit config options for Android & iOS', () => { 21 | const args = ['alice-bobbi']; 22 | 23 | const options = { 24 | platforms: 'android,ios', 25 | tvosEnabled: true, 26 | githubAccount: 'alicebits', 27 | authorName: 'Alice', 28 | authorEmail: 'contact@alice.me', 29 | license: 'ISC', 30 | }; 31 | 32 | return action(args, options) 33 | .then(() => { expect(mysnap).toMatchSnapshot(); }); 34 | }); 35 | -------------------------------------------------------------------------------- /.github/workflows/stryker.yml: -------------------------------------------------------------------------------- 1 | # based on GitHub template: 2 | name: Stryker CI 3 | 4 | # Controls when the action will run. Triggers the workflow on push or pull request 5 | # events but only for the master branch 6 | on: 7 | push: 8 | branches: [ master ] 9 | 10 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 11 | jobs: 12 | # This workflow contains a single job called "build" 13 | build: 14 | # The type of runner that the job will run on 15 | runs-on: ubuntu-latest 16 | 17 | # Steps represent a sequence of tasks that will be executed as part of the job 18 | steps: 19 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 20 | - uses: actions/checkout@v2 21 | 22 | # Runs a single command using the runners shell 23 | - run: npm install 24 | 25 | # ref: 26 | # https://github.com/stryker-mutator/robobar-example/blob/master/.github/workflows/mutation-testing.yml 27 | - name: Run Stryker, with API key 28 | run: npm run stryker 29 | env: 30 | STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019-present Christopher J. Brody aka @brodybits (Chris Brody) 4 | Copyright (c) 2016 Johannes Stein 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ master, brodybits-* ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ${{ matrix.os }} 16 | 17 | strategy: 18 | # ref: 19 | # - https://help.github.com/en/actions/configuring-and-managing-workflows/configuring-a-workflow#configuring-a-build-matrix 20 | # - https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix 21 | matrix: 22 | os: 23 | - macos-latest 24 | - windows-latest 25 | node-version: 26 | - 12.x 27 | - 14.x 28 | - 15.x 29 | 30 | steps: 31 | - uses: actions/checkout@v2 32 | - name: Use Node.js ${{ matrix.node-version }} 33 | uses: actions/setup-node@v1 34 | with: 35 | node-version: ${{ matrix.node-version }} 36 | - run: npm i 37 | - run: npm run build --if-present 38 | - run: npm test 39 | env: 40 | CI: true 41 | -------------------------------------------------------------------------------- /lib/normalized-options.js: -------------------------------------------------------------------------------- 1 | const { paramCase } = require('param-case'); 2 | 3 | const { pascalCase } = require('pascal-case'); 4 | 5 | const PACKAGE_NAME_PREFIX = 'react-native-'; 6 | 7 | function transformPackageName (name) { 8 | const paramCaseName = paramCase(name); 9 | 10 | return paramCaseName.startsWith(PACKAGE_NAME_PREFIX) 11 | ? paramCaseName 12 | : PACKAGE_NAME_PREFIX + paramCaseName; 13 | } 14 | 15 | module.exports = (options) => { 16 | const { name, packageName, objectClassName } = options; 17 | 18 | if (typeof name !== 'string') { 19 | throw new TypeError("Please write your library's name"); 20 | } 21 | 22 | // namespace - library API member removed since Windows platform 23 | // is now removed (may be added back someday in the future) 24 | // const namespace = options.namespace; 25 | 26 | return Object.assign( 27 | { name }, 28 | options, 29 | packageName 30 | ? {} 31 | : { packageName: transformPackageName(name) }, 32 | objectClassName 33 | ? {} 34 | : { objectClassName: `${pascalCase(name)}` }, 35 | // namespace - library API member removed since Windows platform 36 | // is now removed (may be added back someday in the future) 37 | // namespace 38 | // ? {} 39 | // : { namespace: pascalCase(name).split(/(?=[A-Z])/).join('.') }, 40 | ); 41 | }; 42 | -------------------------------------------------------------------------------- /tests/integration/cli/create/with-defaults/cli-create-with-defaults.test.js: -------------------------------------------------------------------------------- 1 | const execa = require('execa'); 2 | const path = require('path'); 3 | 4 | const readdirs = require('recursive-readdir'); 5 | 6 | const fs = require('fs-extra'); 7 | 8 | test('CLI creates correct package artifacts on file system, with no options', async () => { 9 | const mysnap = []; 10 | 11 | const name = `integration-test-package`; 12 | 13 | const modulePackageName = `react-native-${name}`; 14 | 15 | // remove test artifacts just in case: 16 | await fs.remove(modulePackageName); 17 | 18 | await execa.command(`node ${path.resolve('bin/cli.js')} ${name}`); 19 | 20 | const filesUnsorted = await readdirs(modulePackageName); 21 | 22 | // with sorting, since underlying readdirs does not guarantee the order 23 | // (using [].concat() function call to avoid overwriting a local object) 24 | const files = [].concat(filesUnsorted).sort(); 25 | 26 | // THANKS for guidance: 27 | // https://stackoverflow.com/questions/37576685/using-async-await-with-a-foreach-loop/37576787#37576787 28 | // FUTURE TBD use a utility function to do this more functionally 29 | for (const path of files) { 30 | mysnap.push({ 31 | name: path.replace(/\\/g, '/'), 32 | theContent: await fs.readFile(path, 'utf8') 33 | }); 34 | } 35 | 36 | expect(mysnap).toMatchSnapshot(); 37 | 38 | // cleanup generated test artifacts: 39 | await fs.remove(modulePackageName); 40 | }); 41 | -------------------------------------------------------------------------------- /tests/with-mocks/cli/command/action-func/with-logging/with-bogus-platforms/empty-string/cli-command-with-empty-platforms-string.test.js: -------------------------------------------------------------------------------- 1 | const action = require('../../../../../../../../lib/cli-command.js').action; 2 | 3 | // special compact mocks for this test: 4 | const mysnap = []; 5 | const mockpushit = x => mysnap.push(x); 6 | jest.mock('fs-extra', () => ({ 7 | outputFile: (outputFileName, theContent) => { 8 | mockpushit({ 9 | outputFileName: outputFileName.replace(/\\/g, '/'), 10 | theContent 11 | }); 12 | return Promise.resolve(); 13 | }, 14 | ensureDir: (dir) => { 15 | mockpushit({ ensureDir: dir.replace(/\\/g, '/') }); 16 | return Promise.resolve(); 17 | }, 18 | })); 19 | jest.mock('console', () => ({ 20 | info: (...args) => { 21 | mockpushit({ 22 | // TBD EXTRA WORKAROUND HACK for non-deterministic elapsed time in log 23 | info: args.map(line => line.replace(/It took.*s/g, 'It took XXX')) 24 | }); 25 | }, 26 | // console.log is no longer expected 27 | // log: (...args) => ... 28 | warn: (...args) => { 29 | mockpushit({ warn: [].concat(args) }); 30 | }, 31 | error: (...args) => { 32 | mockpushit({ error: [].concat(args) }); 33 | }, 34 | })); 35 | 36 | test(`create alice-bobbi module with logging, with platforms: ''`, async () => { 37 | const args = ['alice-bobbi']; 38 | 39 | const options = { platforms: '' }; 40 | 41 | await action(args, options); 42 | 43 | expect(mysnap).toMatchSnapshot(); 44 | }); 45 | -------------------------------------------------------------------------------- /tests/with-mocks/cli/command/action-func/with-logging/with-bogus-platforms/bogus-name/cli-command-with-bogus-platforms-name.test.js: -------------------------------------------------------------------------------- 1 | const action = require('../../../../../../../../lib/cli-command.js').action; 2 | 3 | // special compact mocks for this test: 4 | const mysnap = []; 5 | const mockpushit = x => mysnap.push(x); 6 | jest.mock('fs-extra', () => ({ 7 | outputFile: (outputFileName, theContent) => { 8 | mockpushit({ 9 | outputFileName: outputFileName.replace(/\\/g, '/'), 10 | theContent 11 | }); 12 | return Promise.resolve(); 13 | }, 14 | ensureDir: (dir) => { 15 | mockpushit({ ensureDir: dir.replace(/\\/g, '/') }); 16 | return Promise.resolve(); 17 | }, 18 | })); 19 | jest.mock('console', () => ({ 20 | info: (...args) => { 21 | mockpushit({ 22 | // TBD EXTRA WORKAROUND HACK for non-deterministic elapsed time in log 23 | info: args.map(line => line.replace(/It took.*s/g, 'It took XXX')) 24 | }); 25 | }, 26 | // console.log is no longer expected 27 | // log: (...args) => ... 28 | warn: (...args) => { 29 | mockpushit({ warn: [].concat(args) }); 30 | }, 31 | error: (...args) => { 32 | mockpushit({ error: [].concat(args) }); 33 | }, 34 | })); 35 | 36 | test(`create alice-bobbi module with logging, with platforms: 'bogus'`, async () => { 37 | const args = ['alice-bobbi']; 38 | 39 | const options = { platforms: 'bogus' }; 40 | 41 | await action(args, options); 42 | 43 | expect(mysnap).toMatchSnapshot(); 44 | }); 45 | -------------------------------------------------------------------------------- /tests/integration/cli/create/view/cli-create-with-view.test.js: -------------------------------------------------------------------------------- 1 | const execa = require('execa'); 2 | const path = require('path'); 3 | 4 | const readdirs = require('recursive-readdir'); 5 | 6 | const fs = require('fs-extra'); 7 | 8 | test('CLI creates correct view module package artifacts on file system using `--view` option (and no other options)', async () => { 9 | const mysnap = []; 10 | 11 | const name = `integration-view-test-package`; 12 | 13 | const modulePackageName = `react-native-${name}`; 14 | 15 | // remove test artifacts just in case: 16 | await fs.remove(modulePackageName); 17 | 18 | await execa.command(`node ${path.resolve('bin/cli.js')} --is-view ${name}`); 19 | 20 | const filesUnsorted = await readdirs(modulePackageName); 21 | 22 | // with sorting, since underlying readdirs does not guarantee the order 23 | // (using [].concat() function call to avoid overwriting a local object) 24 | const files = [].concat(filesUnsorted).sort(); 25 | 26 | // THANKS for guidance: 27 | // https://stackoverflow.com/questions/37576685/using-async-await-with-a-foreach-loop/37576787#37576787 28 | // FUTURE TBD use a utility function to do this more functionally 29 | for (const path of files) { 30 | mysnap.push({ 31 | name: path.replace(/\\/g, '/'), 32 | theContent: await fs.readFile(path, 'utf8') 33 | }); 34 | } 35 | 36 | expect(mysnap).toMatchSnapshot(); 37 | 38 | // cleanup generated test artifacts: 39 | await fs.remove(modulePackageName); 40 | }); 41 | -------------------------------------------------------------------------------- /tests/with-mocks/cli/command/action-func/with-logging/with-error/__snapshots__/cli-command-with-logging-with-error.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`create alice-bobbi module with logging, with fs error (with defaults for Android & iOS) 1`] = ` 4 | Array [ 5 | Object { 6 | "warn": Array [ 7 | "While \`{DEFAULT_NATIVE_PACKAGE_ID}\` is the default package 8 | identifier, it is recommended to customize the package identifier.", 9 | ], 10 | }, 11 | Object { 12 | "info": Array [ 13 | "CREATE new React Native module with the following options: 14 | 15 | name: alice-bobbi 16 | full package name: react-native-alice-bobbi 17 | is view: false 18 | object class name: AliceBobbi 19 | Android nativePackageId: com.reactlibrary 20 | platforms: android,ios 21 | Apple tvosEnabled: false 22 | authorName: Your Name 23 | authorEmail: yourname@email.com 24 | author githubAccount: github_account 25 | license: MIT 26 | useAppleNetworking: false 27 | ", 28 | ], 29 | }, 30 | Object { 31 | "info": Array [ 32 | "CREATE: Generating the React Native library module", 33 | ], 34 | }, 35 | Object { 36 | "ensureDir": "react-native-alice-bobbi", 37 | }, 38 | Object { 39 | "error": Array [ 40 | "Error while creating library module react-native-alice-bobbi<...>", 41 | ], 42 | }, 43 | Object { 44 | "error": Array [ 45 | "Error: ENOPERM not permitted 46 | <...>", 47 | ], 48 | }, 49 | ] 50 | `; 51 | -------------------------------------------------------------------------------- /tests/with-injection/helpers/io-inject.js: -------------------------------------------------------------------------------- 1 | const mycwd = require('process').cwd(); 2 | 3 | module.exports = (mysnap) => ({ 4 | fs: { 5 | outputFile: (name, content) => { 6 | mysnap.push( 7 | `* outputFile name: ${name.replace(mycwd, '...').replace(/\\/g, '/')} 8 | content: 9 | -------- 10 | ${content} 11 | <<<<<<<< ======== >>>>>>>> 12 | `); 13 | 14 | return Promise.resolve(); 15 | }, 16 | 17 | ensureDir: (dir) => { 18 | mysnap.push(`* ensureDir dir: ${dir.replace(mycwd, '...').replace(/\\/g, '/')}\n`); 19 | return Promise.resolve(); 20 | }, 21 | readFileSync: (jsonFilePath) => { 22 | mysnap.push({ 23 | call: 'fs.readFileSync', 24 | jsonFilePath: jsonFilePath.replace(mycwd, '...').replace(/\\/g, '/'), 25 | }); 26 | return `{ 27 | "name": "example", 28 | "scripts": { 29 | "test": "echo 'not implemented' && exit 1" 30 | } 31 | }`; 32 | }, 33 | writeFileSync: (path, json, options) => { 34 | mysnap.push({ 35 | call: 'fs.writeFileSync', 36 | filePath: path.replace(mycwd, '...').replace(/\\/g, '/'), 37 | json, 38 | options, 39 | }); 40 | }, 41 | }, 42 | execa: { 43 | commandSync: (command, opts) => { 44 | const options = { ...opts, ...(opts.cwd ? { cwd: opts.cwd.replace(mycwd, '...').replace(/\\/g, '/') } : {}) }; 45 | mysnap.push( 46 | `* execa.commandSync command: ${command} options: ${JSON.stringify(options)}\n`); 47 | }, 48 | }, 49 | reactNativeInit: (projectNameArray, opts) => { 50 | const options = { ...opts, ...(opts.directory ? { directory: opts.directory.replace(/\\/g, '/') } : {}) }; 51 | mysnap.push({ call: 'reactNativeInit', nameArray: projectNameArray, options }); 52 | } 53 | }); 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-react-native-module", 3 | "version": "0.20.3-dev", 4 | "description": "Tool to create a React Native library module or view module with a single command", 5 | "bin": "bin/cli.js", 6 | "main": "lib/lib.js", 7 | "engines": { 8 | "node": ">= 12" 9 | }, 10 | "scripts": { 11 | "jest": "cross-env CI=1 jest", 12 | "lint": "eslint .", 13 | "stryker": "stryker run", 14 | "test": "npm run lint && npm run jest" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/brodybits/create-react-native-module.git" 19 | }, 20 | "keywords": [ 21 | "react-native", 22 | "react", 23 | "library", 24 | "create", 25 | "module", 26 | "native", 27 | "view", 28 | "component", 29 | "ios", 30 | "android", 31 | "windows" 32 | ], 33 | "files": [ 34 | "LICENSE", 35 | "CHANGELOG.md", 36 | "bin", 37 | "lib", 38 | "templates", 39 | "!.eslint*.*", 40 | "README.md" 41 | ], 42 | "author": "Christopher J. Brody ", 43 | "license": "MIT", 44 | "bugs": { 45 | "url": "https://github.com/brodybits/create-react-native-module/issues" 46 | }, 47 | "homepage": "https://github.com/brodybits/create-react-native-module#readme", 48 | "dependencies": { 49 | "commander": "^6.2.1", 50 | "console": "^0.7.2", 51 | "execa": "^5.1.1", 52 | "fs-extra": "^10.0.0", 53 | "jsonfile": "^6.1.0", 54 | "log-symbols": "^4.1.0", 55 | "node-emoji": "^1.10.0", 56 | "outdent": "^0.8.0", 57 | "param-case": "^3.0.4", 58 | "pascal-case": "^3.1.2", 59 | "please-upgrade-node": "^3.2.0", 60 | "react-native-init-func": "^0.0.6", 61 | "update-notifier": "^5.1.0" 62 | }, 63 | "devDependencies": { 64 | "@stryker-mutator/core": "^3.3.1", 65 | "@stryker-mutator/javascript-mutator": "^3.3.1", 66 | "@stryker-mutator/jest-runner": "^3.3.1", 67 | "cross-env": "^7.0.3", 68 | "eslint": "^7.32.0", 69 | "eslint-config-standard": "^14.1.1", 70 | "eslint-plugin-import": "^2.23.4", 71 | "eslint-plugin-node": "^11.1.0", 72 | "eslint-plugin-promise": "^5.1.0", 73 | "eslint-plugin-standard": "^5.0.0", 74 | "jest": "^27.0.6", 75 | "recursive-readdir": "^2.2.2" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /tests/with-mocks/cli/program/with-missing-args/cli-program-with-missing-args.test.js: -------------------------------------------------------------------------------- 1 | // special compact mocks for this test: 2 | const mysnap = []; 3 | const mockpushit = x => mysnap.push(x); 4 | jest.mock('please-upgrade-node', () => ({ name, engines }) => { 5 | // only snapshot a limited number of fields 6 | expect(name).toBeDefined(); 7 | expect(engines).toBeDefined(); 8 | mockpushit({ 'please-upgrade-node': { name, engines } }); 9 | }); 10 | jest.mock('update-notifier', () => ({ pkg }) => { 11 | // only check a limited number of fields in pkg 12 | expect(pkg.name).toBeDefined(); 13 | expect(pkg.version).toBeDefined(); 14 | return { notify: () => mockpushit({ notify: {} }) }; 15 | }); 16 | const mockCommander = { 17 | args: ['test-package'], 18 | version: (version) => { 19 | // actual value of version not expected to be stable 20 | expect(version).toBeDefined(); 21 | mockpushit({ version: 'x' }); 22 | return mockCommander; 23 | }, 24 | usage: (usage) => { 25 | mockpushit({ usage }); 26 | return mockCommander; 27 | }, 28 | description: (description) => { 29 | mockpushit({ description }); 30 | return mockCommander; 31 | }, 32 | action: (_) => { 33 | mockpushit({ action: {} }); 34 | return mockCommander; 35 | }, 36 | option: (...args) => { 37 | mockpushit({ option: { args } }); 38 | return mockCommander; 39 | }, 40 | parse: (argv) => { 41 | // ensure that cli-program.js adds the `--help` option: 42 | expect(argv.length).toBe(3); 43 | expect(argv[2]).toBe('--help'); 44 | mockpushit({ parse: { argv } }); 45 | }, 46 | }; 47 | jest.mock('commander', () => mockCommander); 48 | 49 | // TBD hackish mock(s) - testing with missing CLI arguments: 50 | process.argv = ['node', './bin/cli.js']; 51 | 52 | test('mocked cli-program.js shows help in case of missing args', () => { 53 | // FUTURE TBD define this kind of a relative path near the beginning 54 | // of the test script 55 | require('../../../../../lib/cli-program.js'); 56 | 57 | // Using a 1 ms timer to wait for the 58 | // CLI program func to finish. 59 | // FUTURE TBD this looks like a bad smell 60 | // that should be resolved someday. 61 | return new Promise((resolve) => setTimeout(resolve, 1)) 62 | .then(() => { expect(mysnap).toMatchSnapshot(); }); 63 | }); 64 | -------------------------------------------------------------------------------- /tests/with-mocks/lib/create/with-example/with-logging/with-defaults/create-with-example-with-defaults.test.js: -------------------------------------------------------------------------------- 1 | const lib = require('../../../../../../../lib/lib.js'); 2 | 3 | const mockcwd = require('process').cwd(); 4 | 5 | // special compact mocks for this test: 6 | const mysnap = []; 7 | const mockpushit = x => mysnap.push(x); 8 | jest.mock('fs-extra', () => ({ 9 | outputFile: (outputFileName, theContent) => { 10 | mockpushit({ 11 | outputFileName: outputFileName.replace(mockcwd, '...').replace(/\\/g, '/'), 12 | theContent 13 | }); 14 | return Promise.resolve(); 15 | }, 16 | ensureDir: (dir) => { 17 | mockpushit({ ensureDir: dir.replace(mockcwd, '...').replace(/\\/g, '/') }); 18 | return Promise.resolve(); 19 | }, 20 | readFileSync: (path) => { 21 | mockpushit({ readFileSyncFromPath: path.replace(/\\/g, '/') }); 22 | return `{ "name": "x", "scripts": { "test": "exit 1" } }`; 23 | }, 24 | writeFileSync: (path, json, options) => { 25 | mockpushit({ 26 | writeFileSyncToPath: path.replace(mockcwd, '...').replace(/\\/g, '/'), 27 | json, 28 | options 29 | }); 30 | }, 31 | })); 32 | jest.mock('execa', () => ({ 33 | commandSync: (command, opts) => { 34 | const options = { ...opts, ...(opts.cwd ? { cwd: opts.cwd.replace(mockcwd, '...').replace(/\\/g, '/') } : {}) }; 35 | mockpushit({ commandSync: command, options }); 36 | } 37 | })); 38 | jest.mock('react-native-init-func', () => (projectNameArray, opts) => { 39 | const options = { ...opts, ...(opts.directory ? { directory: opts.directory.replace(/\\/g, '/') } : {}) }; 40 | mockpushit({ call: 'reactNativeInit', nameArray: projectNameArray, options }); 41 | }); 42 | jest.mock('console', () => ({ 43 | info: (...args) => { 44 | mockpushit({ 45 | info: args.map(line => line.replace(mockcwd, '...').replace(/\\/g, '/')) 46 | }); 47 | }, 48 | log: (...args) => { 49 | mockpushit({ log: [].concat(args) }); 50 | }, 51 | warn: (...args) => { 52 | mockpushit({ warn: [].concat(args) }); 53 | }, 54 | error: (...args) => { 55 | mockpushit({ error: [].concat(args) }); 56 | }, 57 | })); 58 | 59 | test('create alice-bobbi module using mocked lib with logging, with example, for Android & iOS with defaults', async () => { 60 | const options = { 61 | platforms: ['android', 'ios'], 62 | name: 'alice-bobbi', 63 | generateExample: true, 64 | }; 65 | 66 | await lib(options); 67 | 68 | expect(mysnap).toMatchSnapshot(); 69 | }); 70 | -------------------------------------------------------------------------------- /tests/with-mocks/cli/command/action-func/with-logging/with-error/cli-command-with-logging-with-error.test.js: -------------------------------------------------------------------------------- 1 | const action = require('../../../../../../../lib/cli-command.js').action; 2 | 3 | // special compact mocks for this test: 4 | const mysnap = []; 5 | const mockpushit = x => mysnap.push(x); 6 | jest.mock('fs-extra', () => ({ 7 | ensureDir: (dir) => { 8 | mockpushit({ ensureDir: dir }); 9 | return Promise.reject(new Error(`ENOPERM not permitted`)); 10 | }, 11 | outputFile: (outputFileName, theContent) => { 12 | // NOT EXPECTED: 13 | mockpushit({ outputFileName, theContent }); 14 | return Promise.reject(new Error(`ENOPERM not permitted`)); 15 | }, 16 | })); 17 | jest.mock('console', () => ({ 18 | info: (...args) => { 19 | mockpushit({ 20 | // TBD EXTRA WORKAROUND HACK for non-deterministic elapsed time in log 21 | info: args.map(line => line.replace(/It took.*s/g, 'It took XXX')) 22 | }); 23 | }, 24 | // console.log is no longer expected 25 | // log: (...args) => ... 26 | warn: (...args) => { 27 | mockpushit({ warn: [].concat(args) }); 28 | }, 29 | error: (first, ...rest) => { 30 | mockpushit({ 31 | error: [].concat( 32 | [].concat( 33 | first 34 | // QUICK WORKAROUND for Node.js 8 vs ... 35 | .split(/at.*ensureDir/)[0].concat('<...>') 36 | /* NOT NEEDED with QUICK WORKAROUND above: 37 | // Check trace with relative path 38 | // THANKS for guidance: 39 | // * https://stackoverflow.com/questions/1144783/how-to-replace-all-occurrences-of-a-string/1145525#1145525 40 | // * https://github.com/tunnckoCore/clean-stacktrace-relative-paths/blob/v1.0.4/index.js#L59 41 | .split(process.cwd()).join('...') 42 | // IGNORE test-dependant trace info 43 | .split(/at.*JestTest/)[0] 44 | // IGNORE line number in cli-command.js 45 | // in order to avoid sensitivity to mutation testing 46 | // (miss potentially surviving mutants) 47 | .replace(/cli-command.js:.*\)/, 'cli-command.js:...') 48 | // WORKAROUND for Windows: 49 | .replace(/\\/g, '/') 50 | // ... */ 51 | ), 52 | rest 53 | ) 54 | }); 55 | }, 56 | })); 57 | 58 | test('create alice-bobbi module with logging, with fs error (with defaults for Android & iOS)', async () => { 59 | const args = ['alice-bobbi']; 60 | 61 | await action(args, {}); 62 | 63 | expect(mysnap).toMatchSnapshot(); 64 | }); 65 | -------------------------------------------------------------------------------- /tests/with-mocks/lib/create/with-example/with-logging/with-error/with-yarn-error-logging.test.js: -------------------------------------------------------------------------------- 1 | const lib = require('../../../../../../../lib/lib.js'); 2 | 3 | const mockcwd = require('process').cwd(); 4 | 5 | // special compact mocks for this test: 6 | const mysnap = []; 7 | const mockpushit = x => mysnap.push(x); 8 | jest.mock('fs-extra', () => ({ 9 | outputFile: (outputFileName, theContent) => { 10 | mockpushit({ 11 | outputFileName: outputFileName.replace(mockcwd, '...').replace(/\\/g, '/'), 12 | theContent 13 | }); 14 | return Promise.resolve(); 15 | }, 16 | ensureDir: (dir) => { 17 | mockpushit({ ensureDir: dir.replace(mockcwd, '...').replace(/\\/g, '/') }); 18 | return Promise.resolve(); 19 | }, 20 | readFileSync: (path) => { 21 | mockpushit({ readFileSyncFromPath: path.replace(/\\/g, '/') }); 22 | return `{ "name": "x", "scripts": { "test": "exit 1" } }`; 23 | }, 24 | writeFileSync: (path, json, options) => { 25 | mockpushit({ 26 | writeFileSyncToPath: path.replace(/\\/g, '/'), 27 | json, 28 | options 29 | }); 30 | }, 31 | })); 32 | jest.mock('execa', () => ({ 33 | commandSync: (command, opts) => { 34 | const options = { ...opts, ...(opts.cwd ? { cwd: opts.cwd.replace(mockcwd, '...').replace(/\\/g, '/') } : {}) }; 35 | mockpushit({ commandSync: command, options }); 36 | if (/yarn add/.test(command)) { 37 | throw new Error('ENOPERM not permitted'); 38 | } 39 | } 40 | })); 41 | jest.mock('react-native-init-func', () => (projectNameArray, opts) => { 42 | const options = { ...opts, ...(opts.directory ? { directory: opts.directory.replace(/\\/g, '/') } : {}) }; 43 | mockpushit({ call: 'reactNativeInit', nameArray: projectNameArray, options }); 44 | }); 45 | jest.mock('console', () => ({ 46 | info: (...args) => { 47 | mockpushit({ 48 | info: args.map(line => line.replace(mockcwd, '...').replace(/\\/g, '/')) 49 | }); 50 | }, 51 | log: (...args) => { 52 | mockpushit({ log: [].concat(args) }); 53 | }, 54 | warn: (...args) => { 55 | mockpushit({ warn: [].concat(args) }); 56 | }, 57 | error: (...args) => { 58 | mockpushit({ error: [].concat(args) }); 59 | }, 60 | })); 61 | 62 | test('create alice-bobbi module using mocked lib with example, with `yarn add` failure with console logging', async () => { 63 | const options = { 64 | platforms: ['android', 'ios'], 65 | name: 'alice-bobbi', 66 | generateExample: true, 67 | }; 68 | 69 | let error; 70 | try { 71 | // expected to throw: 72 | await lib(options); 73 | } catch (e) { 74 | error = e; 75 | } 76 | expect(error).toBeDefined(); 77 | expect(error.message).toMatch(/ENOPERM not permitted/); 78 | expect(mysnap).toMatchSnapshot(); 79 | }); 80 | -------------------------------------------------------------------------------- /tests/with-mocks/cli/program/with-defaults/for-android/cli-program-with-defaults-for-android.test.js: -------------------------------------------------------------------------------- 1 | // special compact mocks for this test: 2 | const mysnap = []; 3 | const mockpushit = x => mysnap.push(x); 4 | jest.mock('please-upgrade-node', () => ({ name, engines }) => { 5 | // only snapshot a limited number of fields 6 | expect(name).toBeDefined(); 7 | expect(engines).toBeDefined(); 8 | mockpushit({ 'please-upgrade-node': { name, engines } }); 9 | }); 10 | jest.mock('update-notifier', () => ({ pkg }) => { 11 | // only check a limited number of fields in pkg 12 | expect(pkg.name).toBeDefined(); 13 | expect(pkg.version).toBeDefined(); 14 | return { notify: () => mockpushit({ notify: {} }) }; 15 | }); 16 | jest.mock('fs-extra', () => ({ 17 | outputFile: (outputFileName, theContent) => { 18 | mockpushit({ 19 | outputFileName: outputFileName.replace(/\\/g, '/'), 20 | theContent 21 | }); 22 | return Promise.resolve(); 23 | }, 24 | ensureDir: (dir) => { 25 | mockpushit({ ensureDir: dir.replace(/\\/g, '/') }); 26 | return Promise.resolve(); 27 | }, 28 | })); 29 | const mockCommanderState = { actionFunction: null }; 30 | const mockCommander = { 31 | args: ['test-package'], 32 | version: (version) => { 33 | // actual value of version not expected to be stable 34 | expect(version).toBeDefined(); 35 | mockpushit({ version: 'x' }); 36 | return mockCommander; 37 | }, 38 | usage: (usage) => { 39 | mockpushit({ usage }); 40 | return mockCommander; 41 | }, 42 | description: (description) => { 43 | mockpushit({ description }); 44 | return mockCommander; 45 | }, 46 | action: (actionFunction) => { 47 | mockpushit({ action: { actionFunction } }); 48 | mockCommanderState.actionFunction = actionFunction; 49 | return mockCommander; 50 | }, 51 | option: (...args) => { 52 | mockpushit({ option: { args } }); 53 | return mockCommander; 54 | }, 55 | parse: (argv) => { 56 | mockpushit({ parse: { argv } }); 57 | mockCommanderState.actionFunction.apply( 58 | { opts: () => ({ platforms: 'android' }) }, 59 | [{ bogus: {} }, ['test-package']]); 60 | }, 61 | help: () => { 62 | throw new Error('help call not expected in this test'); 63 | }, 64 | }; 65 | jest.mock('commander', () => mockCommander); 66 | 67 | // TBD hackish mock(s): 68 | process.argv = ['node', 'create-cli.js', 'test-package']; 69 | 70 | test('mocked cli-program.js runs correctly defaults', () => { 71 | require('../../../../../../lib/cli-program.js'); 72 | // Using a 1 ms timer to wait for the 73 | // CLI program func to finish. 74 | // FUTURE TBD this looks like a bad smell 75 | // that should be resolved someday. 76 | return new Promise((resolve) => setTimeout(resolve, 1)) 77 | .then(() => { expect(mysnap).toMatchSnapshot(); }); 78 | }); 79 | -------------------------------------------------------------------------------- /tests/with-mocks/lib/create/with-example/with-logging/with-options/create-with-example-with-options.test.js: -------------------------------------------------------------------------------- 1 | const lib = require('../../../../../../../lib/lib.js'); 2 | 3 | const mockcwd = require('process').cwd(); 4 | 5 | // special compact mocks for this test: 6 | const mysnap = []; 7 | const mockpushit = x => mysnap.push(x); 8 | jest.mock('fs-extra', () => ({ 9 | outputFile: (outputFileName, theContent) => { 10 | mockpushit({ 11 | outputFileName: outputFileName.replace(mockcwd, '...').replace(/\\/g, '/'), 12 | theContent 13 | }); 14 | return Promise.resolve(); 15 | }, 16 | ensureDir: (dir) => { 17 | mockpushit({ ensureDir: dir.replace(mockcwd, '...').replace(/\\/g, '/') }); 18 | return Promise.resolve(); 19 | }, 20 | readFileSync: (path) => { 21 | mockpushit({ readFileSyncFromPath: path.replace(mockcwd, '...').replace(/\\/g, '/') }); 22 | return `{ "name": "x", "scripts": { "test": "exit 1" } }`; 23 | }, 24 | writeFileSync: (path, json, options) => { 25 | mockpushit({ 26 | writeFileSyncToPath: path.replace(mockcwd, '...').replace(/\\/g, '/'), 27 | json, 28 | options 29 | }); 30 | }, 31 | })); 32 | jest.mock('execa', () => ({ 33 | commandSync: (command, opts) => { 34 | const options = { ...opts, ...(opts.cwd ? { cwd: opts.cwd.replace(mockcwd, '...').replace(/\\/g, '/') } : {}) }; 35 | mockpushit({ commandSync: command, options }); 36 | } 37 | })); 38 | jest.mock('react-native-init-func', () => (projectNameArray, opts) => { 39 | const options = { ...opts, ...(opts.directory ? { directory: opts.directory.replace(/\\/g, '/') } : {}) }; 40 | mockpushit({ call: 'reactNativeInit', nameArray: projectNameArray, options }); 41 | }); 42 | jest.mock('console', () => ({ 43 | info: (...args) => { 44 | mockpushit({ 45 | info: args.map(line => line.replace(mockcwd, '...').replace(/\\/g, '/')) 46 | }); 47 | }, 48 | log: (...args) => { 49 | mockpushit({ log: [].concat(args) }); 50 | }, 51 | warn: (...args) => { 52 | mockpushit({ warn: [].concat(args) }); 53 | }, 54 | error: (...args) => { 55 | mockpushit({ error: [].concat(args) }); 56 | }, 57 | })); 58 | 59 | test('create alice-bobbi module using mocked lib with logging, with example, for Android & iOS with config options including `exampleFileLinkage: true`', async () => { 60 | const options = { 61 | platforms: ['android', 'ios'], 62 | name: 'alice-bobbi', 63 | nativePackageId: 'com.alicebits', 64 | tvosEnabled: true, 65 | githubAccount: 'alicebits', 66 | authorName: 'Alice', 67 | authorEmail: 'contact@alice.me', 68 | license: 'ISC', 69 | generateExample: true, 70 | exampleFileLinkage: true, 71 | exampleName: 'demo', 72 | exampleReactNativeTemplate: 'react-native@npm:react-native-tvos' 73 | }; 74 | 75 | await lib(options); 76 | 77 | expect(mysnap).toMatchSnapshot(); 78 | }); 79 | -------------------------------------------------------------------------------- /tests/with-injection/create/with-defaults/bogus-platforms/bogus-name/__snapshots__/bogus-platforms-name.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`create alice-bobbi module with defaults, with platforms: 'bogus' 1`] = ` 4 | Array [ 5 | "* ensureDir dir: react-native-alice-bobbi 6 | ", 7 | "* ensureDir dir: react-native-alice-bobbi/ 8 | ", 9 | "* ensureDir dir: react-native-alice-bobbi/ 10 | ", 11 | "* ensureDir dir: react-native-alice-bobbi/ 12 | ", 13 | "* ensureDir dir: react-native-alice-bobbi/ 14 | ", 15 | "* outputFile name: react-native-alice-bobbi/README.md 16 | content: 17 | -------- 18 | # react-native-alice-bobbi 19 | 20 | ## Getting started 21 | 22 | \`$ npm install react-native-alice-bobbi --save\` 23 | 24 | ### Mostly automatic installation 25 | 26 | \`$ react-native link react-native-alice-bobbi\` 27 | 28 | ## Usage 29 | \`\`\`javascript 30 | import AliceBobbi from 'react-native-alice-bobbi'; 31 | 32 | // TODO: What to do with the module? 33 | AliceBobbi; 34 | \`\`\` 35 | 36 | <<<<<<<< ======== >>>>>>>> 37 | ", 38 | "* outputFile name: react-native-alice-bobbi/package.json 39 | content: 40 | -------- 41 | { 42 | \\"name\\": \\"react-native-alice-bobbi\\", 43 | \\"title\\": \\"React Native Alice Bobbi\\", 44 | \\"version\\": \\"1.0.0\\", 45 | \\"description\\": \\"TODO\\", 46 | \\"main\\": \\"index.js\\", 47 | \\"files\\": [ 48 | \\"README.md\\", 49 | \\"index.js\\" 50 | ], 51 | \\"scripts\\": { 52 | \\"test\\": \\"echo \\\\\\"Error: no test specified\\\\\\" && exit 1\\" 53 | }, 54 | \\"repository\\": { 55 | \\"type\\": \\"git\\", 56 | \\"url\\": \\"git+https://github.com/github_account/react-native-alice-bobbi.git\\", 57 | \\"baseUrl\\": \\"https://github.com/github_account/react-native-alice-bobbi\\" 58 | }, 59 | \\"keywords\\": [ 60 | \\"react-native\\" 61 | ], 62 | \\"author\\": { 63 | \\"name\\": \\"Your Name\\", 64 | \\"email\\": \\"yourname@email.com\\" 65 | }, 66 | \\"license\\": \\"MIT\\", 67 | \\"licenseFilename\\": \\"LICENSE\\", 68 | \\"readmeFilename\\": \\"README.md\\", 69 | \\"peerDependencies\\": { 70 | \\"react\\": \\">=16.8.1\\", 71 | \\"react-native\\": \\">=0.60.0-rc.0 <1.0.x\\" 72 | }, 73 | \\"devDependencies\\": { 74 | \\"react\\": \\"^16.9.0\\", 75 | \\"react-native\\": \\"^0.61.5\\" 76 | } 77 | } 78 | 79 | <<<<<<<< ======== >>>>>>>> 80 | ", 81 | "* outputFile name: react-native-alice-bobbi/index.js 82 | content: 83 | -------- 84 | // main index.js 85 | 86 | import { NativeModules } from 'react-native'; 87 | 88 | const { AliceBobbi } = NativeModules; 89 | 90 | export default AliceBobbi; 91 | 92 | <<<<<<<< ======== >>>>>>>> 93 | ", 94 | "* outputFile name: react-native-alice-bobbi/.gitignore 95 | content: 96 | -------- 97 | # OSX 98 | # 99 | .DS_Store 100 | 101 | # node.js 102 | # 103 | node_modules/ 104 | npm-debug.log 105 | yarn-error.log 106 | 107 | <<<<<<<< ======== >>>>>>>> 108 | ", 109 | ] 110 | `; 111 | -------------------------------------------------------------------------------- /tests/with-injection/create/with-defaults/bogus-platforms/empty-array/__snapshots__/bogus-platforms-empty-array.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`create alice-bobbi module with defaults, with bogus platforms: [] 1`] = ` 4 | Array [ 5 | "* ensureDir dir: react-native-alice-bobbi 6 | ", 7 | "* ensureDir dir: react-native-alice-bobbi/ 8 | ", 9 | "* ensureDir dir: react-native-alice-bobbi/ 10 | ", 11 | "* ensureDir dir: react-native-alice-bobbi/ 12 | ", 13 | "* ensureDir dir: react-native-alice-bobbi/ 14 | ", 15 | "* outputFile name: react-native-alice-bobbi/README.md 16 | content: 17 | -------- 18 | # react-native-alice-bobbi 19 | 20 | ## Getting started 21 | 22 | \`$ npm install react-native-alice-bobbi --save\` 23 | 24 | ### Mostly automatic installation 25 | 26 | \`$ react-native link react-native-alice-bobbi\` 27 | 28 | ## Usage 29 | \`\`\`javascript 30 | import AliceBobbi from 'react-native-alice-bobbi'; 31 | 32 | // TODO: What to do with the module? 33 | AliceBobbi; 34 | \`\`\` 35 | 36 | <<<<<<<< ======== >>>>>>>> 37 | ", 38 | "* outputFile name: react-native-alice-bobbi/package.json 39 | content: 40 | -------- 41 | { 42 | \\"name\\": \\"react-native-alice-bobbi\\", 43 | \\"title\\": \\"React Native Alice Bobbi\\", 44 | \\"version\\": \\"1.0.0\\", 45 | \\"description\\": \\"TODO\\", 46 | \\"main\\": \\"index.js\\", 47 | \\"files\\": [ 48 | \\"README.md\\", 49 | \\"index.js\\" 50 | ], 51 | \\"scripts\\": { 52 | \\"test\\": \\"echo \\\\\\"Error: no test specified\\\\\\" && exit 1\\" 53 | }, 54 | \\"repository\\": { 55 | \\"type\\": \\"git\\", 56 | \\"url\\": \\"git+https://github.com/github_account/react-native-alice-bobbi.git\\", 57 | \\"baseUrl\\": \\"https://github.com/github_account/react-native-alice-bobbi\\" 58 | }, 59 | \\"keywords\\": [ 60 | \\"react-native\\" 61 | ], 62 | \\"author\\": { 63 | \\"name\\": \\"Your Name\\", 64 | \\"email\\": \\"yourname@email.com\\" 65 | }, 66 | \\"license\\": \\"MIT\\", 67 | \\"licenseFilename\\": \\"LICENSE\\", 68 | \\"readmeFilename\\": \\"README.md\\", 69 | \\"peerDependencies\\": { 70 | \\"react\\": \\">=16.8.1\\", 71 | \\"react-native\\": \\">=0.60.0-rc.0 <1.0.x\\" 72 | }, 73 | \\"devDependencies\\": { 74 | \\"react\\": \\"^16.9.0\\", 75 | \\"react-native\\": \\"^0.61.5\\" 76 | } 77 | } 78 | 79 | <<<<<<<< ======== >>>>>>>> 80 | ", 81 | "* outputFile name: react-native-alice-bobbi/index.js 82 | content: 83 | -------- 84 | // main index.js 85 | 86 | import { NativeModules } from 'react-native'; 87 | 88 | const { AliceBobbi } = NativeModules; 89 | 90 | export default AliceBobbi; 91 | 92 | <<<<<<<< ======== >>>>>>>> 93 | ", 94 | "* outputFile name: react-native-alice-bobbi/.gitignore 95 | content: 96 | -------- 97 | # OSX 98 | # 99 | .DS_Store 100 | 101 | # node.js 102 | # 103 | node_modules/ 104 | npm-debug.log 105 | yarn-error.log 106 | 107 | <<<<<<<< ======== >>>>>>>> 108 | ", 109 | ] 110 | `; 111 | -------------------------------------------------------------------------------- /tests/with-injection/create/with-defaults/bogus-platforms/empty-string/__snapshots__/bogus-platforms-empty-string.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`create alice-bobbi module with defaults, with bogus platforms: '' 1`] = ` 4 | Array [ 5 | "* ensureDir dir: react-native-alice-bobbi 6 | ", 7 | "* ensureDir dir: react-native-alice-bobbi/ 8 | ", 9 | "* ensureDir dir: react-native-alice-bobbi/ 10 | ", 11 | "* ensureDir dir: react-native-alice-bobbi/ 12 | ", 13 | "* ensureDir dir: react-native-alice-bobbi/ 14 | ", 15 | "* outputFile name: react-native-alice-bobbi/README.md 16 | content: 17 | -------- 18 | # react-native-alice-bobbi 19 | 20 | ## Getting started 21 | 22 | \`$ npm install react-native-alice-bobbi --save\` 23 | 24 | ### Mostly automatic installation 25 | 26 | \`$ react-native link react-native-alice-bobbi\` 27 | 28 | ## Usage 29 | \`\`\`javascript 30 | import AliceBobbi from 'react-native-alice-bobbi'; 31 | 32 | // TODO: What to do with the module? 33 | AliceBobbi; 34 | \`\`\` 35 | 36 | <<<<<<<< ======== >>>>>>>> 37 | ", 38 | "* outputFile name: react-native-alice-bobbi/package.json 39 | content: 40 | -------- 41 | { 42 | \\"name\\": \\"react-native-alice-bobbi\\", 43 | \\"title\\": \\"React Native Alice Bobbi\\", 44 | \\"version\\": \\"1.0.0\\", 45 | \\"description\\": \\"TODO\\", 46 | \\"main\\": \\"index.js\\", 47 | \\"files\\": [ 48 | \\"README.md\\", 49 | \\"index.js\\" 50 | ], 51 | \\"scripts\\": { 52 | \\"test\\": \\"echo \\\\\\"Error: no test specified\\\\\\" && exit 1\\" 53 | }, 54 | \\"repository\\": { 55 | \\"type\\": \\"git\\", 56 | \\"url\\": \\"git+https://github.com/github_account/react-native-alice-bobbi.git\\", 57 | \\"baseUrl\\": \\"https://github.com/github_account/react-native-alice-bobbi\\" 58 | }, 59 | \\"keywords\\": [ 60 | \\"react-native\\" 61 | ], 62 | \\"author\\": { 63 | \\"name\\": \\"Your Name\\", 64 | \\"email\\": \\"yourname@email.com\\" 65 | }, 66 | \\"license\\": \\"MIT\\", 67 | \\"licenseFilename\\": \\"LICENSE\\", 68 | \\"readmeFilename\\": \\"README.md\\", 69 | \\"peerDependencies\\": { 70 | \\"react\\": \\">=16.8.1\\", 71 | \\"react-native\\": \\">=0.60.0-rc.0 <1.0.x\\" 72 | }, 73 | \\"devDependencies\\": { 74 | \\"react\\": \\"^16.9.0\\", 75 | \\"react-native\\": \\"^0.61.5\\" 76 | } 77 | } 78 | 79 | <<<<<<<< ======== >>>>>>>> 80 | ", 81 | "* outputFile name: react-native-alice-bobbi/index.js 82 | content: 83 | -------- 84 | // main index.js 85 | 86 | import { NativeModules } from 'react-native'; 87 | 88 | const { AliceBobbi } = NativeModules; 89 | 90 | export default AliceBobbi; 91 | 92 | <<<<<<<< ======== >>>>>>>> 93 | ", 94 | "* outputFile name: react-native-alice-bobbi/.gitignore 95 | content: 96 | -------- 97 | # OSX 98 | # 99 | .DS_Store 100 | 101 | # node.js 102 | # 103 | node_modules/ 104 | npm-debug.log 105 | yarn-error.log 106 | 107 | <<<<<<<< ======== >>>>>>>> 108 | ", 109 | ] 110 | `; 111 | -------------------------------------------------------------------------------- /tests/integration/cli/help/__snapshots__/cli-help.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`bin/cli.js --help returns expected output 1`] = ` 4 | "Usage: cli [options] 5 | 6 | creates a React Native library module for one or more platforms 7 | 8 | Options: 9 | -V, --version output the version number 10 | --package-name [packageName] The full package name to be used in package.json. Default: react-native-(name in param-case) 11 | --is-view Generate the package as a very simple native view component. Status: EXPERIMENTAL, with limited testing. 12 | --object-class-name [objectClassName] The name of the object class to be exported by both JavaScript and native code. Default: (name in PascalCase) 13 | --native-package-id [nativePackageId] [Android] The native Java package identifier used for Android (default: \\"com.reactlibrary\\") 14 | --platforms Platforms the library module will be created for - comma separated (default: \\"ios,android\\") 15 | --tvos-enabled Generate the module with tvOS build enabled (requires react-native-tvos fork, with minimum version of 0.60, and iOS platform to be enabled) 16 | --github-account [githubAccount] The github account where the library module is hosted (default: \\"github_account\\") 17 | --author-name [authorName] The author's name (default: \\"Your Name\\") 18 | --author-email [authorEmail] The author's email (default: \\"yourname@email.com\\") 19 | --license [license] The license type (default: \\"MIT\\") 20 | --use-apple-networking [iOS] Use \`AFNetworking\` dependency as a sample in the podspec & use it from the iOS code 21 | --generate-example Generate an example project and add the library module to it with symlink by defult, with overwrite of example metro.config.js to add workaround for Metro symlink issue - requires Yarn to be installed globally 22 | --example-file-linkage DEPRECATED: do \`yarn add file:../\` instead of \`yarn add link:../\` in a generated example project, and add a postinstall workaround script, with no overwrite of example metro.config.js 23 | --example-name [exampleName] Name for the example project (default: \\"example\\") 24 | --example-react-native-template [exampleReactNativeTemplate] The React Native template used for the generated example project, for example: react-native-tvos or react-native-tvos@0.62.2-1 (requires --tvos-enabled option); react-native@0.62 (default: \\"react-native@latest\\") 25 | --write-example-podfile [iOS] EXPERIMENTAL FEATURE NOT SUPPORTED: write (or overwrite) example ios/Podfile 26 | -h, --help display help for command" 27 | `; 28 | -------------------------------------------------------------------------------- /tests/integration/cli/noargs/__snapshots__/cli-noargs.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`bin/cli.js with no arguments returns expected output 1`] = ` 4 | "Usage: cli [options] 5 | 6 | creates a React Native library module for one or more platforms 7 | 8 | Options: 9 | -V, --version output the version number 10 | --package-name [packageName] The full package name to be used in package.json. Default: react-native-(name in param-case) 11 | --is-view Generate the package as a very simple native view component. Status: EXPERIMENTAL, with limited testing. 12 | --object-class-name [objectClassName] The name of the object class to be exported by both JavaScript and native code. Default: (name in PascalCase) 13 | --native-package-id [nativePackageId] [Android] The native Java package identifier used for Android (default: \\"com.reactlibrary\\") 14 | --platforms Platforms the library module will be created for - comma separated (default: \\"ios,android\\") 15 | --tvos-enabled Generate the module with tvOS build enabled (requires react-native-tvos fork, with minimum version of 0.60, and iOS platform to be enabled) 16 | --github-account [githubAccount] The github account where the library module is hosted (default: \\"github_account\\") 17 | --author-name [authorName] The author's name (default: \\"Your Name\\") 18 | --author-email [authorEmail] The author's email (default: \\"yourname@email.com\\") 19 | --license [license] The license type (default: \\"MIT\\") 20 | --use-apple-networking [iOS] Use \`AFNetworking\` dependency as a sample in the podspec & use it from the iOS code 21 | --generate-example Generate an example project and add the library module to it with symlink by defult, with overwrite of example metro.config.js to add workaround for Metro symlink issue - requires Yarn to be installed globally 22 | --example-file-linkage DEPRECATED: do \`yarn add file:../\` instead of \`yarn add link:../\` in a generated example project, and add a postinstall workaround script, with no overwrite of example metro.config.js 23 | --example-name [exampleName] Name for the example project (default: \\"example\\") 24 | --example-react-native-template [exampleReactNativeTemplate] The React Native template used for the generated example project, for example: react-native-tvos or react-native-tvos@0.62.2-1 (requires --tvos-enabled option); react-native@0.62 (default: \\"react-native@latest\\") 25 | --write-example-podfile [iOS] EXPERIMENTAL FEATURE NOT SUPPORTED: write (or overwrite) example ios/Podfile 26 | -h, --help display help for command" 27 | `; 28 | -------------------------------------------------------------------------------- /tests/with-injection/cli/command/object/__snapshots__/lib-cli-command-object-text.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`cli-command object 1`] = ` 4 | Object { 5 | "action": [Function], 6 | "description": "creates a React Native library module for one or more platforms", 7 | "name": "create-library", 8 | "options": Array [ 9 | Object { 10 | "command": "--package-name [packageName]", 11 | "description": "The full package name to be used in package.json. Default: react-native-(name in param-case)", 12 | }, 13 | Object { 14 | "command": "--is-view", 15 | "description": "Generate the package as a very simple native view component. Status: EXPERIMENTAL, with limited testing.", 16 | }, 17 | Object { 18 | "command": "--object-class-name [objectClassName]", 19 | "description": "The name of the object class to be exported by both JavaScript and native code. Default: (name in PascalCase)", 20 | }, 21 | Object { 22 | "command": "--native-package-id [nativePackageId]", 23 | "default": "com.reactlibrary", 24 | "description": "[Android] The native Java package identifier used for Android", 25 | }, 26 | Object { 27 | "command": "--platforms ", 28 | "default": "ios,android", 29 | "description": "Platforms the library module will be created for - comma separated", 30 | }, 31 | Object { 32 | "command": "--tvos-enabled", 33 | "description": "Generate the module with tvOS build enabled (requires react-native-tvos fork, with minimum version of 0.60, and iOS platform to be enabled)", 34 | }, 35 | Object { 36 | "command": "--github-account [githubAccount]", 37 | "default": "github_account", 38 | "description": "The github account where the library module is hosted", 39 | }, 40 | Object { 41 | "command": "--author-name [authorName]", 42 | "default": "Your Name", 43 | "description": "The author's name", 44 | }, 45 | Object { 46 | "command": "--author-email [authorEmail]", 47 | "default": "yourname@email.com", 48 | "description": "The author's email", 49 | }, 50 | Object { 51 | "command": "--license [license]", 52 | "default": "MIT", 53 | "description": "The license type", 54 | }, 55 | Object { 56 | "command": "--use-apple-networking", 57 | "description": "[iOS] Use \`AFNetworking\` dependency as a sample in the podspec & use it from the iOS code", 58 | }, 59 | Object { 60 | "command": "--generate-example", 61 | "description": "Generate an example project and add the library module to it with symlink by defult, with overwrite of example metro.config.js to add workaround for Metro symlink issue - requires Yarn to be installed globally", 62 | }, 63 | Object { 64 | "command": "--example-file-linkage", 65 | "description": "DEPRECATED: do \`yarn add file:../\` instead of \`yarn add link:../\` in a generated example project, and add a postinstall workaround script, with no overwrite of example metro.config.js", 66 | }, 67 | Object { 68 | "command": "--example-name [exampleName]", 69 | "default": "example", 70 | "description": "Name for the example project", 71 | }, 72 | Object { 73 | "command": "--example-react-native-template [exampleReactNativeTemplate]", 74 | "default": "react-native@latest", 75 | "description": "The React Native template used for the generated example project, for example: react-native-tvos or react-native-tvos@0.62.2-1 (requires --tvos-enabled option); react-native@0.62", 76 | }, 77 | Object { 78 | "command": "--write-example-podfile", 79 | "description": "[iOS] EXPERIMENTAL FEATURE NOT SUPPORTED: write (or overwrite) example ios/Podfile", 80 | }, 81 | ], 82 | "usage": "[options] ", 83 | } 84 | `; 85 | -------------------------------------------------------------------------------- /templates/general.js: -------------------------------------------------------------------------------- 1 | module.exports = [{ 2 | name: () => 'README.md', 3 | content: ({ packageName, objectClassName }) => 4 | `# ${packageName} 5 | 6 | ## Getting started 7 | 8 | \`$ npm install ${packageName} --save\` 9 | 10 | ### Mostly automatic installation 11 | 12 | \`$ react-native link ${packageName}\` 13 | 14 | ## Usage 15 | \`\`\`javascript 16 | import ${objectClassName} from '${packageName}'; 17 | 18 | // TODO: What to do with the module? 19 | ${objectClassName}; 20 | \`\`\` 21 | `, 22 | }, { 23 | name: () => 'package.json', 24 | content: ({ packageName, platforms, githubAccount, authorName, authorEmail, license }) => { 25 | const files = 26 | `[ 27 | "README.md",` + 28 | (platforms.indexOf('android') >= 0 ? ` 29 | "android",` : ``) + ` 30 | "index.js"` + 31 | (platforms.indexOf('ios') >= 0 ? `, 32 | "ios", 33 | "${packageName}.podspec"` : ``) + ` 34 | ]`; 35 | 36 | const peerDependencies = 37 | `{ 38 | "react": ">=16.8.1", 39 | "react-native": ">=0.60.0-rc.0 <1.0.x" 40 | }`; 41 | 42 | const devDependencies = 43 | `{ 44 | "react": "^16.9.0", 45 | "react-native": "^0.61.5" 46 | }`; 47 | 48 | return `{ 49 | "name": "${packageName}", 50 | "title": "${packageName.split('-').map(word => word[0].toUpperCase() + word.substr(1)).join(' ')}", 51 | "version": "1.0.0", 52 | "description": "TODO", 53 | "main": "index.js", 54 | "files": ${files}, 55 | "scripts": { 56 | "test": "echo \\"Error: no test specified\\" && exit 1" 57 | }, 58 | "repository": { 59 | "type": "git", 60 | "url": "git+https://github.com/${githubAccount}/${packageName}.git", 61 | "baseUrl": "https://github.com/${githubAccount}/${packageName}" 62 | }, 63 | "keywords": [ 64 | "react-native" 65 | ], 66 | "author": { 67 | "name": "${authorName}", 68 | "email": "${authorEmail}" 69 | }, 70 | "license": "${license}", 71 | "licenseFilename": "LICENSE", 72 | "readmeFilename": "README.md", 73 | "peerDependencies": ${peerDependencies}, 74 | "devDependencies": ${devDependencies} 75 | } 76 | `; 77 | } 78 | }, { 79 | // for module without view: 80 | name: ({ isView }) => !isView && 'index.js', 81 | content: ({ objectClassName }) => 82 | `// main index.js 83 | 84 | import { NativeModules } from 'react-native'; 85 | 86 | const { ${objectClassName} } = NativeModules; 87 | 88 | export default ${objectClassName}; 89 | `, 90 | }, { 91 | // for module with view: 92 | name: ({ isView }) => isView && 'index.js', 93 | content: ({ objectClassName }) => 94 | `// main index.js 95 | 96 | import { requireNativeComponent } from 'react-native'; 97 | 98 | const ${objectClassName} = requireNativeComponent('${objectClassName}', null); 99 | 100 | export default ${objectClassName}; 101 | `, 102 | }, { 103 | name: () => '.gitignore', 104 | content: ({ platforms }) => { 105 | let content = `# OSX 106 | # 107 | .DS_Store 108 | 109 | # node.js 110 | # 111 | node_modules/ 112 | npm-debug.log 113 | yarn-error.log 114 | `; 115 | 116 | if (platforms.indexOf('ios') >= 0) { 117 | content += 118 | ` 119 | # Xcode 120 | # 121 | build/ 122 | *.pbxuser 123 | !default.pbxuser 124 | *.mode1v3 125 | !default.mode1v3 126 | *.mode2v3 127 | !default.mode2v3 128 | *.perspectivev3 129 | !default.perspectivev3 130 | xcuserdata 131 | *.xccheckout 132 | *.moved-aside 133 | DerivedData 134 | *.hmap 135 | *.ipa 136 | *.xcuserstate 137 | project.xcworkspace 138 | `; 139 | } 140 | 141 | if (platforms.indexOf('android') >= 0) { 142 | content += 143 | ` 144 | # Android/IntelliJ 145 | # 146 | build/ 147 | .idea 148 | .gradle 149 | local.properties 150 | *.iml 151 | 152 | # BUCK 153 | buck-out/ 154 | \\.buckd/ 155 | *.keystore 156 | `; 157 | } 158 | 159 | return content; 160 | } 161 | }]; 162 | -------------------------------------------------------------------------------- /tests/with-mocks/cli/program/with-example/with-logging/cli-program-with-example-with-logging.test.js: -------------------------------------------------------------------------------- 1 | const mockcwd = require('process').cwd(); 2 | 3 | // special compact mocks for this test: 4 | const mysnap = []; 5 | const mockpushit = x => mysnap.push(x); 6 | jest.mock('please-upgrade-node', () => ({ name, engines }) => { 7 | // only snapshot a limited number of fields 8 | expect(name).toBeDefined(); 9 | expect(engines).toBeDefined(); 10 | mockpushit({ 'please-upgrade-node': { name, engines } }); 11 | }); 12 | jest.mock('update-notifier', () => ({ pkg }) => { 13 | // only check a limited number of fields in pkg 14 | expect(pkg.name).toBeDefined(); 15 | expect(pkg.version).toBeDefined(); 16 | return { notify: () => mockpushit({ notify: {} }) }; 17 | }); 18 | jest.mock('fs-extra', () => ({ 19 | outputFile: (outputFileName, theContent) => { 20 | mockpushit({ 21 | outputFileName: outputFileName.replace(mockcwd, '...').replace(/\\/g, '/'), 22 | theContent 23 | }); 24 | return Promise.resolve(); 25 | }, 26 | ensureDir: (dir) => { 27 | mockpushit({ ensureDir: dir.replace(mockcwd, '...').replace(/\\/g, '/') }); 28 | return Promise.resolve(); 29 | }, 30 | })); 31 | const mockCommanderState = { actionFunction: null }; 32 | const mockOptions = { platforms: 'android,ios', generateExample: true }; 33 | const mockCommander = { 34 | args: ['test-package'], 35 | version: (version) => { 36 | // actual value of version not expected to be stable 37 | expect(version).toBeDefined(); 38 | mockpushit({ version: 'x' }); 39 | return mockCommander; 40 | }, 41 | usage: (usage) => { 42 | mockpushit({ usage }); 43 | return mockCommander; 44 | }, 45 | description: (description) => { 46 | mockpushit({ description }); 47 | return mockCommander; 48 | }, 49 | action: (actionFunction) => { 50 | mockpushit({ action: { actionFunction } }); 51 | mockCommanderState.actionFunction = actionFunction; 52 | return mockCommander; 53 | }, 54 | option: (...args) => { 55 | mockpushit({ option: { args } }); 56 | return mockCommander; 57 | }, 58 | parse: (argv) => { 59 | mockpushit({ parse: { argv } }); 60 | mockCommanderState.actionFunction.apply( 61 | // inject results of parsing args here: 62 | { opts: () => mockOptions }, 63 | [{ bogus: {} }, ['test-package']] 64 | ); 65 | }, 66 | help: () => { 67 | throw new Error('help call not expected in this test'); 68 | }, 69 | }; 70 | jest.mock('commander', () => mockCommander); 71 | jest.mock('execa', () => ({ 72 | commandSync: (command, opts) => { 73 | const options = { ...opts, ...(opts.cwd ? { cwd: opts.cwd.replace(mockcwd, '...').replace(/\\/g, '/') } : {}) }; 74 | mockpushit({ commandSync: command, options }); 75 | } 76 | })); 77 | jest.mock('react-native-init-func', () => (projectNameArray, opts) => { 78 | const options = { ...opts, ...(opts.directory ? { directory: opts.directory.replace(/\\/g, '/') } : {}) }; 79 | mockpushit({ call: 'reactNativeInit', nameArray: projectNameArray, options }); 80 | }); 81 | jest.mock('console', () => ({ 82 | info: (...args) => { 83 | mockpushit({ 84 | // TBD EXTRA WORKAROUND HACK for non-deterministic elapsed time in log etc. 85 | info: args.map(line => line.replace(mockcwd, '...').replace(/\\/g, '/').replace(/It took.*s/g, 'It took XXX')) 86 | }); 87 | }, 88 | // console.log is no longer expected 89 | // log: (...args) => ... 90 | warn: (...args) => { 91 | mockpushit({ warn: [].concat(args) }); 92 | }, 93 | error: (...args) => { 94 | throw new Error('console error not expected'); 95 | }, 96 | })); 97 | 98 | // TBD hackish injection 99 | // (NOTE that the results of parsing the args is injected above) 100 | process.argv = ['node', 'create-cli.js', 'test-package']; 101 | 102 | test('mocked cli-program.js runs correctly with example, with logging', () => { 103 | require('../../../../../../lib/cli-program.js'); 104 | // Using a 1 ms timer to wait for the 105 | // CLI program func to finish. 106 | // FUTURE TBD this looks like a bad smell 107 | // that should be resolved someday. 108 | return new Promise((resolve) => setTimeout(resolve, 1)) 109 | .then(() => { expect(mysnap).toMatchSnapshot(); }); 110 | }); 111 | -------------------------------------------------------------------------------- /tests/with-mocks/cli/command/action-func/with-logging/with-bogus-platforms/empty-string/__snapshots__/cli-command-with-empty-platforms-string.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`create alice-bobbi module with logging, with platforms: '' 1`] = ` 4 | Array [ 5 | Object { 6 | "warn": Array [ 7 | "While \`{DEFAULT_NATIVE_PACKAGE_ID}\` is the default package 8 | identifier, it is recommended to customize the package identifier.", 9 | ], 10 | }, 11 | Object { 12 | "info": Array [ 13 | "CREATE new React Native module with the following options: 14 | 15 | name: alice-bobbi 16 | full package name: react-native-alice-bobbi 17 | is view: false 18 | object class name: AliceBobbi 19 | Android nativePackageId: com.reactlibrary 20 | platforms: 21 | Apple tvosEnabled: false 22 | authorName: Your Name 23 | authorEmail: yourname@email.com 24 | author githubAccount: github_account 25 | license: MIT 26 | useAppleNetworking: false 27 | ", 28 | ], 29 | }, 30 | Object { 31 | "info": Array [ 32 | "CREATE: Generating the React Native library module", 33 | ], 34 | }, 35 | Object { 36 | "ensureDir": "react-native-alice-bobbi", 37 | }, 38 | Object { 39 | "ensureDir": "react-native-alice-bobbi/", 40 | }, 41 | Object { 42 | "ensureDir": "react-native-alice-bobbi/", 43 | }, 44 | Object { 45 | "ensureDir": "react-native-alice-bobbi/", 46 | }, 47 | Object { 48 | "ensureDir": "react-native-alice-bobbi/", 49 | }, 50 | Object { 51 | "outputFileName": "react-native-alice-bobbi/README.md", 52 | "theContent": "# react-native-alice-bobbi 53 | 54 | ## Getting started 55 | 56 | \`$ npm install react-native-alice-bobbi --save\` 57 | 58 | ### Mostly automatic installation 59 | 60 | \`$ react-native link react-native-alice-bobbi\` 61 | 62 | ## Usage 63 | \`\`\`javascript 64 | import AliceBobbi from 'react-native-alice-bobbi'; 65 | 66 | // TODO: What to do with the module? 67 | AliceBobbi; 68 | \`\`\` 69 | ", 70 | }, 71 | Object { 72 | "outputFileName": "react-native-alice-bobbi/package.json", 73 | "theContent": "{ 74 | \\"name\\": \\"react-native-alice-bobbi\\", 75 | \\"title\\": \\"React Native Alice Bobbi\\", 76 | \\"version\\": \\"1.0.0\\", 77 | \\"description\\": \\"TODO\\", 78 | \\"main\\": \\"index.js\\", 79 | \\"files\\": [ 80 | \\"README.md\\", 81 | \\"index.js\\" 82 | ], 83 | \\"scripts\\": { 84 | \\"test\\": \\"echo \\\\\\"Error: no test specified\\\\\\" && exit 1\\" 85 | }, 86 | \\"repository\\": { 87 | \\"type\\": \\"git\\", 88 | \\"url\\": \\"git+https://github.com/github_account/react-native-alice-bobbi.git\\", 89 | \\"baseUrl\\": \\"https://github.com/github_account/react-native-alice-bobbi\\" 90 | }, 91 | \\"keywords\\": [ 92 | \\"react-native\\" 93 | ], 94 | \\"author\\": { 95 | \\"name\\": \\"Your Name\\", 96 | \\"email\\": \\"yourname@email.com\\" 97 | }, 98 | \\"license\\": \\"MIT\\", 99 | \\"licenseFilename\\": \\"LICENSE\\", 100 | \\"readmeFilename\\": \\"README.md\\", 101 | \\"peerDependencies\\": { 102 | \\"react\\": \\">=16.8.1\\", 103 | \\"react-native\\": \\">=0.60.0-rc.0 <1.0.x\\" 104 | }, 105 | \\"devDependencies\\": { 106 | \\"react\\": \\"^16.9.0\\", 107 | \\"react-native\\": \\"^0.61.5\\" 108 | } 109 | } 110 | ", 111 | }, 112 | Object { 113 | "outputFileName": "react-native-alice-bobbi/index.js", 114 | "theContent": "// main index.js 115 | 116 | import { NativeModules } from 'react-native'; 117 | 118 | const { AliceBobbi } = NativeModules; 119 | 120 | export default AliceBobbi; 121 | ", 122 | }, 123 | Object { 124 | "outputFileName": "react-native-alice-bobbi/.gitignore", 125 | "theContent": "# OSX 126 | # 127 | .DS_Store 128 | 129 | # node.js 130 | # 131 | node_modules/ 132 | npm-debug.log 133 | yarn-error.log 134 | ", 135 | }, 136 | Object { 137 | "info": Array [ 138 | " 139 | 📚 Created library module react-native-alice-bobbi in \`./react-native-alice-bobbi\`. 140 | 🕘 It took XXX. 141 | 142 | ==================================================== 143 | YOU'RE ALL SET! 144 | 145 | 💡 next time consider using \`--generate-example\` to add a generated example! 146 | ", 147 | ], 148 | }, 149 | ] 150 | `; 151 | -------------------------------------------------------------------------------- /tests/with-mocks/cli/command/action-func/with-logging/with-bogus-platforms/bogus-name/__snapshots__/cli-command-with-bogus-platforms-name.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`create alice-bobbi module with logging, with platforms: 'bogus' 1`] = ` 4 | Array [ 5 | Object { 6 | "warn": Array [ 7 | "While \`{DEFAULT_NATIVE_PACKAGE_ID}\` is the default package 8 | identifier, it is recommended to customize the package identifier.", 9 | ], 10 | }, 11 | Object { 12 | "info": Array [ 13 | "CREATE new React Native module with the following options: 14 | 15 | name: alice-bobbi 16 | full package name: react-native-alice-bobbi 17 | is view: false 18 | object class name: AliceBobbi 19 | Android nativePackageId: com.reactlibrary 20 | platforms: bogus 21 | Apple tvosEnabled: false 22 | authorName: Your Name 23 | authorEmail: yourname@email.com 24 | author githubAccount: github_account 25 | license: MIT 26 | useAppleNetworking: false 27 | ", 28 | ], 29 | }, 30 | Object { 31 | "info": Array [ 32 | "CREATE: Generating the React Native library module", 33 | ], 34 | }, 35 | Object { 36 | "ensureDir": "react-native-alice-bobbi", 37 | }, 38 | Object { 39 | "ensureDir": "react-native-alice-bobbi/", 40 | }, 41 | Object { 42 | "ensureDir": "react-native-alice-bobbi/", 43 | }, 44 | Object { 45 | "ensureDir": "react-native-alice-bobbi/", 46 | }, 47 | Object { 48 | "ensureDir": "react-native-alice-bobbi/", 49 | }, 50 | Object { 51 | "outputFileName": "react-native-alice-bobbi/README.md", 52 | "theContent": "# react-native-alice-bobbi 53 | 54 | ## Getting started 55 | 56 | \`$ npm install react-native-alice-bobbi --save\` 57 | 58 | ### Mostly automatic installation 59 | 60 | \`$ react-native link react-native-alice-bobbi\` 61 | 62 | ## Usage 63 | \`\`\`javascript 64 | import AliceBobbi from 'react-native-alice-bobbi'; 65 | 66 | // TODO: What to do with the module? 67 | AliceBobbi; 68 | \`\`\` 69 | ", 70 | }, 71 | Object { 72 | "outputFileName": "react-native-alice-bobbi/package.json", 73 | "theContent": "{ 74 | \\"name\\": \\"react-native-alice-bobbi\\", 75 | \\"title\\": \\"React Native Alice Bobbi\\", 76 | \\"version\\": \\"1.0.0\\", 77 | \\"description\\": \\"TODO\\", 78 | \\"main\\": \\"index.js\\", 79 | \\"files\\": [ 80 | \\"README.md\\", 81 | \\"index.js\\" 82 | ], 83 | \\"scripts\\": { 84 | \\"test\\": \\"echo \\\\\\"Error: no test specified\\\\\\" && exit 1\\" 85 | }, 86 | \\"repository\\": { 87 | \\"type\\": \\"git\\", 88 | \\"url\\": \\"git+https://github.com/github_account/react-native-alice-bobbi.git\\", 89 | \\"baseUrl\\": \\"https://github.com/github_account/react-native-alice-bobbi\\" 90 | }, 91 | \\"keywords\\": [ 92 | \\"react-native\\" 93 | ], 94 | \\"author\\": { 95 | \\"name\\": \\"Your Name\\", 96 | \\"email\\": \\"yourname@email.com\\" 97 | }, 98 | \\"license\\": \\"MIT\\", 99 | \\"licenseFilename\\": \\"LICENSE\\", 100 | \\"readmeFilename\\": \\"README.md\\", 101 | \\"peerDependencies\\": { 102 | \\"react\\": \\">=16.8.1\\", 103 | \\"react-native\\": \\">=0.60.0-rc.0 <1.0.x\\" 104 | }, 105 | \\"devDependencies\\": { 106 | \\"react\\": \\"^16.9.0\\", 107 | \\"react-native\\": \\"^0.61.5\\" 108 | } 109 | } 110 | ", 111 | }, 112 | Object { 113 | "outputFileName": "react-native-alice-bobbi/index.js", 114 | "theContent": "// main index.js 115 | 116 | import { NativeModules } from 'react-native'; 117 | 118 | const { AliceBobbi } = NativeModules; 119 | 120 | export default AliceBobbi; 121 | ", 122 | }, 123 | Object { 124 | "outputFileName": "react-native-alice-bobbi/.gitignore", 125 | "theContent": "# OSX 126 | # 127 | .DS_Store 128 | 129 | # node.js 130 | # 131 | node_modules/ 132 | npm-debug.log 133 | yarn-error.log 134 | ", 135 | }, 136 | Object { 137 | "info": Array [ 138 | " 139 | 📚 Created library module react-native-alice-bobbi in \`./react-native-alice-bobbi\`. 140 | 🕘 It took XXX. 141 | 142 | ==================================================== 143 | YOU'RE ALL SET! 144 | 145 | 💡 next time consider using \`--generate-example\` to add a generated example! 146 | ", 147 | ], 148 | }, 149 | ] 150 | `; 151 | -------------------------------------------------------------------------------- /tests/with-mocks/cli/program/with-missing-args/__snapshots__/cli-program-with-missing-args.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`mocked cli-program.js shows help in case of missing args 1`] = ` 4 | Array [ 5 | Object { 6 | "please-upgrade-node": Object { 7 | "engines": Object { 8 | "node": ">= 12", 9 | }, 10 | "name": "create-react-native-module", 11 | }, 12 | }, 13 | Object { 14 | "notify": Object {}, 15 | }, 16 | Object { 17 | "version": "x", 18 | }, 19 | Object { 20 | "usage": "[options] ", 21 | }, 22 | Object { 23 | "description": "creates a React Native library module for one or more platforms", 24 | }, 25 | Object { 26 | "action": Object {}, 27 | }, 28 | Object { 29 | "option": Object { 30 | "args": Array [ 31 | "--package-name [packageName]", 32 | "The full package name to be used in package.json. Default: react-native-(name in param-case)", 33 | [Function], 34 | undefined, 35 | ], 36 | }, 37 | }, 38 | Object { 39 | "option": Object { 40 | "args": Array [ 41 | "--is-view", 42 | "Generate the package as a very simple native view component. Status: EXPERIMENTAL, with limited testing.", 43 | [Function], 44 | undefined, 45 | ], 46 | }, 47 | }, 48 | Object { 49 | "option": Object { 50 | "args": Array [ 51 | "--object-class-name [objectClassName]", 52 | "The name of the object class to be exported by both JavaScript and native code. Default: (name in PascalCase)", 53 | [Function], 54 | undefined, 55 | ], 56 | }, 57 | }, 58 | Object { 59 | "option": Object { 60 | "args": Array [ 61 | "--native-package-id [nativePackageId]", 62 | "[Android] The native Java package identifier used for Android", 63 | [Function], 64 | "com.reactlibrary", 65 | ], 66 | }, 67 | }, 68 | Object { 69 | "option": Object { 70 | "args": Array [ 71 | "--platforms ", 72 | "Platforms the library module will be created for - comma separated", 73 | [Function], 74 | "ios,android", 75 | ], 76 | }, 77 | }, 78 | Object { 79 | "option": Object { 80 | "args": Array [ 81 | "--tvos-enabled", 82 | "Generate the module with tvOS build enabled (requires react-native-tvos fork, with minimum version of 0.60, and iOS platform to be enabled)", 83 | [Function], 84 | undefined, 85 | ], 86 | }, 87 | }, 88 | Object { 89 | "option": Object { 90 | "args": Array [ 91 | "--github-account [githubAccount]", 92 | "The github account where the library module is hosted", 93 | [Function], 94 | "github_account", 95 | ], 96 | }, 97 | }, 98 | Object { 99 | "option": Object { 100 | "args": Array [ 101 | "--author-name [authorName]", 102 | "The author's name", 103 | [Function], 104 | "Your Name", 105 | ], 106 | }, 107 | }, 108 | Object { 109 | "option": Object { 110 | "args": Array [ 111 | "--author-email [authorEmail]", 112 | "The author's email", 113 | [Function], 114 | "yourname@email.com", 115 | ], 116 | }, 117 | }, 118 | Object { 119 | "option": Object { 120 | "args": Array [ 121 | "--license [license]", 122 | "The license type", 123 | [Function], 124 | "MIT", 125 | ], 126 | }, 127 | }, 128 | Object { 129 | "option": Object { 130 | "args": Array [ 131 | "--use-apple-networking", 132 | "[iOS] Use \`AFNetworking\` dependency as a sample in the podspec & use it from the iOS code", 133 | [Function], 134 | undefined, 135 | ], 136 | }, 137 | }, 138 | Object { 139 | "option": Object { 140 | "args": Array [ 141 | "--generate-example", 142 | "Generate an example project and add the library module to it with symlink by defult, with overwrite of example metro.config.js to add workaround for Metro symlink issue - requires Yarn to be installed globally", 143 | [Function], 144 | undefined, 145 | ], 146 | }, 147 | }, 148 | Object { 149 | "option": Object { 150 | "args": Array [ 151 | "--example-file-linkage", 152 | "DEPRECATED: do \`yarn add file:../\` instead of \`yarn add link:../\` in a generated example project, and add a postinstall workaround script, with no overwrite of example metro.config.js", 153 | [Function], 154 | undefined, 155 | ], 156 | }, 157 | }, 158 | Object { 159 | "option": Object { 160 | "args": Array [ 161 | "--example-name [exampleName]", 162 | "Name for the example project", 163 | [Function], 164 | "example", 165 | ], 166 | }, 167 | }, 168 | Object { 169 | "option": Object { 170 | "args": Array [ 171 | "--example-react-native-template [exampleReactNativeTemplate]", 172 | "The React Native template used for the generated example project, for example: react-native-tvos or react-native-tvos@0.62.2-1 (requires --tvos-enabled option); react-native@0.62", 173 | [Function], 174 | "react-native@latest", 175 | ], 176 | }, 177 | }, 178 | Object { 179 | "option": Object { 180 | "args": Array [ 181 | "--write-example-podfile", 182 | "[iOS] EXPERIMENTAL FEATURE NOT SUPPORTED: write (or overwrite) example ios/Podfile", 183 | [Function], 184 | undefined, 185 | ], 186 | }, 187 | }, 188 | Object { 189 | "parse": Object { 190 | "argv": Array [ 191 | "node", 192 | "./bin/cli.js", 193 | "--help", 194 | ], 195 | }, 196 | }, 197 | ] 198 | `; 199 | -------------------------------------------------------------------------------- /lib/cli-command.js: -------------------------------------------------------------------------------- 1 | const { info, error } = require('console'); 2 | 3 | const emoji = require('node-emoji'); 4 | 5 | const logSymbols = require('log-symbols'); 6 | 7 | const normalizedOptions = require('./normalized-options'); 8 | 9 | const createLibraryModule = require('./lib'); 10 | 11 | const postCreateInstructions = ({ 12 | packageName, 13 | platforms, 14 | generateExample, 15 | exampleName 16 | }) => ` 17 | ==================================================== 18 | YOU'RE ALL SET! 19 | ` + (generateExample 20 | ? ` 21 | ${emoji.get('bulb')} check out the example app in ${packageName}/${exampleName} 22 | ${emoji.get('bulb')} recommended: run Metro Bundler in a new shell 23 | ${logSymbols.info} (cd ${packageName}/${exampleName} && yarn start) 24 | ${emoji.get('bulb')} enter the following commands to run the example app: 25 | ${logSymbols.info} cd ${packageName}/${exampleName} 26 | ${platforms.split(',').map(platform => 27 | `${logSymbols.info} yarn ${platform} # for React Native 0.60: npx react-native run-${platform}` 28 | ).join(` 29 | `)} 30 | ${logSymbols.warning} IMPORTANT NOTICES 31 | ${logSymbols.warning} After clean checkout, these first steps are needed: 32 | ${logSymbols.info} run Yarn in ${packageName}/${exampleName}/ios 33 | ${logSymbols.info} (cd ${packageName}/${exampleName} && yarn) 34 | ${logSymbols.info} do \`pod install\` for iOS in ${packageName}/${exampleName}/ios 35 | ${logSymbols.info} cd ${packageName}/${exampleName} 36 | ${logSymbols.info} (cd ios && pod install) 37 | ${logSymbols.warning} KNOWN ISSUE with adding dependencies to the library root 38 | ${logSymbols.info} see https://github.com/brodybits/create-react-native-module/issues/308 39 | ` 40 | : ` 41 | ${emoji.get('bulb')} next time consider using \`--generate-example\` to add a generated example! 42 | `); 43 | 44 | module.exports = { 45 | name: 'create-library', 46 | description: 'creates a React Native library module for one or more platforms', 47 | usage: '[options] ', 48 | action: (args, options) => { 49 | if (!args || args.length < 1) { 50 | throw new Error('missing lib module name'); 51 | } 52 | 53 | if (args.length > 1) { 54 | throw new Error('too many arguments'); 55 | } 56 | 57 | const name = args[0]; 58 | 59 | const beforeCreation = Date.now(); 60 | 61 | const preNormalizedOptions = Object.assign({}, { name }, options); 62 | 63 | // NOTE: There is a trick where the new normalizedOptions() 64 | // from normalized-options.js is applied by both command.js & lib.js. 65 | // This is to ensure that the CLI gets the correct module name for the 66 | // final log message, and that the exported programmatic 67 | // function can be completely tested from using the CLI. 68 | 69 | const createOptions = normalizedOptions(preNormalizedOptions); 70 | 71 | const rootModuleName = createOptions.packageName; 72 | 73 | return createLibraryModule(createOptions).then(() => { 74 | info(` 75 | ${emoji.get('books')} Created library module ${rootModuleName} in \`./${rootModuleName}\`. 76 | ${emoji.get('clock9')} It took ${Date.now() - beforeCreation}ms. 77 | ${postCreateInstructions(createOptions)}`); 78 | }).catch((err) => { 79 | error(`Error while creating library module ${rootModuleName}`); 80 | 81 | if (err.stack) { 82 | error(err.stack); 83 | } 84 | }); 85 | }, 86 | options: [{ 87 | command: '--package-name [packageName]', 88 | description: 'The full package name to be used in package.json. Default: react-native-(name in param-case)', 89 | }, { 90 | command: '--is-view', 91 | description: 'Generate the package as a very simple native view component. Status: EXPERIMENTAL, with limited testing.', 92 | }, { 93 | command: '--object-class-name [objectClassName]', 94 | description: 'The name of the object class to be exported by both JavaScript and native code. Default: (name in PascalCase)', 95 | }, { 96 | command: '--native-package-id [nativePackageId]', 97 | description: '[Android] The native Java package identifier used for Android', 98 | default: 'com.reactlibrary', 99 | }, { 100 | command: '--platforms ', 101 | description: 'Platforms the library module will be created for - comma separated', 102 | default: 'ios,android', 103 | }, { 104 | command: '--tvos-enabled', 105 | description: 'Generate the module with tvOS build enabled (requires react-native-tvos fork, with minimum version of 0.60, and iOS platform to be enabled)', 106 | }, { 107 | command: '--github-account [githubAccount]', 108 | description: 'The github account where the library module is hosted', 109 | default: 'github_account', 110 | }, { 111 | command: '--author-name [authorName]', 112 | description: 'The author\'s name', 113 | default: 'Your Name', 114 | }, { 115 | command: '--author-email [authorEmail]', 116 | description: 'The author\'s email', 117 | default: 'yourname@email.com', 118 | }, { 119 | command: '--license [license]', 120 | description: 'The license type', 121 | default: 'MIT', 122 | }, { 123 | command: '--use-apple-networking', 124 | description: '[iOS] Use `AFNetworking` dependency as a sample in the podspec & use it from the iOS code', 125 | }, { 126 | command: '--generate-example', 127 | description: 'Generate an example project and add the library module to it with symlink by defult, with overwrite of example metro.config.js to add workaround for Metro symlink issue - requires Yarn to be installed globally', 128 | }, { 129 | command: '--example-file-linkage', 130 | description: "DEPRECATED: do `yarn add file:../` instead of `yarn add link:../` in a generated example project, and add a postinstall workaround script, with no overwrite of example metro.config.js", 131 | }, { 132 | command: '--example-name [exampleName]', 133 | description: 'Name for the example project', 134 | default: 'example', 135 | }, { 136 | command: '--example-react-native-template [exampleReactNativeTemplate]', 137 | description: 'The React Native template used for the generated example project, for example: react-native-tvos or react-native-tvos@0.62.2-1 (requires --tvos-enabled option); react-native@0.62', 138 | default: 'react-native@latest', 139 | }, { 140 | command: '--write-example-podfile', 141 | description: '[iOS] EXPERIMENTAL FEATURE NOT SUPPORTED: write (or overwrite) example ios/Podfile', 142 | }] 143 | }; 144 | -------------------------------------------------------------------------------- /templates/example.js: -------------------------------------------------------------------------------- 1 | const outdent = require('outdent'); 2 | 3 | module.exports = [{ 4 | // only needed in case of `exampleFileLinkage: true`: 5 | name: ({ exampleFileLinkage }) => 6 | exampleFileLinkage ? 'scripts/examples_postinstall.js' : undefined, 7 | content: ({ exampleName }) => 8 | `#!/usr/bin/env node 9 | 10 | /* 11 | * Using libraries within examples and linking them within packages.json like: 12 | * "react-native-library-name": "file:../" 13 | * will cause problems with the metro bundler if the example will run via 14 | * \`react-native run-[ios|android]\`. This will result in an error as the metro 15 | * bundler will find multiple versions for the same module while resolving it. 16 | * The reason for that is that if the library is installed it also copies in the 17 | * example folder itself as well as the node_modules folder of the library 18 | * although their are defined in .npmignore and should be ignored in theory. 19 | * 20 | * This postinstall script removes the node_modules folder as well as all 21 | * entries from the libraries .npmignore file within the examples node_modules 22 | * folder after the library was installed. This should resolve the metro 23 | * bundler issue mentioned above. 24 | * 25 | * It is expected this scripts lives in the libraries root folder within a 26 | * scripts folder. As first parameter the relative path to the libraries 27 | * folder within the example's node_modules folder may be provided. 28 | * This script will determine the path from this project's package.json file 29 | * if no such relative path is provided. 30 | * An example's package.json entry could look like: 31 | * "postinstall": "node ../scripts/examples_postinstall.js node_modules/react-native-library-name/" 32 | */ 33 | 34 | 'use strict'; 35 | 36 | const fs = require('fs'); 37 | const path = require('path'); 38 | 39 | /// Delete all files and directories for the given path 40 | const removeFileDirectoryRecursively = fileDirPath => { 41 | // Remove file 42 | if (!fs.lstatSync(fileDirPath).isDirectory()) { 43 | fs.unlinkSync(fileDirPath); 44 | return; 45 | } 46 | 47 | // Go down the directory an remove each file / directory recursively 48 | fs.readdirSync(fileDirPath).forEach(entry => { 49 | const entryPath = path.join(fileDirPath, entry); 50 | removeFileDirectoryRecursively(entryPath); 51 | }); 52 | fs.rmdirSync(fileDirPath); 53 | }; 54 | 55 | /// Remove ${exampleName}/node_modules/react-native-library-name/node_modules directory 56 | const removeLibraryNodeModulesPath = (libraryNodeModulesPath) => { 57 | const nodeModulesPath = path.resolve(libraryNodeModulesPath, 'node_modules') 58 | 59 | if (!fs.existsSync(nodeModulesPath)) { 60 | console.log(\`No node_modules path found at \${nodeModulesPath}. Skipping delete.\`) 61 | return; 62 | } 63 | 64 | console.log(\`Deleting: \${nodeModulesPath}\`) 65 | try { 66 | removeFileDirectoryRecursively(nodeModulesPath); 67 | console.log(\`Successfully deleted: \${nodeModulesPath}\`) 68 | } catch (err) { 69 | console.log(\`Error deleting \${nodeModulesPath}: \${err.message}\`); 70 | } 71 | }; 72 | 73 | /// Remove all entries from the .npmignore within ${exampleName}/node_modules/react-native-library-name/ 74 | const removeLibraryNpmIgnorePaths = (npmIgnorePath, libraryNodeModulesPath) => { 75 | if (!fs.existsSync(npmIgnorePath)) { 76 | console.log(\`No .npmignore path found at \${npmIgnorePath}. Skipping deleting content.\`); 77 | return; 78 | } 79 | 80 | fs.readFileSync(npmIgnorePath, 'utf8').split(/\\r?\\n/).forEach(entry => { 81 | if (entry.length === 0) { 82 | return 83 | } 84 | 85 | const npmIgnoreLibraryNodeModulesEntryPath = path.resolve(libraryNodeModulesPath, entry); 86 | if (!fs.existsSync(npmIgnoreLibraryNodeModulesEntryPath)) { 87 | return; 88 | } 89 | 90 | console.log(\`Deleting: \${npmIgnoreLibraryNodeModulesEntryPath}\`) 91 | try { 92 | removeFileDirectoryRecursively(npmIgnoreLibraryNodeModulesEntryPath); 93 | console.log(\`Successfully deleted: \${npmIgnoreLibraryNodeModulesEntryPath}\`) 94 | } catch (err) { 95 | console.log(\`Error deleting \${npmIgnoreLibraryNodeModulesEntryPath}: \${err.message}\`); 96 | } 97 | }); 98 | }; 99 | 100 | // Main start sweeping process 101 | (() => { 102 | // Read out dir of example project 103 | const exampleDir = process.cwd(); 104 | 105 | console.log(\`Starting postinstall cleanup for \${exampleDir}\`); 106 | 107 | // Resolve the React Native library's path within the example's node_modules directory 108 | const libraryNodeModulesPath = process.argv.length > 2 109 | ? path.resolve(exampleDir, process.argv[2]) 110 | : path.resolve(exampleDir, 'node_modules', require('../package.json').name); 111 | 112 | console.log(\`Removing unwanted artifacts for \${libraryNodeModulesPath}\`); 113 | 114 | removeLibraryNodeModulesPath(libraryNodeModulesPath); 115 | 116 | const npmIgnorePath = path.resolve(__dirname, '../.npmignore'); 117 | removeLibraryNpmIgnorePaths(npmIgnorePath, libraryNodeModulesPath); 118 | })(); 119 | ` 120 | }, { 121 | // metro.config.js workarounds needed in case of `exampleFileLinkage: false`: 122 | name: ({ exampleName, exampleFileLinkage }) => 123 | exampleFileLinkage ? undefined : `${exampleName}/metro.config.js`, 124 | content: ({ packageName, exampleName }) => `// metro.config.js 125 | // 126 | // with multiple workarounds for this issue with symlinks: 127 | // https://github.com/facebook/metro/issues/1 128 | // 129 | // with thanks to @johnryan () 130 | // for the pointers to multiple workaround solutions here: 131 | // https://github.com/facebook/metro/issues/1#issuecomment-541642857 132 | // 133 | // see also this discussion: 134 | // https://github.com/brodybits/create-react-native-module/issues/232 135 | 136 | const path = require('path') 137 | 138 | module.exports = { 139 | // workaround for an issue with symlinks encountered starting with 140 | // metro@0.55 / React Native 0.61 141 | // (not needed with React Native 0.60 / metro@0.54) 142 | resolver: { 143 | extraNodeModules: new Proxy( 144 | {}, 145 | { get: (_, name) => path.resolve('.', 'node_modules', name) } 146 | ) 147 | }, 148 | 149 | // quick workaround for another issue with symlinks 150 | watchFolders: [path.resolve('.'), path.resolve('..')] 151 | } 152 | `, 153 | }, { 154 | name: ({ exampleName, writeExamplePodfile }) => 155 | writeExamplePodfile ? `${exampleName}/ios/Podfile` : undefined, 156 | content: ({ packageName, exampleName }) => 157 | outdent({ trimTrailingNewline: false })` 158 | platform :ios, '10.0' 159 | 160 | target '${exampleName}' do 161 | rn_path = '../node_modules/react-native' 162 | 163 | pod 'yoga', path: "#{rn_path}/ReactCommon/yoga/yoga.podspec" 164 | pod 'DoubleConversion', :podspec => "#{rn_path}/third-party-podspecs/DoubleConversion.podspec" 165 | pod 'Folly', :podspec => "#{rn_path}/third-party-podspecs/Folly.podspec" 166 | pod 'glog', :podspec => "#{rn_path}/third-party-podspecs/GLog.podspec" 167 | pod 'React', path: rn_path, subspecs: [ 168 | 'Core', 169 | 'CxxBridge', 170 | 'RCTAnimation', 171 | 'RCTActionSheet', 172 | 'RCTImage', 173 | 'RCTLinkingIOS', 174 | 'RCTNetwork', 175 | 'RCTSettings', 176 | 'RCTText', 177 | 'RCTVibration', 178 | 'RCTWebSocket', 179 | 'RCTPushNotification', 180 | 'RCTCameraRoll', 181 | 'RCTSettings', 182 | 'RCTBlob', 183 | 'RCTGeolocation', 184 | 'DevSupport' 185 | ] 186 | 187 | pod '${packageName}', :path => '../../${packageName}.podspec' 188 | end 189 | `, 190 | }, { 191 | name: ({ exampleName }) => `${exampleName}/App.js`, 192 | content: ({ packageName, objectClassName, isView }) => 193 | `/** 194 | * Sample React Native App 195 | * 196 | * adapted from App.js generated by the following command: 197 | * 198 | * react-native init example 199 | * 200 | * https://github.com/facebook/react-native 201 | */ 202 | 203 | import React, { Component } from 'react'; 204 | import { Platform, StyleSheet, Text, View } from 'react-native'; 205 | import ${objectClassName} from '${packageName}';` + 206 | (!isView 207 | ? ` 208 | 209 | export default class App extends Component<{}> { 210 | state = { 211 | status: 'starting', 212 | message: '--' 213 | }; 214 | componentDidMount() { 215 | ${objectClassName}.sampleMethod('Testing', 123, (message) => { 216 | this.setState({ 217 | status: 'native callback received', 218 | message 219 | }); 220 | }); 221 | } 222 | render() { 223 | return ( 224 | 225 | ☆${objectClassName} example☆ 226 | STATUS: {this.state.status} 227 | ☆NATIVE CALLBACK MESSAGE☆ 228 | {this.state.message} 229 | 230 | ); 231 | } 232 | }` 233 | : ` 234 | 235 | export default class App extends Component<{}> { 236 | render() { 237 | return ( 238 | 239 | ☆${objectClassName} example☆ 240 | STATUS: loaded 241 | ☆☆☆ 242 | <${objectClassName} /> 243 | 244 | ); 245 | } 246 | }`) + 247 | ` 248 | 249 | const styles = StyleSheet.create({ 250 | container: { 251 | flex: 1, 252 | justifyContent: 'center', 253 | alignItems: 'center', 254 | backgroundColor: '#F5FCFF', 255 | }, 256 | welcome: { 257 | fontSize: 20, 258 | textAlign: 'center', 259 | margin: 10, 260 | }, 261 | instructions: { 262 | textAlign: 'center', 263 | color: '#333333', 264 | marginBottom: 5, 265 | }, 266 | }); 267 | ` 268 | }]; 269 | -------------------------------------------------------------------------------- /templates/android.js: -------------------------------------------------------------------------------- 1 | module.exports = platform => [{ 2 | name: () => `${platform}/build.gradle`, 3 | content: ({ nativePackageId }) => `// ${platform}/build.gradle 4 | 5 | // based on: 6 | // 7 | // * https://github.com/facebook/react-native/blob/0.60-stable/template/android/build.gradle 8 | // previous location: 9 | // - https://github.com/facebook/react-native/blob/0.58-stable/local-cli/templates/HelloWorld/android/build.gradle 10 | // 11 | // * https://github.com/facebook/react-native/blob/0.60-stable/template/android/app/build.gradle 12 | // previous location: 13 | // - https://github.com/facebook/react-native/blob/0.58-stable/local-cli/templates/HelloWorld/android/app/build.gradle 14 | 15 | // These defaults should reflect the SDK versions used by 16 | // the minimum React Native version supported. 17 | def DEFAULT_COMPILE_SDK_VERSION = 28 18 | def DEFAULT_BUILD_TOOLS_VERSION = '28.0.3' 19 | def DEFAULT_MIN_SDK_VERSION = 16 20 | def DEFAULT_TARGET_SDK_VERSION = 28 21 | 22 | def safeExtGet(prop, fallback) { 23 | rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback 24 | } 25 | 26 | apply plugin: 'com.android.library' 27 | apply plugin: 'maven' 28 | 29 | buildscript { 30 | // The Android Gradle plugin is only required when opening the android folder stand-alone. 31 | // This avoids unnecessary downloads and potential conflicts when the library is included as a 32 | // module dependency in an application project. 33 | // ref: https://docs.gradle.org/current/userguide/tutorial_using_tasks.html#sec:build_script_external_dependencies 34 | if (project == rootProject) { 35 | repositories { 36 | google() 37 | } 38 | dependencies { 39 | // This should reflect the Gradle plugin version used by 40 | // the minimum React Native version supported. 41 | classpath 'com.android.tools.build:gradle:3.4.1' 42 | } 43 | } 44 | } 45 | 46 | android { 47 | compileSdkVersion safeExtGet('compileSdkVersion', DEFAULT_COMPILE_SDK_VERSION) 48 | buildToolsVersion safeExtGet('buildToolsVersion', DEFAULT_BUILD_TOOLS_VERSION) 49 | defaultConfig { 50 | minSdkVersion safeExtGet('minSdkVersion', DEFAULT_MIN_SDK_VERSION) 51 | targetSdkVersion safeExtGet('targetSdkVersion', DEFAULT_TARGET_SDK_VERSION) 52 | versionCode 1 53 | versionName "1.0" 54 | } 55 | lintOptions { 56 | abortOnError false 57 | } 58 | } 59 | 60 | repositories { 61 | // ref: https://www.baeldung.com/maven-local-repository 62 | mavenLocal() 63 | maven { 64 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 65 | url "$rootDir/../node_modules/react-native/android" 66 | } 67 | maven { 68 | // Android JSC is installed from npm 69 | url "$rootDir/../node_modules/jsc-android/dist" 70 | } 71 | google() 72 | } 73 | 74 | dependencies { 75 | //noinspection GradleDynamicVersion 76 | implementation 'com.facebook.react:react-native:+' // From node_modules 77 | } 78 | 79 | def configureReactNativePom(def pom) { 80 | def packageJson = new groovy.json.JsonSlurper().parseText(file('../package.json').text) 81 | 82 | pom.project { 83 | name packageJson.title 84 | artifactId packageJson.name 85 | version = packageJson.version 86 | group = "${nativePackageId}" 87 | description packageJson.description 88 | url packageJson.repository.baseUrl 89 | 90 | licenses { 91 | license { 92 | name packageJson.license 93 | url packageJson.repository.baseUrl + '/blob/master/' + packageJson.licenseFilename 94 | distribution 'repo' 95 | } 96 | } 97 | 98 | developers { 99 | developer { 100 | id packageJson.author.username 101 | name packageJson.author.name 102 | } 103 | } 104 | } 105 | } 106 | 107 | afterEvaluate { project -> 108 | // some Gradle build hooks ref: 109 | // https://www.oreilly.com/library/view/gradle-beyond-the/9781449373801/ch03.html 110 | task androidJavadoc(type: Javadoc) { 111 | source = android.sourceSets.main.java.srcDirs 112 | classpath += files(android.bootClasspath) 113 | classpath += files(project.getConfigurations().getByName('compile').asList()) 114 | include '**/*.java' 115 | } 116 | 117 | task androidJavadocJar(type: Jar, dependsOn: androidJavadoc) { 118 | classifier = 'javadoc' 119 | from androidJavadoc.destinationDir 120 | } 121 | 122 | task androidSourcesJar(type: Jar) { 123 | classifier = 'sources' 124 | from android.sourceSets.main.java.srcDirs 125 | include '**/*.java' 126 | } 127 | 128 | android.libraryVariants.all { variant -> 129 | def name = variant.name.capitalize() 130 | def javaCompileTask = variant.javaCompileProvider.get() 131 | 132 | task "jar\${name}"(type: Jar, dependsOn: javaCompileTask) { 133 | from javaCompileTask.destinationDir 134 | } 135 | } 136 | 137 | artifacts { 138 | archives androidSourcesJar 139 | archives androidJavadocJar 140 | } 141 | 142 | task installArchives(type: Upload) { 143 | configuration = configurations.archives 144 | repositories.mavenDeployer { 145 | // Deploy to react-native-event-bridge/maven, ready to publish to npm 146 | repository url: "file://\${projectDir}/../android/maven" 147 | configureReactNativePom pom 148 | } 149 | } 150 | } 151 | `, 152 | }, { 153 | name: () => `${platform}/src/main/AndroidManifest.xml`, 154 | content: ({ nativePackageId }) => ` 155 | 156 | 158 | 159 | 160 | `, 161 | }, { 162 | // for module without view: 163 | name: ({ objectClassName, nativePackageId, isView }) => 164 | !isView && 165 | `${platform}/src/main/java/${nativePackageId.split('.').join('/')}/${objectClassName}Module.java`, 166 | content: ({ objectClassName, nativePackageId, isView }) => 167 | !isView && 168 | `// ${objectClassName}Module.java 169 | 170 | package ${nativePackageId}; 171 | 172 | import com.facebook.react.bridge.ReactApplicationContext; 173 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 174 | import com.facebook.react.bridge.ReactMethod; 175 | import com.facebook.react.bridge.Callback; 176 | 177 | public class ${objectClassName}Module extends ReactContextBaseJavaModule { 178 | 179 | private final ReactApplicationContext reactContext; 180 | 181 | public ${objectClassName}Module(ReactApplicationContext reactContext) { 182 | super(reactContext); 183 | this.reactContext = reactContext; 184 | } 185 | 186 | @Override 187 | public String getName() { 188 | return "${objectClassName}"; 189 | } 190 | 191 | @ReactMethod 192 | public void sampleMethod(String stringArgument, int numberArgument, Callback callback) { 193 | // TODO: Implement some actually useful functionality 194 | callback.invoke("Received numberArgument: " + numberArgument + " stringArgument: " + stringArgument); 195 | } 196 | } 197 | `, 198 | }, { 199 | // manager for view: 200 | name: ({ objectClassName, nativePackageId, isView }) => 201 | isView && 202 | `${platform}/src/main/java/${nativePackageId.split('.').join('/')}/${objectClassName}Manager.java`, 203 | content: ({ objectClassName, nativePackageId, isView }) => 204 | isView && 205 | `// ${objectClassName}Manager.java 206 | 207 | package ${nativePackageId}; 208 | 209 | import android.view.View; 210 | 211 | import androidx.appcompat.widget.AppCompatCheckBox; 212 | 213 | import com.facebook.react.uimanager.SimpleViewManager; 214 | import com.facebook.react.uimanager.ThemedReactContext; 215 | 216 | public class ${objectClassName}Manager extends SimpleViewManager { 217 | 218 | public static final String REACT_CLASS = "${objectClassName}"; 219 | 220 | @Override 221 | public String getName() { 222 | return REACT_CLASS; 223 | } 224 | 225 | @Override 226 | public View createViewInstance(ThemedReactContext c) { 227 | // TODO: Implement some actually useful functionality 228 | AppCompatCheckBox cb = new AppCompatCheckBox(c); 229 | cb.setChecked(true); 230 | return cb; 231 | } 232 | } 233 | `, 234 | }, { 235 | // package for module without view: 236 | name: ({ objectClassName, nativePackageId, isView }) => 237 | !isView && 238 | `${platform}/src/main/java/${nativePackageId.split('.').join('/')}/${objectClassName}Package.java`, 239 | content: ({ objectClassName, nativePackageId, isView }) => 240 | !isView && 241 | `// ${objectClassName}Package.java 242 | 243 | package ${nativePackageId}; 244 | 245 | import java.util.Arrays; 246 | import java.util.Collections; 247 | import java.util.List; 248 | 249 | import com.facebook.react.ReactPackage; 250 | import com.facebook.react.bridge.NativeModule; 251 | import com.facebook.react.bridge.ReactApplicationContext; 252 | import com.facebook.react.uimanager.ViewManager; 253 | 254 | public class ${objectClassName}Package implements ReactPackage { 255 | @Override 256 | public List createNativeModules(ReactApplicationContext reactContext) { 257 | return Arrays.asList(new ${objectClassName}Module(reactContext)); 258 | } 259 | 260 | @Override 261 | public List createViewManagers(ReactApplicationContext reactContext) { 262 | return Collections.emptyList(); 263 | } 264 | } 265 | `, 266 | }, { 267 | // package for manager for view: 268 | name: ({ objectClassName, nativePackageId, isView }) => 269 | isView && 270 | `${platform}/src/main/java/${nativePackageId.split('.').join('/')}/${objectClassName}Package.java`, 271 | content: ({ objectClassName, nativePackageId, isView }) => 272 | isView && 273 | `// ${objectClassName}Package.java 274 | 275 | package ${nativePackageId}; 276 | 277 | import java.util.Arrays; 278 | import java.util.Collections; 279 | import java.util.List; 280 | 281 | import com.facebook.react.ReactPackage; 282 | import com.facebook.react.bridge.NativeModule; 283 | import com.facebook.react.bridge.ReactApplicationContext; 284 | import com.facebook.react.uimanager.ViewManager; 285 | 286 | public class ${objectClassName}Package implements ReactPackage { 287 | @Override 288 | public List createNativeModules(ReactApplicationContext reactContext) { 289 | return Collections.emptyList(); 290 | } 291 | 292 | @Override 293 | public List createViewManagers(ReactApplicationContext reactContext) { 294 | return Arrays.asList(new ${objectClassName}Manager()); 295 | } 296 | } 297 | `, 298 | }, { 299 | name: () => `${platform}/README.md`, 300 | content: () => `README 301 | ====== 302 | 303 | If you want to publish the lib as a maven dependency, follow these steps before publishing a new version to npm: 304 | 305 | 1. Be sure to have the Android [SDK](https://developer.android.com/studio/index.html) and [NDK](https://developer.android.com/ndk/guides/index.html) installed 306 | 2. Be sure to have a \`local.properties\` file in this folder that points to the Android SDK and NDK 307 | \`\`\` 308 | ndk.dir=/Users/{username}/Library/Android/sdk/ndk-bundle 309 | sdk.dir=/Users/{username}/Library/Android/sdk 310 | \`\`\` 311 | 3. Delete the \`maven\` folder 312 | 4. Run \`./gradlew installArchives\` 313 | 5. Verify that latest set of generated files is in the maven folder with the correct version number 314 | ` 315 | }]; 316 | -------------------------------------------------------------------------------- /lib/lib.js: -------------------------------------------------------------------------------- 1 | // Node.js built-in: 2 | 3 | const path = require('path'); 4 | 5 | // External imports: 6 | 7 | const { info, warn, error } = require('console'); 8 | 9 | // default execa object 10 | const execaDefault = require('execa'); 11 | 12 | // default fs object 13 | const fsExtra = require('fs-extra'); 14 | 15 | const jsonfile = require('jsonfile'); 16 | 17 | // default reactNativeInit function 18 | const reactNativeInitDefault = require('react-native-init-func'); 19 | 20 | // Internal imports: 21 | 22 | const normalizedOptions = require('./normalized-options'); 23 | 24 | // Imports from templates 25 | 26 | const templates = require('../templates'); 27 | const exampleTemplates = require('../templates/example'); 28 | 29 | const DEFAULT_NATIVE_PACKAGE_ID = 'com.reactlibrary'; 30 | const DEFAULT_PLATFORMS = ['android', 'ios']; 31 | const DEFAULT_GITHUB_ACCOUNT = 'github_account'; 32 | const DEFAULT_AUTHOR_NAME = 'Your Name'; 33 | const DEFAULT_AUTHOR_EMAIL = 'yourname@email.com'; 34 | const DEFAULT_LICENSE = 'MIT'; 35 | const DEFAULT_GENERATE_EXAMPLE = false; 36 | const DEFAULT_EXAMPLE_NAME = 'example'; 37 | const DEFAULT_EXAMPLE_REACT_NATIVE_TEMPLATE = 'react-native@latest'; 38 | 39 | const renderTemplateIfValid = (fs, root, template, templateArgs) => { 40 | // avoid throwing an exception in case there is no valid template.name member 41 | const name = !!template.name && template.name(templateArgs); 42 | if (!name) return Promise.resolve(); 43 | 44 | const filename = path.join(root, name); 45 | const [baseDir] = filename.split(path.basename(filename)); 46 | 47 | return fs.ensureDir(baseDir).then(() => 48 | fs.outputFile(filename, template.content(templateArgs)) 49 | ); 50 | }; 51 | 52 | // FUTURE TBD make this asynchronous and possibly more functional: 53 | const npmAddScriptSync = (packageJsonPath, script, fs) => { 54 | try { 55 | var packageJson = jsonfile.readFileSync(packageJsonPath, { fs }); 56 | if (!packageJson.scripts) packageJson.scripts = {}; 57 | packageJson.scripts[script.key] = script.value; 58 | jsonfile.writeFileSync(packageJsonPath, packageJson, { fs, spaces: 2 }); 59 | } catch (e) { 60 | if (/ENOENT.*package.json/.test(e.message)) { 61 | throw new Error(`The package.json at path: ${packageJsonPath} does not exist.`); 62 | } else { 63 | throw e; 64 | } 65 | } 66 | }; 67 | 68 | const generateWithNormalizedOptions = ({ 69 | name, 70 | packageName, 71 | objectClassName, 72 | nativePackageId = DEFAULT_NATIVE_PACKAGE_ID, 73 | // namespace - library API member removed since Windows platform 74 | // is now removed (may be added back someday in the future) 75 | // namespace, 76 | platforms = DEFAULT_PLATFORMS, 77 | tvosEnabled = false, 78 | githubAccount = DEFAULT_GITHUB_ACCOUNT, 79 | authorName = DEFAULT_AUTHOR_NAME, 80 | authorEmail = DEFAULT_AUTHOR_EMAIL, 81 | license = DEFAULT_LICENSE, 82 | isView = false, 83 | useAppleNetworking = false, 84 | generateExample = DEFAULT_GENERATE_EXAMPLE, 85 | exampleFileLinkage = false, 86 | exampleName = DEFAULT_EXAMPLE_NAME, 87 | exampleReactNativeTemplate = DEFAULT_EXAMPLE_REACT_NATIVE_TEMPLATE, 88 | writeExamplePodfile = false, 89 | }, { 90 | fs = fsExtra, // (this can be mocked out for testing purposes) 91 | execa = execaDefault, // (this can be mocked out for testing purposes) 92 | reactNativeInit = reactNativeInitDefault // (can be overridden or mocked out for testing purposes) 93 | }) => { 94 | if (nativePackageId === DEFAULT_NATIVE_PACKAGE_ID) { 95 | warn(`While \`{DEFAULT_NATIVE_PACKAGE_ID}\` is the default package 96 | identifier, it is recommended to customize the package identifier.`); 97 | } 98 | 99 | // Note that the some of these console log messages are logged as 100 | // info instead of verbose since they are needed to help 101 | // make sense of the console output from the third-party tools. 102 | 103 | info( 104 | `CREATE new React Native module with the following options: 105 | 106 | name: ${name} 107 | full package name: ${packageName} 108 | is view: ${isView} 109 | object class name: ${objectClassName} 110 | Android nativePackageId: ${nativePackageId} 111 | platforms: ${platforms} 112 | Apple tvosEnabled: ${tvosEnabled} 113 | authorName: ${authorName} 114 | authorEmail: ${authorEmail} 115 | author githubAccount: ${githubAccount} 116 | license: ${license} 117 | useAppleNetworking: ${useAppleNetworking} 118 | ` + (generateExample 119 | ? ` 120 | generateExample: ${generateExample} 121 | exampleFileLinkage: ${exampleFileLinkage} 122 | exampleName: ${exampleName} 123 | exampleReactNativeTemplate: ${exampleReactNativeTemplate} 124 | writeExamplePodfile: ${writeExamplePodfile} 125 | ` : ``)); 126 | 127 | // QUICK LOCAL INJECTION overwite of existing execSync / commandSync call from 128 | // mockable execa object for now (at least): 129 | const commandSync = execa.commandSync; 130 | 131 | if (generateExample) { 132 | const yarnVersionCommand = 'yarn --version'; 133 | const checkCliOptions = { stdio: 'inherit' }; 134 | const errorRemedyMessage = 'yarn CLI is needed to generate example project'; 135 | 136 | try { 137 | info('CREATE: Check for valid Yarn CLI tool version, as needed to generate the example project'); 138 | commandSync(yarnVersionCommand, checkCliOptions); 139 | info(`${yarnVersionCommand} ok`); 140 | } catch (e) { 141 | throw new Error(`${yarnVersionCommand} failed ... ${errorRemedyMessage}`); 142 | } 143 | 144 | // NOTE: While the pod tool is also required for example on iOS, 145 | // react-native CLI will help the user install this tool if needed. 146 | } 147 | 148 | info('CREATE: Generating the React Native library module'); 149 | 150 | const generateLibraryModule = () => { 151 | return fs.ensureDir(packageName).then(() => { 152 | return Promise.all(templates.filter((template) => { 153 | if (template.platform) { 154 | return (platforms.indexOf(template.platform) >= 0); 155 | } 156 | 157 | return true; 158 | }).map((template) => { 159 | const templateArgs = { 160 | packageName, 161 | objectClassName, 162 | nativePackageId, 163 | // namespace - library API member removed since Windows platform 164 | // is now removed (may be added back someday in the future) 165 | // namespace, 166 | platforms, 167 | tvosEnabled, 168 | githubAccount, 169 | authorName, 170 | authorEmail, 171 | license, 172 | isView, 173 | useAppleNetworking, 174 | }; 175 | 176 | return renderTemplateIfValid(fs, packageName, template, templateArgs); 177 | })); 178 | }); 179 | }; 180 | 181 | // This separate promise makes it easier to generate 182 | // multiple test or sample apps in the future. 183 | const generateExampleApp = 184 | () => { 185 | info(`CREATE example app with the following template: ${exampleReactNativeTemplate}`); 186 | 187 | // resolve **absolute** module & example paths before 188 | // react-native-init-func function call 189 | // which may affect the process cwd state 190 | // (absolute paths seem to be needed for the steps below function properly) 191 | const modulePath = path.resolve('.', packageName); 192 | const pathExampleApp = path.join(modulePath, exampleName); 193 | const exampleAppSubdirectory = path.join('.', packageName, exampleName); 194 | 195 | // (with the work done in a promise chain) 196 | return Promise.resolve() 197 | .then(() => { 198 | return reactNativeInit([exampleName], { 199 | template: exampleReactNativeTemplate, 200 | directory: exampleAppSubdirectory 201 | }); 202 | }) 203 | .then(() => { 204 | // Render the example template 205 | const templateArgs = { 206 | packageName, 207 | objectClassName, 208 | isView, 209 | useAppleNetworking, 210 | exampleFileLinkage, 211 | exampleName, 212 | writeExamplePodfile, 213 | }; 214 | 215 | return Promise.all( 216 | exampleTemplates.map((template) => { 217 | return renderTemplateIfValid(fs, modulePath, template, templateArgs); 218 | }) 219 | ); 220 | }) 221 | .then(() => { 222 | // Adds and link the new library 223 | return new Promise((resolve) => { 224 | // postinstall workaround script *only* needed in case 225 | // the DEPRECATED --example-file-linkage option 226 | // (exampleFileLinkage: true) is used 227 | // (not needed otherwise): 228 | if (exampleFileLinkage) { 229 | info('Adding the cleanup postinstall *workaround* task to the example app'); 230 | npmAddScriptSync(`${pathExampleApp}/package.json`, { 231 | key: 'postinstall', 232 | value: `node ../scripts/examples_postinstall.js` 233 | }, fs); 234 | } 235 | 236 | // Add and link the new library 237 | info('Linking the new module library to the example app'); 238 | const addLinkLibraryCommand = exampleFileLinkage 239 | ? 'yarn add file:../' 240 | : 'yarn add link:../'; 241 | const addLinkLibraryOptions = { cwd: pathExampleApp, stdio: 'inherit' }; 242 | try { 243 | commandSync(addLinkLibraryCommand, addLinkLibraryOptions); 244 | } catch (e) { 245 | error('Yarn failure for example, aborting'); 246 | throw (e); 247 | } 248 | 249 | // Since React Native 0.60, pod install must be done (again) 250 | // after adding the native library to the generated example. 251 | if (platforms.indexOf('ios') !== -1) { 252 | const iosExampleAppProjectPath = 253 | `${pathExampleApp}/ios`; 254 | 255 | const podCommandOptions = { 256 | cwd: iosExampleAppProjectPath, 257 | stdio: 'inherit' 258 | }; 259 | 260 | // pod tool is needed at this point for iOS 261 | try { 262 | info( 263 | `check for valid pod version in ${iosExampleAppProjectPath}`); 264 | 265 | commandSync('pod --version', podCommandOptions); 266 | } catch (e) { 267 | error('pod --version failure, aborting with broken example app'); 268 | throw (e); 269 | } 270 | 271 | info(`running pod install in ${iosExampleAppProjectPath}`); 272 | 273 | try { 274 | commandSync('pod install', podCommandOptions); 275 | } catch (e) { 276 | error('pod install failure, aborting with broken example app'); 277 | throw (e); 278 | } 279 | } 280 | return resolve(); 281 | }); 282 | }); 283 | }; 284 | 285 | return generateLibraryModule().then(() => { 286 | return (generateExample 287 | ? generateExampleApp() 288 | : Promise.resolve() 289 | ); 290 | }); 291 | }; 292 | 293 | // lib function that acccepts options argument and optionally 294 | // a hidden ioImports object argument which is 295 | // mockable, unstable, and not documented 296 | module.exports = function lib (options) { 297 | // get hidden ioImports object argument if available 298 | const ioImports = (arguments.length > 1) 299 | ? arguments[1] 300 | : {}; 301 | 302 | return generateWithNormalizedOptions( 303 | normalizedOptions(options), 304 | ioImports); 305 | }; 306 | -------------------------------------------------------------------------------- /tests/with-injection/create/view/with-options/for-android/__snapshots__/lib-view-android-config-options.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`create alice-bobbi view module with config options for Android only 1`] = ` 4 | Array [ 5 | "* ensureDir dir: react-native-alice-bobbi 6 | ", 7 | "* ensureDir dir: react-native-alice-bobbi/ 8 | ", 9 | "* ensureDir dir: react-native-alice-bobbi/ 10 | ", 11 | "* ensureDir dir: react-native-alice-bobbi/ 12 | ", 13 | "* ensureDir dir: react-native-alice-bobbi/ 14 | ", 15 | "* ensureDir dir: react-native-alice-bobbi/android/ 16 | ", 17 | "* ensureDir dir: react-native-alice-bobbi/android/src/main/ 18 | ", 19 | "* ensureDir dir: react-native-alice-bobbi/android/src/main/java/com/reactlibrary/ 20 | ", 21 | "* ensureDir dir: react-native-alice-bobbi/android/src/main/java/com/reactlibrary/ 22 | ", 23 | "* ensureDir dir: react-native-alice-bobbi/android/ 24 | ", 25 | "* outputFile name: react-native-alice-bobbi/README.md 26 | content: 27 | -------- 28 | # react-native-alice-bobbi 29 | 30 | ## Getting started 31 | 32 | \`$ npm install react-native-alice-bobbi --save\` 33 | 34 | ### Mostly automatic installation 35 | 36 | \`$ react-native link react-native-alice-bobbi\` 37 | 38 | ## Usage 39 | \`\`\`javascript 40 | import AliceBobbi from 'react-native-alice-bobbi'; 41 | 42 | // TODO: What to do with the module? 43 | AliceBobbi; 44 | \`\`\` 45 | 46 | <<<<<<<< ======== >>>>>>>> 47 | ", 48 | "* outputFile name: react-native-alice-bobbi/package.json 49 | content: 50 | -------- 51 | { 52 | \\"name\\": \\"react-native-alice-bobbi\\", 53 | \\"title\\": \\"React Native Alice Bobbi\\", 54 | \\"version\\": \\"1.0.0\\", 55 | \\"description\\": \\"TODO\\", 56 | \\"main\\": \\"index.js\\", 57 | \\"files\\": [ 58 | \\"README.md\\", 59 | \\"android\\", 60 | \\"index.js\\" 61 | ], 62 | \\"scripts\\": { 63 | \\"test\\": \\"echo \\\\\\"Error: no test specified\\\\\\" && exit 1\\" 64 | }, 65 | \\"repository\\": { 66 | \\"type\\": \\"git\\", 67 | \\"url\\": \\"git+https://github.com/alicebits/react-native-alice-bobbi.git\\", 68 | \\"baseUrl\\": \\"https://github.com/alicebits/react-native-alice-bobbi\\" 69 | }, 70 | \\"keywords\\": [ 71 | \\"react-native\\" 72 | ], 73 | \\"author\\": { 74 | \\"name\\": \\"Alice\\", 75 | \\"email\\": \\"contact@alice.me\\" 76 | }, 77 | \\"license\\": \\"ISC\\", 78 | \\"licenseFilename\\": \\"LICENSE\\", 79 | \\"readmeFilename\\": \\"README.md\\", 80 | \\"peerDependencies\\": { 81 | \\"react\\": \\">=16.8.1\\", 82 | \\"react-native\\": \\">=0.60.0-rc.0 <1.0.x\\" 83 | }, 84 | \\"devDependencies\\": { 85 | \\"react\\": \\"^16.9.0\\", 86 | \\"react-native\\": \\"^0.61.5\\" 87 | } 88 | } 89 | 90 | <<<<<<<< ======== >>>>>>>> 91 | ", 92 | "* outputFile name: react-native-alice-bobbi/index.js 93 | content: 94 | -------- 95 | // main index.js 96 | 97 | import { requireNativeComponent } from 'react-native'; 98 | 99 | const AliceBobbi = requireNativeComponent('AliceBobbi', null); 100 | 101 | export default AliceBobbi; 102 | 103 | <<<<<<<< ======== >>>>>>>> 104 | ", 105 | "* outputFile name: react-native-alice-bobbi/.gitignore 106 | content: 107 | -------- 108 | # OSX 109 | # 110 | .DS_Store 111 | 112 | # node.js 113 | # 114 | node_modules/ 115 | npm-debug.log 116 | yarn-error.log 117 | 118 | # Android/IntelliJ 119 | # 120 | build/ 121 | .idea 122 | .gradle 123 | local.properties 124 | *.iml 125 | 126 | # BUCK 127 | buck-out/ 128 | \\\\.buckd/ 129 | *.keystore 130 | 131 | <<<<<<<< ======== >>>>>>>> 132 | ", 133 | "* outputFile name: react-native-alice-bobbi/android/build.gradle 134 | content: 135 | -------- 136 | // android/build.gradle 137 | 138 | // based on: 139 | // 140 | // * https://github.com/facebook/react-native/blob/0.60-stable/template/android/build.gradle 141 | // previous location: 142 | // - https://github.com/facebook/react-native/blob/0.58-stable/local-cli/templates/HelloWorld/android/build.gradle 143 | // 144 | // * https://github.com/facebook/react-native/blob/0.60-stable/template/android/app/build.gradle 145 | // previous location: 146 | // - https://github.com/facebook/react-native/blob/0.58-stable/local-cli/templates/HelloWorld/android/app/build.gradle 147 | 148 | // These defaults should reflect the SDK versions used by 149 | // the minimum React Native version supported. 150 | def DEFAULT_COMPILE_SDK_VERSION = 28 151 | def DEFAULT_BUILD_TOOLS_VERSION = '28.0.3' 152 | def DEFAULT_MIN_SDK_VERSION = 16 153 | def DEFAULT_TARGET_SDK_VERSION = 28 154 | 155 | def safeExtGet(prop, fallback) { 156 | rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback 157 | } 158 | 159 | apply plugin: 'com.android.library' 160 | apply plugin: 'maven' 161 | 162 | buildscript { 163 | // The Android Gradle plugin is only required when opening the android folder stand-alone. 164 | // This avoids unnecessary downloads and potential conflicts when the library is included as a 165 | // module dependency in an application project. 166 | // ref: https://docs.gradle.org/current/userguide/tutorial_using_tasks.html#sec:build_script_external_dependencies 167 | if (project == rootProject) { 168 | repositories { 169 | google() 170 | } 171 | dependencies { 172 | // This should reflect the Gradle plugin version used by 173 | // the minimum React Native version supported. 174 | classpath 'com.android.tools.build:gradle:3.4.1' 175 | } 176 | } 177 | } 178 | 179 | android { 180 | compileSdkVersion safeExtGet('compileSdkVersion', DEFAULT_COMPILE_SDK_VERSION) 181 | buildToolsVersion safeExtGet('buildToolsVersion', DEFAULT_BUILD_TOOLS_VERSION) 182 | defaultConfig { 183 | minSdkVersion safeExtGet('minSdkVersion', DEFAULT_MIN_SDK_VERSION) 184 | targetSdkVersion safeExtGet('targetSdkVersion', DEFAULT_TARGET_SDK_VERSION) 185 | versionCode 1 186 | versionName \\"1.0\\" 187 | } 188 | lintOptions { 189 | abortOnError false 190 | } 191 | } 192 | 193 | repositories { 194 | // ref: https://www.baeldung.com/maven-local-repository 195 | mavenLocal() 196 | maven { 197 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 198 | url \\"$rootDir/../node_modules/react-native/android\\" 199 | } 200 | maven { 201 | // Android JSC is installed from npm 202 | url \\"$rootDir/../node_modules/jsc-android/dist\\" 203 | } 204 | google() 205 | } 206 | 207 | dependencies { 208 | //noinspection GradleDynamicVersion 209 | implementation 'com.facebook.react:react-native:+' // From node_modules 210 | } 211 | 212 | def configureReactNativePom(def pom) { 213 | def packageJson = new groovy.json.JsonSlurper().parseText(file('../package.json').text) 214 | 215 | pom.project { 216 | name packageJson.title 217 | artifactId packageJson.name 218 | version = packageJson.version 219 | group = \\"com.reactlibrary\\" 220 | description packageJson.description 221 | url packageJson.repository.baseUrl 222 | 223 | licenses { 224 | license { 225 | name packageJson.license 226 | url packageJson.repository.baseUrl + '/blob/master/' + packageJson.licenseFilename 227 | distribution 'repo' 228 | } 229 | } 230 | 231 | developers { 232 | developer { 233 | id packageJson.author.username 234 | name packageJson.author.name 235 | } 236 | } 237 | } 238 | } 239 | 240 | afterEvaluate { project -> 241 | // some Gradle build hooks ref: 242 | // https://www.oreilly.com/library/view/gradle-beyond-the/9781449373801/ch03.html 243 | task androidJavadoc(type: Javadoc) { 244 | source = android.sourceSets.main.java.srcDirs 245 | classpath += files(android.bootClasspath) 246 | classpath += files(project.getConfigurations().getByName('compile').asList()) 247 | include '**/*.java' 248 | } 249 | 250 | task androidJavadocJar(type: Jar, dependsOn: androidJavadoc) { 251 | classifier = 'javadoc' 252 | from androidJavadoc.destinationDir 253 | } 254 | 255 | task androidSourcesJar(type: Jar) { 256 | classifier = 'sources' 257 | from android.sourceSets.main.java.srcDirs 258 | include '**/*.java' 259 | } 260 | 261 | android.libraryVariants.all { variant -> 262 | def name = variant.name.capitalize() 263 | def javaCompileTask = variant.javaCompileProvider.get() 264 | 265 | task \\"jar\${name}\\"(type: Jar, dependsOn: javaCompileTask) { 266 | from javaCompileTask.destinationDir 267 | } 268 | } 269 | 270 | artifacts { 271 | archives androidSourcesJar 272 | archives androidJavadocJar 273 | } 274 | 275 | task installArchives(type: Upload) { 276 | configuration = configurations.archives 277 | repositories.mavenDeployer { 278 | // Deploy to react-native-event-bridge/maven, ready to publish to npm 279 | repository url: \\"file://\${projectDir}/../android/maven\\" 280 | configureReactNativePom pom 281 | } 282 | } 283 | } 284 | 285 | <<<<<<<< ======== >>>>>>>> 286 | ", 287 | "* outputFile name: react-native-alice-bobbi/android/src/main/AndroidManifest.xml 288 | content: 289 | -------- 290 | 291 | 292 | 294 | 295 | 296 | 297 | <<<<<<<< ======== >>>>>>>> 298 | ", 299 | "* outputFile name: react-native-alice-bobbi/android/src/main/java/com/reactlibrary/AliceBobbiManager.java 300 | content: 301 | -------- 302 | // AliceBobbiManager.java 303 | 304 | package com.reactlibrary; 305 | 306 | import android.view.View; 307 | 308 | import androidx.appcompat.widget.AppCompatCheckBox; 309 | 310 | import com.facebook.react.uimanager.SimpleViewManager; 311 | import com.facebook.react.uimanager.ThemedReactContext; 312 | 313 | public class AliceBobbiManager extends SimpleViewManager { 314 | 315 | public static final String REACT_CLASS = \\"AliceBobbi\\"; 316 | 317 | @Override 318 | public String getName() { 319 | return REACT_CLASS; 320 | } 321 | 322 | @Override 323 | public View createViewInstance(ThemedReactContext c) { 324 | // TODO: Implement some actually useful functionality 325 | AppCompatCheckBox cb = new AppCompatCheckBox(c); 326 | cb.setChecked(true); 327 | return cb; 328 | } 329 | } 330 | 331 | <<<<<<<< ======== >>>>>>>> 332 | ", 333 | "* outputFile name: react-native-alice-bobbi/android/src/main/java/com/reactlibrary/AliceBobbiPackage.java 334 | content: 335 | -------- 336 | // AliceBobbiPackage.java 337 | 338 | package com.reactlibrary; 339 | 340 | import java.util.Arrays; 341 | import java.util.Collections; 342 | import java.util.List; 343 | 344 | import com.facebook.react.ReactPackage; 345 | import com.facebook.react.bridge.NativeModule; 346 | import com.facebook.react.bridge.ReactApplicationContext; 347 | import com.facebook.react.uimanager.ViewManager; 348 | 349 | public class AliceBobbiPackage implements ReactPackage { 350 | @Override 351 | public List createNativeModules(ReactApplicationContext reactContext) { 352 | return Collections.emptyList(); 353 | } 354 | 355 | @Override 356 | public List createViewManagers(ReactApplicationContext reactContext) { 357 | return Arrays.asList(new AliceBobbiManager()); 358 | } 359 | } 360 | 361 | <<<<<<<< ======== >>>>>>>> 362 | ", 363 | "* outputFile name: react-native-alice-bobbi/android/README.md 364 | content: 365 | -------- 366 | README 367 | ====== 368 | 369 | If you want to publish the lib as a maven dependency, follow these steps before publishing a new version to npm: 370 | 371 | 1. Be sure to have the Android [SDK](https://developer.android.com/studio/index.html) and [NDK](https://developer.android.com/ndk/guides/index.html) installed 372 | 2. Be sure to have a \`local.properties\` file in this folder that points to the Android SDK and NDK 373 | \`\`\` 374 | ndk.dir=/Users/{username}/Library/Android/sdk/ndk-bundle 375 | sdk.dir=/Users/{username}/Library/Android/sdk 376 | \`\`\` 377 | 3. Delete the \`maven\` folder 378 | 4. Run \`./gradlew installArchives\` 379 | 5. Verify that latest set of generated files is in the maven folder with the correct version number 380 | 381 | <<<<<<<< ======== >>>>>>>> 382 | ", 383 | ] 384 | `; 385 | -------------------------------------------------------------------------------- /tests/with-injection/create/with-options/for-android/__snapshots__/create-with-options-for-android.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`create alice-bobbi module with config options for Android only 1`] = ` 4 | Array [ 5 | "* ensureDir dir: react-native-alice-bobbi 6 | ", 7 | "* ensureDir dir: react-native-alice-bobbi/ 8 | ", 9 | "* ensureDir dir: react-native-alice-bobbi/ 10 | ", 11 | "* ensureDir dir: react-native-alice-bobbi/ 12 | ", 13 | "* ensureDir dir: react-native-alice-bobbi/ 14 | ", 15 | "* ensureDir dir: react-native-alice-bobbi/android/ 16 | ", 17 | "* ensureDir dir: react-native-alice-bobbi/android/src/main/ 18 | ", 19 | "* ensureDir dir: react-native-alice-bobbi/android/src/main/java/com/alicebits/ 20 | ", 21 | "* ensureDir dir: react-native-alice-bobbi/android/src/main/java/com/alicebits/ 22 | ", 23 | "* ensureDir dir: react-native-alice-bobbi/android/ 24 | ", 25 | "* outputFile name: react-native-alice-bobbi/README.md 26 | content: 27 | -------- 28 | # react-native-alice-bobbi 29 | 30 | ## Getting started 31 | 32 | \`$ npm install react-native-alice-bobbi --save\` 33 | 34 | ### Mostly automatic installation 35 | 36 | \`$ react-native link react-native-alice-bobbi\` 37 | 38 | ## Usage 39 | \`\`\`javascript 40 | import AliceBobbi from 'react-native-alice-bobbi'; 41 | 42 | // TODO: What to do with the module? 43 | AliceBobbi; 44 | \`\`\` 45 | 46 | <<<<<<<< ======== >>>>>>>> 47 | ", 48 | "* outputFile name: react-native-alice-bobbi/package.json 49 | content: 50 | -------- 51 | { 52 | \\"name\\": \\"react-native-alice-bobbi\\", 53 | \\"title\\": \\"React Native Alice Bobbi\\", 54 | \\"version\\": \\"1.0.0\\", 55 | \\"description\\": \\"TODO\\", 56 | \\"main\\": \\"index.js\\", 57 | \\"files\\": [ 58 | \\"README.md\\", 59 | \\"android\\", 60 | \\"index.js\\" 61 | ], 62 | \\"scripts\\": { 63 | \\"test\\": \\"echo \\\\\\"Error: no test specified\\\\\\" && exit 1\\" 64 | }, 65 | \\"repository\\": { 66 | \\"type\\": \\"git\\", 67 | \\"url\\": \\"git+https://github.com/alicebits/react-native-alice-bobbi.git\\", 68 | \\"baseUrl\\": \\"https://github.com/alicebits/react-native-alice-bobbi\\" 69 | }, 70 | \\"keywords\\": [ 71 | \\"react-native\\" 72 | ], 73 | \\"author\\": { 74 | \\"name\\": \\"Alice\\", 75 | \\"email\\": \\"contact@alice.me\\" 76 | }, 77 | \\"license\\": \\"ISC\\", 78 | \\"licenseFilename\\": \\"LICENSE\\", 79 | \\"readmeFilename\\": \\"README.md\\", 80 | \\"peerDependencies\\": { 81 | \\"react\\": \\">=16.8.1\\", 82 | \\"react-native\\": \\">=0.60.0-rc.0 <1.0.x\\" 83 | }, 84 | \\"devDependencies\\": { 85 | \\"react\\": \\"^16.9.0\\", 86 | \\"react-native\\": \\"^0.61.5\\" 87 | } 88 | } 89 | 90 | <<<<<<<< ======== >>>>>>>> 91 | ", 92 | "* outputFile name: react-native-alice-bobbi/index.js 93 | content: 94 | -------- 95 | // main index.js 96 | 97 | import { NativeModules } from 'react-native'; 98 | 99 | const { AliceBobbi } = NativeModules; 100 | 101 | export default AliceBobbi; 102 | 103 | <<<<<<<< ======== >>>>>>>> 104 | ", 105 | "* outputFile name: react-native-alice-bobbi/.gitignore 106 | content: 107 | -------- 108 | # OSX 109 | # 110 | .DS_Store 111 | 112 | # node.js 113 | # 114 | node_modules/ 115 | npm-debug.log 116 | yarn-error.log 117 | 118 | # Android/IntelliJ 119 | # 120 | build/ 121 | .idea 122 | .gradle 123 | local.properties 124 | *.iml 125 | 126 | # BUCK 127 | buck-out/ 128 | \\\\.buckd/ 129 | *.keystore 130 | 131 | <<<<<<<< ======== >>>>>>>> 132 | ", 133 | "* outputFile name: react-native-alice-bobbi/android/build.gradle 134 | content: 135 | -------- 136 | // android/build.gradle 137 | 138 | // based on: 139 | // 140 | // * https://github.com/facebook/react-native/blob/0.60-stable/template/android/build.gradle 141 | // previous location: 142 | // - https://github.com/facebook/react-native/blob/0.58-stable/local-cli/templates/HelloWorld/android/build.gradle 143 | // 144 | // * https://github.com/facebook/react-native/blob/0.60-stable/template/android/app/build.gradle 145 | // previous location: 146 | // - https://github.com/facebook/react-native/blob/0.58-stable/local-cli/templates/HelloWorld/android/app/build.gradle 147 | 148 | // These defaults should reflect the SDK versions used by 149 | // the minimum React Native version supported. 150 | def DEFAULT_COMPILE_SDK_VERSION = 28 151 | def DEFAULT_BUILD_TOOLS_VERSION = '28.0.3' 152 | def DEFAULT_MIN_SDK_VERSION = 16 153 | def DEFAULT_TARGET_SDK_VERSION = 28 154 | 155 | def safeExtGet(prop, fallback) { 156 | rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback 157 | } 158 | 159 | apply plugin: 'com.android.library' 160 | apply plugin: 'maven' 161 | 162 | buildscript { 163 | // The Android Gradle plugin is only required when opening the android folder stand-alone. 164 | // This avoids unnecessary downloads and potential conflicts when the library is included as a 165 | // module dependency in an application project. 166 | // ref: https://docs.gradle.org/current/userguide/tutorial_using_tasks.html#sec:build_script_external_dependencies 167 | if (project == rootProject) { 168 | repositories { 169 | google() 170 | } 171 | dependencies { 172 | // This should reflect the Gradle plugin version used by 173 | // the minimum React Native version supported. 174 | classpath 'com.android.tools.build:gradle:3.4.1' 175 | } 176 | } 177 | } 178 | 179 | android { 180 | compileSdkVersion safeExtGet('compileSdkVersion', DEFAULT_COMPILE_SDK_VERSION) 181 | buildToolsVersion safeExtGet('buildToolsVersion', DEFAULT_BUILD_TOOLS_VERSION) 182 | defaultConfig { 183 | minSdkVersion safeExtGet('minSdkVersion', DEFAULT_MIN_SDK_VERSION) 184 | targetSdkVersion safeExtGet('targetSdkVersion', DEFAULT_TARGET_SDK_VERSION) 185 | versionCode 1 186 | versionName \\"1.0\\" 187 | } 188 | lintOptions { 189 | abortOnError false 190 | } 191 | } 192 | 193 | repositories { 194 | // ref: https://www.baeldung.com/maven-local-repository 195 | mavenLocal() 196 | maven { 197 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 198 | url \\"$rootDir/../node_modules/react-native/android\\" 199 | } 200 | maven { 201 | // Android JSC is installed from npm 202 | url \\"$rootDir/../node_modules/jsc-android/dist\\" 203 | } 204 | google() 205 | } 206 | 207 | dependencies { 208 | //noinspection GradleDynamicVersion 209 | implementation 'com.facebook.react:react-native:+' // From node_modules 210 | } 211 | 212 | def configureReactNativePom(def pom) { 213 | def packageJson = new groovy.json.JsonSlurper().parseText(file('../package.json').text) 214 | 215 | pom.project { 216 | name packageJson.title 217 | artifactId packageJson.name 218 | version = packageJson.version 219 | group = \\"com.alicebits\\" 220 | description packageJson.description 221 | url packageJson.repository.baseUrl 222 | 223 | licenses { 224 | license { 225 | name packageJson.license 226 | url packageJson.repository.baseUrl + '/blob/master/' + packageJson.licenseFilename 227 | distribution 'repo' 228 | } 229 | } 230 | 231 | developers { 232 | developer { 233 | id packageJson.author.username 234 | name packageJson.author.name 235 | } 236 | } 237 | } 238 | } 239 | 240 | afterEvaluate { project -> 241 | // some Gradle build hooks ref: 242 | // https://www.oreilly.com/library/view/gradle-beyond-the/9781449373801/ch03.html 243 | task androidJavadoc(type: Javadoc) { 244 | source = android.sourceSets.main.java.srcDirs 245 | classpath += files(android.bootClasspath) 246 | classpath += files(project.getConfigurations().getByName('compile').asList()) 247 | include '**/*.java' 248 | } 249 | 250 | task androidJavadocJar(type: Jar, dependsOn: androidJavadoc) { 251 | classifier = 'javadoc' 252 | from androidJavadoc.destinationDir 253 | } 254 | 255 | task androidSourcesJar(type: Jar) { 256 | classifier = 'sources' 257 | from android.sourceSets.main.java.srcDirs 258 | include '**/*.java' 259 | } 260 | 261 | android.libraryVariants.all { variant -> 262 | def name = variant.name.capitalize() 263 | def javaCompileTask = variant.javaCompileProvider.get() 264 | 265 | task \\"jar\${name}\\"(type: Jar, dependsOn: javaCompileTask) { 266 | from javaCompileTask.destinationDir 267 | } 268 | } 269 | 270 | artifacts { 271 | archives androidSourcesJar 272 | archives androidJavadocJar 273 | } 274 | 275 | task installArchives(type: Upload) { 276 | configuration = configurations.archives 277 | repositories.mavenDeployer { 278 | // Deploy to react-native-event-bridge/maven, ready to publish to npm 279 | repository url: \\"file://\${projectDir}/../android/maven\\" 280 | configureReactNativePom pom 281 | } 282 | } 283 | } 284 | 285 | <<<<<<<< ======== >>>>>>>> 286 | ", 287 | "* outputFile name: react-native-alice-bobbi/android/src/main/AndroidManifest.xml 288 | content: 289 | -------- 290 | 291 | 292 | 294 | 295 | 296 | 297 | <<<<<<<< ======== >>>>>>>> 298 | ", 299 | "* outputFile name: react-native-alice-bobbi/android/src/main/java/com/alicebits/AliceBobbiModule.java 300 | content: 301 | -------- 302 | // AliceBobbiModule.java 303 | 304 | package com.alicebits; 305 | 306 | import com.facebook.react.bridge.ReactApplicationContext; 307 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 308 | import com.facebook.react.bridge.ReactMethod; 309 | import com.facebook.react.bridge.Callback; 310 | 311 | public class AliceBobbiModule extends ReactContextBaseJavaModule { 312 | 313 | private final ReactApplicationContext reactContext; 314 | 315 | public AliceBobbiModule(ReactApplicationContext reactContext) { 316 | super(reactContext); 317 | this.reactContext = reactContext; 318 | } 319 | 320 | @Override 321 | public String getName() { 322 | return \\"AliceBobbi\\"; 323 | } 324 | 325 | @ReactMethod 326 | public void sampleMethod(String stringArgument, int numberArgument, Callback callback) { 327 | // TODO: Implement some actually useful functionality 328 | callback.invoke(\\"Received numberArgument: \\" + numberArgument + \\" stringArgument: \\" + stringArgument); 329 | } 330 | } 331 | 332 | <<<<<<<< ======== >>>>>>>> 333 | ", 334 | "* outputFile name: react-native-alice-bobbi/android/src/main/java/com/alicebits/AliceBobbiPackage.java 335 | content: 336 | -------- 337 | // AliceBobbiPackage.java 338 | 339 | package com.alicebits; 340 | 341 | import java.util.Arrays; 342 | import java.util.Collections; 343 | import java.util.List; 344 | 345 | import com.facebook.react.ReactPackage; 346 | import com.facebook.react.bridge.NativeModule; 347 | import com.facebook.react.bridge.ReactApplicationContext; 348 | import com.facebook.react.uimanager.ViewManager; 349 | 350 | public class AliceBobbiPackage implements ReactPackage { 351 | @Override 352 | public List createNativeModules(ReactApplicationContext reactContext) { 353 | return Arrays.asList(new AliceBobbiModule(reactContext)); 354 | } 355 | 356 | @Override 357 | public List createViewManagers(ReactApplicationContext reactContext) { 358 | return Collections.emptyList(); 359 | } 360 | } 361 | 362 | <<<<<<<< ======== >>>>>>>> 363 | ", 364 | "* outputFile name: react-native-alice-bobbi/android/README.md 365 | content: 366 | -------- 367 | README 368 | ====== 369 | 370 | If you want to publish the lib as a maven dependency, follow these steps before publishing a new version to npm: 371 | 372 | 1. Be sure to have the Android [SDK](https://developer.android.com/studio/index.html) and [NDK](https://developer.android.com/ndk/guides/index.html) installed 373 | 2. Be sure to have a \`local.properties\` file in this folder that points to the Android SDK and NDK 374 | \`\`\` 375 | ndk.dir=/Users/{username}/Library/Android/sdk/ndk-bundle 376 | sdk.dir=/Users/{username}/Library/Android/sdk 377 | \`\`\` 378 | 3. Delete the \`maven\` folder 379 | 4. Run \`./gradlew installArchives\` 380 | 5. Verify that latest set of generated files is in the maven folder with the correct version number 381 | 382 | <<<<<<<< ======== >>>>>>>> 383 | ", 384 | ] 385 | `; 386 | -------------------------------------------------------------------------------- /templates/ios.js: -------------------------------------------------------------------------------- 1 | module.exports = platform => [{ 2 | name: ({ packageName }) => `${packageName}.podspec`, 3 | content: ({ packageName, tvosEnabled, githubAccount, authorName, authorEmail, license, useAppleNetworking }) => 4 | `# ${packageName}.podspec 5 | 6 | require "json" 7 | 8 | package = JSON.parse(File.read(File.join(__dir__, "package.json"))) 9 | 10 | Pod::Spec.new do |s| 11 | s.name = "${packageName}" 12 | s.version = package["version"] 13 | s.summary = package["description"] 14 | s.description = <<-DESC 15 | ${packageName} 16 | DESC 17 | s.homepage = "https://github.com/${githubAccount}/${packageName}" 18 | # brief license entry: 19 | s.license = "${license}" 20 | # optional - use expanded license entry instead: 21 | # s.license = { :type => "${license}", :file => "LICENSE" } 22 | s.authors = { "${authorName}" => "${authorEmail}" } 23 | s.platforms = { :ios => "9.0"${tvosEnabled ? `, :tvos => "10.0"` : ``} } 24 | s.source = { :git => "https://github.com/${githubAccount}/${packageName}.git", :tag => "#{s.version}" } 25 | 26 | s.source_files = "ios/**/*.{h,c,cc,cpp,m,mm,swift}" 27 | s.requires_arc = true 28 | 29 | s.dependency "React" 30 | ${useAppleNetworking ? `s.dependency 'AFNetworking', '~> 3.0'` : `# ...`} 31 | # s.dependency "..." 32 | end 33 | 34 | `, 35 | }, { 36 | // header for module without view: 37 | name: ({ objectClassName, isView }) => !isView && `${platform}/${objectClassName}.h`, 38 | content: ({ objectClassName }) => `// ${objectClassName}.h 39 | 40 | #import 41 | 42 | @interface ${objectClassName} : NSObject 43 | 44 | @end 45 | `, 46 | }, { 47 | // implementation of module without view: 48 | name: ({ objectClassName, isView }) => !isView && `${platform}/${objectClassName}.m`, 49 | content: ({ objectClassName, useAppleNetworking }) => `// ${objectClassName}.m 50 | 51 | #import "${objectClassName}.h" 52 | 53 | ${useAppleNetworking ? ` 54 | #import 55 | ` : ``} 56 | @implementation ${objectClassName} 57 | 58 | RCT_EXPORT_MODULE() 59 | 60 | RCT_EXPORT_METHOD(sampleMethod:(NSString *)stringArgument numberParameter:(nonnull NSNumber *)numberArgument callback:(RCTResponseSenderBlock)callback) 61 | ` + 62 | // sample method body with tabs for truthy useAppleNetworking *only*: 63 | (useAppleNetworking 64 | ? `{ 65 | AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; 66 | manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:@"text/plain"]; 67 | [manager GET:@"https://httpstat.us/200" parameters:nil progress:nil success:^(NSURLSessionTask *task, id responseObject) { 68 | callback(@[[NSString stringWithFormat: @"numberArgument: %@ stringArgument: %@ resp: %@", numberArgument, stringArgument, responseObject]]); 69 | } failure:^(NSURLSessionTask *operation, NSError *error) { 70 | callback(@[[NSString stringWithFormat: @"numberArgument: %@ stringArgument: %@ err: %@", numberArgument, stringArgument, error]]); 71 | }]; 72 | }` 73 | : `{ 74 | // TODO: Implement some actually useful functionality 75 | callback(@[[NSString stringWithFormat: @"numberArgument: %@ stringArgument: %@", numberArgument, stringArgument]]); 76 | }`) + ` 77 | 78 | @end 79 | `, 80 | }, { 81 | // header for module with view: 82 | name: ({ objectClassName, isView }) => isView && `${platform}/${objectClassName}.h`, 83 | content: ({ objectClassName }) => `// ${objectClassName}.h 84 | 85 | #import 86 | 87 | @interface ${objectClassName} : RCTViewManager 88 | 89 | @end 90 | `, 91 | }, { 92 | // implementation of module with view: 93 | name: ({ objectClassName, isView }) => isView && `${platform}/${objectClassName}.m`, 94 | content: ({ objectClassName }) => `// ${objectClassName}.m 95 | 96 | #import "${objectClassName}.h" 97 | 98 | @implementation ${objectClassName} 99 | 100 | RCT_EXPORT_MODULE() 101 | 102 | - (UIView *)view 103 | { 104 | // TODO: Implement some actually useful functionality 105 | UILabel * label = [[UILabel alloc] init]; 106 | [label setTextColor:[UIColor redColor]]; 107 | [label setText: @"*****"]; 108 | [label sizeToFit]; 109 | UIView * wrapper = [[UIView alloc] init]; 110 | [wrapper addSubview:label]; 111 | return wrapper; 112 | } 113 | 114 | @end 115 | `, 116 | }, { 117 | name: ({ objectClassName }) => `${platform}/${objectClassName}.xcworkspace/contents.xcworkspacedata`, 118 | content: ({ objectClassName }) => ` 119 | 121 | 123 | 124 | 125 | `, 126 | }, { 127 | name: ({ objectClassName }) => `${platform}/${objectClassName}.xcodeproj/project.pbxproj`, 128 | content: ({ objectClassName }) => `// !$*UTF8*$! 129 | { 130 | archiveVersion = 1; 131 | classes = { 132 | }; 133 | objectVersion = 46; 134 | objects = { 135 | 136 | /* Begin PBXCopyFilesBuildPhase section */ 137 | 58B511D91A9E6C8500147676 /* CopyFiles */ = { 138 | isa = PBXCopyFilesBuildPhase; 139 | buildActionMask = 2147483647; 140 | dstPath = "include/$(PRODUCT_NAME)"; 141 | dstSubfolderSpec = 16; 142 | files = ( 143 | ); 144 | runOnlyForDeploymentPostprocessing = 0; 145 | }; 146 | /* End PBXCopyFilesBuildPhase section */ 147 | 148 | /* Begin PBXFileReference section */ 149 | 134814201AA4EA6300B7C361 /* lib${objectClassName}.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = lib${objectClassName}.a; sourceTree = BUILT_PRODUCTS_DIR; }; 150 | /* End PBXFileReference section */ 151 | 152 | /* Begin PBXFrameworksBuildPhase section */ 153 | 58B511D81A9E6C8500147676 /* Frameworks */ = { 154 | isa = PBXFrameworksBuildPhase; 155 | buildActionMask = 2147483647; 156 | files = ( 157 | ); 158 | runOnlyForDeploymentPostprocessing = 0; 159 | }; 160 | /* End PBXFrameworksBuildPhase section */ 161 | 162 | /* Begin PBXGroup section */ 163 | 134814211AA4EA7D00B7C361 /* Products */ = { 164 | isa = PBXGroup; 165 | children = ( 166 | 134814201AA4EA6300B7C361 /* lib${objectClassName}.a */, 167 | ); 168 | name = Products; 169 | sourceTree = ""; 170 | }; 171 | 58B511D21A9E6C8500147676 = { 172 | isa = PBXGroup; 173 | children = ( 174 | 134814211AA4EA7D00B7C361 /* Products */, 175 | ); 176 | sourceTree = ""; 177 | }; 178 | /* End PBXGroup section */ 179 | 180 | /* Begin PBXNativeTarget section */ 181 | 58B511DA1A9E6C8500147676 /* ${objectClassName} */ = { 182 | isa = PBXNativeTarget; 183 | buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "${objectClassName}" */; 184 | buildPhases = ( 185 | 58B511D71A9E6C8500147676 /* Sources */, 186 | 58B511D81A9E6C8500147676 /* Frameworks */, 187 | 58B511D91A9E6C8500147676 /* CopyFiles */, 188 | ); 189 | buildRules = ( 190 | ); 191 | dependencies = ( 192 | ); 193 | name = ${objectClassName}; 194 | productName = RCTDataManager; 195 | productReference = 134814201AA4EA6300B7C361 /* lib${objectClassName}.a */; 196 | productType = "com.apple.product-type.library.static"; 197 | }; 198 | /* End PBXNativeTarget section */ 199 | 200 | /* Begin PBXProject section */ 201 | 58B511D31A9E6C8500147676 /* Project object */ = { 202 | isa = PBXProject; 203 | attributes = { 204 | LastUpgradeCheck = 0920; 205 | ORGANIZATIONNAME = Facebook; 206 | TargetAttributes = { 207 | 58B511DA1A9E6C8500147676 = { 208 | CreatedOnToolsVersion = 6.1.1; 209 | }; 210 | }; 211 | }; 212 | buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "${objectClassName}" */; 213 | compatibilityVersion = "Xcode 3.2"; 214 | developmentRegion = en; 215 | hasScannedForEncodings = 0; 216 | knownRegions = ( 217 | en, 218 | Base, 219 | ); 220 | mainGroup = 58B511D21A9E6C8500147676; 221 | productRefGroup = 58B511D21A9E6C8500147676; 222 | projectDirPath = ""; 223 | projectRoot = ""; 224 | targets = ( 225 | 58B511DA1A9E6C8500147676 /* ${objectClassName} */, 226 | ); 227 | }; 228 | /* End PBXProject section */ 229 | 230 | /* Begin PBXSourcesBuildPhase section */ 231 | 58B511D71A9E6C8500147676 /* Sources */ = { 232 | isa = PBXSourcesBuildPhase; 233 | buildActionMask = 2147483647; 234 | files = ( 235 | ); 236 | runOnlyForDeploymentPostprocessing = 0; 237 | }; 238 | /* End PBXSourcesBuildPhase section */ 239 | 240 | /* Begin XCBuildConfiguration section */ 241 | 58B511ED1A9E6C8500147676 /* Debug */ = { 242 | isa = XCBuildConfiguration; 243 | buildSettings = { 244 | ALWAYS_SEARCH_USER_PATHS = NO; 245 | CLANG_ANALYZER_NONNULL = YES; 246 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 247 | CLANG_CXX_LIBRARY = "libc++"; 248 | CLANG_ENABLE_MODULES = YES; 249 | CLANG_ENABLE_OBJC_ARC = YES; 250 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 251 | CLANG_WARN_BOOL_CONVERSION = YES; 252 | CLANG_WARN_COMMA = YES; 253 | CLANG_WARN_CONSTANT_CONVERSION = YES; 254 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 255 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 256 | CLANG_WARN_EMPTY_BODY = YES; 257 | CLANG_WARN_ENUM_CONVERSION = YES; 258 | CLANG_WARN_INFINITE_RECURSION = YES; 259 | CLANG_WARN_INT_CONVERSION = YES; 260 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 261 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 262 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 263 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 264 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 265 | CLANG_WARN_STRICT_PROTOTYPES = YES; 266 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 267 | CLANG_WARN_UNREACHABLE_CODE = YES; 268 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 269 | COPY_PHASE_STRIP = NO; 270 | ENABLE_STRICT_OBJC_MSGSEND = YES; 271 | ENABLE_TESTABILITY = YES; 272 | GCC_C_LANGUAGE_STANDARD = gnu99; 273 | GCC_DYNAMIC_NO_PIC = NO; 274 | GCC_NO_COMMON_BLOCKS = YES; 275 | GCC_OPTIMIZATION_LEVEL = 0; 276 | GCC_PREPROCESSOR_DEFINITIONS = ( 277 | "DEBUG=1", 278 | "$(inherited)", 279 | ); 280 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 281 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 282 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 283 | GCC_WARN_UNDECLARED_SELECTOR = YES; 284 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 285 | GCC_WARN_UNUSED_FUNCTION = YES; 286 | GCC_WARN_UNUSED_VARIABLE = YES; 287 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 288 | LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)"; 289 | LIBRARY_SEARCH_PATHS = ( 290 | "\\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\\"", 291 | "\\"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)\\"", 292 | "\\"$(inherited)\\"", 293 | ); 294 | MTL_ENABLE_DEBUG_INFO = YES; 295 | ONLY_ACTIVE_ARCH = YES; 296 | SDKROOT = iphoneos; 297 | }; 298 | name = Debug; 299 | }; 300 | 58B511EE1A9E6C8500147676 /* Release */ = { 301 | isa = XCBuildConfiguration; 302 | buildSettings = { 303 | ALWAYS_SEARCH_USER_PATHS = NO; 304 | CLANG_ANALYZER_NONNULL = YES; 305 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 306 | CLANG_CXX_LIBRARY = "libc++"; 307 | CLANG_ENABLE_MODULES = YES; 308 | CLANG_ENABLE_OBJC_ARC = YES; 309 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 310 | CLANG_WARN_BOOL_CONVERSION = YES; 311 | CLANG_WARN_COMMA = YES; 312 | CLANG_WARN_CONSTANT_CONVERSION = YES; 313 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 314 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 315 | CLANG_WARN_EMPTY_BODY = YES; 316 | CLANG_WARN_ENUM_CONVERSION = YES; 317 | CLANG_WARN_INFINITE_RECURSION = YES; 318 | CLANG_WARN_INT_CONVERSION = YES; 319 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 320 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 321 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 322 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 323 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 324 | CLANG_WARN_STRICT_PROTOTYPES = YES; 325 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 326 | CLANG_WARN_UNREACHABLE_CODE = YES; 327 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 328 | COPY_PHASE_STRIP = YES; 329 | ENABLE_NS_ASSERTIONS = NO; 330 | ENABLE_STRICT_OBJC_MSGSEND = YES; 331 | GCC_C_LANGUAGE_STANDARD = gnu99; 332 | GCC_NO_COMMON_BLOCKS = YES; 333 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 334 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 335 | GCC_WARN_UNDECLARED_SELECTOR = YES; 336 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 337 | GCC_WARN_UNUSED_FUNCTION = YES; 338 | GCC_WARN_UNUSED_VARIABLE = YES; 339 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 340 | LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)"; 341 | LIBRARY_SEARCH_PATHS = ( 342 | "\\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\\"", 343 | "\\"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)\\"", 344 | "\\"$(inherited)\\"", 345 | ); 346 | MTL_ENABLE_DEBUG_INFO = NO; 347 | SDKROOT = iphoneos; 348 | VALIDATE_PRODUCT = YES; 349 | }; 350 | name = Release; 351 | }; 352 | 58B511F01A9E6C8500147676 /* Debug */ = { 353 | isa = XCBuildConfiguration; 354 | buildSettings = { 355 | HEADER_SEARCH_PATHS = ( 356 | "$(inherited)", 357 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 358 | "$(SRCROOT)/../../../React/**", 359 | "$(SRCROOT)/../../react-native/React/**", 360 | ); 361 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 362 | OTHER_LDFLAGS = "-ObjC"; 363 | PRODUCT_NAME = ${objectClassName}; 364 | SKIP_INSTALL = YES; 365 | }; 366 | name = Debug; 367 | }; 368 | 58B511F11A9E6C8500147676 /* Release */ = { 369 | isa = XCBuildConfiguration; 370 | buildSettings = { 371 | HEADER_SEARCH_PATHS = ( 372 | "$(inherited)", 373 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 374 | "$(SRCROOT)/../../../React/**", 375 | "$(SRCROOT)/../../react-native/React/**", 376 | ); 377 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 378 | OTHER_LDFLAGS = "-ObjC"; 379 | PRODUCT_NAME = ${objectClassName}; 380 | SKIP_INSTALL = YES; 381 | }; 382 | name = Release; 383 | }; 384 | /* End XCBuildConfiguration section */ 385 | 386 | /* Begin XCConfigurationList section */ 387 | 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "${objectClassName}" */ = { 388 | isa = XCConfigurationList; 389 | buildConfigurations = ( 390 | 58B511ED1A9E6C8500147676 /* Debug */, 391 | 58B511EE1A9E6C8500147676 /* Release */, 392 | ); 393 | defaultConfigurationIsVisible = 0; 394 | defaultConfigurationName = Release; 395 | }; 396 | 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "${objectClassName}" */ = { 397 | isa = XCConfigurationList; 398 | buildConfigurations = ( 399 | 58B511F01A9E6C8500147676 /* Debug */, 400 | 58B511F11A9E6C8500147676 /* Release */, 401 | ); 402 | defaultConfigurationIsVisible = 0; 403 | defaultConfigurationName = Release; 404 | }; 405 | /* End XCConfigurationList section */ 406 | }; 407 | rootObject = 58B511D31A9E6C8500147676 /* Project object */; 408 | } 409 | `, 410 | }]; 411 | -------------------------------------------------------------------------------- /tests/with-injection/create/with-example/for-android-only/__snapshots__/create-with-example-for-android-only.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`create alice-bobbi module with example for Android only 1`] = ` 4 | Array [ 5 | "* execa.commandSync command: yarn --version options: {\\"stdio\\":\\"inherit\\"} 6 | ", 7 | "* ensureDir dir: react-native-alice-bobbi 8 | ", 9 | "* ensureDir dir: react-native-alice-bobbi/ 10 | ", 11 | "* ensureDir dir: react-native-alice-bobbi/ 12 | ", 13 | "* ensureDir dir: react-native-alice-bobbi/ 14 | ", 15 | "* ensureDir dir: react-native-alice-bobbi/ 16 | ", 17 | "* ensureDir dir: react-native-alice-bobbi/android/ 18 | ", 19 | "* ensureDir dir: react-native-alice-bobbi/android/src/main/ 20 | ", 21 | "* ensureDir dir: react-native-alice-bobbi/android/src/main/java/com/reactlibrary/ 22 | ", 23 | "* ensureDir dir: react-native-alice-bobbi/android/src/main/java/com/reactlibrary/ 24 | ", 25 | "* ensureDir dir: react-native-alice-bobbi/android/ 26 | ", 27 | "* outputFile name: react-native-alice-bobbi/README.md 28 | content: 29 | -------- 30 | # react-native-alice-bobbi 31 | 32 | ## Getting started 33 | 34 | \`$ npm install react-native-alice-bobbi --save\` 35 | 36 | ### Mostly automatic installation 37 | 38 | \`$ react-native link react-native-alice-bobbi\` 39 | 40 | ## Usage 41 | \`\`\`javascript 42 | import AliceBobbi from 'react-native-alice-bobbi'; 43 | 44 | // TODO: What to do with the module? 45 | AliceBobbi; 46 | \`\`\` 47 | 48 | <<<<<<<< ======== >>>>>>>> 49 | ", 50 | "* outputFile name: react-native-alice-bobbi/package.json 51 | content: 52 | -------- 53 | { 54 | \\"name\\": \\"react-native-alice-bobbi\\", 55 | \\"title\\": \\"React Native Alice Bobbi\\", 56 | \\"version\\": \\"1.0.0\\", 57 | \\"description\\": \\"TODO\\", 58 | \\"main\\": \\"index.js\\", 59 | \\"files\\": [ 60 | \\"README.md\\", 61 | \\"android\\", 62 | \\"index.js\\" 63 | ], 64 | \\"scripts\\": { 65 | \\"test\\": \\"echo \\\\\\"Error: no test specified\\\\\\" && exit 1\\" 66 | }, 67 | \\"repository\\": { 68 | \\"type\\": \\"git\\", 69 | \\"url\\": \\"git+https://github.com/github_account/react-native-alice-bobbi.git\\", 70 | \\"baseUrl\\": \\"https://github.com/github_account/react-native-alice-bobbi\\" 71 | }, 72 | \\"keywords\\": [ 73 | \\"react-native\\" 74 | ], 75 | \\"author\\": { 76 | \\"name\\": \\"Your Name\\", 77 | \\"email\\": \\"yourname@email.com\\" 78 | }, 79 | \\"license\\": \\"MIT\\", 80 | \\"licenseFilename\\": \\"LICENSE\\", 81 | \\"readmeFilename\\": \\"README.md\\", 82 | \\"peerDependencies\\": { 83 | \\"react\\": \\">=16.8.1\\", 84 | \\"react-native\\": \\">=0.60.0-rc.0 <1.0.x\\" 85 | }, 86 | \\"devDependencies\\": { 87 | \\"react\\": \\"^16.9.0\\", 88 | \\"react-native\\": \\"^0.61.5\\" 89 | } 90 | } 91 | 92 | <<<<<<<< ======== >>>>>>>> 93 | ", 94 | "* outputFile name: react-native-alice-bobbi/index.js 95 | content: 96 | -------- 97 | // main index.js 98 | 99 | import { NativeModules } from 'react-native'; 100 | 101 | const { AliceBobbi } = NativeModules; 102 | 103 | export default AliceBobbi; 104 | 105 | <<<<<<<< ======== >>>>>>>> 106 | ", 107 | "* outputFile name: react-native-alice-bobbi/.gitignore 108 | content: 109 | -------- 110 | # OSX 111 | # 112 | .DS_Store 113 | 114 | # node.js 115 | # 116 | node_modules/ 117 | npm-debug.log 118 | yarn-error.log 119 | 120 | # Android/IntelliJ 121 | # 122 | build/ 123 | .idea 124 | .gradle 125 | local.properties 126 | *.iml 127 | 128 | # BUCK 129 | buck-out/ 130 | \\\\.buckd/ 131 | *.keystore 132 | 133 | <<<<<<<< ======== >>>>>>>> 134 | ", 135 | "* outputFile name: react-native-alice-bobbi/android/build.gradle 136 | content: 137 | -------- 138 | // android/build.gradle 139 | 140 | // based on: 141 | // 142 | // * https://github.com/facebook/react-native/blob/0.60-stable/template/android/build.gradle 143 | // previous location: 144 | // - https://github.com/facebook/react-native/blob/0.58-stable/local-cli/templates/HelloWorld/android/build.gradle 145 | // 146 | // * https://github.com/facebook/react-native/blob/0.60-stable/template/android/app/build.gradle 147 | // previous location: 148 | // - https://github.com/facebook/react-native/blob/0.58-stable/local-cli/templates/HelloWorld/android/app/build.gradle 149 | 150 | // These defaults should reflect the SDK versions used by 151 | // the minimum React Native version supported. 152 | def DEFAULT_COMPILE_SDK_VERSION = 28 153 | def DEFAULT_BUILD_TOOLS_VERSION = '28.0.3' 154 | def DEFAULT_MIN_SDK_VERSION = 16 155 | def DEFAULT_TARGET_SDK_VERSION = 28 156 | 157 | def safeExtGet(prop, fallback) { 158 | rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback 159 | } 160 | 161 | apply plugin: 'com.android.library' 162 | apply plugin: 'maven' 163 | 164 | buildscript { 165 | // The Android Gradle plugin is only required when opening the android folder stand-alone. 166 | // This avoids unnecessary downloads and potential conflicts when the library is included as a 167 | // module dependency in an application project. 168 | // ref: https://docs.gradle.org/current/userguide/tutorial_using_tasks.html#sec:build_script_external_dependencies 169 | if (project == rootProject) { 170 | repositories { 171 | google() 172 | } 173 | dependencies { 174 | // This should reflect the Gradle plugin version used by 175 | // the minimum React Native version supported. 176 | classpath 'com.android.tools.build:gradle:3.4.1' 177 | } 178 | } 179 | } 180 | 181 | android { 182 | compileSdkVersion safeExtGet('compileSdkVersion', DEFAULT_COMPILE_SDK_VERSION) 183 | buildToolsVersion safeExtGet('buildToolsVersion', DEFAULT_BUILD_TOOLS_VERSION) 184 | defaultConfig { 185 | minSdkVersion safeExtGet('minSdkVersion', DEFAULT_MIN_SDK_VERSION) 186 | targetSdkVersion safeExtGet('targetSdkVersion', DEFAULT_TARGET_SDK_VERSION) 187 | versionCode 1 188 | versionName \\"1.0\\" 189 | } 190 | lintOptions { 191 | abortOnError false 192 | } 193 | } 194 | 195 | repositories { 196 | // ref: https://www.baeldung.com/maven-local-repository 197 | mavenLocal() 198 | maven { 199 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 200 | url \\"$rootDir/../node_modules/react-native/android\\" 201 | } 202 | maven { 203 | // Android JSC is installed from npm 204 | url \\"$rootDir/../node_modules/jsc-android/dist\\" 205 | } 206 | google() 207 | } 208 | 209 | dependencies { 210 | //noinspection GradleDynamicVersion 211 | implementation 'com.facebook.react:react-native:+' // From node_modules 212 | } 213 | 214 | def configureReactNativePom(def pom) { 215 | def packageJson = new groovy.json.JsonSlurper().parseText(file('../package.json').text) 216 | 217 | pom.project { 218 | name packageJson.title 219 | artifactId packageJson.name 220 | version = packageJson.version 221 | group = \\"com.reactlibrary\\" 222 | description packageJson.description 223 | url packageJson.repository.baseUrl 224 | 225 | licenses { 226 | license { 227 | name packageJson.license 228 | url packageJson.repository.baseUrl + '/blob/master/' + packageJson.licenseFilename 229 | distribution 'repo' 230 | } 231 | } 232 | 233 | developers { 234 | developer { 235 | id packageJson.author.username 236 | name packageJson.author.name 237 | } 238 | } 239 | } 240 | } 241 | 242 | afterEvaluate { project -> 243 | // some Gradle build hooks ref: 244 | // https://www.oreilly.com/library/view/gradle-beyond-the/9781449373801/ch03.html 245 | task androidJavadoc(type: Javadoc) { 246 | source = android.sourceSets.main.java.srcDirs 247 | classpath += files(android.bootClasspath) 248 | classpath += files(project.getConfigurations().getByName('compile').asList()) 249 | include '**/*.java' 250 | } 251 | 252 | task androidJavadocJar(type: Jar, dependsOn: androidJavadoc) { 253 | classifier = 'javadoc' 254 | from androidJavadoc.destinationDir 255 | } 256 | 257 | task androidSourcesJar(type: Jar) { 258 | classifier = 'sources' 259 | from android.sourceSets.main.java.srcDirs 260 | include '**/*.java' 261 | } 262 | 263 | android.libraryVariants.all { variant -> 264 | def name = variant.name.capitalize() 265 | def javaCompileTask = variant.javaCompileProvider.get() 266 | 267 | task \\"jar\${name}\\"(type: Jar, dependsOn: javaCompileTask) { 268 | from javaCompileTask.destinationDir 269 | } 270 | } 271 | 272 | artifacts { 273 | archives androidSourcesJar 274 | archives androidJavadocJar 275 | } 276 | 277 | task installArchives(type: Upload) { 278 | configuration = configurations.archives 279 | repositories.mavenDeployer { 280 | // Deploy to react-native-event-bridge/maven, ready to publish to npm 281 | repository url: \\"file://\${projectDir}/../android/maven\\" 282 | configureReactNativePom pom 283 | } 284 | } 285 | } 286 | 287 | <<<<<<<< ======== >>>>>>>> 288 | ", 289 | "* outputFile name: react-native-alice-bobbi/android/src/main/AndroidManifest.xml 290 | content: 291 | -------- 292 | 293 | 294 | 296 | 297 | 298 | 299 | <<<<<<<< ======== >>>>>>>> 300 | ", 301 | "* outputFile name: react-native-alice-bobbi/android/src/main/java/com/reactlibrary/AliceBobbiModule.java 302 | content: 303 | -------- 304 | // AliceBobbiModule.java 305 | 306 | package com.reactlibrary; 307 | 308 | import com.facebook.react.bridge.ReactApplicationContext; 309 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 310 | import com.facebook.react.bridge.ReactMethod; 311 | import com.facebook.react.bridge.Callback; 312 | 313 | public class AliceBobbiModule extends ReactContextBaseJavaModule { 314 | 315 | private final ReactApplicationContext reactContext; 316 | 317 | public AliceBobbiModule(ReactApplicationContext reactContext) { 318 | super(reactContext); 319 | this.reactContext = reactContext; 320 | } 321 | 322 | @Override 323 | public String getName() { 324 | return \\"AliceBobbi\\"; 325 | } 326 | 327 | @ReactMethod 328 | public void sampleMethod(String stringArgument, int numberArgument, Callback callback) { 329 | // TODO: Implement some actually useful functionality 330 | callback.invoke(\\"Received numberArgument: \\" + numberArgument + \\" stringArgument: \\" + stringArgument); 331 | } 332 | } 333 | 334 | <<<<<<<< ======== >>>>>>>> 335 | ", 336 | "* outputFile name: react-native-alice-bobbi/android/src/main/java/com/reactlibrary/AliceBobbiPackage.java 337 | content: 338 | -------- 339 | // AliceBobbiPackage.java 340 | 341 | package com.reactlibrary; 342 | 343 | import java.util.Arrays; 344 | import java.util.Collections; 345 | import java.util.List; 346 | 347 | import com.facebook.react.ReactPackage; 348 | import com.facebook.react.bridge.NativeModule; 349 | import com.facebook.react.bridge.ReactApplicationContext; 350 | import com.facebook.react.uimanager.ViewManager; 351 | 352 | public class AliceBobbiPackage implements ReactPackage { 353 | @Override 354 | public List createNativeModules(ReactApplicationContext reactContext) { 355 | return Arrays.asList(new AliceBobbiModule(reactContext)); 356 | } 357 | 358 | @Override 359 | public List createViewManagers(ReactApplicationContext reactContext) { 360 | return Collections.emptyList(); 361 | } 362 | } 363 | 364 | <<<<<<<< ======== >>>>>>>> 365 | ", 366 | "* outputFile name: react-native-alice-bobbi/android/README.md 367 | content: 368 | -------- 369 | README 370 | ====== 371 | 372 | If you want to publish the lib as a maven dependency, follow these steps before publishing a new version to npm: 373 | 374 | 1. Be sure to have the Android [SDK](https://developer.android.com/studio/index.html) and [NDK](https://developer.android.com/ndk/guides/index.html) installed 375 | 2. Be sure to have a \`local.properties\` file in this folder that points to the Android SDK and NDK 376 | \`\`\` 377 | ndk.dir=/Users/{username}/Library/Android/sdk/ndk-bundle 378 | sdk.dir=/Users/{username}/Library/Android/sdk 379 | \`\`\` 380 | 3. Delete the \`maven\` folder 381 | 4. Run \`./gradlew installArchives\` 382 | 5. Verify that latest set of generated files is in the maven folder with the correct version number 383 | 384 | <<<<<<<< ======== >>>>>>>> 385 | ", 386 | Object { 387 | "call": "reactNativeInit", 388 | "nameArray": Array [ 389 | "example", 390 | ], 391 | "options": Object { 392 | "directory": "react-native-alice-bobbi/example", 393 | "template": "react-native@latest", 394 | }, 395 | }, 396 | "* ensureDir dir: .../react-native-alice-bobbi/example/ 397 | ", 398 | "* ensureDir dir: .../react-native-alice-bobbi/example/ 399 | ", 400 | "* outputFile name: .../react-native-alice-bobbi/example/metro.config.js 401 | content: 402 | -------- 403 | // metro.config.js 404 | // 405 | // with multiple workarounds for this issue with symlinks: 406 | // https://github.com/facebook/metro/issues/1 407 | // 408 | // with thanks to @johnryan () 409 | // for the pointers to multiple workaround solutions here: 410 | // https://github.com/facebook/metro/issues/1#issuecomment-541642857 411 | // 412 | // see also this discussion: 413 | // https://github.com/brodybits/create-react-native-module/issues/232 414 | 415 | const path = require('path') 416 | 417 | module.exports = { 418 | // workaround for an issue with symlinks encountered starting with 419 | // metro@0.55 / React Native 0.61 420 | // (not needed with React Native 0.60 / metro@0.54) 421 | resolver: { 422 | extraNodeModules: new Proxy( 423 | {}, 424 | { get: (_, name) => path.resolve('.', 'node_modules', name) } 425 | ) 426 | }, 427 | 428 | // quick workaround for another issue with symlinks 429 | watchFolders: [path.resolve('.'), path.resolve('..')] 430 | } 431 | 432 | <<<<<<<< ======== >>>>>>>> 433 | ", 434 | "* outputFile name: .../react-native-alice-bobbi/example/App.js 435 | content: 436 | -------- 437 | /** 438 | * Sample React Native App 439 | * 440 | * adapted from App.js generated by the following command: 441 | * 442 | * react-native init example 443 | * 444 | * https://github.com/facebook/react-native 445 | */ 446 | 447 | import React, { Component } from 'react'; 448 | import { Platform, StyleSheet, Text, View } from 'react-native'; 449 | import AliceBobbi from 'react-native-alice-bobbi'; 450 | 451 | export default class App extends Component<{}> { 452 | state = { 453 | status: 'starting', 454 | message: '--' 455 | }; 456 | componentDidMount() { 457 | AliceBobbi.sampleMethod('Testing', 123, (message) => { 458 | this.setState({ 459 | status: 'native callback received', 460 | message 461 | }); 462 | }); 463 | } 464 | render() { 465 | return ( 466 | 467 | ☆AliceBobbi example☆ 468 | STATUS: {this.state.status} 469 | ☆NATIVE CALLBACK MESSAGE☆ 470 | {this.state.message} 471 | 472 | ); 473 | } 474 | } 475 | 476 | const styles = StyleSheet.create({ 477 | container: { 478 | flex: 1, 479 | justifyContent: 'center', 480 | alignItems: 'center', 481 | backgroundColor: '#F5FCFF', 482 | }, 483 | welcome: { 484 | fontSize: 20, 485 | textAlign: 'center', 486 | margin: 10, 487 | }, 488 | instructions: { 489 | textAlign: 'center', 490 | color: '#333333', 491 | marginBottom: 5, 492 | }, 493 | }); 494 | 495 | <<<<<<<< ======== >>>>>>>> 496 | ", 497 | "* execa.commandSync command: yarn add link:../ options: {\\"cwd\\":\\".../react-native-alice-bobbi/example\\",\\"stdio\\":\\"inherit\\"} 498 | ", 499 | ] 500 | `; 501 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # create-react-native-module 2 | 3 | Tool to create a React Native library module or native view component, with a single command. 4 | 5 | [![GitHub license](https://img.shields.io/github/license/brodybits/create-react-native-module.svg?color=blue&style=for-the-badge)](./LICENSE) 6 | [![npm](https://img.shields.io/npm/v/create-react-native-module.svg?color=green&style=for-the-badge)](https://www.npmjs.com/package/create-react-native-module) 7 | [![Mutation testing badge](https://img.shields.io/endpoint?style=for-the-badge&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fbrodybits%2Fcreate-react-native-module%2Fmaster)](https://dashboard.stryker-mutator.io/reports/github.com/brodybits/create-react-native-module/master) 8 | [![npm downloads](https://img.shields.io/npm/dw/create-react-native-module.svg?label=npm%20downloads&style=for-the-badge)](https://npmcharts.com/compare/create-react-native-module?minimal=true) 9 | [![total npm downloads](https://img.shields.io/npm/dt/create-react-native-module.svg?label=total%20npm%20downloads&style=for-the-badge)](https://npmcharts.com/compare/create-react-native-module?minimal=true) 10 | [![GitHub watchers](https://img.shields.io/github/watchers/brodybits/create-react-native-module.svg?style=for-the-badge)](https://github.com/brodybits/create-react-native-module/watchers) 11 | [![GitHub stars](https://img.shields.io/github/stars/brodybits/create-react-native-module.svg?label=GitHub%20stars&style=for-the-badge)](https://github.com/brodybits/create-react-native-module/stargazers) 12 | [![GitHub forks](https://img.shields.io/github/forks/brodybits/create-react-native-module.svg?style=for-the-badge)](https://github.com/brodybits/create-react-native-module/network/members) 13 | [![open bugs](https://img.shields.io/github/issues-raw/brodybits/create-react-native-module/bug.svg?color=d73a4a&label=open%20bugs&style=for-the-badge)](https://github.com/brodybits/create-react-native-module/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aopen+label%3Abug) 14 | [![total open issues](https://img.shields.io/github/issues-raw/brodybits/create-react-native-module.svg?label=total%20open%20issues&style=for-the-badge)](https://github.com/brodybits/create-react-native-module/issues) 15 | [![GitHub pull requests](https://img.shields.io/github/issues-pr-raw/brodybits/create-react-native-module.svg?style=for-the-badge)](https://github.com/brodybits/create-react-native-module/pulls) 16 | 17 | See below for command-line usage, example with no view, and example with an extremely simple native view. 18 | 19 | This tool based on [`react-native-create-library`](https://www.npmjs.com/package/react-native-create-library), with working example callbacks, optional native view, and more updates added by [@brodybits (Christoper J. Brody aka Chris Brody)](https://github.com/brodybits) and other [contributors](https://github.com/brodybits/create-react-native-module/graphs/contributors). 20 | 21 | ### Support options 22 | 23 | - community support via [issues](https://github.com/brodybits/create-react-native-module/issues) 24 | - commercial support is available, please contact if interested: 25 | 26 | 27 | 28 | ### General status 29 | 30 | - Minimum React Native version: `0.60` (outdated), `0.64` or upcoming `0.65` (recommended) - see issue [`#423`](https://github.com/brodybits/create-react-native-module/issues/423) 31 | - It is recommended to disable or remove Flipper on iOS in case of debug build issues - see issue [`#422`](https://github.com/brodybits/create-react-native-module/issues/422) 32 | - generated example app with symlink by default, has known issue with adding dependencies to the library root - see issue [`#308`](https://github.com/brodybits/create-react-native-module/issues/308) 33 | - Platform fork support 34 | - tvOS platform fork 35 | - requires use of `--tvos-enabled` option as documented below 36 | - requires the [`react-native-tvos`](https://www.npmjs.com/package/react-native-tvos) fork, with minimum version of `0.60.x` (newer version is *highly recommended*) ref: 37 | - [`react-native-tvos/react-native-tvos#11`](https://github.com/react-native-tvos/react-native-tvos/issues/11) 38 | - issue [`#95`](https://github.com/brodybits/create-react-native-module/issues/95) 39 | - It is recommended to disable or remove Flipper in case of debug build issues - see issue [`#422`](https://github.com/brodybits/create-react-native-module/issues/422) 40 | - Out-of-tree target platforms 41 | - Windows target platform - not supported directly, please use `react-native-windows-init` according to this procedure: https://aka.ms/RNW-NativeModuleSetup 42 | - for future consideration: macOS (see issue [`#94`](https://github.com/brodybits/create-react-native-module/issues/94)) 43 | 44 | ### Why might you need this? 45 | 46 | If you are looking to create a native module for React Native, you need some native code for each platform you want to support and then some JavaScript code to bind it all together. Setting this up by yourself can be time-consuming. 47 | 48 | This is where this tool comes in. It creates a boilerplate with all current best practices in mind. 49 | Why not use `react-native new-library`? Unfortunately that command doesn't create an up-to-date library, requires an already initialized React Native project and only sets up the iOS side of things. 50 | 51 | ### Alternatives 52 | 53 | - [`otobank/create-react-native-module`](https://github.com/otobank/create-react-native-module) - fork with TypeScript, Swift, and Kotlin support, with some other updates and some less useful features removed; see also issue [`#425`](https://github.com/brodybits/create-react-native-module/issues/425) 54 | - [`brodybits/react-native-module-init`](https://github.com/brodybits/react-native-module-init) - new interactive CLI that uses the templates from this utiity 55 | - [`react-native-community/bob`](https://github.com/react-native-community/bob) - opinionated, interactive library CLI that is designed to support both native libraries and libraries with web support 56 | 57 | __Outdated alternatives:__ see [acknowledgements](#acknowledgements) below 58 | 59 | ## Installation 60 | 61 | Package required to be installed globally if the recommended example app is generated: 62 | 63 | - [`yarn`](https://www.npmjs.com/package/yarn) 64 | 65 | ```console 66 | $ npm install -g yarn 67 | ``` 68 | 69 | To install this package: 70 | 71 | ```console 72 | $ npm install -g create-react-native-module 73 | ``` 74 | 75 | ## Command-line usage 76 | 77 | Navigate into an empty directory to execute the command. 78 | 79 | ```console 80 | $ create-react-native-module MyFancyLibrary 81 | ``` 82 | 83 | This will create the folder `MyFancyLibrary` in which the library will be created in. 84 | 85 | Now install dependencies by running this command in the newly created library. 86 | 87 | ```console 88 | $ npm install 89 | ``` 90 | 91 | ```console 92 | Usage: create-react-native-module [options] 93 | 94 | Options: 95 | 96 | -V, --version output the version number 97 | --package-name The full package name to be used in package.json. Default: react-native-(name in param-case) 98 | --is-view Generate the package as a very simple native view component. Status: EXPERIMENTAL, with limited testing. 99 | --object-class-name The name of the object class to be exported by both JavaScript and native code. Default: (name in PascalCase) 100 | --native-package-id [Android] The native Java package identifier used for Android (Default: `com.reactlibrary`) 101 | --platforms Platforms the library module will be created for - comma separated (Default: `ios,android`) 102 | --tvos-enabled Generate the module with tvOS build enabled (requires react-native-tvos fork, with minimum version of 0.60, and iOS platform to be enabled) 103 | --github-account The github account where the library module is hosted (Default: `github_account`) 104 | --author-name The author's name (Default: `Your Name`) 105 | --author-email The author's email (Default: `yourname@email.com`) 106 | --license The license type (Default: `MIT`) 107 | --use-apple-networking [iOS] EXPERIMENTAL FEATURE NOT SUPPORTED: Use `AFNetworking` dependency as a sample in the podspec & use it from the iOS code - see issue #426 108 | --generate-example Generate an example project and add the library module to it with symlink by defult, with overwrite of example metro.config.js to add workaround for Metro symlink issue - requires Yarn to be installed globally 109 | --example-file-linkage DEPRECATED: do `yarn add file:../` instead of `yarn add link:../` in a generated example project, and add a postinstall workaround script, with no overwrite of example metro.config.js 110 | --example-name Name for the example project (default: `example`) 111 | --example-react-native-template <...> The React Native template used for the generated example project, for example: react-native-tvos or react-native-tvos@0.62.2-1 (requires --tvos-enabled option); react-native@0.62 (default: `react-native@latest`) 112 | --write-example-podfile [iOS] EXPERIMENTAL FEATURE NOT SUPPORTED: write (or overwrite) example ios/Podfile 113 | -h, --help output usage information 114 | ``` 115 | 116 | ## Programmatic usage 117 | 118 | ```javascript 119 | const createLibraryModule = require('create-react-native-module'); 120 | 121 | createLibraryModule({ 122 | name: 'MyFancyLibraryModule' 123 | }).then(() => { 124 | console.log('Oh yay! My library module has been created!'); 125 | }) 126 | ``` 127 | 128 | #### Options 129 | 130 | ```javascript 131 | { 132 | name: String, /* The name of the library (mandatory) */ 133 | packageName: String, /* The full package name to be used in package.json. Default: react-native-(name in param-case) */ 134 | isView: Boolean, /* Generate the package as a very simple native view component. Status: EXPERIMENTAL, with limited testing. (Default: false) */ 135 | objectClassName: String, /* The name of the object class to be exported by both JavaScript and native code. Default: (name in PascalCase) */ 136 | platforms: Array | String, /* Platforms the library will be created for. (Default: ['android', 'ios']) */ 137 | nativePackageId: String, /* [Android] The native Java package identifier used for Android (Default: `com.reactlibrary`) */ 138 | tvosEnabled: Boolean, /* Generate the module with tvOS build enabled (requires react-native-tvos fork, with minimum version of 0.60, and iOS platform to be enabled) */ 139 | githubAccount: String, /* The github account where the library is hosted (Default: `github_account`) */ 140 | authorName: String, /* The author's name (Default: `Your Name`) */ 141 | authorEmail: String, /* The author's email (Default: `yourname@email.com`) */ 142 | license: String, /* The license type of this library (Default: `MIT`) */ 143 | useAppleNetworking: Boolean, /* [iOS] EXPERIMENTAL FEATURE NOT SUPPORTED: Use `AFNetworking` dependency as a sample in the podspec & use it from the iOS code - see issue #426 (Default: false) */ 144 | generateExample: Boolean, /* Generate an example project and add the library module to it with symlink by defult, with overwrite of example metro.config.js to add workaround for Metro symlink issue - requires Yarn to be installed globally (Default: false) */ 145 | exampleFileLinkage: Boolean, /* DEPRECATED: do `yarn add file:../` instead of `yarn add link:../` in a generated example project, and add a postinstall workaround script, with no overwrite of example metro.config.js (Default: false) */ 146 | exampleName: String, /* Name for the example project (Default: `example`) */ 147 | exampleReactNativeTemplate: String, /* The React Native template used for the generated example project, for example: react-native-tvos or react-native-tvos@0.62.2-1 (requires --tvos-enabled option); react-native@0.62 (Default: `react-native@latest`) */ 148 | writeExamplePodfile: Boolean, /* [iOS] EXPERIMENTAL FEATURE NOT SUPPORTED: write (or overwrite) example ios/Podfile (Default: false) */ 149 | } 150 | ``` 151 | 152 | ## Examples 153 | 154 | ### Example module with no view 155 | 156 | __Create the module with no view:__ 157 | 158 | ```console 159 | create-react-native-module --package-identifier io.mylibrary --generate-example AliceHelper 160 | ``` 161 | 162 | The module would be generated in the `react-native-alice-helper` subdirectory, and the example test app would be in `react-native-alice-helper/example`. 163 | 164 | Then go into the example app subdirectory: 165 | 166 | ```console 167 | cd react-native-alice-helper/example 168 | ``` 169 | 170 | #### Running the example app 171 | 172 | **Recommended:** Follow the instructions shown in the end of the console log output, which are more likely to be up-to-date. 173 | 174 | __Extra notes:__ 175 | 176 | _Within the example test app subdirectory:_ 177 | 178 | It is *recommended* to start the Metro Bundler manually (within `react-native-alice-helper/example`), which would run in the foreground: 179 | 180 | ```console 181 | yarn start 182 | ``` 183 | 184 | Otherwise, React Native will open its own window to run the Metro Bundler. 185 | 186 | To run on Android, do the following command (within `react-native-alice-helper/example`): 187 | 188 | ```console 189 | yarn android 190 | ``` 191 | 192 | for React Native `0.60`: `npx react-native run-android` 193 | 194 | This assumes that the `ANDROID_HOME` environmental variable is set properly. Here is a sample command that does not make such an assumption on a mac: 195 | 196 | ```console 197 | ANDROID_HOME=~/Library/Android/sdk npx react-native run-android 198 | ``` 199 | 200 | For iOS: 201 | 202 | Extra installation step needed _in case of clean checkout only_: 203 | 204 | ```console 205 | cd ios && pod install && cd .. 206 | ``` 207 | 208 | Then to run on iOS: 209 | 210 | ```console 211 | yarn ios 212 | ``` 213 | 214 | for React Native `0.60`: `npx react-native run-ios` 215 | 216 | or do the following command to open the iOS project in Xcode: 217 | 218 | ```console 219 | open ios/example.xcodeproj 220 | ``` 221 | 222 | #### Expected result 223 | 224 | The example app shows the following indications: 225 | 226 | - STATUS: native callback received 227 | - NATIVE CALLBACK MESSAGE with the number argument and string argument values that are received by the native module 228 | 229 | ### Example view module 230 | 231 | EXPERIMENTAL feature with limited testing 232 | 233 | __Create the module with an extremely simple view:__ 234 | 235 | ```console 236 | create-react-native-module --package-identifier io.mylibrary --is-view --generate-example CarolWidget 237 | ``` 238 | 239 | The module would be generated in the `react-native-carol-widget` subdirectory, and the example test app would be in `react-native-carol-widget/example`. 240 | 241 | Then go into the example app subdirectory: 242 | 243 | ```console 244 | cd react-native-carol-widget/example 245 | ``` 246 | 247 | #### Running the view example app 248 | 249 | **Recommended:** Follow the instructions shown in the end of the console log output, which are more likely to be up-to-date. 250 | 251 | __Some extra notes:__ 252 | 253 | _Within the example test app subdirectory:_ 254 | 255 | It is *recommended* to start the Metro Bundler manually as described above (within `react-native-carol-widget/example`): 256 | 257 | ```console 258 | yarn start 259 | ``` 260 | 261 | To run on Android: do `yarn android` or `npx react-native run-android` as described for the other example above. 262 | 263 | To run on iOS (as described above): 264 | 265 | - _in case of clean checkout **only**_: do `pod install` in `ios` subdirectory 266 | - do `yarn ios`, `npx react-native run-ios`, or `open ios/example.xcodeproj` 267 | 268 | __Expected result:__ 269 | 270 | - on Android: a check box that is checked (and cannot be changed) 271 | - on iOS: a label with 5 red asterisks 272 | 273 | ## Acknowledgements 274 | 275 | - [`react-native-create-library`](https://www.npmjs.com/package/react-native-create-library) - original basis of this project 276 | - [`react-native-share`](https://www.npmjs.com/package/react-native-share) - was acknowledged as "a great source of inspiration" for [`react-native-create-library`](https://www.npmjs.com/package/react-native-create-library) 277 | 278 | ## License 279 | 280 | [MIT](./LICENSE) 281 | --------------------------------------------------------------------------------