expo-module-scripts 5.1.0-canary-20251120-e46b3cc → 5.1.0-canary-20251205-756eb7a

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/.eslintrc.js +1 -1
  2. package/CHANGELOG.md +7 -0
  3. package/babel.config.base.js +1 -1
  4. package/babel.config.cli.js +6 -6
  5. package/babel.config.plugin.js +1 -1
  6. package/babel.config.scripts.js +1 -1
  7. package/babel.config.utils.js +1 -1
  8. package/bin/expo-module-babel +3 -5
  9. package/bin/expo-module-build +24 -28
  10. package/bin/expo-module-clean +30 -13
  11. package/bin/expo-module-configure +83 -62
  12. package/bin/expo-module-eslint +3 -5
  13. package/bin/expo-module-jest +3 -5
  14. package/bin/expo-module-lint +16 -27
  15. package/bin/expo-module-prepare +30 -17
  16. package/bin/expo-module-prepublishOnly +4 -6
  17. package/bin/expo-module-readme +16 -13
  18. package/bin/expo-module-test +28 -27
  19. package/bin/expo-module-tsc +4 -4
  20. package/bin/expo-module-typecheck +8 -8
  21. package/bin/expo-module.js +2 -3
  22. package/{createJestPreset.js → createJestPreset.cjs} +2 -2
  23. package/ios/{jest-preset.js → jest-preset.cjs} +1 -1
  24. package/jest-preset-scripts.cjs +2 -0
  25. package/{jest-preset.js → jest-preset.cjs} +1 -1
  26. package/package.json +51 -5
  27. package/universal/{jest-preset.js → jest-preset.cjs} +1 -1
  28. package/utils/commandUtils.js +55 -0
  29. package/utils/fileUtils.js +11 -0
  30. package/bin/npx +0 -13
  31. package/jest-preset-scripts.js +0 -2
  32. /package/{eslint.config.base.js → eslint.config.base.cjs} +0 -0
  33. /package/{eslintrc.base.js → eslintrc.base.cjs} +0 -0
  34. /package/{jest-preset-cli.js → jest-preset-cli.cjs} +0 -0
  35. /package/{jest-preset-plugin.js → jest-preset-plugin.cjs} +0 -0
  36. /package/{jest-preset-utils.js → jest-preset-utils.cjs} +0 -0
  37. /package/{jest-setup-react-19.js → jest-setup-react-19.cjs} +0 -0
