@better-update/cli 0.40.0 → 0.41.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/dist/index.mjs +191 -35
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
package/dist/index.mjs
CHANGED
|
@@ -35,7 +35,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
35
35
|
|
|
36
36
|
//#endregion
|
|
37
37
|
//#region package.json
|
|
38
|
-
var version = "0.
|
|
38
|
+
var version = "0.41.0";
|
|
39
39
|
|
|
40
40
|
//#endregion
|
|
41
41
|
//#region src/lib/interactive-mode.ts
|
|
@@ -19942,20 +19942,98 @@ const walkUpForLockfile = (startCwd, dir) => Effect.gen(function* () {
|
|
|
19942
19942
|
*/
|
|
19943
19943
|
const detectWorkspaceRoot = (cwd) => walkUpForLockfile(cwd, cwd);
|
|
19944
19944
|
/**
|
|
19945
|
-
*
|
|
19946
|
-
*
|
|
19947
|
-
*
|
|
19945
|
+
* Rebase a single nested `.gitignore` line so it applies only within `prefix`
|
|
19946
|
+
* (the posix dir path + trailing slash) when folded into the workspace-root
|
|
19947
|
+
* matcher. Returns `undefined` for blanks/comments. Anchored patterns (a
|
|
19948
|
+
* leading or interior `/`) are prefixed as-is; unanchored ones (which match at
|
|
19949
|
+
* any depth) become `prefix**\/pat`. Negation and directory-only trailing
|
|
19950
|
+
* slashes are preserved.
|
|
19951
|
+
*/
|
|
19952
|
+
const rebaseGitignoreLine = (prefix, rawLine) => {
|
|
19953
|
+
const line = rawLine.replace(/(?<!\\)\s+$/u, "");
|
|
19954
|
+
if (line === "" || line.startsWith("#")) return;
|
|
19955
|
+
const negate = line.startsWith("!");
|
|
19956
|
+
const unescaped = negate || line.startsWith(String.raw`\#`) || line.startsWith(String.raw`\!`) ? line.slice(1) : line;
|
|
19957
|
+
const hadLeadingSlash = unescaped.startsWith("/");
|
|
19958
|
+
const body = hadLeadingSlash ? unescaped.slice(1) : unescaped;
|
|
19959
|
+
if (body === "") return;
|
|
19960
|
+
const withoutTrailingSlash = body.endsWith("/") ? body.slice(0, -1) : body;
|
|
19961
|
+
const rebased = hadLeadingSlash || withoutTrailingSlash.includes("/") ? `${prefix}${body}` : `${prefix}**/${body}`;
|
|
19962
|
+
return negate ? `!${rebased}` : rebased;
|
|
19963
|
+
};
|
|
19964
|
+
/**
|
|
19965
|
+
* Rebase every line of a nested `.gitignore` to its own directory (`relDir`,
|
|
19966
|
+
* posix, relative to the workspace root) — mirroring how git scopes a
|
|
19967
|
+
* `.gitignore` to the dir it lives in.
|
|
19968
|
+
*/
|
|
19969
|
+
const rebaseGitignore = (relDir, content) => {
|
|
19970
|
+
const prefix = `${relDir}/`;
|
|
19971
|
+
return content.split(/\r?\n/u).map((rawLine) => rebaseGitignoreLine(prefix, rawLine)).filter((pattern) => pattern !== void 0);
|
|
19972
|
+
};
|
|
19973
|
+
const safeReadDir = async (dir) => {
|
|
19974
|
+
try {
|
|
19975
|
+
return await promises.readdir(dir, { withFileTypes: true });
|
|
19976
|
+
} catch {
|
|
19977
|
+
return [];
|
|
19978
|
+
}
|
|
19979
|
+
};
|
|
19980
|
+
const safeReadText = async (file) => {
|
|
19981
|
+
try {
|
|
19982
|
+
return await promises.readFile(file, "utf8");
|
|
19983
|
+
} catch {
|
|
19984
|
+
return "";
|
|
19985
|
+
}
|
|
19986
|
+
};
|
|
19987
|
+
const isDirectory = async (file) => {
|
|
19988
|
+
try {
|
|
19989
|
+
return (await promises.lstat(file)).isDirectory();
|
|
19990
|
+
} catch {
|
|
19991
|
+
return false;
|
|
19992
|
+
}
|
|
19993
|
+
};
|
|
19994
|
+
/**
|
|
19995
|
+
* Fold every NESTED `.gitignore` under `workspaceRoot` into `ig`, each rebased
|
|
19996
|
+
* to its own directory (git scopes a `.gitignore` to the dir it lives in, and
|
|
19997
|
+
* EAS — which stages via git — honors that). The walk prunes any directory the
|
|
19998
|
+
* matcher-so-far already ignores, so it never descends into `node_modules`,
|
|
19999
|
+
* build outputs, or a subtree excluded by a shallower `.gitignore` — including
|
|
20000
|
+
* the entries a just-loaded nested `.gitignore` adds. The root `.gitignore` is
|
|
20001
|
+
* added by the caller, so the walk skips it.
|
|
20002
|
+
*/
|
|
20003
|
+
const addNestedGitignores = (workspaceRoot, ig) => Effect.promise(async () => {
|
|
20004
|
+
const walk = async (absDir, relDir) => {
|
|
20005
|
+
const entries = await safeReadDir(absDir);
|
|
20006
|
+
if (relDir !== "" && entries.some((entry) => entry.isFile() && entry.name === ".gitignore")) {
|
|
20007
|
+
const content = await safeReadText(path.join(absDir, ".gitignore"));
|
|
20008
|
+
if (content !== "") ig.add(rebaseGitignore(relDir, content));
|
|
20009
|
+
}
|
|
20010
|
+
const subdirs = entries.filter((entry) => entry.isDirectory());
|
|
20011
|
+
for (const entry of subdirs) {
|
|
20012
|
+
const childRel = relDir === "" ? entry.name : `${relDir}/${entry.name}`;
|
|
20013
|
+
if (!ig.ignores(childRel) && !ig.ignores(`${childRel}/`)) await walk(path.join(absDir, entry.name), childRel);
|
|
20014
|
+
}
|
|
20015
|
+
};
|
|
20016
|
+
await walk(workspaceRoot, "");
|
|
20017
|
+
});
|
|
20018
|
+
/**
|
|
20019
|
+
* Build an `Ignore` matcher for the workspace root. `.easignore` REPLACES every
|
|
20020
|
+
* `.gitignore` when present (matches EAS semantics); otherwise the root
|
|
20021
|
+
* `.gitignore` plus every NESTED `.gitignore` (git semantics) is layered on top
|
|
20022
|
+
* of the always-ignore baseline.
|
|
19948
20023
|
*
|
|
19949
20024
|
* When `includeNativeSource` is set, the native source dirs are re-included
|
|
19950
|
-
*
|
|
19951
|
-
*
|
|
20025
|
+
* before the nested scan (so the scan descends into committed `android/`/`ios/`
|
|
20026
|
+
* and folds in their nested ignores), then their build outputs re-excluded last,
|
|
20027
|
+
* so a committed `ios/`/`android/` reaches staging intact.
|
|
19952
20028
|
*/
|
|
19953
20029
|
const buildIgnoreInstance = (workspaceRoot, options = {}) => Effect.gen(function* () {
|
|
19954
20030
|
const fs = yield* FileSystem.FileSystem;
|
|
19955
20031
|
const ig = ignore();
|
|
19956
20032
|
ig.add([...ALWAYS_IGNORE]);
|
|
20033
|
+
const base = options.appRelPath === void 0 || options.appRelPath === "" ? "" : `${options.appRelPath}/`;
|
|
19957
20034
|
const easignorePath = path.join(workspaceRoot, ".easignore");
|
|
19958
|
-
|
|
20035
|
+
const hasEasignore = yield* fs.exists(easignorePath).pipe(Effect.orElseSucceed(() => false));
|
|
20036
|
+
if (hasEasignore) {
|
|
19959
20037
|
const content = yield* fs.readFileString(easignorePath).pipe(Effect.orElseSucceed(() => ""));
|
|
19960
20038
|
ig.add(content);
|
|
19961
20039
|
} else {
|
|
@@ -19965,11 +20043,9 @@ const buildIgnoreInstance = (workspaceRoot, options = {}) => Effect.gen(function
|
|
|
19965
20043
|
ig.add(content);
|
|
19966
20044
|
}
|
|
19967
20045
|
}
|
|
19968
|
-
if (options.includeNativeSource === true) {
|
|
19969
|
-
|
|
19970
|
-
|
|
19971
|
-
ig.add(NATIVE_BUILD_OUTPUTS.map((entry) => `${base}${entry}`));
|
|
19972
|
-
}
|
|
20046
|
+
if (options.includeNativeSource === true) ig.add([`!${base}android`, `!${base}ios`]);
|
|
20047
|
+
if (!hasEasignore) yield* addNestedGitignores(workspaceRoot, ig);
|
|
20048
|
+
if (options.includeNativeSource === true) ig.add(NATIVE_BUILD_OUTPUTS.map((entry) => `${base}${entry}`));
|
|
19973
20049
|
return ig;
|
|
19974
20050
|
});
|
|
19975
20051
|
const copyProjectTree = (params) => Effect.tryPromise({
|
|
@@ -19977,11 +20053,12 @@ const copyProjectTree = (params) => Effect.tryPromise({
|
|
|
19977
20053
|
await promises.cp(params.source, params.dest, {
|
|
19978
20054
|
recursive: true,
|
|
19979
20055
|
dereference: false,
|
|
19980
|
-
filter: (src) => {
|
|
20056
|
+
filter: async (src) => {
|
|
19981
20057
|
const rel = path.relative(params.source, src);
|
|
19982
20058
|
if (rel === "") return true;
|
|
19983
20059
|
const posixRel = rel.split(path.sep).join("/");
|
|
19984
|
-
|
|
20060
|
+
const isDir = await isDirectory(src);
|
|
20061
|
+
return !params.ig.ignores(isDir ? `${posixRel}/` : posixRel);
|
|
19985
20062
|
}
|
|
19986
20063
|
});
|
|
19987
20064
|
},
|
|
@@ -23929,6 +24006,26 @@ const discoverSignedTargets = (options) => Effect.gen(function* () {
|
|
|
23929
24006
|
if (results.length === 0) return yield* new XcodeProjectError({ message: `No signed native targets found in ${pbxprojPath} for configuration "${options.configurationName}".` });
|
|
23930
24007
|
return results;
|
|
23931
24008
|
});
|
|
24009
|
+
/**
|
|
24010
|
+
* Like {@link discoverSignedTargets}, but tolerant of a project that hasn't been
|
|
24011
|
+
* prebuilt: returns `undefined` when `iosDir` is absent or contains no
|
|
24012
|
+
* `.xcodeproj` (a managed Expo app before `expo prebuild`). A genuine parse
|
|
24013
|
+
* error in an existing project still surfaces. Used by `credentials configure`,
|
|
24014
|
+
* which runs outside a build and must degrade to the main bundle id from the
|
|
24015
|
+
* Expo config when the native project isn't generated yet.
|
|
24016
|
+
*/
|
|
24017
|
+
const discoverSignedTargetsIfPresent = (options) => Effect.gen(function* () {
|
|
24018
|
+
const fs = yield* FileSystem.FileSystem;
|
|
24019
|
+
if (!(yield* fs.exists(options.iosDir).pipe(Effect.orElseSucceed(() => false)))) return;
|
|
24020
|
+
if (!(yield* fs.readDirectory(options.iosDir).pipe(Effect.orElseSucceed(() => []))).some((entry) => entry.endsWith(".xcodeproj"))) return;
|
|
24021
|
+
return yield* discoverSignedTargets(options);
|
|
24022
|
+
});
|
|
24023
|
+
/**
|
|
24024
|
+
* Pick the "main" target from a discovered set: the application product type if
|
|
24025
|
+
* present, otherwise the first signed target. Shared by the build pipeline and
|
|
24026
|
+
* `credentials configure` so both agree on which bundle id is primary.
|
|
24027
|
+
*/
|
|
24028
|
+
const pickMainTarget = (signedTargets) => signedTargets.find((target) => target.productType === "com.apple.product-type.application") ?? signedTargets[0];
|
|
23932
24029
|
|
|
23933
24030
|
//#endregion
|
|
23934
24031
|
//#region src/lib/xcpretty-formatter.ts
|
|
@@ -24160,7 +24257,6 @@ const installProfileForTarget = (target, profileByBundle) => {
|
|
|
24160
24257
|
installed
|
|
24161
24258
|
})));
|
|
24162
24259
|
};
|
|
24163
|
-
const pickMainTarget = (signedTargets) => signedTargets.find((target) => target.productType === "com.apple.product-type.application") ?? signedTargets[0];
|
|
24164
24260
|
const runIosDeviceBuild = (input) => Effect.gen(function* () {
|
|
24165
24261
|
const { api, tempDir, projectRoot, iosProfile, envVars } = input;
|
|
24166
24262
|
const runtime = yield* CliRuntime;
|
|
@@ -24783,6 +24879,7 @@ const runBuildWorkflow = (options) => Effect.scoped(Effect.gen(function* () {
|
|
|
24783
24879
|
BETTER_UPDATE_BUILD_PLATFORM: platform,
|
|
24784
24880
|
BETTER_UPDATE_BUILD_PROFILE: profile.name,
|
|
24785
24881
|
BETTER_UPDATE_BUILD_PROJECT_ID: projectId,
|
|
24882
|
+
EXPO_NO_GIT_STATUS: "1",
|
|
24786
24883
|
...compact({ BETTER_UPDATE_BUILD_GIT_COMMIT_HASH: rawGitContext.commit })
|
|
24787
24884
|
};
|
|
24788
24885
|
const { appMeta, runtimeVersion } = isExpo ? yield* resolveExpoBuildMeta({
|
|
@@ -28346,6 +28443,12 @@ const accessCommand = defineCommand({
|
|
|
28346
28443
|
|
|
28347
28444
|
//#endregion
|
|
28348
28445
|
//#region src/commands/credentials/configure.ts
|
|
28446
|
+
/**
|
|
28447
|
+
* Xcode build configuration whose `PRODUCT_BUNDLE_IDENTIFIER`s the configure
|
|
28448
|
+
* wizard reads when discovering signed targets. Distribution signing always
|
|
28449
|
+
* runs against the Release configuration.
|
|
28450
|
+
*/
|
|
28451
|
+
const IOS_DISCOVERY_CONFIGURATION = "Release";
|
|
28349
28452
|
const bindAndroidFcmGsa = (api, input) => Effect.gen(function* () {
|
|
28350
28453
|
const app = (yield* api.androidApplicationIdentifiers.list({ path: { projectId: input.projectId } })).items.find((entry) => entry.packageName === input.applicationIdentifier);
|
|
28351
28454
|
if (app === void 0) return yield* new MissingCredentialsError({
|
|
@@ -28440,6 +28543,68 @@ const configureIos = (args) => Effect.gen(function* () {
|
|
|
28440
28543
|
yield* printHuman("Run with --rebind to switch certificate, profile, or ASC key.");
|
|
28441
28544
|
yield* printHuman("Run with --bind-push-key <id> / --bind-asc-key <id> to update a single binding.");
|
|
28442
28545
|
});
|
|
28546
|
+
const configureIosTargets = (args) => Effect.gen(function* () {
|
|
28547
|
+
yield* printHuman(`Configuring iOS credentials for ${args.targets.length} signed target(s) (${args.distribution})...`);
|
|
28548
|
+
yield* Effect.forEach(args.targets, (target) => Effect.gen(function* () {
|
|
28549
|
+
const input = {
|
|
28550
|
+
projectId: args.projectId,
|
|
28551
|
+
bundleIdentifier: target.bundleId,
|
|
28552
|
+
distribution: args.distribution
|
|
28553
|
+
};
|
|
28554
|
+
yield* printHuman("");
|
|
28555
|
+
yield* printHuman(`${target.targetName} (${target.bundleId})`);
|
|
28556
|
+
yield* ensureIosCredentials(args.api, input, { freezeCredentials: false });
|
|
28557
|
+
yield* showIosBinding(args.api, input);
|
|
28558
|
+
}), { concurrency: 1 });
|
|
28559
|
+
yield* printHuman("");
|
|
28560
|
+
yield* printHuman("Run with --bundle <id> --rebind to switch a target's certificate, profile, or ASC key.");
|
|
28561
|
+
yield* printHuman("Run with --bundle <id> --bind-push-key <id> / --bind-asc-key <id> to update a single binding.");
|
|
28562
|
+
});
|
|
28563
|
+
const runConfigureIos = (args) => Effect.gen(function* () {
|
|
28564
|
+
const { api, projectId, root, distribution } = args;
|
|
28565
|
+
const singleBundleOnly = args.bundle !== void 0 || args.rebind || args.bindPushKey !== void 0 || args.bindAscKey !== void 0;
|
|
28566
|
+
const iosMeta = yield* readAppMetaOptional(root, "ios");
|
|
28567
|
+
if (!singleBundleOnly) {
|
|
28568
|
+
const targets = yield* discoverSignedTargetsIfPresent({
|
|
28569
|
+
iosDir: path.join(root, "ios"),
|
|
28570
|
+
configurationName: IOS_DISCOVERY_CONFIGURATION
|
|
28571
|
+
});
|
|
28572
|
+
const main = targets === void 0 ? void 0 : pickMainTarget(targets);
|
|
28573
|
+
if (targets !== void 0 && main !== void 0) {
|
|
28574
|
+
yield* configureIosTargets({
|
|
28575
|
+
api,
|
|
28576
|
+
projectId,
|
|
28577
|
+
distribution,
|
|
28578
|
+
targets
|
|
28579
|
+
});
|
|
28580
|
+
return {
|
|
28581
|
+
platform: "ios",
|
|
28582
|
+
projectId,
|
|
28583
|
+
distribution,
|
|
28584
|
+
bundleIdentifier: main.bundleId,
|
|
28585
|
+
bundleIdentifiers: targets.map((target) => target.bundleId)
|
|
28586
|
+
};
|
|
28587
|
+
}
|
|
28588
|
+
if (iosMeta.bundleId !== void 0) yield* printHuman("No prebuilt iOS project found — configuring the main bundle only. Run `expo prebuild` (or a build) once so app-extension targets are discovered and configured.");
|
|
28589
|
+
}
|
|
28590
|
+
const bundleIdentifier = args.bundle ?? iosMeta.bundleId ?? (yield* promptText("iOS bundle identifier"));
|
|
28591
|
+
yield* configureIos({
|
|
28592
|
+
api,
|
|
28593
|
+
projectId,
|
|
28594
|
+
bundleIdentifier,
|
|
28595
|
+
distribution,
|
|
28596
|
+
rebind: args.rebind,
|
|
28597
|
+
bindPushKey: args.bindPushKey,
|
|
28598
|
+
bindAscKey: args.bindAscKey
|
|
28599
|
+
});
|
|
28600
|
+
return {
|
|
28601
|
+
platform: "ios",
|
|
28602
|
+
projectId,
|
|
28603
|
+
distribution,
|
|
28604
|
+
bundleIdentifier,
|
|
28605
|
+
bundleIdentifiers: [bundleIdentifier]
|
|
28606
|
+
};
|
|
28607
|
+
});
|
|
28443
28608
|
const configureCommand$1 = defineCommand({
|
|
28444
28609
|
meta: {
|
|
28445
28610
|
name: "configure",
|
|
@@ -28453,7 +28618,7 @@ const configureCommand$1 = defineCommand({
|
|
|
28453
28618
|
},
|
|
28454
28619
|
bundle: {
|
|
28455
28620
|
type: "string",
|
|
28456
|
-
description: "iOS bundle identifier (defaults to app
|
|
28621
|
+
description: "iOS bundle identifier to scope to a single target (defaults to configuring every signed target — main app + extensions — discovered from the Xcode project)"
|
|
28457
28622
|
},
|
|
28458
28623
|
"android-package": {
|
|
28459
28624
|
type: "string",
|
|
@@ -28497,25 +28662,16 @@ const configureCommand$1 = defineCommand({
|
|
|
28497
28662
|
}, {
|
|
28498
28663
|
value: "android",
|
|
28499
28664
|
label: "Android"
|
|
28500
|
-
}]))) === "ios") {
|
|
28501
|
-
|
|
28502
|
-
|
|
28503
|
-
|
|
28504
|
-
|
|
28505
|
-
|
|
28506
|
-
|
|
28507
|
-
|
|
28508
|
-
|
|
28509
|
-
|
|
28510
|
-
bindAscKey: args["bind-asc-key"]
|
|
28511
|
-
});
|
|
28512
|
-
return {
|
|
28513
|
-
platform: "ios",
|
|
28514
|
-
projectId,
|
|
28515
|
-
bundleIdentifier,
|
|
28516
|
-
distribution: args.distribution
|
|
28517
|
-
};
|
|
28518
|
-
}
|
|
28665
|
+
}]))) === "ios") return yield* runConfigureIos({
|
|
28666
|
+
api,
|
|
28667
|
+
projectId,
|
|
28668
|
+
root,
|
|
28669
|
+
bundle: args.bundle,
|
|
28670
|
+
distribution: args.distribution,
|
|
28671
|
+
rebind: args.rebind ?? false,
|
|
28672
|
+
bindPushKey: args["bind-push-key"],
|
|
28673
|
+
bindAscKey: args["bind-asc-key"]
|
|
28674
|
+
});
|
|
28519
28675
|
const androidMeta = yield* readAppMetaOptional(root, "android");
|
|
28520
28676
|
const applicationIdentifier = args["android-package"] ?? androidMeta.androidPackage ?? (yield* promptText("Android application identifier"));
|
|
28521
28677
|
yield* configureAndroid({
|