expo-desktop 0.1.0 → 0.1.2
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/package.json +10 -2
- package/src/cli.ts +53 -3
- package/src/common/app-json.ts +35 -0
- package/src/common/apply-config-plugins.ts +76 -0
- package/src/common/arktype.ts +19 -0
- package/src/common/child-process.ts +55 -0
- package/src/common/clack.ts +11 -0
- package/src/{define-config.ts → common/define-config.ts} +2 -18
- package/src/common/npm.ts +199 -0
- package/src/common/with-internal.ts +19 -0
- package/src/create-app/command.ts +159 -0
- package/src/create-app/create-expo-desktop-app.ts +754 -0
- package/src/create-app/preview-file-tree.ts +50 -0
- package/src/create-app/prompt-for-version.ts +465 -0
- package/src/prebuild/command.ts +99 -0
- package/src/prebuild/resolve-options.ts +34 -0
- package/src/commands/new-expo-desktop-project.ts +0 -69
- /package/src/{commands/add-expo-desktop-to-existing-expo-app.ts → add-app/command.ts} +0 -0
- /package/src/{app-config.ts → common/app-config.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-desktop",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Best-effort desktop support for Expo",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"android",
|
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
"author": "Jamie Birch <14055146+shirakaba@users.noreply.github.com> (https://github.com/shirakaba)",
|
|
20
20
|
"repository": {
|
|
21
21
|
"type": "git",
|
|
22
|
-
"url": "
|
|
22
|
+
"url": "https://github.com/shirakaba/expo-desktop.git",
|
|
23
|
+
"directory": "packages/expo-desktop"
|
|
23
24
|
},
|
|
24
25
|
"bin": {
|
|
25
26
|
"expo-desktop": "src/cli.ts"
|
|
@@ -43,15 +44,22 @@
|
|
|
43
44
|
"@clack/prompts": "^1.2.0",
|
|
44
45
|
"arktype": "^2.2.0",
|
|
45
46
|
"citty": "^0.2.2",
|
|
47
|
+
"expo-desktop-prebuild-config": "workspace:",
|
|
46
48
|
"kleur": "^4.1.5",
|
|
47
49
|
"toml": "^4.1.1"
|
|
48
50
|
},
|
|
49
51
|
"devDependencies": {
|
|
52
|
+
"@expo/config": "catalog:react-native-81",
|
|
53
|
+
"@expo/config-plugins": "catalog:react-native-81",
|
|
50
54
|
"@tsconfig/node24": "catalog:base",
|
|
51
55
|
"@types/bun": "^1.3.13",
|
|
52
56
|
"@types/node": "catalog:base",
|
|
53
57
|
"@typescript/native-preview": "catalog:base"
|
|
54
58
|
},
|
|
59
|
+
"peerDependencies": {
|
|
60
|
+
"@expo/config": ">=12.0.0",
|
|
61
|
+
"@expo/config-plugins": ">=54.0.0"
|
|
62
|
+
},
|
|
55
63
|
"lint-staged": {
|
|
56
64
|
"scripts/update-schema.ts": [
|
|
57
65
|
"node --run schema",
|
package/src/cli.ts
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
import { defineCommand, runMain } from "citty";
|
|
4
4
|
import { default as kleur } from "kleur";
|
|
5
|
-
import { grey } from "kleur/colors";
|
|
5
|
+
import { dim, grey } from "kleur/colors";
|
|
6
6
|
|
|
7
7
|
import packageJson from "../package.json" with { type: "json" };
|
|
8
|
+
import { prebuild } from "./prebuild/command.ts";
|
|
8
9
|
|
|
9
10
|
const main = defineCommand({
|
|
10
11
|
meta: {
|
|
@@ -16,7 +17,7 @@ const main = defineCommand({
|
|
|
16
17
|
"create-app": defineCommand({
|
|
17
18
|
meta: { name: "create-app", description: "Create a new Expo Desktop project" },
|
|
18
19
|
args: {
|
|
19
|
-
|
|
20
|
+
"filesafe-name": {
|
|
20
21
|
type: "string",
|
|
21
22
|
description: `The ${kleur.bold("filesafe name")} for the app in alphanumeric format ${grey("(Example: 'MyApp123')")}`,
|
|
22
23
|
valueHint: "name",
|
|
@@ -31,9 +32,58 @@ const main = defineCommand({
|
|
|
31
32
|
description: `The ${kleur.bold("reverse DNS")} for the app ${grey("(Example: 'com.example.my-app-123')")}`,
|
|
32
33
|
valueHint: "name",
|
|
33
34
|
},
|
|
35
|
+
version: {
|
|
36
|
+
type: "string",
|
|
37
|
+
description: `The ${kleur.bold("minor version")} of React Native to align on ${grey("(Examples: '0.80', 'latest')")}`,
|
|
38
|
+
valueHint: "version",
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
async run({ args }) {
|
|
42
|
+
(await import("./create-app/command.ts")).newExpoDesktopProject(args);
|
|
43
|
+
},
|
|
44
|
+
}),
|
|
45
|
+
prebuild: defineCommand({
|
|
46
|
+
meta: { name: "prebuild", description: "Prebuild an Expo Desktop project" },
|
|
47
|
+
args: {
|
|
48
|
+
clean: {
|
|
49
|
+
type: "boolean",
|
|
50
|
+
description: "Delete the native folders and regenerate them before applying changes",
|
|
51
|
+
},
|
|
52
|
+
"no-install": {
|
|
53
|
+
type: "boolean",
|
|
54
|
+
description: "Skip installing npm packages and CocoaPods",
|
|
55
|
+
},
|
|
56
|
+
npm: {
|
|
57
|
+
type: "boolean",
|
|
58
|
+
description: `Use npm to install dependencies. ${dim("(Default when package-lock.json exists)")}`,
|
|
59
|
+
},
|
|
60
|
+
yarn: {
|
|
61
|
+
type: "boolean",
|
|
62
|
+
description: `Use yarn to install dependencies. ${dim("(Default when yarn.lock exists)")}`,
|
|
63
|
+
},
|
|
64
|
+
bun: {
|
|
65
|
+
type: "boolean",
|
|
66
|
+
description: `Use bun to install dependencies. ${dim("(Default when bun.lock exists)")}`,
|
|
67
|
+
},
|
|
68
|
+
pnpm: {
|
|
69
|
+
type: "boolean",
|
|
70
|
+
description: `Use pnpm to install dependencies. ${dim("(Default when pnpm-lock.yaml exists)")}`,
|
|
71
|
+
},
|
|
72
|
+
template: {
|
|
73
|
+
type: "string",
|
|
74
|
+
description:
|
|
75
|
+
"Project template to clone from. File path pointing to a local tar file, npm package or a github repo",
|
|
76
|
+
valueHint: "template",
|
|
77
|
+
},
|
|
78
|
+
platform: {
|
|
79
|
+
type: "string",
|
|
80
|
+
description: `Platforms to sync: macos, windows, desktop ${dim("(Default: desktop)")}`,
|
|
81
|
+
valueHint: "desktop|macos|windows",
|
|
82
|
+
alias: "p",
|
|
83
|
+
},
|
|
34
84
|
},
|
|
35
85
|
async run({ args }) {
|
|
36
|
-
(await import("./
|
|
86
|
+
(await import("./prebuild/command.ts")).prebuild(args);
|
|
37
87
|
},
|
|
38
88
|
}),
|
|
39
89
|
},
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type } from "arktype";
|
|
2
|
+
|
|
3
|
+
export const ExpoConfig = type({
|
|
4
|
+
"name?": "string",
|
|
5
|
+
"slug?": "string",
|
|
6
|
+
"platforms?": type('"android" | "ios" | "web" | "macos" | "windows"').array(),
|
|
7
|
+
"version?": "string",
|
|
8
|
+
"ios?": type({
|
|
9
|
+
"bundleIdentifier?": "string",
|
|
10
|
+
}),
|
|
11
|
+
"macos?": type({
|
|
12
|
+
"bundleIdentifier?": "string",
|
|
13
|
+
}),
|
|
14
|
+
// "windows?": type({
|
|
15
|
+
// "projectName?": "string",
|
|
16
|
+
// "displayName?": "string",
|
|
17
|
+
// "namespace?": "string",
|
|
18
|
+
// }),
|
|
19
|
+
"android?": type({
|
|
20
|
+
"package?": "string",
|
|
21
|
+
}),
|
|
22
|
+
"plugins?": type(type(["string", "...", "unknown[]"]).or("string")).array(),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const AppJson = type({
|
|
26
|
+
"expo?": ExpoConfig,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export const PackageJson = type({
|
|
30
|
+
"name?": "string",
|
|
31
|
+
"scripts?": "Record<string, string>",
|
|
32
|
+
"dependencies?": "Record<string, string>",
|
|
33
|
+
"devDependencies?": "Record<string, string>",
|
|
34
|
+
"peerDependencies?": "Record<string, string>",
|
|
35
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
|
|
4
|
+
import { withInternal } from "./with-internal.ts";
|
|
5
|
+
|
|
6
|
+
const require = createRequire(import.meta.url);
|
|
7
|
+
const { getPrebuildConfigAsync } =
|
|
8
|
+
require("expo-desktop-prebuild-config") as typeof import("expo-desktop-prebuild-config");
|
|
9
|
+
const { compileModsAsync } =
|
|
10
|
+
require("expo-desktop-config-plugins") as typeof import("expo-desktop-config-plugins");
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Applies config plugins.
|
|
14
|
+
* @see https://github.com/microsoft/react-native-test-app/blob/trunk/packages/app/scripts/config-plugins/apply.mjs
|
|
15
|
+
*/
|
|
16
|
+
export async function applyConfigPlugins(
|
|
17
|
+
options: PrebuildOptions,
|
|
18
|
+
): Promise<Awaited<ReturnType<typeof compileModsAsync>> | undefined> {
|
|
19
|
+
const { projectRoot } = options;
|
|
20
|
+
|
|
21
|
+
// To avoid making expo-desktop depend on Expo SDK 54 when we might be running
|
|
22
|
+
// on an Expo 55 project, we import Expo deps from the project itself.
|
|
23
|
+
let expoConfigModule: typeof import("@expo/config");
|
|
24
|
+
try {
|
|
25
|
+
expoConfigModule = require(
|
|
26
|
+
path.dirname(require.resolve("@expo/config/package.json", { paths: [projectRoot] })),
|
|
27
|
+
);
|
|
28
|
+
} catch (cause) {
|
|
29
|
+
throw new Error(
|
|
30
|
+
`Error importing "@expo/config" relative to projectRoot "${projectRoot}". Make sure to install node modules before running any prebuilds, and make sure that the project depends on the package named "expo".`,
|
|
31
|
+
{ cause },
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
const { getConfig } = expoConfigModule;
|
|
35
|
+
|
|
36
|
+
let expoConfigPluginsModule: typeof import("@expo/config-plugins");
|
|
37
|
+
try {
|
|
38
|
+
expoConfigPluginsModule = require(
|
|
39
|
+
path.dirname(require.resolve("@expo/config-plugins/package.json", { paths: [projectRoot] })),
|
|
40
|
+
);
|
|
41
|
+
} catch (cause) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`Error importing "@expo/config-plugins" relative to projectRoot "${projectRoot}". Make sure to install node modules before running any prebuilds, and make sure that the project depends on the package named "expo".`,
|
|
44
|
+
{ cause },
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
const { withPlugins } = expoConfigPluginsModule;
|
|
48
|
+
|
|
49
|
+
// (1) Filter out platforms that aren't in the app.json.
|
|
50
|
+
// https://github.com/expo/expo/blob/8dd645080f52927e2a8bf406167da7241a1d46d8/packages/%40expo/cli/src/prebuild/prebuildAsync.ts#L74
|
|
51
|
+
let { exp: expoConfig } = getConfig(projectRoot);
|
|
52
|
+
const { platforms, plugins } = expoConfig;
|
|
53
|
+
if (platforms?.length) {
|
|
54
|
+
const finalPlatforms = options.platforms.filter((platform) => platforms.includes(platform));
|
|
55
|
+
if (finalPlatforms.length > 0) {
|
|
56
|
+
options.platforms = finalPlatforms;
|
|
57
|
+
} else {
|
|
58
|
+
const requestedPlatforms = options.platforms.join(", ");
|
|
59
|
+
console.warn(
|
|
60
|
+
`⚠️ Requested prebuild for "${requestedPlatforms}", but only "${platforms.join(", ")}" is present in app config ("expo.platforms" entry). Continuing with "${requestedPlatforms}".`,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const prebuildConfig = await getPrebuildConfigAsync(projectRoot, options);
|
|
66
|
+
expoConfig = prebuildConfig.exp;
|
|
67
|
+
|
|
68
|
+
return compileModsAsync(
|
|
69
|
+
withPlugins(withInternal(expoConfig, options), plugins as Array<string>),
|
|
70
|
+
options,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export type PrebuildOptions = {
|
|
75
|
+
projectRoot: string;
|
|
76
|
+
} & Parameters<typeof getPrebuildConfigAsync>[1];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ArkErrors } from "arktype";
|
|
2
|
+
|
|
3
|
+
import { green, grey, yellow } from "kleur/colors";
|
|
4
|
+
|
|
5
|
+
export function makePrettySummary({ byPath }: ArkErrors) {
|
|
6
|
+
const summary = new Array<string>();
|
|
7
|
+
|
|
8
|
+
for (const [path, error] of Object.entries(byPath)) {
|
|
9
|
+
const actualIndex = error.problem.lastIndexOf(" (was ");
|
|
10
|
+
const problem =
|
|
11
|
+
actualIndex === -1
|
|
12
|
+
? error.problem
|
|
13
|
+
: `${error.problem.startsWith("must be matched by ") ? `must be matched by ${green(error.problem.slice("must be matched by ".length, actualIndex))}` : error.problem.slice(0, actualIndex)}${grey(error.problem.slice(actualIndex))}`;
|
|
14
|
+
|
|
15
|
+
summary.push(`${yellow(path)} ${problem}`);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return summary;
|
|
19
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { log } from "@clack/prompts";
|
|
2
|
+
import { spawn, type SpawnOptions } from "node:child_process";
|
|
3
|
+
import { env } from "node:process";
|
|
4
|
+
import readline from "node:readline";
|
|
5
|
+
|
|
6
|
+
export async function promisifiedSpawn({
|
|
7
|
+
command,
|
|
8
|
+
args,
|
|
9
|
+
options = {},
|
|
10
|
+
}: {
|
|
11
|
+
command: string;
|
|
12
|
+
args: Array<string>;
|
|
13
|
+
options?: SpawnOptions;
|
|
14
|
+
}) {
|
|
15
|
+
const cp = spawn(command, args, { env, ...options });
|
|
16
|
+
|
|
17
|
+
const { stdout, stderr } = cp;
|
|
18
|
+
if (
|
|
19
|
+
stdout &&
|
|
20
|
+
(Array.isArray(options?.stdio) ? options?.stdio.at(1) : options?.stdio) !== "inherit"
|
|
21
|
+
) {
|
|
22
|
+
readline
|
|
23
|
+
.createInterface({ input: stdout })
|
|
24
|
+
.on("line", (line) => log.message(line, { spacing: 0 }));
|
|
25
|
+
}
|
|
26
|
+
if (
|
|
27
|
+
stderr &&
|
|
28
|
+
(Array.isArray(options?.stdio) ? options?.stdio.at(2) : options?.stdio) !== "inherit"
|
|
29
|
+
) {
|
|
30
|
+
readline
|
|
31
|
+
.createInterface({ input: stderr })
|
|
32
|
+
.on("line", (line) => log.message(line, { spacing: 0 }));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let cpError: Error | null = null;
|
|
36
|
+
cp.on("error", (error) => {
|
|
37
|
+
if (!cpError) {
|
|
38
|
+
cpError = error;
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const { promise, resolve, reject } = Promise.withResolvers<void>();
|
|
43
|
+
cp.on("close", (code, signal) => {
|
|
44
|
+
if (cpError || code !== 0) {
|
|
45
|
+
reject(
|
|
46
|
+
new Error(`Exited with code ${code} (signal ${signal})`, cpError ? { cause: cpError } : {}),
|
|
47
|
+
);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
resolve();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
return promise;
|
|
55
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { log, type LogMessageOptions } from "@clack/prompts";
|
|
2
|
+
import kleur from "kleur";
|
|
3
|
+
|
|
4
|
+
export function title(text: string, opts?: LogMessageOptions) {
|
|
5
|
+
const { spacing, ...rest } = opts ?? {};
|
|
6
|
+
|
|
7
|
+
log.info(kleur.bold(kleur.inverse(` ${text} `)), { withGuide: false, ...rest });
|
|
8
|
+
if (opts?.spacing) {
|
|
9
|
+
log.message("", { withGuide: false, spacing: Math.max(0, opts.spacing - 1) });
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { green, grey, yellow } from "kleur/colors";
|
|
1
|
+
import { type } from "arktype";
|
|
3
2
|
|
|
4
3
|
import { AppConfig, PartialAppConfig } from "./app-config.ts";
|
|
4
|
+
import { makePrettySummary } from "./arktype.ts";
|
|
5
5
|
|
|
6
6
|
export function defineAppConfig(config: typeof PartialAppConfig.infer) {
|
|
7
7
|
const partial = PartialAppConfig(config);
|
|
@@ -49,19 +49,3 @@ export function defineAppConfig(config: typeof PartialAppConfig.infer) {
|
|
|
49
49
|
|
|
50
50
|
return full;
|
|
51
51
|
}
|
|
52
|
-
|
|
53
|
-
function makePrettySummary({ byPath }: ArkErrors) {
|
|
54
|
-
const summary = new Array<string>();
|
|
55
|
-
|
|
56
|
-
for (const [path, error] of Object.entries(byPath)) {
|
|
57
|
-
const actualIndex = error.problem.lastIndexOf(" (was ");
|
|
58
|
-
const problem =
|
|
59
|
-
actualIndex === -1
|
|
60
|
-
? error.problem
|
|
61
|
-
: `${error.problem.startsWith("must be matched by ") ? `must be matched by ${green(error.problem.slice("must be matched by ".length, actualIndex))}` : error.problem.slice(0, actualIndex)}${grey(error.problem.slice(actualIndex))}`;
|
|
62
|
-
|
|
63
|
-
summary.push(`${yellow(path)} ${problem}`);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return summary;
|
|
67
|
-
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { type } from "arktype";
|
|
2
|
+
|
|
3
|
+
import { makePrettySummary } from "./arktype.ts";
|
|
4
|
+
|
|
5
|
+
export async function getPackageInfo(packageName: string) {
|
|
6
|
+
const res = await fetch(`https://registry.npmjs.org/${packageName}`);
|
|
7
|
+
const data = await res.json();
|
|
8
|
+
|
|
9
|
+
const response = NpmResponse(data);
|
|
10
|
+
if (response instanceof type.errors) {
|
|
11
|
+
// console.log(`Invalid config:\n${makePrettySummary(partial).join("\n")}`);
|
|
12
|
+
throw new Error(`Invalid config:\n${makePrettySummary(response).join("\n")}`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return response;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function filterVersions({
|
|
19
|
+
npmInfo,
|
|
20
|
+
distTag,
|
|
21
|
+
fromMajor,
|
|
22
|
+
fromMinor,
|
|
23
|
+
fromPatch,
|
|
24
|
+
includePrereleases,
|
|
25
|
+
}: {
|
|
26
|
+
npmInfo: NpmResponseType;
|
|
27
|
+
distTag?: string;
|
|
28
|
+
fromMajor?: number;
|
|
29
|
+
fromMinor?: number;
|
|
30
|
+
fromPatch?: number;
|
|
31
|
+
includePrereleases?: boolean;
|
|
32
|
+
}) {
|
|
33
|
+
const map: VersionsMap = {};
|
|
34
|
+
const { "dist-tags": distTags, versions } = npmInfo;
|
|
35
|
+
|
|
36
|
+
if (distTag) {
|
|
37
|
+
const version = distTags[distTag];
|
|
38
|
+
|
|
39
|
+
const match = semverMatcher.exec(version);
|
|
40
|
+
if (match) {
|
|
41
|
+
const [fullMatch, major, minor, patch, prerelease, _buildmetadata] = match;
|
|
42
|
+
const majorInt = parseInt(major);
|
|
43
|
+
const minorInt = parseInt(minor);
|
|
44
|
+
const patchInt = parseInt(patch);
|
|
45
|
+
|
|
46
|
+
if (!map[majorInt][minorInt][patchInt]) {
|
|
47
|
+
map[majorInt][minorInt][patchInt] = {
|
|
48
|
+
prereleases: [],
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!!prerelease) {
|
|
53
|
+
map[majorInt][minorInt][patchInt].prereleases.push(fullMatch);
|
|
54
|
+
} else {
|
|
55
|
+
map[majorInt][minorInt][patchInt].release = fullMatch;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return { filtered: [version], map };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const filtered = new Array<string>();
|
|
63
|
+
for (const version of versions) {
|
|
64
|
+
const match = semverMatcher.exec(version);
|
|
65
|
+
if (!match) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
const [fullMatch, major, minor, patch, prerelease, _buildmetadata] = match;
|
|
69
|
+
const majorInt = parseInt(major);
|
|
70
|
+
const minorInt = parseInt(minor);
|
|
71
|
+
const patchInt = parseInt(patch);
|
|
72
|
+
|
|
73
|
+
if (typeof fromMajor === "number" && majorInt < fromMajor) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (typeof fromMinor === "number" && minorInt < fromMinor) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (typeof fromPatch === "number" && patchInt < fromPatch) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (!includePrereleases && !!prerelease) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
filtered.push(fullMatch);
|
|
86
|
+
|
|
87
|
+
if (!map[majorInt]) {
|
|
88
|
+
map[majorInt] = {};
|
|
89
|
+
}
|
|
90
|
+
if (!map[majorInt][minorInt]) {
|
|
91
|
+
map[majorInt][minorInt] = {};
|
|
92
|
+
}
|
|
93
|
+
if (!map[majorInt][minorInt][patchInt]) {
|
|
94
|
+
map[majorInt][minorInt][patchInt] = {
|
|
95
|
+
prereleases: [],
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!!prerelease) {
|
|
100
|
+
map[majorInt][minorInt][patchInt].prereleases.push(fullMatch);
|
|
101
|
+
} else {
|
|
102
|
+
map[majorInt][minorInt][patchInt].release = fullMatch;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return { filtered, map };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
type VersionsMap = {
|
|
110
|
+
[major: number]: {
|
|
111
|
+
[minor: number]: { [patch: number]: { release?: string; prereleases: Array<string> } };
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
type HighestStableMinorMap = {
|
|
115
|
+
[major: number]: {
|
|
116
|
+
[minor: number]: string;
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export function getHighestStableMinors(map: VersionsMap) {
|
|
121
|
+
const minorMap: HighestStableMinorMap = {};
|
|
122
|
+
|
|
123
|
+
for (const major in map) {
|
|
124
|
+
const majorInt = parseInt(major);
|
|
125
|
+
if (!minorMap[majorInt]) {
|
|
126
|
+
minorMap[majorInt] = {};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
for (const minor in map[major]) {
|
|
130
|
+
const minorInt = parseInt(minor);
|
|
131
|
+
|
|
132
|
+
for (const patch in map[major][minor]) {
|
|
133
|
+
const patchInt = parseInt(patch);
|
|
134
|
+
if (!map[major][minor][patch].release) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const current = minorMap[majorInt][minorInt];
|
|
139
|
+
if (!current) {
|
|
140
|
+
minorMap[majorInt][minorInt] = map[major][minor][patch].release;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const currentMatch = semverMatcher.exec(current);
|
|
145
|
+
if (!currentMatch) {
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const [, , , currentPatch] = currentMatch;
|
|
149
|
+
|
|
150
|
+
if (patchInt <= parseInt(currentPatch)) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
minorMap[majorInt][minorInt] = `${major}.${minor}.${patch}`;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return minorMap;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const NpmResponse = type({
|
|
163
|
+
name: "string",
|
|
164
|
+
"dist-tags": "Record<string, string.semver>",
|
|
165
|
+
|
|
166
|
+
versions: type("Record<string, Record<string, unknown>>").pipe((record) => Object.keys(record)),
|
|
167
|
+
|
|
168
|
+
// Other potentially useful fields that we'll avoid needlessly validating for
|
|
169
|
+
// now:
|
|
170
|
+
// time: "Record<string, string.date.iso>",
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
export type NpmResponseType = typeof NpmResponse.inferOut;
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Exposing the underlying pattern used by string.semver.
|
|
177
|
+
* @see https://semver.org/
|
|
178
|
+
*/
|
|
179
|
+
export const semverMatcher =
|
|
180
|
+
/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[A-Za-z-][\dA-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][\dA-Za-z-]*))*))?(?:\+([\dA-Za-z-]+(?:\.[\dA-Za-z-]+)*))?$/;
|
|
181
|
+
|
|
182
|
+
export function packageManagerExec(packageManager: "npm" | "bun" | "pnpm") {
|
|
183
|
+
const args = new Array<string>();
|
|
184
|
+
let command: string;
|
|
185
|
+
switch (packageManager) {
|
|
186
|
+
case "bun":
|
|
187
|
+
command = "bunx";
|
|
188
|
+
break;
|
|
189
|
+
case "npm":
|
|
190
|
+
command = "npm";
|
|
191
|
+
args.push("dlx");
|
|
192
|
+
break;
|
|
193
|
+
case "pnpm":
|
|
194
|
+
command = "pnpm";
|
|
195
|
+
args.push("dlx");
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return { args, command };
|
|
199
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ConfigPlugin } from "@expo/config-plugins";
|
|
2
|
+
|
|
3
|
+
import type { PrebuildOptions } from "./apply-config-plugins.ts";
|
|
4
|
+
|
|
5
|
+
type Internals = Omit<PrebuildOptions, "appJsonPath">;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @see https://github.com/microsoft/react-native-test-app/blob/0951cf5a3727c01d2ef25540eb796eb56b14ae04/packages/app/scripts/config-plugins/plugins/withInternal.mjs#L9
|
|
9
|
+
*/
|
|
10
|
+
export const withInternal: ConfigPlugin<Internals> = (config, internals) => {
|
|
11
|
+
// @ts-ignore TODO
|
|
12
|
+
config._internal = {
|
|
13
|
+
isDebug: false,
|
|
14
|
+
// @ts-ignore TODO
|
|
15
|
+
...config._internal,
|
|
16
|
+
...internals,
|
|
17
|
+
};
|
|
18
|
+
return config;
|
|
19
|
+
};
|