package/.eslintrc.js CHANGED
@@ -1,4 +1,4 @@
1
- module.exports = {
1
+ export default {
2
2
  root: true,
3
3
  extends: ['universe/node'],
4
4
  };
package/CHANGELOG.md CHANGED
@@ -7,6 +7,7 @@
7
7
  ### 🎉 New features
8
8
 
9
9
  - Ensure `loader()` functions are stripped from client bundles ([#40670](https://github.com/expo/expo/pull/40670) by [@hassankhan](https://github.com/hassankhan))
10
+ - Rewrite using Node.js to add Windows support. ([#36296](https://github.com/expo/expo/pull/36296) by [@Simek](https://github.com/Simek) and [@kudo](https://github.com/kudo))
10
11
 
11
12
  ### 🐛 Bug fixes
12
13
 
@@ -14,6 +15,12 @@
14
15
 
15
16
  - Add more tests related files to the `.npmignore` template. ([#39551](https://github.com/expo/expo/pull/39551) by [@Simek](https://github.com/Simek))
16
17
 
18
+ ## 5.0.8 - 2025-12-04
19
+
20
+ ### 💡 Others
21
+
22
+ - Update to `glob@^13.0.0` ([#41079](https://github.com/expo/expo/pull/41079) by [@kitten](https://github.com/kitten))
23
+
17
24
  ## 5.0.7 — 2025-09-10
18
25
 
19
26
  _This version does not introduce any user-facing changes._
@@ -1,4 +1,4 @@
1
- module.exports = function (api) {
1
+ export default function (api) {
2
2
  api.cache(true);
3
3
  return {
4
4
  presets: ['babel-preset-expo'],
@@ -1,9 +1,9 @@
1
- module.exports = function (api) {
1
+ export default function (api) {
2
2
  api.cache(true);
3
3
  return {
4
4
  presets: [
5
5
  [
6
- require('@babel/preset-env'),
6
+ '@babel/preset-env',
7
7
  {
8
8
  modules: false, // Disable the default `modules-commonjs`, to enable lazy evaluation
9
9
  targets: {
@@ -11,13 +11,13 @@ module.exports = function (api) {
11
11
  },
12
12
  },
13
13
  ],
14
- require('@babel/preset-typescript'),
14
+ '@babel/preset-typescript',
15
15
  ],
16
16
  plugins: [
17
- require('babel-plugin-dynamic-import-node'),
18
- require('@babel/plugin-transform-export-namespace-from'),
17
+ 'babel-plugin-dynamic-import-node',
18
+ '@babel/plugin-transform-export-namespace-from',
19
19
  [
20
- require('@babel/plugin-transform-modules-commonjs'),
20
+ '@babel/plugin-transform-modules-commonjs',
21
21
  {
22
22
  lazy: () => true,
23
23
  },
@@ -1,4 +1,4 @@
1
- module.exports = function (api) {
1
+ export default function (api) {
2
2
  api.cache(true);
3
3
  return {
4
4
  plugins: ['@babel/plugin-transform-flow-strip-types'],
@@ -1 +1 @@
1
- module.exports = require('./babel.config.cli');
1
+ export * from './babel.config.cli';
@@ -1,4 +1,4 @@
1
- module.exports = function (api) {
1
+ export default function (api) {
2
2
  api.cache(true);
3
3
  return {
4
4
  plugins: ['@babel/plugin-transform-flow-strip-types'],
@@ -1,7 +1,5 @@
1
- #!/usr/bin/env bash
1
+ #!/usr/bin/env node
2
2
 
3
- set -eo pipefail
3
+ import { getArgs, packageManagerExecAsync } from '../utils/commandUtils.js';
4
4
 
5
- script_dir="$(dirname "$0")"
6
-
7
- "$script_dir/npx" babel "$@"
5
+ await packageManagerExecAsync(['babel', ...getArgs()]);
@@ -1,34 +1,30 @@
1
- #!/usr/bin/env bash
1
+ #!/usr/bin/env node
2
2
 
3
- set -eo pipefail
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
4
6
 
5
- script_dir="$(dirname "$0")"
7
+ import { commandRunner, getArgs, addWatchFlagIfNeeded } from '../utils/commandUtils.js';
6
8
 
7
- args=("$@")
9
+ const dirname = path.dirname(fileURLToPath(import.meta.url));
8
10
 
9
- # If the command is used like `yarn build plugin`, set the --build option to point to
10
- # plugin/tsconfig.json
11
- extra_module_build_types=("plugin" "cli" "utils" "scripts")
12
- for i in "${extra_module_build_types[@]}"
13
- do
14
- if [ "$1" == "$i" ]; then
15
- # Check if tsconfig.json exists in the directory
16
- if [ -f "$(pwd)/$i/tsconfig.json" ]; then
17
- # `--build` must be the first argument, so reset the array
18
- args=()
19
- args+=("--build")
20
- args+=("$(pwd)/$i")
21
- # Push the rest of the arguments minus the `plugin` arg
22
- args+=("${@:2}")
23
- else
24
- echo "tsconfig.json not found in $@, skipping build for $@/"
25
- exit
26
- fi
27
- fi
28
- done
11
+ const EXTRA_MODULE_BUILD_TARGETS = ['plugin', 'cli', 'utils', 'scripts'];
29
12
 
30
- if [[ -t 1 && (-z "$CI" && -z "$EXPO_NONINTERACTIVE") ]]; then
31
- args+=("--watch")
32
- fi
13
+ let args = getArgs();
33
14
 
34
- "$script_dir/expo-module-tsc" "${args[@]}"
15
+ // If the command is used like `yarn build plugin`, set the --build option to point to plugin/tsconfig.json
16
+ if (EXTRA_MODULE_BUILD_TARGETS.includes(args[0])) {
17
+ const target = args[0];
18
+ const targetDir = path.join(process.cwd(), args[0]);
19
+ if (fs.existsSync(path.join(targetDir, 'tsconfig.json'))) {
20
+ // Push the rest of the arguments minus the `plugin` arg
21
+ const restArgs = args.slice(1);
22
+ args = ['--build', targetDir, ...restArgs];
23
+ } else {
24
+ console.log(`tsconfig.json not found in ${target}, skipping build for ${target}`);
25
+ process.exit(0);
26
+ }
27
+ }
28
+
29
+ args = addWatchFlagIfNeeded(args);
30
+ await commandRunner(path.join(dirname, 'expo-module-tsc'), args);
@@ -1,16 +1,33 @@
1
- #!/usr/bin/env bash
1
+ #!/usr/bin/env node
2
2
 
3
- set -eo pipefail
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
4
5
 
5
- if [[ ! -f package.json ]]; then
6
- echo "The current working directory is not a package's root directory"
7
- exit 1
8
- fi
6
+ import { directoryExistsAsync } from '../utils/fileUtils.js';
9
7
 
10
- directory=$1
11
- # Support `yarn clean plugin` to delete ./plugin/build/
12
- if [[ -n $directory ]]; then
13
- rm -rf "$directory/build"
14
- else
15
- rm -rf build
16
- fi
8
+ async function runAsync() {
9
+ if (!fs.existsSync(path.join(process.cwd(), 'package.json'))) {
10
+ console.error("The current working directory is not a package's root directory");
11
+ process.exit(1);
12
+ }
13
+
14
+ // Support `yarn clean plugin` to delete ./plugin/build/
15
+ const directory = process.argv[2];
16
+ if (directory && (await directoryExistsAsync(path.join(process.cwd(), directory)))) {
17
+ await fs.promises.rm(path.join(process.cwd(), directory, 'build'), {
18
+ recursive: true,
19
+ force: true,
20
+ });
21
+ } else {
22
+ await fs.promises.rm(path.join(process.cwd(), 'build'), { recursive: true, force: true });
23
+ }
24
+ }
25
+
26
+ (async () => {
27
+ try {
28
+ await runAsync();
29
+ } catch (error) {
30
+ console.error(error);
31
+ process.exit(error.status || error.code || 1);
32
+ }
33
+ })();
@@ -1,77 +1,98 @@
1
- #!/usr/bin/env bash
1
+ #!/usr/bin/env node
2
2
 
3
- set -eo pipefail
3
+ import { glob } from 'glob';
4
+ import crypto from 'node:crypto';
5
+ import fs from 'node:fs';
6
+ import path from 'node:path';
7
+ import { fileURLToPath } from 'node:url';
4
8
 
5
- script_dir="$(dirname "$0")"
9
+ import { commandRunner } from '../utils/commandUtils.js';
10
+ import { toPosixPath } from '../utils/fileUtils.js';
6
11
 
7
- shopt -s dotglob
12
+ const dirname = path.dirname(fileURLToPath(import.meta.url));
8
13
 
9
- # an optional template file will be synced only if the target file is present in the file system,
10
- # and its content contains the string `@generated`.
11
- OPTIONAL_TEMPLATE_FILES=(
12
- # add a relative file path from `templates/` for each new optional file
13
- "eslint.config.js"
14
- "scripts/with-node.sh"
15
- )
14
+ // an optional template file will be synced only if the target file is present in the file system,
15
+ // and its content contains the string `@generated`.
16
+ const OPTIONAL_TEMPLATE_FILES = [
17
+ // add a relative file path from `templates/` for each new optional file
18
+ 'eslint.config.js',
19
+ path.join('scripts', 'with-node.sh'),
20
+ ];
16
21
 
17
- # returns relative file paths inside a given directory without the leading "./".
18
- # usage: get_relative_files "/path/to/dir"
19
- get_relative_files() {
20
- pushd "$1" > /dev/null
21
- local files=$(find . -type f | cut -c 3-)
22
- popd > /dev/null
23
- echo "$files"
24
- }
22
+ async function runAsync() {
23
+ await commandRunner(path.join(dirname, 'expo-module-readme'));
25
24
 
25
+ const templatesDir = path.join(dirname, '..', 'templates');
26
+ const templateDirents = await glob('**/*', {
27
+ cwd: templatesDir,
28
+ withFileTypes: true,
29
+ });
30
+ const templateFiles = templateDirents
31
+ .filter((dirent) => dirent.isFile())
32
+ .map((dirent) => path.relative(templatesDir, path.join(dirent.parentPath, dirent.name)));
26
33
 
27
- # syncs the source file if the target file is missing or the existing file contains `@generated`.
28
- # usage: sync_file_if_missing "/path/source_path" "/path/target_path"
29
- sync_file_if_missing() {
30
- local source=$1
31
- local target=$2
32
- # echo "sync_file_if_missing $source -> $target"
33
- if [ ! -f "$target" ] || grep --quiet "@generated" "$target"; then
34
- rsync --checksum "$source" "$target"
35
- fi
34
+ await Promise.all(
35
+ templateFiles.map(async (templateFile) => {
36
+ if (OPTIONAL_TEMPLATE_FILES.includes(toPosixPath(templateFile))) {
37
+ await syncFileIfPresentAsync(
38
+ path.join(templatesDir, templateFile),
39
+ path.join(process.cwd(), templateFile)
40
+ );
41
+ } else {
42
+ await syncFileIfMissingAsync(
43
+ path.join(templatesDir, templateFile),
44
+ path.join(process.cwd(), templateFile)
45
+ );
46
+ }
47
+ })
48
+ );
36
49
  }
37
50
 
38
- # syncs the source file if the target file is present in the file system and its content contains `@generated`.
39
- # usage: sync_file_if_present "/path/source_path" "/path/target_path"
40
- sync_file_if_present() {
41
- local source=$1
42
- local target=$2
43
- # echo "sync_file_if_present $source -> $target"
44
- if [ -f "$target" ] && grep --quiet "@generated" "$target"; then
45
- rsync --checksum "$source" "$target"
46
- fi
47
- }
51
+ (async () => {
52
+ try {
53
+ await runAsync();
54
+ } catch (error) {
55
+ console.error(error);
56
+ process.exit(error.status || error.code || 1);
57
+ }
58
+ })();
48
59
 
49
- # check if a file is listed in `OPTIONAL_TEMPLATE_FILES`.
50
- # usage: if is_optional_file "path/to/file"; then ... fi
51
- is_optional_file() {
52
- local file=$1
53
- for optional_file in "${OPTIONAL_TEMPLATE_FILES[@]}"; do
54
- if [ "$file" = "$optional_file" ]; then
55
- true
56
- return
57
- fi
58
- done
60
+ /**
61
+ * syncs the source file if the target file is missing or the existing file contains `@generated`.
62
+ */
63
+ async function syncFileIfMissingAsync(sourcePath, targetPath) {
64
+ const shouldSync = !fs.existsSync(targetPath) || (await hasGeneratedMarkerAsync(targetPath));
65
+ if (shouldSync) {
66
+ await syncFileAsync(sourcePath, targetPath);
67
+ }
68
+ }
59
69
 
60
- false
61
- return
70
+ /**
71
+ * syncs the source file if the target file is present in the file system and its content contains `@generated`.
72
+ */
73
+ async function syncFileIfPresentAsync(sourcePath, targetPath) {
74
+ const shouldSync = fs.existsSync(targetPath) && (await hasGeneratedMarkerAsync(targetPath));
75
+ if (shouldSync) {
76
+ await syncFileAsync(sourcePath, targetPath);
77
+ }
62
78
  }
63
79
 
64
- #
65
- # script main starts from here
66
- #
80
+ async function syncFileAsync(sourcePath, targetPath) {
81
+ // NOTE(kudo): the performance is definitely not as good as rsync, not sure if we should just copy the file anyway.
82
+ const sourceData = await fs.promises.readFile(sourcePath);
83
+ const sourceChecksum = crypto.createHash('md5').update(sourceData).digest('hex');
84
+ let targetChecksum = '';
85
+ if (fs.existsSync(targetPath)) {
86
+ const targetData = await fs.promises.readFile(targetPath);
87
+ targetChecksum = crypto.createHash('md5').update(targetData).digest('hex');
88
+ }
67
89
 
68
- "$script_dir/expo-module-readme"
90
+ if (sourceChecksum !== targetChecksum) {
91
+ await fs.promises.cp(sourcePath, targetPath, { recursive: true, force: true });
92
+ }
93
+ }
69
94
 
70
- template_files=$(get_relative_files "$script_dir/../templates")
71
- for template_relative_file in $template_files; do
72
- if is_optional_file "$template_relative_file"; then
73
- sync_file_if_present "$script_dir/../templates/$template_relative_file" "$template_relative_file"
74
- else
75
- sync_file_if_missing "$script_dir/../templates/$template_relative_file" "$template_relative_file"
76
- fi
77
- done
95
+ async function hasGeneratedMarkerAsync(filePath) {
96
+ const data = await fs.promises.readFile(filePath, 'utf8');
97
+ return data.includes('@generated');
98
+ }
@@ -1,7 +1,5 @@
1
- #!/usr/bin/env bash
1
+ #!/usr/bin/env node
2
2
 
3
- set -eo pipefail
3
+ import { getArgs, packageManagerExecAsync } from '../utils/commandUtils.js';
4
4
 
5
- script_dir="$(dirname "$0")"
6
-
7
- "$script_dir/npx" eslint "$@"
5
+ await packageManagerExecAsync(['eslint', ...getArgs()]);
@@ -1,7 +1,5 @@
1
- #!/usr/bin/env bash
1
+ #!/usr/bin/env node
2
2
 
3
- set -eo pipefail
3
+ import { getArgs, packageManagerExecAsync } from '../utils/commandUtils.js';
4
4
 
5
- script_dir="$(dirname "$0")"
6
-
7
- "$script_dir/npx" jest "$@"
5
+ await packageManagerExecAsync(['jest', ...getArgs()]);
@@ -1,33 +1,22 @@
1
- #!/usr/bin/env bash
1
+ #!/usr/bin/env node
2
2
 
3
- set -eo pipefail
3
+ import path from 'node:path';
4
+ import { commandRunner, getArgs } from '../utils/commandUtils.js';
5
+ import { fileURLToPath } from 'node:url';
4
6
 
5
- script_dir="$(dirname "$0")"
7
+ const dirname = path.dirname(fileURLToPath(import.meta.url));
6
8
 
7
- args=()
9
+ const EXTRA_MODULE_BUILD_TARGETS = ['plugin', 'cli', 'utils', 'scripts'];
8
10
 
9
- # If the command is used like `yarn lint plugin` then set the target to `plugin/src`
10
- extra_module_build_types=("plugin" "cli" "utils" "scripts")
11
- found_target=""
12
- for i in "${extra_module_build_types[@]}"
13
- do
14
- if [ "$1" == "$i" ]; then
15
- found_target=$i
16
- fi
17
- done
11
+ let args = getArgs();
18
12
 
19
- if [[ -n "$found_target" ]]; then
20
- # Push the rest of the arguments minus the `plugin` arg
21
- args+=("${@:2}")
22
- args+=("$found_target/src")
23
- if ! [[ -d "$found_target" ]]; then
24
- # Good DX cuz Expo
25
- printf "\n\033[1;33mThe \`$found_target/src\` folder does not exist in this project; please create it and try again.\033[0m\n\n"
26
- exit 0
27
- fi
28
- else
29
- args+=("$@")
30
- args+=("src")
31
- fi
13
+ // If the command is used like `yarn lint plugin` then set the target to `plugin/src`
14
+ if (EXTRA_MODULE_BUILD_TARGETS.includes(args[0])) {
15
+ // Push the rest of the arguments minus the `plugin` arg
16
+ const target = args[0];
17
+ args = [...args.slice(1), `${target}/src`];
18
+ } else {
19
+ args = [...args, 'src'];
20
+ }
32
21
 
33
- "$script_dir/expo-module-eslint" "${args[@]}"
22
+ await commandRunner(path.join(dirname, 'expo-module-eslint'), args);
@@ -1,22 +1,35 @@
1
- #!/usr/bin/env bash
1
+ #!/usr/bin/env node
2
2
 
3
- set -eo pipefail
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
4
5
 
5
- script_dir="$(dirname "$0")"
6
+ import { commandRunner } from '../utils/commandUtils.js';
7
+ import { directoryExistsAsync } from '../utils/fileUtils.js';
6
8
 
7
- export EXPO_NONINTERACTIVE=1
9
+ const dirname = path.dirname(fileURLToPath(import.meta.url));
8
10
 
9
- echo "Configuring module"
10
- "$script_dir/expo-module-clean"
11
- "$script_dir/expo-module-configure"
12
- "$script_dir/expo-module-build"
11
+ process.env.EXPO_NONINTERACTIVE = 1;
13
12
 
14
- extra_module_build_types=("plugin" "cli" "utils" "scripts")
15
- for i in "${extra_module_build_types[@]}"
16
- do
17
- if [[ -d "$i" ]]; then
18
- echo "Configuring $i"
19
- "$script_dir/expo-module-clean" "$i"
20
- "$script_dir/expo-module-build" "$i"
21
- fi
22
- done
13
+ async function runAsync() {
14
+ console.log('Configuring module');
15
+ await commandRunner(path.join(dirname, 'expo-module-clean'));
16
+ await commandRunner(path.join(dirname, 'expo-module-configure'));
17
+ await commandRunner(path.join(dirname, 'expo-module-build'));
18
+
19
+ for (const target of ['plugin', 'cli', 'utils', 'scripts']) {
20
+ if (await directoryExistsAsync(path.join(process.cwd(), target))) {
21
+ console.log(`Configuring ${target}`);
22
+ await commandRunner(path.join(dirname, 'expo-module-clean'), [target]);
23
+ await commandRunner(path.join(dirname, 'expo-module-build'), [target]);
24
+ }
25
+ }
26
+ }
27
+
28
+ (async () => {
29
+ try {
30
+ await runAsync();
31
+ } catch (error) {
32
+ console.error(error);
33
+ process.exit(error.status || error.code || 1);
34
+ }
35
+ })();
@@ -1,9 +1,7 @@
1
- #!/usr/bin/env bash
1
+ #!/usr/bin/env node
2
2
 
3
- set -eo pipefail
3
+ import { packageManagerExecAsync } from '../utils/commandUtils.js';
4
4
 
5
- script_dir="$(dirname "$0")"
5
+ process.env.EXPO_NONINTERACTIVE = 1;
6
6
 
7
- export EXPO_NONINTERACTIVE=1
8
-
9
- "$script_dir/npx" proofread
7
+ await packageManagerExecAsync(['proofread']);
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env node
2
- 'use strict';
3
2
 
4
- const fs = require('fs');
5
- const { globSync } = require('glob');
6
- const path = require('path');
3
+ import { globSync } from 'glob';
4
+ import fs from 'node:fs';
5
+ import path from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
7
+
8
+ const dirname = path.dirname(fileURLToPath(import.meta.url));
7
9
 
8
10
  function replaceAll(current, replacement, content) {
9
11
  const regexp = new RegExp(escapeRegExp('${' + current + '}'), 'g');
@@ -14,7 +16,7 @@ function escapeRegExp(string) {
14
16
  return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
15
17
  }
16
18
 
17
- function removeUnapplicableSections(name, content) {
19
+ function removeInapplicableSections(name, content) {
18
20
  const opener = `<!--- remove for ${name} --->`;
19
21
  const closer = `<!--- end remove for ${name} --->`;
20
22
  let nextContent = content;
@@ -40,7 +42,7 @@ function removeOptionals(content) {
40
42
  const DEFAULT_HOMEPAGE = 'https://docs.expo.dev/versions/latest/';
41
43
 
42
44
  function generateREADME() {
43
- const template = path.join(__dirname, '..', 'templates', 'README.md');
45
+ const template = path.join(dirname, '..', 'templates', 'README.md');
44
46
  let readme = fs.readFileSync(template, 'utf8');
45
47
  const pkg = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf8'));
46
48
  const packageName = pkg.name;
@@ -59,9 +61,10 @@ function generateREADME() {
59
61
 
60
62
  if (isIOS) {
61
63
  const podspecs = globSync('{ios/**/,}*.podspec');
62
- if (!podspecs[0]) throw new Error(
63
- `The Expo module named "${packageName}" contains an "ios" directory but does not define a Podspec, which is required for modules with iOS implementations.`
64
- );
64
+ if (!podspecs[0])
65
+ throw new Error(
66
+ `The Expo module named "${packageName}" contains an "ios" directory but does not define a Podspec, which is required for modules with iOS implementations.`
67
+ );
65
68
  const podspecPath = podspecs[0];
66
69
  const podName = (() => {
67
70
  const parts = podspecPath.split('/');
@@ -70,11 +73,11 @@ function generateREADME() {
70
73
 
71
74
  readme = replaceAll('podName', podName, readme);
72
75
  } else {
73
- readme = removeUnapplicableSections('no-ios', readme);
76
+ readme = removeInapplicableSections('no-ios', readme);
74
77
  }
75
78
 
76
79
  if (isInterface) {
77
- readme = removeUnapplicableSections('interfaces', readme);
80
+ readme = removeInapplicableSections('interfaces', readme);
78
81
  } else {
79
82
  let docName;
80
83
  if (homepage === DEFAULT_HOMEPAGE || homepage.startsWith('https://github.com')) {
@@ -124,13 +127,13 @@ function generateREADME() {
124
127
 
125
128
  readme = replaceAll('androidPackageName', androidPackageName, readme);
126
129
  } else {
127
- readme = removeUnapplicableSections('no-package', readme);
130
+ readme = removeInapplicableSections('no-package', readme);
128
131
  }
129
132
  }
130
133
 
131
134
  readme = replaceAll('androidPackagePath', androidPackagePath, readme);
132
135
  } else {
133
- readme = removeUnapplicableSections('no-android', readme);
136
+ readme = removeInapplicableSections('no-android', readme);
134
137
  }
135
138
  }
136
139
  return removeOptionals(readme);
@@ -1,35 +1,36 @@
1
- #!/usr/bin/env bash
1
+ #!/usr/bin/env node
2
2
 
3
- set -eo pipefail
3
+ import fs from 'node:fs';
4
+ import { createRequire } from 'node:module';
5
+ import path from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
4
7
 
5
- script_dir="$(dirname "$0")"
8
+ import { commandRunner, getArgs, addWatchFlagIfNeeded } from '../utils/commandUtils.js';
6
9
 
7
- args=("$@")
10
+ const dirname = path.dirname(fileURLToPath(import.meta.url));
11
+ const require = createRequire(import.meta.url);
8
12
 
9
- # If the command is used like `yarn test plugin`, set the --rootDir option to the `plugin` directory
10
- extra_module_build_types=("plugin" "cli" "utils" "scripts")
11
- for i in "${extra_module_build_types[@]}"
12
- do
13
- if [ "$1" == "$i" ]; then
14
- args=()
15
- args+=("--rootDir")
16
- args+=("$i")
13
+ const EXTRA_MODULE_BUILD_TARGETS = ['plugin', 'cli', 'utils', 'scripts'];
17
14
 
18
- if [[ -f "$i/jest.config.js" ]]; then
19
- args+=("--config")
20
- args+=("$i/jest.config.js")
21
- else
22
- args+=("--config")
23
- args+=("$(node --print "require.resolve('expo-module-scripts/jest-preset-$i.js')")")
24
- fi
15
+ let args = getArgs();
25
16
 
26
- # Push the rest of the arguments minus the `plugin` arg
27
- args+=("${@:2}")
28
- fi
29
- done
17
+ // If the command is used like `yarn test plugin`, set the --rootDir option to the `plugin` directory
18
+ if (EXTRA_MODULE_BUILD_TARGETS.includes(args[0])) {
19
+ const target = args[0];
20
+ const targetDir = path.join(process.cwd(), target);
21
+ const restArgs = args.slice(1);
22
+ args = ['--rootDir', target];
30
23
 
31
- if [[ -t 1 && (-z "$CI" && -z "$EXPO_NONINTERACTIVE") ]]; then
32
- args+=("--watch")
33
- fi
24
+ if (fs.existsSync(path.join(targetDir, 'jest.config.js'))) {
25
+ args.push('--config', `${target}/jest.config.js`);
26
+ } else {
27
+ const jestConfigPath = require.resolve(`expo-module-scripts/jest-preset-${target}.js`);
28
+ args.push('--config', jestConfigPath);
29
+ }
30
+ // Push the rest of the arguments minus the `plugin` arg
31
+ args.push(...restArgs);
32
+ }
34
33
 
35
- "$script_dir/expo-module-jest" "${args[@]}"
34
+ args = addWatchFlagIfNeeded(args);
35
+
36
+ await commandRunner(path.join(dirname, 'expo-module-jest'), args);
@@ -1,7 +1,7 @@
1
- #!/usr/bin/env bash
1
+ #!/usr/bin/env node
2
2
 
3
- set -eo pipefail
3
+ import { getArgs, packageManagerExecAsync } from '../utils/commandUtils.js';
4
4
 
5
- script_dir="$(dirname "$0")"
5
+ const args = getArgs();
6
6
 
7
- "$script_dir/npx" tsc "$@"
7
+ await packageManagerExecAsync(['tsc', ...args]);
@@ -1,12 +1,12 @@
1
- #!/usr/bin/env bash
1
+ #!/usr/bin/env node
2
2
 
3
- set -eo pipefail
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
4
5
 
5
- script_dir="$(dirname "$0")"
6
+ import { commandRunner, getArgs } from '../utils/commandUtils.js';
6
7
 
7
- args=("$@")
8
- if [[ -t 1 && (-z "$CI" && -z "$EXPO_NONINTERACTIVE") ]]; then
9
- args+=("--watch")
10
- fi
8
+ const dirname = path.dirname(fileURLToPath(import.meta.url));
11
9
 
12
- "$script_dir/expo-module-tsc" --noEmit "${args[@]}"
10
+ const args = getArgs({ maybeAddWatchFlag: true });
11
+
12
+ await commandRunner(path.join(dirname, 'expo-module-tsc'), ['--noEmit', ...args]);
@@ -1,9 +1,8 @@
1
1
  #!/usr/bin/env node
2
- 'use strict';
3
2
 
4
- const { program } = require('commander');
5
- const process = require('process');
3
+ import { program } from 'commander';
6
4
 
5
+ // Common scripts
7
6
  program.command('configure', `Generate common configuration files`);
8
7
  program.command('readme', `Generate README`);
9
8
  program.command(
@@ -4,7 +4,7 @@ const fs = require('fs');
4
4
  const path = require('path');
5
5
  const { resolveWorkspaceRoot } = require('resolve-workspace-root');
6
6
 
7
- module.exports = function (basePreset) {
7
+ module.exports = function createJestPreset(basePreset) {
8
8
  // Explicitly catch and log errors since Jest sometimes suppresses error messages
9
9
  try {
10
10
  return _createJestPreset(basePreset);
@@ -73,7 +73,7 @@ function _createJestPreset(basePreset) {
73
73
  ...basePreset.transform,
74
74
  },
75
75
  // Add the React 19 workaround
76
- setupFiles: [...basePreset.setupFiles, require.resolve('./jest-setup-react-19.js')],
76
+ setupFiles: [...basePreset.setupFiles, require.resolve('./jest-setup-react-19.cjs')],
77
77
  };
78
78
  }
79
79
 
@@ -1,4 +1,4 @@
1
- const createJestPreset = require('../createJestPreset');
1
+ const createJestPreset = require('../createJestPreset.cjs');
2
2
 
3
3
  console.warn(
4
4
  'The Jest preset "expo-module-scripts/ios" is deprecated, please convert your tests to universal tests and use "expo-module-scripts" instead'
@@ -0,0 +1,2 @@
1
+ /** @type {import('jest').Config} */
2
+ module.exports = require('./jest-preset-cli.cjs');
@@ -1,6 +1,6 @@
1
1
  const { withWatchPlugins } = require('jest-expo/config');
2
2
 
3
- const createJestPreset = require('./createJestPreset');
3
+ const createJestPreset = require('./createJestPreset.cjs');
4
4
 
5
5
  module.exports = withWatchPlugins({
6
6
  projects: [
package/package.json CHANGED
@@ -1,11 +1,56 @@
1
1
  {
2
2
  "name": "expo-module-scripts",
3
- "version": "5.1.0-canary-20251120-e46b3cc",
3
+ "version": "5.1.0-canary-20251205-756eb7a",
4
4
  "description": "A private package for various tasks for Expo module packages like compiling and testing",
5
5
  "main": "index.js",
6
+ "type": "module",
6
7
  "bin": {
7
8
  "expo-module": "bin/expo-module.js"
8
9
  },
10
+ "exports": {
11
+ "./package.json": "./package.json",
12
+ "./babel.config.*": {
13
+ "require": "./babel.config.*.js"
14
+ },
15
+ "./babel.config.*.js": {
16
+ "require": "./babel.config.*.js"
17
+ },
18
+ "./eslintrc.base.js": {
19
+ "require": "./eslintrc.base.cjs"
20
+ },
21
+ "./eslint.config.base": {
22
+ "require": "./eslint.config.base.cjs"
23
+ },
24
+ "./jest-preset-*": {
25
+ "import": "./jest-preset-*.cjs",
26
+ "require": "./jest-preset-*.cjs"
27
+ },
28
+ "./jest-preset-*.js": {
29
+ "import": "./jest-preset-*.cjs",
30
+ "require": "./jest-preset-*.cjs"
31
+ },
32
+ "./jest-preset": {
33
+ "import": "./jest-preset.cjs",
34
+ "require": "./jest-preset.cjs"
35
+ },
36
+ "./jest-preset.js": {
37
+ "import": "./jest-preset.cjs",
38
+ "require": "./jest-preset.cjs"
39
+ },
40
+ "./*/jest-preset": {
41
+ "import": "./*/jest-preset.cjs",
42
+ "require": "./*/jest-preset.cjs"
43
+ },
44
+ "./*/jest-preset.js": {
45
+ "import": "./*/jest-preset.cjs",
46
+ "require": "./*/jest-preset.cjs"
47
+ },
48
+ "./tsconfig.*": "./tsconfig.*.json",
49
+ "./tsconfig": "./tsconfig.json",
50
+ "./types/*.d.ts": {
51
+ "types": "./types/*.d.ts"
52
+ }
53
+ },
9
54
  "scripts": {
10
55
  "lint": "expo-module eslint ."
11
56
  },
@@ -31,15 +76,16 @@
31
76
  "@babel/preset-env": "^7.23.8",
32
77
  "@babel/preset-typescript": "^7.23.3",
33
78
  "@expo/npm-proofread": "^1.0.1",
79
+ "@expo/spawn-async": "^1.7.2",
34
80
  "@testing-library/react-native": "^13.2.0",
35
81
  "@tsconfig/node18": "^18.2.2",
36
82
  "@types/jest": "^29.2.1",
37
83
  "babel-plugin-dynamic-import-node": "^2.3.3",
38
- "babel-preset-expo": "54.1.0-canary-20251120-e46b3cc",
84
+ "babel-preset-expo": "54.1.0-canary-20251205-756eb7a",
39
85
  "commander": "^12.1.0",
40
- "eslint-config-universe": "15.0.4-canary-20251120-e46b3cc",
41
- "glob": "^10.4.2",
42
- "jest-expo": "55.0.0-canary-20251120-e46b3cc",
86
+ "eslint-config-universe": "15.0.4-canary-20251205-756eb7a",
87
+ "glob": "^13.0.0",
88
+ "jest-expo": "55.0.0-canary-20251205-756eb7a",
43
89
  "jest-snapshot-prettier": "npm:prettier@^2",
44
90
  "jest-watch-typeahead": "2.2.1",
45
91
  "resolve-workspace-root": "^2.0.0",
@@ -1,6 +1,6 @@
1
1
  const { withWatchPlugins } = require('jest-expo/config');
2
2
 
3
- const createJestPreset = require('../createJestPreset');
3
+ const createJestPreset = require('../createJestPreset.cjs');
4
4
 
5
5
  console.warn(
6
6
  'The Jest preset "expo-module-scripts/universal" is deprecated; please use the alias "expo-module-scripts" instead'
@@ -0,0 +1,55 @@
1
+ import spawnAsync from '@expo/spawn-async';
2
+
3
+ export async function commandRunner(command, params = [], { cwd } = {}) {
4
+ return await spawnAsync(command, params, {
5
+ stdio: 'inherit',
6
+ cwd: cwd ?? process.cwd(),
7
+ }).catch((error) => {
8
+ console.error(`Command failed: ${command} ${params.join(' ')}`);
9
+ if (error.message) {
10
+ console.error(error.message);
11
+ }
12
+ process.exit(error.status || error.code || 1);
13
+ });
14
+ }
15
+
16
+ export async function packageManagerExecAsync(params, { cwd } = {}) {
17
+ let command = '';
18
+ const args = [];
19
+
20
+ const npmConfigUserAgent = process.env.npm_config_user_agent;
21
+ if (npmConfigUserAgent?.includes('yarn')) {
22
+ command = 'yarn';
23
+ args.push(...params);
24
+ } else if (npmConfigUserAgent?.includes('pnpm')) {
25
+ command = 'pnpm';
26
+ args.push('exec', ...params);
27
+ } else if (npmConfigUserAgent?.includes('bun')) {
28
+ command = 'bunx';
29
+ args.push(...params);
30
+ } else {
31
+ command = 'npx';
32
+ args.push(...params);
33
+ }
34
+
35
+ return commandRunner(command, args, { cwd });
36
+ }
37
+
38
+ export function getArgs({ maybeAddWatchFlag = false } = {}) {
39
+ let args = process.argv.slice(2);
40
+ if (maybeAddWatchFlag) {
41
+ args = addWatchFlagIfNeeded(args);
42
+ }
43
+ return args;
44
+ }
45
+
46
+ function shouldAddWatchFlag() {
47
+ return process.stdout.isTTY && !process.env.CI && !process.env.EXPO_NONINTERACTIVE;
48
+ }
49
+
50
+ export function addWatchFlagIfNeeded(args) {
51
+ if (shouldAddWatchFlag() && !args.includes('--watch')) {
52
+ args.push('--watch');
53
+ }
54
+ return args;
55
+ }
@@ -0,0 +1,11 @@
1
+ import fs from 'node:fs';
2
+
3
+ const REGEXP_REPLACE_SLASHES = /\\/g;
4
+
5
+ export async function directoryExistsAsync(file) {
6
+ return (await fs.promises.stat(file).catch(() => null))?.isDirectory() ?? false;
7
+ }
8
+
9
+ export function toPosixPath(filePath) {
10
+ return filePath.replace(REGEXP_REPLACE_SLASHES, '/');
11
+ }
package/bin/npx DELETED
@@ -1,13 +0,0 @@
1
- #!/usr/bin/env bash
2
-
3
- # Runs `npx` through Yarn or pnpm if necessary so that the environment variables are set up similarly across
4
- # Yarn, pnpm and npm.
5
-
6
- # shellcheck disable=SC2154
7
- if [[ "$npm_config_user_agent" =~ yarn ]] && [ -x "$(command -v yarn)" ]; then
8
- yarn exec -- npx "$@"
9
- elif [[ "$npm_config_user_agent" =~ pnpm ]] && [ -x "$(command -v pnpm)" ]; then
10
- pnpm exec -- npx "$@"
11
- else
12
- npx "$@"
13
- fi
@@ -1,2 +0,0 @@
1
- /** @type {import('jest').Config} */
2
- module.exports = require('./jest-preset-cli');
File without changes
File without changes
File without changes