@vanenshi/expo-plugins 0.1.1
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/LICENSE +21 -0
- package/README.md +143 -0
- package/build/display-name/index.d.ts +11 -0
- package/build/display-name/index.js +51 -0
- package/build/google-fonts/discovery.d.ts +34 -0
- package/build/google-fonts/discovery.js +91 -0
- package/build/google-fonts/index.d.ts +43 -0
- package/build/google-fonts/index.js +89 -0
- package/build/index.d.ts +4 -0
- package/build/index.js +11 -0
- package/build/utils/logger.d.ts +8 -0
- package/build/utils/logger.js +60 -0
- package/display-name.js +2 -0
- package/google-fonts.js +3 -0
- package/package.json +51 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Amir Shekari
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# @vanenshi/expo-plugins
|
|
2
|
+
|
|
3
|
+
A small collection of [Expo config plugins](https://docs.expo.dev/config-plugins/introduction/).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npx expo install @vanenshi/expo-plugins
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## `withDisplayName`
|
|
14
|
+
|
|
15
|
+
**Goal:** Give each build variant (dev / beta / prod) its own home-screen
|
|
16
|
+
display name on both iOS and Android — without touching the internal root `name`
|
|
17
|
+
(which drives bundle structure and forces slow clean rebuilds when changed).
|
|
18
|
+
|
|
19
|
+
**Underneath:**
|
|
20
|
+
- **iOS** — sets `CFBundleDisplayName` in `Info.plist` via `withInfoPlist`.
|
|
21
|
+
- **Android** — sets `app_name` in `strings.xml` via `withStringsXml`.
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
// app.config.ts
|
|
25
|
+
export default {
|
|
26
|
+
name: "MyApp", // internal project name — unchanged
|
|
27
|
+
plugins: [
|
|
28
|
+
["@vanenshi/expo-plugins/display-name", { displayName: "MyApp (Beta)" }],
|
|
29
|
+
],
|
|
30
|
+
};
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Options
|
|
34
|
+
|
|
35
|
+
| Option | Type | Description |
|
|
36
|
+
| ------------- | -------- | -------------------------------------------------- |
|
|
37
|
+
| `displayName` | `string` | Name shown to users on the home screen (required). |
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## `withExpoGoogleFonts`
|
|
42
|
+
|
|
43
|
+
**Goal:** Embed `@expo-google-fonts/*` faces into the native projects without
|
|
44
|
+
manually listing every font path in your app config. The plugin reads the
|
|
45
|
+
weight/style folder layout from your installed packages, resolves the `.ttf`
|
|
46
|
+
file paths, and delegates to `expo-font` — so you only declare _which_ font
|
|
47
|
+
families and weights you want.
|
|
48
|
+
|
|
49
|
+
**Underneath:**
|
|
50
|
+
- **Discovery** — walks `@expo-google-fonts/<name>` package folders, parses
|
|
51
|
+
folder names like `400Regular` / `700Bold_Italic`, and collects `.ttf` paths.
|
|
52
|
+
- **Delegation** — passes the resolved font manifest to `expo-font/app.plugin`
|
|
53
|
+
(`withFonts`), which handles embedding into both Android assets and iOS
|
|
54
|
+
`Info.plist`.
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
npx expo install expo-font @expo-google-fonts/roboto @expo-google-fonts/inter
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"plugins": [
|
|
63
|
+
[
|
|
64
|
+
"@vanenshi/expo-plugins/google-fonts",
|
|
65
|
+
{
|
|
66
|
+
"fonts": [
|
|
67
|
+
{ "packageName": "roboto", "weights": [400, 500, 700], "importItalic": true },
|
|
68
|
+
{ "packageName": "inter", "weights": [400, 600] }
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
]
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
No separate `["expo-font", ...]` entry is needed.
|
|
77
|
+
|
|
78
|
+
> Need the raw `expo-font` options (e.g. to merge with local fonts)? Import
|
|
79
|
+
> `buildExpoGoogleFontsOptions(input)` and pass the result to
|
|
80
|
+
> `["expo-font", options]` yourself.
|
|
81
|
+
|
|
82
|
+
### Options
|
|
83
|
+
|
|
84
|
+
| Option | Type | Default | Description |
|
|
85
|
+
| --------------- | ------------------ | --------------- | ------------------------------------------------------ |
|
|
86
|
+
| `fonts` | `GoogleFontSpec[]` | — | Fonts to embed (see below). |
|
|
87
|
+
| `projectRoot` | `string` | `process.cwd()` | Where to resolve `@expo-google-fonts/*` packages from. |
|
|
88
|
+
| `warnOnMissing` | `boolean` | `true` | Warn when a package, weight, or `.ttf` is missing. |
|
|
89
|
+
|
|
90
|
+
`GoogleFontSpec`: `{ packageName, fontFamily?, weights?, importItalic? }` —
|
|
91
|
+
`weights` defaults to `[400]`, `fontFamily` defaults to the package's metadata
|
|
92
|
+
family name.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Development
|
|
97
|
+
|
|
98
|
+
This package uses [`expo-module-scripts`](https://github.com/expo/expo/tree/main/packages/expo-module-scripts) and follows [`expo/config-plugins`](https://github.com/expo/config-plugins) conventions.
|
|
99
|
+
|
|
100
|
+
```sh
|
|
101
|
+
pnpm install
|
|
102
|
+
pnpm run build # compile src/ → build/
|
|
103
|
+
pnpm run lint
|
|
104
|
+
pnpm test
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Example app
|
|
108
|
+
|
|
109
|
+
`apps/app` is a minimal Expo app wiring both plugins, used to verify plugins
|
|
110
|
+
work end-to-end via `expo prebuild`:
|
|
111
|
+
|
|
112
|
+
```sh
|
|
113
|
+
cd apps/app
|
|
114
|
+
npm install
|
|
115
|
+
npx expo prebuild --clean
|
|
116
|
+
grep "Example (Beta)" android/app/src/main/res/values/strings.xml
|
|
117
|
+
grep "CFBundleDisplayName" ios/*/Info.plist
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
See [`apps/app/README.md`](apps/app/README.md) for details.
|
|
121
|
+
|
|
122
|
+
### Publishing
|
|
123
|
+
|
|
124
|
+
CI publishes to npm on every **GitHub Release** ([`.github/workflows/publish.yml`](.github/workflows/publish.yml)).
|
|
125
|
+
Add an `NPM_TOKEN` repo secret (npm Automation token), then:
|
|
126
|
+
|
|
127
|
+
```sh
|
|
128
|
+
pnpm version <patch|minor|major>
|
|
129
|
+
git push --follow-tags
|
|
130
|
+
# create a GitHub Release from the new tag — CI builds, tests, and publishes
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Architecture decisions
|
|
136
|
+
|
|
137
|
+
See [`docs/adr/`](docs/adr/) for the recorded decisions behind this package's design:
|
|
138
|
+
package structure ([ADR-0001](docs/adr/0001-single-package.md)),
|
|
139
|
+
toolchain ([ADR-0002](docs/adr/0002-expo-module-scripts.md)),
|
|
140
|
+
logging ([ADR-0003](docs/adr/0003-warning-aggregator-logger.md)),
|
|
141
|
+
font discovery ([ADR-0004](docs/adr/0004-google-fonts-auto-discovery.md)),
|
|
142
|
+
folder-per-plugin layout ([ADR-0005](docs/adr/0005-folder-per-plugin-layout.md)),
|
|
143
|
+
and cross-platform display name ([ADR-0006](docs/adr/0006-cross-platform-display-name.md)).
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ConfigPlugin } from "expo/config-plugins";
|
|
2
|
+
export interface DisplayNameProps {
|
|
3
|
+
/**
|
|
4
|
+
* Display name shown to users on the home screen. Applied to
|
|
5
|
+
* `CFBundleDisplayName` (iOS) and `app_name` in `strings.xml` (Android).
|
|
6
|
+
* When omitted the plugin is a no-op.
|
|
7
|
+
*/
|
|
8
|
+
displayName?: string;
|
|
9
|
+
}
|
|
10
|
+
declare const _default: ConfigPlugin<void | DisplayNameProps>;
|
|
11
|
+
export default _default;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const config_plugins_1 = require("expo/config-plugins");
|
|
4
|
+
const logger_1 = require("../utils/logger");
|
|
5
|
+
const logger = (0, logger_1.createLogger)("@vanenshi/expo-plugins/display-name");
|
|
6
|
+
const pkg = require("../../package.json");
|
|
7
|
+
/**
|
|
8
|
+
* Sets a custom display name on both iOS and Android without touching the root
|
|
9
|
+
* `name` (which drives bundle structure and internal module naming). Lets you
|
|
10
|
+
* give each build variant (dev, beta, prod) its own user-facing name while
|
|
11
|
+
* keeping internal naming consistent.
|
|
12
|
+
*
|
|
13
|
+
* Underneath:
|
|
14
|
+
* - iOS: sets `CFBundleDisplayName` in `Info.plist` via `withInfoPlist`.
|
|
15
|
+
* - Android: sets `app_name` in `strings.xml` via `withStringsXml`.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* // app.config.ts
|
|
19
|
+
* plugins: [["@vanenshi/expo-plugins/display-name", { displayName: "My App (Beta)" }]]
|
|
20
|
+
*/
|
|
21
|
+
const withDisplayName = (config, props) => {
|
|
22
|
+
const displayName = props?.displayName;
|
|
23
|
+
if (!displayName)
|
|
24
|
+
return config;
|
|
25
|
+
// Android
|
|
26
|
+
config = (0, config_plugins_1.withStringsXml)(config, (modConfig) => {
|
|
27
|
+
if (modConfig.name) {
|
|
28
|
+
logger.warnAndroid(`displayName is set — ignoring abstract "name": ${modConfig.name}`);
|
|
29
|
+
}
|
|
30
|
+
const strings = modConfig.modResults.resources.string ?? [];
|
|
31
|
+
const entry = strings.find((item) => item.$.name === "app_name");
|
|
32
|
+
if (entry) {
|
|
33
|
+
entry._ = displayName;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
strings.push({ $: { name: "app_name" }, _: displayName });
|
|
37
|
+
modConfig.modResults.resources.string = strings;
|
|
38
|
+
}
|
|
39
|
+
return modConfig;
|
|
40
|
+
});
|
|
41
|
+
// iOS
|
|
42
|
+
config = (0, config_plugins_1.withInfoPlist)(config, (modConfig) => {
|
|
43
|
+
if (modConfig.name) {
|
|
44
|
+
logger.warnIOS(`displayName is set — ignoring abstract "name": ${modConfig.name}`);
|
|
45
|
+
}
|
|
46
|
+
modConfig.modResults.CFBundleDisplayName = displayName;
|
|
47
|
+
return modConfig;
|
|
48
|
+
});
|
|
49
|
+
return config;
|
|
50
|
+
};
|
|
51
|
+
exports.default = (0, config_plugins_1.createRunOncePlugin)(withDisplayName, "@vanenshi/expo-plugins/display-name", pkg.version);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
type FontStyle = "normal" | "italic";
|
|
2
|
+
export interface FontFace {
|
|
3
|
+
path: string;
|
|
4
|
+
absPath: string;
|
|
5
|
+
weight: number;
|
|
6
|
+
style: FontStyle;
|
|
7
|
+
}
|
|
8
|
+
export interface DiscoveredPackage {
|
|
9
|
+
fontFamily: string;
|
|
10
|
+
faces: Map<string, FontFace>;
|
|
11
|
+
}
|
|
12
|
+
export declare const TTF_REQUIRE_RE: RegExp;
|
|
13
|
+
export declare const FOLDER_TO_WEIGHT: Record<string, number>;
|
|
14
|
+
export declare function parseFolder(folder: string): {
|
|
15
|
+
weight: number;
|
|
16
|
+
style: FontStyle;
|
|
17
|
+
} | null;
|
|
18
|
+
/**
|
|
19
|
+
* Parses an already-resolved `@expo-google-fonts/*` package directory.
|
|
20
|
+
* Pure: no `require.resolve`, no network. Suitable for unit testing with a
|
|
21
|
+
* committed fixture directory.
|
|
22
|
+
*/
|
|
23
|
+
export declare function parsePackage(pkgDir: string, packageName: string): DiscoveredPackage | null;
|
|
24
|
+
/**
|
|
25
|
+
* Resolves the `@expo-google-fonts/<packageName>` package directory via
|
|
26
|
+
* `require.resolve`. This is the only side-effectful seam; keep it thin.
|
|
27
|
+
*/
|
|
28
|
+
export declare function resolvePackageDir(packageName: string, projectRoot: string): string | null;
|
|
29
|
+
/**
|
|
30
|
+
* Discovers a Google Fonts package: resolves it then parses its contents.
|
|
31
|
+
* Emits a warning and returns null when the package is missing or malformed.
|
|
32
|
+
*/
|
|
33
|
+
export declare function discoverPackage(packageName: string, projectRoot: string, warnOnMissing: boolean): DiscoveredPackage | null;
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.FOLDER_TO_WEIGHT = exports.TTF_REQUIRE_RE = void 0;
|
|
7
|
+
exports.parseFolder = parseFolder;
|
|
8
|
+
exports.parsePackage = parsePackage;
|
|
9
|
+
exports.resolvePackageDir = resolvePackageDir;
|
|
10
|
+
exports.discoverPackage = discoverPackage;
|
|
11
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
12
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
13
|
+
const logger_1 = require("../utils/logger");
|
|
14
|
+
const logger = (0, logger_1.createLogger)("@vanenshi/expo-plugins/google-fonts");
|
|
15
|
+
exports.TTF_REQUIRE_RE = /require\(['"]\.\/([^'"]+\.ttf)['"]\)/g;
|
|
16
|
+
exports.FOLDER_TO_WEIGHT = {
|
|
17
|
+
"100Thin": 100,
|
|
18
|
+
"200ExtraLight": 200,
|
|
19
|
+
"300Light": 300,
|
|
20
|
+
"400Regular": 400,
|
|
21
|
+
"500Medium": 500,
|
|
22
|
+
"600SemiBold": 600,
|
|
23
|
+
"700Bold": 700,
|
|
24
|
+
"800ExtraBold": 800,
|
|
25
|
+
"900Black": 900,
|
|
26
|
+
};
|
|
27
|
+
function parseFolder(folder) {
|
|
28
|
+
const italic = folder.endsWith("_Italic");
|
|
29
|
+
const base = italic ? folder.slice(0, -"_Italic".length) : folder;
|
|
30
|
+
if (!(base in exports.FOLDER_TO_WEIGHT))
|
|
31
|
+
return null;
|
|
32
|
+
const weight = exports.FOLDER_TO_WEIGHT[base] ?? 400;
|
|
33
|
+
return { weight, style: italic ? "italic" : "normal" };
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Parses an already-resolved `@expo-google-fonts/*` package directory.
|
|
37
|
+
* Pure: no `require.resolve`, no network. Suitable for unit testing with a
|
|
38
|
+
* committed fixture directory.
|
|
39
|
+
*/
|
|
40
|
+
function parsePackage(pkgDir, packageName) {
|
|
41
|
+
const metadataPath = node_path_1.default.join(pkgDir, "metadata.json");
|
|
42
|
+
const indexPath = node_path_1.default.join(pkgDir, "index.js");
|
|
43
|
+
if (!node_fs_1.default.existsSync(metadataPath) || !node_fs_1.default.existsSync(indexPath)) {
|
|
44
|
+
logger.warn(`@expo-google-fonts/${packageName} is missing metadata.json or index.js.`);
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const { family } = JSON.parse(node_fs_1.default.readFileSync(metadataPath, "utf8"));
|
|
48
|
+
const faces = new Map();
|
|
49
|
+
for (const match of node_fs_1.default
|
|
50
|
+
.readFileSync(indexPath, "utf8")
|
|
51
|
+
.matchAll(exports.TTF_REQUIRE_RE)) {
|
|
52
|
+
const relPath = match[1];
|
|
53
|
+
const parsed = parseFolder(node_path_1.default.dirname(relPath));
|
|
54
|
+
if (!parsed)
|
|
55
|
+
continue;
|
|
56
|
+
faces.set(`${parsed.weight}:${parsed.style}`, {
|
|
57
|
+
...parsed,
|
|
58
|
+
path: `./node_modules/@expo-google-fonts/${packageName}/${relPath}`,
|
|
59
|
+
absPath: node_path_1.default.join(pkgDir, relPath),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
return { fontFamily: family, faces };
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Resolves the `@expo-google-fonts/<packageName>` package directory via
|
|
66
|
+
* `require.resolve`. This is the only side-effectful seam; keep it thin.
|
|
67
|
+
*/
|
|
68
|
+
function resolvePackageDir(packageName, projectRoot) {
|
|
69
|
+
try {
|
|
70
|
+
return node_path_1.default.dirname(require.resolve(`@expo-google-fonts/${packageName}/package.json`, {
|
|
71
|
+
paths: [projectRoot],
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Discovers a Google Fonts package: resolves it then parses its contents.
|
|
80
|
+
* Emits a warning and returns null when the package is missing or malformed.
|
|
81
|
+
*/
|
|
82
|
+
function discoverPackage(packageName, projectRoot, warnOnMissing) {
|
|
83
|
+
const pkgDir = resolvePackageDir(packageName, projectRoot);
|
|
84
|
+
if (!pkgDir) {
|
|
85
|
+
if (warnOnMissing) {
|
|
86
|
+
logger.warn(`@expo-google-fonts/${packageName} is not installed — run: npx expo install @expo-google-fonts/${packageName}`);
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
return parsePackage(pkgDir, packageName);
|
|
91
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ConfigPlugin } from "expo/config-plugins";
|
|
2
|
+
export interface GoogleFontSpec {
|
|
3
|
+
packageName: string;
|
|
4
|
+
/** `fontFamily` style prop value — defaults to `metadata.json` family (e.g. `"Roboto"`). */
|
|
5
|
+
fontFamily?: string;
|
|
6
|
+
weights?: number[];
|
|
7
|
+
importItalic?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface BuildExpoFontPluginOptionsInput {
|
|
10
|
+
fonts: GoogleFontSpec[];
|
|
11
|
+
projectRoot?: string;
|
|
12
|
+
warnOnMissing?: boolean;
|
|
13
|
+
}
|
|
14
|
+
interface ExpoFontPluginOptions {
|
|
15
|
+
android: {
|
|
16
|
+
fonts: {
|
|
17
|
+
fontFamily: string;
|
|
18
|
+
fontDefinitions: {
|
|
19
|
+
path: string;
|
|
20
|
+
weight: number;
|
|
21
|
+
style?: "italic";
|
|
22
|
+
}[];
|
|
23
|
+
}[];
|
|
24
|
+
};
|
|
25
|
+
ios: {
|
|
26
|
+
fonts: string[];
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Builds the `expo-font` plugin options from installed `@expo-google-fonts/*`
|
|
31
|
+
* packages — Android uses `fontFamily` + weight/style, iOS embeds the resolved
|
|
32
|
+
* file paths. Use this directly only if you want to pass the options to
|
|
33
|
+
* `expo-font` yourself; most projects should use the {@link withExpoGoogleFonts}
|
|
34
|
+
* config plugin instead.
|
|
35
|
+
*/
|
|
36
|
+
export declare function buildExpoGoogleFontsOptions({ fonts, projectRoot, warnOnMissing, }: BuildExpoFontPluginOptionsInput): ExpoFontPluginOptions;
|
|
37
|
+
export interface ExpoGoogleFontsProps {
|
|
38
|
+
fonts?: GoogleFontSpec[];
|
|
39
|
+
projectRoot?: string;
|
|
40
|
+
warnOnMissing?: boolean;
|
|
41
|
+
}
|
|
42
|
+
declare const _default: ConfigPlugin<ExpoGoogleFontsProps>;
|
|
43
|
+
export default _default;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.buildExpoGoogleFontsOptions = buildExpoGoogleFontsOptions;
|
|
7
|
+
const config_plugins_1 = require("expo/config-plugins");
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const discovery_1 = require("./discovery");
|
|
10
|
+
const logger_1 = require("../utils/logger");
|
|
11
|
+
const logger = (0, logger_1.createLogger)("@vanenshi/expo-plugins/google-fonts");
|
|
12
|
+
const pkg = require("../../package.json");
|
|
13
|
+
/**
|
|
14
|
+
* Builds the `expo-font` plugin options from installed `@expo-google-fonts/*`
|
|
15
|
+
* packages — Android uses `fontFamily` + weight/style, iOS embeds the resolved
|
|
16
|
+
* file paths. Use this directly only if you want to pass the options to
|
|
17
|
+
* `expo-font` yourself; most projects should use the {@link withExpoGoogleFonts}
|
|
18
|
+
* config plugin instead.
|
|
19
|
+
*/
|
|
20
|
+
function buildExpoGoogleFontsOptions({ fonts, projectRoot = process.cwd(), warnOnMissing = true, }) {
|
|
21
|
+
const androidFonts = [];
|
|
22
|
+
const iosFonts = [];
|
|
23
|
+
for (const spec of fonts) {
|
|
24
|
+
const pkg = (0, discovery_1.discoverPackage)(spec.packageName, projectRoot, warnOnMissing);
|
|
25
|
+
if (!pkg)
|
|
26
|
+
continue;
|
|
27
|
+
const fontFamily = spec.fontFamily ?? pkg.fontFamily;
|
|
28
|
+
const weights = spec.weights ?? [400];
|
|
29
|
+
const wanted = weights.map((weight) => ({
|
|
30
|
+
weight,
|
|
31
|
+
style: "normal",
|
|
32
|
+
}));
|
|
33
|
+
if (spec.importItalic) {
|
|
34
|
+
wanted.push(...weights.map((weight) => ({ weight, style: "italic" })));
|
|
35
|
+
}
|
|
36
|
+
const fontDefinitions = [];
|
|
37
|
+
for (const { weight, style } of wanted) {
|
|
38
|
+
const face = pkg.faces.get(`${weight}:${style}`);
|
|
39
|
+
if (!face) {
|
|
40
|
+
if (warnOnMissing) {
|
|
41
|
+
logger.warn(`${fontFamily} weight ${weight}${style === "italic" ? " italic" : ""} not found in @expo-google-fonts/${spec.packageName}.`);
|
|
42
|
+
}
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
if (!node_fs_1.default.existsSync(face.absPath)) {
|
|
46
|
+
if (warnOnMissing) {
|
|
47
|
+
logger.warn(`${fontFamily} file missing: ${face.absPath}`);
|
|
48
|
+
}
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
fontDefinitions.push({
|
|
52
|
+
path: face.path,
|
|
53
|
+
weight: face.weight,
|
|
54
|
+
...(face.style === "italic" ? { style: "italic" } : {}),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
if (fontDefinitions.length === 0) {
|
|
58
|
+
if (warnOnMissing) {
|
|
59
|
+
logger.warn(`No faces embedded for ${fontFamily} (@expo-google-fonts/${spec.packageName}).`);
|
|
60
|
+
}
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
androidFonts.push({ fontFamily, fontDefinitions });
|
|
64
|
+
iosFonts.push(...fontDefinitions.map((def) => def.path));
|
|
65
|
+
}
|
|
66
|
+
return { android: { fonts: androidFonts }, ios: { fonts: iosFonts } };
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Config plugin that embeds `@expo-google-fonts/*` faces into the native
|
|
70
|
+
* projects. Resolves the requested weights/styles and delegates to `expo-font`,
|
|
71
|
+
* so you don't need a separate `["expo-font", ...]` entry.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* // app.config.ts
|
|
75
|
+
* plugins: [
|
|
76
|
+
* [
|
|
77
|
+
* "@vanenshi/expo-plugins/google-fonts",
|
|
78
|
+
* { fonts: [{ packageName: "roboto", weights: [400, 700], importItalic: true }] },
|
|
79
|
+
* ],
|
|
80
|
+
* ]
|
|
81
|
+
*/
|
|
82
|
+
const withExpoGoogleFonts = (config, props) => {
|
|
83
|
+
if (!props?.fonts?.length)
|
|
84
|
+
return config;
|
|
85
|
+
const options = buildExpoGoogleFontsOptions(props);
|
|
86
|
+
const withFonts = require("expo-font/app.plugin").default;
|
|
87
|
+
return withFonts(config, options);
|
|
88
|
+
};
|
|
89
|
+
exports.default = (0, config_plugins_1.createRunOncePlugin)(withExpoGoogleFonts, "@vanenshi/expo-plugins/google-fonts", pkg.version);
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { default as withDisplayName } from "./display-name";
|
|
2
|
+
export type { DisplayNameProps } from "./display-name";
|
|
3
|
+
export { default as withExpoGoogleFonts, buildExpoGoogleFontsOptions, } from "./google-fonts";
|
|
4
|
+
export type { GoogleFontSpec, BuildExpoFontPluginOptionsInput, ExpoGoogleFontsProps, } from "./google-fonts";
|
package/build/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.buildExpoGoogleFontsOptions = exports.withExpoGoogleFonts = exports.withDisplayName = void 0;
|
|
7
|
+
var display_name_1 = require("./display-name");
|
|
8
|
+
Object.defineProperty(exports, "withDisplayName", { enumerable: true, get: function () { return __importDefault(display_name_1).default; } });
|
|
9
|
+
var google_fonts_1 = require("./google-fonts");
|
|
10
|
+
Object.defineProperty(exports, "withExpoGoogleFonts", { enumerable: true, get: function () { return __importDefault(google_fonts_1).default; } });
|
|
11
|
+
Object.defineProperty(exports, "buildExpoGoogleFontsOptions", { enumerable: true, get: function () { return google_fonts_1.buildExpoGoogleFontsOptions; } });
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createLogger = createLogger;
|
|
4
|
+
const warned = new Set();
|
|
5
|
+
const bold = (text) => `\x1b[1m${text}\x1b[22m`;
|
|
6
|
+
const yellow = (text) => `\x1b[33m${text}\x1b[39m`;
|
|
7
|
+
/**
|
|
8
|
+
* Scoped logger for Expo config plugins.
|
|
9
|
+
*/
|
|
10
|
+
function createLogger(scope) {
|
|
11
|
+
return {
|
|
12
|
+
warnAndroid(text) {
|
|
13
|
+
const warning = formatWarning({
|
|
14
|
+
platform: "android",
|
|
15
|
+
scope,
|
|
16
|
+
warning: text,
|
|
17
|
+
});
|
|
18
|
+
if (warned.has(warning))
|
|
19
|
+
return;
|
|
20
|
+
warned.add(warning);
|
|
21
|
+
console.warn(warning);
|
|
22
|
+
},
|
|
23
|
+
warnIOS(text) {
|
|
24
|
+
const warning = formatWarning({
|
|
25
|
+
platform: "ios",
|
|
26
|
+
scope,
|
|
27
|
+
warning: text,
|
|
28
|
+
});
|
|
29
|
+
if (warned.has(warning))
|
|
30
|
+
return;
|
|
31
|
+
warned.add(warning);
|
|
32
|
+
console.warn(warning);
|
|
33
|
+
},
|
|
34
|
+
warn(text) {
|
|
35
|
+
const warning = formatWarning({ warning: text });
|
|
36
|
+
if (warned.has(warning))
|
|
37
|
+
return;
|
|
38
|
+
warned.add(warning);
|
|
39
|
+
console.warn(formatWarning({ warning: text }));
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* » @expo-google-fonts/inter is not installed — run: npx expo install @expo-google-fonts/inter
|
|
45
|
+
* » @vanenshi/expo-plugins/display-name[android]: displayName is set — ignoring abstract "name": example-internal
|
|
46
|
+
* » @vanenshi/expo-plugins/display-name[ios]: displayName is set — ignoring abstract "name": example-internal
|
|
47
|
+
*/
|
|
48
|
+
function formatWarning({ platform, scope, warning, }) {
|
|
49
|
+
let prefix = "» ";
|
|
50
|
+
if (scope && platform) {
|
|
51
|
+
prefix += bold(`${scope}[${bold(platform)}]: `);
|
|
52
|
+
}
|
|
53
|
+
else if (scope) {
|
|
54
|
+
prefix += bold(`${scope}: `);
|
|
55
|
+
}
|
|
56
|
+
else if (platform) {
|
|
57
|
+
prefix += bold(`${platform}: `);
|
|
58
|
+
}
|
|
59
|
+
return yellow(`${prefix}${warning}`);
|
|
60
|
+
}
|
package/display-name.js
ADDED
package/google-fonts.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vanenshi/expo-plugins",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "A small collection of Expo config plugins.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"main": "build/index.js",
|
|
7
|
+
"types": "build/index.d.ts",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"files": [
|
|
10
|
+
"build",
|
|
11
|
+
"display-name.js",
|
|
12
|
+
"google-fonts.js"
|
|
13
|
+
],
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/vanenshi/expo-plugins.git"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"react",
|
|
20
|
+
"react-native",
|
|
21
|
+
"expo",
|
|
22
|
+
"config-plugin",
|
|
23
|
+
"expo-plugins"
|
|
24
|
+
],
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@expo/config-types": "^56.0.0",
|
|
30
|
+
"eslint": "^9.39.4",
|
|
31
|
+
"expo-module-scripts": "^56.0.3",
|
|
32
|
+
"jest": "^29.7.0"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"expo": "*",
|
|
36
|
+
"expo-font": "*"
|
|
37
|
+
},
|
|
38
|
+
"peerDependenciesMeta": {
|
|
39
|
+
"expo-font": {
|
|
40
|
+
"optional": true
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "pnpm clean && expo-module build",
|
|
45
|
+
"clean": "expo-module clean && pnpm clean:example",
|
|
46
|
+
"clean:example": "rm -rf example/node_modules/@vanenshi",
|
|
47
|
+
"lint": "expo-module lint",
|
|
48
|
+
"test": "expo-module test",
|
|
49
|
+
"expo-module": "expo-module"
|
|
50
|
+
}
|
|
51
|
+
}
|