@oxyhq/app-preset 0.1.0
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.
- package/README.md +107 -0
- package/app.plugin.js +1 -0
- package/babel/index.js +40 -0
- package/css/base.css +32 -0
- package/eslint/index.js +23 -0
- package/metro/index.js +148 -0
- package/package.json +105 -0
- package/plugin/withOxyAppPreset.js +74 -0
- package/plugin/withOxyBuildProperties.js +88 -0
- package/plugin/withOxyKeychain.js +35 -0
- package/plugin/withSharedUserId.js +36 -0
- package/tsconfig/backend.json +11 -0
- package/tsconfig/base.json +31 -0
- package/tsconfig/frontend.json +11 -0
package/README.md
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# @oxyhq/app-preset
|
|
2
|
+
|
|
3
|
+
The **Oxy distro of Expo** — the shared configuration every Oxy app (Mention,
|
|
4
|
+
Homiio, Allo, accounts, Commons, …) used to copy-paste, centralized into one
|
|
5
|
+
zero-build package. Apps replace four config-plugin entries, a hand-tuned Metro
|
|
6
|
+
config, a Babel config, an ESLint config, a Tailwind CSS header, and three
|
|
7
|
+
tsconfigs with a single dependency, and pick up ecosystem changes with a version
|
|
8
|
+
bump instead of re-editing every repo.
|
|
9
|
+
|
|
10
|
+
There is no build step: this package ships plain CommonJS config plus static CSS
|
|
11
|
+
and JSON.
|
|
12
|
+
|
|
13
|
+
## What it centralizes
|
|
14
|
+
|
|
15
|
+
| Piece | Import | Replaces |
|
|
16
|
+
| --- | --- | --- |
|
|
17
|
+
| Config plugin | `['@oxyhq/app-preset', {}]` | `withSharedUserId` + iOS keychain entitlement + `expo-build-properties` + `@oxyhq/services/plugins/withSharedIdentityReader` |
|
|
18
|
+
| Metro | `@oxyhq/app-preset/metro` | monorepo watch folders, block list, symlink + package-exports resolution, web-font/wasm asset exts, release minifier, NativeWind wrapper |
|
|
19
|
+
| Babel | `@oxyhq/app-preset/babel` | `babel-preset-expo` + `module-resolver` + `react-native-worklets/plugin` |
|
|
20
|
+
| ESLint | `@oxyhq/app-preset/eslint` | `eslint-config-expo/flat` + `dist/*` ignore |
|
|
21
|
+
| CSS base | `@oxyhq/app-preset/base.css` | Tailwind v4 + NativeWind + Bloom design-token imports + SDK `@source` globs |
|
|
22
|
+
| tsconfig | `@oxyhq/app-preset/tsconfig/{base,frontend,backend}.json` | the shared strict/composite TypeScript bases |
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
### 1. Config plugin (`app.config.js` / `app.json`)
|
|
27
|
+
|
|
28
|
+
```js
|
|
29
|
+
plugins: [
|
|
30
|
+
// …app-specific plugins…
|
|
31
|
+
['@oxyhq/app-preset', {}],
|
|
32
|
+
]
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
This adds `android:sharedUserId="so.oxy.shared"`, the iOS
|
|
36
|
+
`keychain-access-groups` entitlement (`$(AppIdentifierPrefix)group.so.oxy.shared`),
|
|
37
|
+
the Oxy `expo-build-properties` defaults (iOS `deploymentTarget 16.4`; Android
|
|
38
|
+
`compileSdk 36` / `targetSdk 35` / ProGuard + resource shrinking), and the
|
|
39
|
+
`@oxyhq/services` shared-identity reader plugin (Android signature permission +
|
|
40
|
+
`<queries>` for silent "Sign in with Oxy").
|
|
41
|
+
|
|
42
|
+
Each piece is individually disableable and overridable:
|
|
43
|
+
|
|
44
|
+
```js
|
|
45
|
+
['@oxyhq/app-preset', {
|
|
46
|
+
sharedUserId: 'so.oxy.shared', // false → skip android:sharedUserId
|
|
47
|
+
keychainGroup: 'group.so.oxy.shared', // false → skip iOS keychain entitlement
|
|
48
|
+
ios: { deploymentTarget: '17.0' }, // deep-merges over defaults; false → skip iOS build props
|
|
49
|
+
android: { targetSdkVersion: 34 }, // deep-merges over defaults; false → skip Android build props
|
|
50
|
+
sharedIdentityReader: true, // false → skip @oxyhq/services reader plugin
|
|
51
|
+
}]
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 2. Metro (`metro.config.js`)
|
|
55
|
+
|
|
56
|
+
```js
|
|
57
|
+
const { createOxyMetroConfig } = require('@oxyhq/app-preset/metro');
|
|
58
|
+
|
|
59
|
+
module.exports = createOxyMetroConfig(__dirname, {
|
|
60
|
+
sharedTypesPackage: '@myapp/shared-types', // optional
|
|
61
|
+
cssInput: './global.css', // optional, this is the default
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 3. Babel (`babel.config.js`)
|
|
66
|
+
|
|
67
|
+
```js
|
|
68
|
+
module.exports = require('@oxyhq/app-preset/babel');
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 4. ESLint (`eslint.config.js`)
|
|
72
|
+
|
|
73
|
+
```js
|
|
74
|
+
const oxyConfig = require('@oxyhq/app-preset/eslint');
|
|
75
|
+
module.exports = [...oxyConfig];
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### 5. CSS (`global.css`)
|
|
79
|
+
|
|
80
|
+
```css
|
|
81
|
+
@import "@oxyhq/app-preset/base.css";
|
|
82
|
+
|
|
83
|
+
/* App-specific globs (later rules win on overlap): */
|
|
84
|
+
@source "./app/**/*.{js,jsx,ts,tsx}";
|
|
85
|
+
@source "./components/**/*.{js,jsx,ts,tsx}";
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 6. TypeScript (`tsconfig.json`)
|
|
89
|
+
|
|
90
|
+
```jsonc
|
|
91
|
+
// frontend
|
|
92
|
+
{ "extends": "@oxyhq/app-preset/tsconfig/frontend.json", "compilerOptions": { "paths": { "@/*": ["./*"] } }, "include": ["**/*.ts", "**/*.tsx"] }
|
|
93
|
+
// backend
|
|
94
|
+
{ "extends": "@oxyhq/app-preset/tsconfig/backend.json", "compilerOptions": { "rootDir": "./", "outDir": "dist" }, "include": ["**/*.ts"] }
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Peer dependencies
|
|
98
|
+
|
|
99
|
+
All peers are **optional** except `expo` — a piece's peer is only needed when you
|
|
100
|
+
use that piece (e.g. `expo-build-properties` only when build properties are
|
|
101
|
+
enabled, `eslint-config-expo` only for the ESLint config). The config plugin and
|
|
102
|
+
factories throw a clear, actionable error if a required peer is missing.
|
|
103
|
+
|
|
104
|
+
## Compatibility
|
|
105
|
+
|
|
106
|
+
Requires **Expo SDK 56+**; validated against **Expo SDK 57 / React Native 0.86**
|
|
107
|
+
(the current Oxy ecosystem target) via `test-app-expo`.
|
package/app.plugin.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('./plugin/withOxyAppPreset');
|
package/babel/index.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @oxyhq/app-preset — Babel config factory.
|
|
3
|
+
*
|
|
4
|
+
* The standard Babel config every Oxy Expo app shares: babel-preset-expo with
|
|
5
|
+
* `unstable_transformImportMeta`, the `@ → ./` module-resolver alias, and the
|
|
6
|
+
* `react-native-worklets/plugin` LAST (Reanimated 4 re-exports it — it must be
|
|
7
|
+
* the final plugin).
|
|
8
|
+
*
|
|
9
|
+
* Usage — a one-line babel.config.js:
|
|
10
|
+
*
|
|
11
|
+
* module.exports = require('@oxyhq/app-preset/babel');
|
|
12
|
+
*
|
|
13
|
+
* The peer packages (`babel-preset-expo`, `babel-plugin-module-resolver`,
|
|
14
|
+
* `react-native-worklets`) resolve from the consuming app's node_modules, where
|
|
15
|
+
* Expo already installs them.
|
|
16
|
+
*
|
|
17
|
+
* @param {import('@babel/core').ConfigAPI} api
|
|
18
|
+
* @returns {import('@babel/core').TransformOptions}
|
|
19
|
+
*/
|
|
20
|
+
module.exports = function oxyBabelPreset(api) {
|
|
21
|
+
api.cache(true);
|
|
22
|
+
return {
|
|
23
|
+
presets: [
|
|
24
|
+
['babel-preset-expo', { unstable_transformImportMeta: true }],
|
|
25
|
+
],
|
|
26
|
+
plugins: [
|
|
27
|
+
// The module-resolver must come first for correct module resolution.
|
|
28
|
+
[
|
|
29
|
+
'module-resolver',
|
|
30
|
+
{
|
|
31
|
+
root: ['./'],
|
|
32
|
+
alias: { '@': './' },
|
|
33
|
+
extensions: ['.ts', '.tsx', '.js', '.jsx', '.json', '.svg'],
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
// Must be LAST.
|
|
37
|
+
'react-native-worklets/plugin',
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
};
|
package/css/base.css
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/* @oxyhq/app-preset — Tailwind v4 + NativeWind base stylesheet.
|
|
2
|
+
*
|
|
3
|
+
* The shared CSS header every Oxy Expo app used to copy-paste: the Tailwind v4
|
|
4
|
+
* layer imports, NativeWind's theme, and Bloom's canonical design tokens
|
|
5
|
+
* (RADIUS scale, spacing, typography, color aliases — the single source of
|
|
6
|
+
* truth generated from `bloomThemeCss()`), plus the `@source` globs that scan
|
|
7
|
+
* the Oxy SDK packages for class names.
|
|
8
|
+
*
|
|
9
|
+
* Import it at the TOP of the app's global.css, then add the app-specific
|
|
10
|
+
* `@source` globs and `@theme` overrides below it (later rules win on overlap):
|
|
11
|
+
*
|
|
12
|
+
* @import "@oxyhq/app-preset/base.css";
|
|
13
|
+
*
|
|
14
|
+
* @source "./app/**\/*.{js,jsx,ts,tsx}";
|
|
15
|
+
* @source "./components/**\/*.{js,jsx,ts,tsx}";
|
|
16
|
+
*
|
|
17
|
+
* The SDK `@source` globs below are RELATIVE TO THIS FILE's location. Published,
|
|
18
|
+
* this file lives at `node_modules/@oxyhq/app-preset/css/base.css`, so
|
|
19
|
+
* `../../services/lib` resolves to `node_modules/@oxyhq/services/lib` and
|
|
20
|
+
* `../../bloom/lib` to `node_modules/@oxyhq/bloom/lib` — @oxyhq/app-preset,
|
|
21
|
+
* @oxyhq/services and @oxyhq/bloom are siblings under `@oxyhq/`. The same two
|
|
22
|
+
* levels up land on `packages/` in the source monorepo, so the globs resolve in
|
|
23
|
+
* both layouts without each app guessing its own node_modules path. */
|
|
24
|
+
|
|
25
|
+
@import "tailwindcss/theme.css" layer(theme);
|
|
26
|
+
@import "tailwindcss/preflight.css" layer(base);
|
|
27
|
+
@import "tailwindcss/utilities.css";
|
|
28
|
+
@import "nativewind/theme";
|
|
29
|
+
@import "@oxyhq/bloom/design-tokens/theme.css";
|
|
30
|
+
|
|
31
|
+
@source "../../services/lib/**/*.{js,jsx}";
|
|
32
|
+
@source "../../bloom/lib/**/*.{js,jsx}";
|
package/eslint/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @oxyhq/app-preset — flat ESLint config.
|
|
3
|
+
*
|
|
4
|
+
* `eslint-config-expo/flat` plus the shared `dist/*` ignore. Consumers spread
|
|
5
|
+
* it and append their own rules:
|
|
6
|
+
*
|
|
7
|
+
* const oxyConfig = require('@oxyhq/app-preset/eslint');
|
|
8
|
+
* module.exports = [...oxyConfig];
|
|
9
|
+
*
|
|
10
|
+
* `eslint` and `eslint-config-expo` resolve from the consuming app's
|
|
11
|
+
* node_modules (both are optional peers of the preset).
|
|
12
|
+
*
|
|
13
|
+
* @type {import('eslint').Linter.Config[]}
|
|
14
|
+
*/
|
|
15
|
+
const { defineConfig } = require('eslint/config');
|
|
16
|
+
const expoConfig = require('eslint-config-expo/flat');
|
|
17
|
+
|
|
18
|
+
module.exports = defineConfig([
|
|
19
|
+
expoConfig,
|
|
20
|
+
{
|
|
21
|
+
ignores: ['dist/*'],
|
|
22
|
+
},
|
|
23
|
+
]);
|
package/metro/index.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @oxyhq/app-preset — Metro config factory.
|
|
3
|
+
*
|
|
4
|
+
* Lifts the shared Metro configuration every Oxy Expo app used to copy-paste:
|
|
5
|
+
* monorepo watch folders, the block list, symlink + package-exports resolution
|
|
6
|
+
* (required by @oxyhq/bloom's subpath exports), the web-font/wasm asset
|
|
7
|
+
* extensions, the release minifier tuning, and the NativeWind wrapper.
|
|
8
|
+
*
|
|
9
|
+
* Usage — a three-line metro.config.js:
|
|
10
|
+
*
|
|
11
|
+
* const { createOxyMetroConfig } = require('@oxyhq/app-preset/metro');
|
|
12
|
+
* module.exports = createOxyMetroConfig(__dirname, {
|
|
13
|
+
* sharedTypesPackage: '@myapp/shared-types',
|
|
14
|
+
* });
|
|
15
|
+
*
|
|
16
|
+
* @param {string} projectRoot Absolute path to the app package (pass `__dirname`).
|
|
17
|
+
* @param {object} [options]
|
|
18
|
+
* @param {string} [options.sharedTypesPackage] Bare name of the app's
|
|
19
|
+
* `packages/shared-types` workspace package. When set, it is aliased to the
|
|
20
|
+
* monorepo `packages/shared-types` dir and its `src` is added to the block
|
|
21
|
+
* list (so Metro never bundles the un-built TypeScript source).
|
|
22
|
+
* @param {string} [options.cssInput='./global.css'] NativeWind CSS entry point.
|
|
23
|
+
* @param {(string|RegExp)[]} [options.extraBlockList=[]] Extra Metro
|
|
24
|
+
* `blockList` patterns appended to the Oxy defaults.
|
|
25
|
+
* @returns {import('metro-config').MetroConfig}
|
|
26
|
+
*/
|
|
27
|
+
const { getDefaultConfig } = require('expo/metro-config');
|
|
28
|
+
const { withNativeWind } = require('nativewind/metro');
|
|
29
|
+
const path = require('path');
|
|
30
|
+
|
|
31
|
+
function blockPath(dir) {
|
|
32
|
+
const resolved = path.resolve(dir);
|
|
33
|
+
return new RegExp(`${resolved.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}/.*`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function createOxyMetroConfig(projectRoot, options = {}) {
|
|
37
|
+
const {
|
|
38
|
+
sharedTypesPackage,
|
|
39
|
+
cssInput = './global.css',
|
|
40
|
+
extraBlockList = [],
|
|
41
|
+
} = options;
|
|
42
|
+
|
|
43
|
+
const monorepoRoot = path.resolve(projectRoot, '../..');
|
|
44
|
+
const config = getDefaultConfig(projectRoot);
|
|
45
|
+
|
|
46
|
+
config.projectRoot = projectRoot;
|
|
47
|
+
|
|
48
|
+
// Include the monorepo root so Metro resolves hoisted deps in root node_modules/.
|
|
49
|
+
config.watchFolders = [monorepoRoot];
|
|
50
|
+
|
|
51
|
+
const blockList = [
|
|
52
|
+
blockPath(path.join(monorepoRoot, 'packages/backend')),
|
|
53
|
+
blockPath(path.join(monorepoRoot, 'docs')),
|
|
54
|
+
/\.expo\/.*/,
|
|
55
|
+
/\.expo-shared\/.*/,
|
|
56
|
+
/\.metro\/.*/,
|
|
57
|
+
/\.cache\/.*/,
|
|
58
|
+
/node_modules\/\.cache\/.*/,
|
|
59
|
+
/\.tsbuildinfo$/,
|
|
60
|
+
/.*\.expo\/types\/.*/,
|
|
61
|
+
/__tests__\/.*/,
|
|
62
|
+
/\.test\.(js|ts|tsx|jsx)$/,
|
|
63
|
+
/\.spec\.(js|ts|tsx|jsx)$/,
|
|
64
|
+
/\.md$/,
|
|
65
|
+
/README/,
|
|
66
|
+
...extraBlockList,
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
const extraNodeModules = {};
|
|
70
|
+
if (sharedTypesPackage) {
|
|
71
|
+
const sharedTypesDir = path.join(monorepoRoot, 'packages/shared-types');
|
|
72
|
+
blockList.push(blockPath(path.join(sharedTypesDir, 'src')));
|
|
73
|
+
extraNodeModules[sharedTypesPackage] = sharedTypesDir;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
config.resolver = {
|
|
77
|
+
...config.resolver,
|
|
78
|
+
blockList,
|
|
79
|
+
extraNodeModules: {
|
|
80
|
+
...config.resolver.extraNodeModules,
|
|
81
|
+
...extraNodeModules,
|
|
82
|
+
},
|
|
83
|
+
// Resolve from the app's node_modules first, then the monorepo root (hoisted deps).
|
|
84
|
+
nodeModulesPaths: [
|
|
85
|
+
path.join(projectRoot, 'node_modules'),
|
|
86
|
+
path.join(monorepoRoot, 'node_modules'),
|
|
87
|
+
],
|
|
88
|
+
// Enable symlinks for workspace resolution.
|
|
89
|
+
unstable_enableSymlinks: true,
|
|
90
|
+
// Enable package.json "exports" resolution (required by @oxyhq/bloom subpath exports).
|
|
91
|
+
unstable_enablePackageExports: true,
|
|
92
|
+
sourceExts: [...config.resolver.sourceExts, 'ts', 'tsx'],
|
|
93
|
+
// Bloom imports `.woff2`/`.woff` fonts directly from JS on web; Metro does not
|
|
94
|
+
// include them in default assetExts, and svg is handled by the transformer.
|
|
95
|
+
assetExts: [
|
|
96
|
+
...config.resolver.assetExts.filter((ext) => ext !== 'svg'),
|
|
97
|
+
'wasm',
|
|
98
|
+
'woff2',
|
|
99
|
+
'woff',
|
|
100
|
+
],
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
config.transformer = {
|
|
104
|
+
...config.transformer,
|
|
105
|
+
minifierConfig: {
|
|
106
|
+
...config.transformer?.minifierConfig,
|
|
107
|
+
keep_classnames: false,
|
|
108
|
+
keep_fnames: false,
|
|
109
|
+
mangle: {
|
|
110
|
+
keep_classnames: false,
|
|
111
|
+
keep_fnames: false,
|
|
112
|
+
},
|
|
113
|
+
output: {
|
|
114
|
+
ascii_only: true,
|
|
115
|
+
quote_style: 3,
|
|
116
|
+
wrap_iife: true,
|
|
117
|
+
},
|
|
118
|
+
sourceMap: {
|
|
119
|
+
includeSources: false,
|
|
120
|
+
},
|
|
121
|
+
toplevel: false,
|
|
122
|
+
compress: {
|
|
123
|
+
arguments: true,
|
|
124
|
+
dead_code: true,
|
|
125
|
+
drop_console: false,
|
|
126
|
+
drop_debugger: true,
|
|
127
|
+
ecma: 2020,
|
|
128
|
+
evaluate: true,
|
|
129
|
+
inline: 1,
|
|
130
|
+
passes: 3,
|
|
131
|
+
reduce_funcs: true,
|
|
132
|
+
reduce_vars: true,
|
|
133
|
+
unsafe: false,
|
|
134
|
+
unsafe_comps: false,
|
|
135
|
+
unsafe_math: false,
|
|
136
|
+
unsafe_methods: false,
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
return withNativeWind(config, {
|
|
142
|
+
input: cssInput,
|
|
143
|
+
inlineRem: 16,
|
|
144
|
+
inlineVariables: false,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
module.exports = { createOxyMetroConfig };
|
package/package.json
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@oxyhq/app-preset",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "The Oxy distro of Expo — centralized Expo config plugin, Metro/Babel/ESLint factories, Tailwind base CSS and TypeScript bases shared by every Oxy app (Mention, Homiio, Allo, accounts, …). A zero-build package: apps replace copy-pasted config with a single dependency bump.",
|
|
5
|
+
"main": "./app.plugin.js",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./app.plugin.js",
|
|
11
|
+
"./app.plugin.js": "./app.plugin.js",
|
|
12
|
+
"./plugin/withOxyAppPreset": "./plugin/withOxyAppPreset.js",
|
|
13
|
+
"./plugin/withSharedUserId": "./plugin/withSharedUserId.js",
|
|
14
|
+
"./plugin/withOxyKeychain": "./plugin/withOxyKeychain.js",
|
|
15
|
+
"./plugin/withOxyBuildProperties": "./plugin/withOxyBuildProperties.js",
|
|
16
|
+
"./metro": "./metro/index.js",
|
|
17
|
+
"./babel": "./babel/index.js",
|
|
18
|
+
"./eslint": "./eslint/index.js",
|
|
19
|
+
"./base.css": "./css/base.css",
|
|
20
|
+
"./css/base.css": "./css/base.css",
|
|
21
|
+
"./tsconfig/base.json": "./tsconfig/base.json",
|
|
22
|
+
"./tsconfig/frontend.json": "./tsconfig/frontend.json",
|
|
23
|
+
"./tsconfig/backend.json": "./tsconfig/backend.json",
|
|
24
|
+
"./package.json": "./package.json"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"app.plugin.js",
|
|
28
|
+
"plugin",
|
|
29
|
+
"metro",
|
|
30
|
+
"babel",
|
|
31
|
+
"eslint",
|
|
32
|
+
"css",
|
|
33
|
+
"tsconfig",
|
|
34
|
+
"README.md"
|
|
35
|
+
],
|
|
36
|
+
"keywords": [
|
|
37
|
+
"oxyhq",
|
|
38
|
+
"expo",
|
|
39
|
+
"react-native",
|
|
40
|
+
"config-plugin",
|
|
41
|
+
"metro",
|
|
42
|
+
"babel",
|
|
43
|
+
"nativewind",
|
|
44
|
+
"preset"
|
|
45
|
+
],
|
|
46
|
+
"repository": {
|
|
47
|
+
"type": "git",
|
|
48
|
+
"url": "https://github.com/oxyhq/sdk",
|
|
49
|
+
"directory": "packages/app-preset"
|
|
50
|
+
},
|
|
51
|
+
"author": "OxyHQ",
|
|
52
|
+
"license": "MIT",
|
|
53
|
+
"homepage": "https://oxy.so",
|
|
54
|
+
"engines": {
|
|
55
|
+
"node": ">=18.0.0"
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"test": "echo \"@oxyhq/app-preset is a zero-build config package — no unit tests\" && exit 0"
|
|
59
|
+
},
|
|
60
|
+
"peerDependencies": {
|
|
61
|
+
"@oxyhq/bloom": "*",
|
|
62
|
+
"@oxyhq/services": "*",
|
|
63
|
+
"babel-plugin-module-resolver": "*",
|
|
64
|
+
"babel-preset-expo": "*",
|
|
65
|
+
"eslint": ">=9.0.0",
|
|
66
|
+
"eslint-config-expo": "*",
|
|
67
|
+
"expo": ">=56.0.0",
|
|
68
|
+
"expo-build-properties": "*",
|
|
69
|
+
"nativewind": ">=5.0.0-preview.0",
|
|
70
|
+
"react-native-worklets": "*",
|
|
71
|
+
"tailwindcss": ">=4.0.0"
|
|
72
|
+
},
|
|
73
|
+
"peerDependenciesMeta": {
|
|
74
|
+
"@oxyhq/bloom": {
|
|
75
|
+
"optional": true
|
|
76
|
+
},
|
|
77
|
+
"@oxyhq/services": {
|
|
78
|
+
"optional": true
|
|
79
|
+
},
|
|
80
|
+
"babel-plugin-module-resolver": {
|
|
81
|
+
"optional": true
|
|
82
|
+
},
|
|
83
|
+
"babel-preset-expo": {
|
|
84
|
+
"optional": true
|
|
85
|
+
},
|
|
86
|
+
"eslint": {
|
|
87
|
+
"optional": true
|
|
88
|
+
},
|
|
89
|
+
"eslint-config-expo": {
|
|
90
|
+
"optional": true
|
|
91
|
+
},
|
|
92
|
+
"expo-build-properties": {
|
|
93
|
+
"optional": true
|
|
94
|
+
},
|
|
95
|
+
"nativewind": {
|
|
96
|
+
"optional": true
|
|
97
|
+
},
|
|
98
|
+
"react-native-worklets": {
|
|
99
|
+
"optional": true
|
|
100
|
+
},
|
|
101
|
+
"tailwindcss": {
|
|
102
|
+
"optional": true
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expo Config Plugin: withOxyAppPreset
|
|
3
|
+
*
|
|
4
|
+
* The single config-plugin entry every Oxy app adds in place of the four
|
|
5
|
+
* copy-pasted plugin entries (`withSharedUserId`, keychain entitlement,
|
|
6
|
+
* `expo-build-properties`, `@oxyhq/services/plugins/withSharedIdentityReader`).
|
|
7
|
+
*
|
|
8
|
+
* In app.config.js / app.json:
|
|
9
|
+
*
|
|
10
|
+
* plugins: [
|
|
11
|
+
* // …app-specific plugins…
|
|
12
|
+
* ['@oxyhq/app-preset', {}],
|
|
13
|
+
* ]
|
|
14
|
+
*
|
|
15
|
+
* Each piece is individually disableable by passing its option as `false`:
|
|
16
|
+
*
|
|
17
|
+
* ['@oxyhq/app-preset', {
|
|
18
|
+
* sharedUserId: 'so.oxy.shared', // false → skip android:sharedUserId
|
|
19
|
+
* keychainGroup: 'group.so.oxy.shared', // false → skip iOS keychain entitlement
|
|
20
|
+
* ios: { deploymentTarget: '17.0' }, // false → skip iOS build properties
|
|
21
|
+
* android: { targetSdkVersion: 34 }, // false → skip Android build properties
|
|
22
|
+
* sharedIdentityReader: true, // false → skip @oxyhq/services reader plugin
|
|
23
|
+
* }]
|
|
24
|
+
*
|
|
25
|
+
* @param {import('expo/config').ExpoConfig} config
|
|
26
|
+
* @param {object} [options]
|
|
27
|
+
* @param {string|false} [options.sharedUserId='so.oxy.shared']
|
|
28
|
+
* @param {string|false} [options.keychainGroup='group.so.oxy.shared']
|
|
29
|
+
* @param {object|false} [options.ios]
|
|
30
|
+
* @param {object|false} [options.android]
|
|
31
|
+
* @param {boolean} [options.sharedIdentityReader=true]
|
|
32
|
+
*/
|
|
33
|
+
const withSharedUserId = require('./withSharedUserId');
|
|
34
|
+
const withOxyKeychain = require('./withOxyKeychain');
|
|
35
|
+
const withOxyBuildProperties = require('./withOxyBuildProperties');
|
|
36
|
+
|
|
37
|
+
module.exports = function withOxyAppPreset(config, options = {}) {
|
|
38
|
+
const {
|
|
39
|
+
sharedUserId = 'so.oxy.shared',
|
|
40
|
+
keychainGroup = 'group.so.oxy.shared',
|
|
41
|
+
ios = {},
|
|
42
|
+
android = {},
|
|
43
|
+
sharedIdentityReader = true,
|
|
44
|
+
} = options;
|
|
45
|
+
|
|
46
|
+
let next = config;
|
|
47
|
+
|
|
48
|
+
if (sharedUserId !== false) {
|
|
49
|
+
next = withSharedUserId(next, sharedUserId);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (keychainGroup !== false) {
|
|
53
|
+
next = withOxyKeychain(next, keychainGroup);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (ios !== false || android !== false) {
|
|
57
|
+
next = withOxyBuildProperties(next, { ios, android });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (sharedIdentityReader !== false) {
|
|
61
|
+
let withSharedIdentityReader;
|
|
62
|
+
try {
|
|
63
|
+
withSharedIdentityReader = require('@oxyhq/services/plugins/withSharedIdentityReader');
|
|
64
|
+
} catch (error) {
|
|
65
|
+
throw new Error(
|
|
66
|
+
"[@oxyhq/app-preset] sharedIdentityReader is enabled but the peer dependency '@oxyhq/services' "
|
|
67
|
+
+ 'is not installed. Install it, or pass `{ sharedIdentityReader: false }` to the preset.',
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
next = withSharedIdentityReader(next);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return next;
|
|
74
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expo Config Plugin: withOxyBuildProperties
|
|
3
|
+
*
|
|
4
|
+
* Wraps `expo-build-properties` with the native build defaults every Oxy app
|
|
5
|
+
* ships with — iOS deployment target and the Android SDK / minification knobs —
|
|
6
|
+
* so apps stop copy-pasting the same `expo-build-properties` block. Caller
|
|
7
|
+
* overrides deep-merge over these defaults.
|
|
8
|
+
*
|
|
9
|
+
* `expo-build-properties` is required lazily (it is an optional peer): a missing
|
|
10
|
+
* install throws a clear, actionable error only when this plugin actually runs
|
|
11
|
+
* with build properties enabled.
|
|
12
|
+
*
|
|
13
|
+
* @param {import('expo/config').ExpoConfig} config
|
|
14
|
+
* @param {object} [options]
|
|
15
|
+
* @param {object|false} [options.ios] iOS overrides, or `false` to skip iOS.
|
|
16
|
+
* @param {object|false} [options.android] Android overrides, or `false` to skip Android.
|
|
17
|
+
*/
|
|
18
|
+
const DEFAULTS = {
|
|
19
|
+
ios: {
|
|
20
|
+
deploymentTarget: '16.4',
|
|
21
|
+
},
|
|
22
|
+
android: {
|
|
23
|
+
compileSdkVersion: 36,
|
|
24
|
+
targetSdkVersion: 35,
|
|
25
|
+
buildToolsVersion: '36.0.0',
|
|
26
|
+
enableProguardInReleaseBuilds: true,
|
|
27
|
+
enableShrinkResourcesInReleaseBuilds: true,
|
|
28
|
+
useLegacyPackaging: false,
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function isPlainObject(value) {
|
|
33
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function deepMerge(base, override) {
|
|
37
|
+
if (!isPlainObject(override)) {
|
|
38
|
+
return base;
|
|
39
|
+
}
|
|
40
|
+
const result = { ...base };
|
|
41
|
+
for (const [key, value] of Object.entries(override)) {
|
|
42
|
+
result[key] = isPlainObject(value) && isPlainObject(result[key])
|
|
43
|
+
? deepMerge(result[key], value)
|
|
44
|
+
: value;
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function resolveBuildPropertiesPlugin() {
|
|
50
|
+
let mod;
|
|
51
|
+
try {
|
|
52
|
+
mod = require('expo-build-properties');
|
|
53
|
+
} catch (error) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
"[@oxyhq/app-preset] withOxyBuildProperties requires the peer dependency 'expo-build-properties'. "
|
|
56
|
+
+ "Install it with `npx expo install expo-build-properties`, or disable it by passing "
|
|
57
|
+
+ '`{ ios: false, android: false }` to the preset.',
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
const plugin = typeof mod === 'function' ? mod : mod.withBuildProperties || mod.default;
|
|
61
|
+
if (typeof plugin !== 'function') {
|
|
62
|
+
throw new Error(
|
|
63
|
+
"[@oxyhq/app-preset] Could not resolve the 'expo-build-properties' config plugin from the installed package.",
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
return plugin;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = function withOxyBuildProperties(config, options = {}) {
|
|
70
|
+
const { ios = {}, android = {} } = options;
|
|
71
|
+
|
|
72
|
+
if (ios === false && android === false) {
|
|
73
|
+
return config;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const props = {};
|
|
77
|
+
if (ios !== false) {
|
|
78
|
+
props.ios = deepMerge(DEFAULTS.ios, ios);
|
|
79
|
+
}
|
|
80
|
+
if (android !== false) {
|
|
81
|
+
props.android = deepMerge(DEFAULTS.android, android);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const withBuildProperties = resolveBuildPropertiesPlugin();
|
|
85
|
+
return withBuildProperties(config, props);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
module.exports.DEFAULTS = DEFAULTS;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expo Config Plugin: withOxyKeychain
|
|
3
|
+
*
|
|
4
|
+
* Adds an iOS `keychain-access-groups` entitlement so Oxy apps signed with the
|
|
5
|
+
* same Team ID can share Keychain items — the iOS half of "sign in once, use
|
|
6
|
+
* everywhere" (the Android half is withSharedUserId + the shared-identity
|
|
7
|
+
* native module). The group is prefixed with `$(AppIdentifierPrefix)` so Xcode
|
|
8
|
+
* expands it to the Team ID at build time.
|
|
9
|
+
*
|
|
10
|
+
* Merge-not-overwrite: if the app (or another plugin, e.g. expo-build-properties)
|
|
11
|
+
* already declared keychain-access-groups, the Oxy group is appended without
|
|
12
|
+
* dropping the existing entries.
|
|
13
|
+
*
|
|
14
|
+
* @param {import('expo/config').ExpoConfig} config
|
|
15
|
+
* @param {string} [keychainGroup='group.so.oxy.shared'] The keychain group,
|
|
16
|
+
* without the `$(AppIdentifierPrefix)` prefix.
|
|
17
|
+
*/
|
|
18
|
+
const { withEntitlementsPlist } = require('expo/config-plugins');
|
|
19
|
+
|
|
20
|
+
module.exports = function withOxyKeychain(config, keychainGroup = 'group.so.oxy.shared') {
|
|
21
|
+
const entry = `$(AppIdentifierPrefix)${keychainGroup}`;
|
|
22
|
+
|
|
23
|
+
return withEntitlementsPlist(config, (config) => {
|
|
24
|
+
const key = 'keychain-access-groups';
|
|
25
|
+
const existing = config.modResults[key];
|
|
26
|
+
const groups = Array.isArray(existing) ? [...existing] : [];
|
|
27
|
+
|
|
28
|
+
if (!groups.includes(entry)) {
|
|
29
|
+
groups.push(entry);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
config.modResults[key] = groups;
|
|
33
|
+
return config;
|
|
34
|
+
});
|
|
35
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expo Config Plugin: withSharedUserId
|
|
3
|
+
*
|
|
4
|
+
* Adds android:sharedUserId to AndroidManifest.xml to enable cross-app data
|
|
5
|
+
* sharing between Oxy apps (Mention, Homiio, accounts, Commons, …).
|
|
6
|
+
*
|
|
7
|
+
* This allows:
|
|
8
|
+
* - Shared cryptographic identity storage
|
|
9
|
+
* - Cross-app authentication (sign in once, use everywhere)
|
|
10
|
+
* - Shared session tokens
|
|
11
|
+
*
|
|
12
|
+
* IMPORTANT:
|
|
13
|
+
* - All Oxy apps MUST use the same sharedUserId: "so.oxy.shared"
|
|
14
|
+
* - Apps MUST be signed with the same certificate
|
|
15
|
+
* - Cannot change sharedUserId after publishing (requires reinstall)
|
|
16
|
+
*
|
|
17
|
+
* @see https://developer.android.com/guide/topics/manifest/manifest-element#uid
|
|
18
|
+
*
|
|
19
|
+
* @param {import('expo/config').ExpoConfig} config
|
|
20
|
+
* @param {string} [sharedUserId='so.oxy.shared'] The android:sharedUserId value.
|
|
21
|
+
*/
|
|
22
|
+
const { withAndroidManifest } = require('expo/config-plugins');
|
|
23
|
+
|
|
24
|
+
module.exports = function withSharedUserId(config, sharedUserId = 'so.oxy.shared') {
|
|
25
|
+
return withAndroidManifest(config, async (config) => {
|
|
26
|
+
const androidManifest = config.modResults.manifest;
|
|
27
|
+
|
|
28
|
+
// Add sharedUserId to the manifest root element
|
|
29
|
+
androidManifest.$ = {
|
|
30
|
+
...androidManifest.$,
|
|
31
|
+
'android:sharedUserId': sharedUserId,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
return config;
|
|
35
|
+
});
|
|
36
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
3
|
+
"//": "@oxyhq/app-preset — Node/Bun backend TypeScript config. Compiled with tsc to dist/ (CommonJS) and THAT emitted output is what the Docker/ECS image runs — module format and emit layout are a runtime contract. Overrides the Oxy base only where this becomes an emitting CommonJS Node service. Consumers set their own `rootDir`/`outDir`/`include` (and `paths` to resolve workspace deps from source during type-check).",
|
|
4
|
+
"extends": "./base.json",
|
|
5
|
+
"compilerOptions": {
|
|
6
|
+
"module": "commonjs",
|
|
7
|
+
"moduleResolution": "node",
|
|
8
|
+
"noEmit": false,
|
|
9
|
+
"declaration": true
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
3
|
+
"//": "@oxyhq/app-preset — shared, runtime-neutral TypeScript base. The strict/safety defaults every Oxy package inherits. This base is TYPE-CHECK ONLY (noEmit): the frontend (tsconfig/frontend.json) and emitting Node packages (tsconfig/backend.json) override module/target/emit for their runtime. Does NOT extend expo/tsconfig.base — that sets customConditions:[\"react-native\"], only valid with bundler/nodenext resolution, which would break the CJS/node packages. The frontend config layers the Expo base on top where bundler resolution is in effect.",
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
"target": "ES2020",
|
|
6
|
+
"lib": ["ES2020"],
|
|
7
|
+
"module": "ES2020",
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
|
|
10
|
+
"strict": true,
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"allowSyntheticDefaultImports": true,
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"forceConsistentCasingInFileNames": true,
|
|
15
|
+
"resolveJsonModule": true,
|
|
16
|
+
|
|
17
|
+
"composite": true,
|
|
18
|
+
"declaration": true,
|
|
19
|
+
"declarationMap": true,
|
|
20
|
+
"sourceMap": true,
|
|
21
|
+
|
|
22
|
+
"noEmit": true
|
|
23
|
+
},
|
|
24
|
+
"exclude": [
|
|
25
|
+
"node_modules",
|
|
26
|
+
"**/node_modules",
|
|
27
|
+
"**/dist",
|
|
28
|
+
"**/build",
|
|
29
|
+
"**/.expo"
|
|
30
|
+
]
|
|
31
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
3
|
+
"//": "@oxyhq/app-preset — Expo / React Native frontend TypeScript config. Not compiled by tsc (Metro/Expo bundles the source); tsc is only for type-checking. Extends BOTH the Oxy base (strict/composite defaults) AND expo/tsconfig.base (React-Native JSX, customConditions:[\"react-native\"], bundler resolution). Later entries win, so the Expo base overrides the Oxy base where they differ; the explicit overrides below win over both. Consumers extend this and add their own `paths` (e.g. \"@/*\") + `include`.",
|
|
4
|
+
"extends": ["./base.json", "expo/tsconfig.base"],
|
|
5
|
+
"compilerOptions": {
|
|
6
|
+
"jsx": "react-native",
|
|
7
|
+
"strict": true,
|
|
8
|
+
"composite": true,
|
|
9
|
+
"noEmit": false
|
|
10
|
+
}
|
|
11
|
+
}
|