hot-updater 1.0.0 → 1.0.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/dist/index.mjs +266 -77
- package/dist/infra-templates/aws/COMMON.md +3 -1
- package/dist/infra-templates/aws/app/provision-client-credential.mjs +5 -3
- package/dist/infra-templates/aws/lambda/index.cjs +1 -1
- package/dist/infra-templates/aws/template.json +10 -10
- package/dist/infra-templates/aws/upgrades/README.md +4 -0
- package/dist/infra-templates/azure/COMMON.md +310 -0
- package/dist/infra-templates/azure/ENVIRONMENT.md +35 -0
- package/dist/infra-templates/azure/SETUP.md +163 -0
- package/dist/infra-templates/azure/app/hot-updater.config.bare.ts +22 -0
- package/dist/infra-templates/azure/app/hot-updater.config.expo.ts +22 -0
- package/dist/infra-templates/azure/app/hot-updater.config.rock.ts +22 -0
- package/dist/infra-templates/azure/app/hotUpdater.ts +20 -0
- package/dist/infra-templates/azure/app/migrate.ts +17 -0
- package/dist/infra-templates/azure/app/provision-client-credential.mjs +89 -0
- package/dist/infra-templates/azure/app/verify-server.mjs +381 -0
- package/dist/infra-templates/azure/azure/function/host.json +8 -0
- package/dist/infra-templates/azure/azure/function/index.mjs +70656 -0
- package/dist/infra-templates/azure/azure/function/package.json +6 -0
- package/dist/infra-templates/azure/template.json +83 -0
- package/dist/infra-templates/azure/upgrades/1.0.0.md +215 -0
- package/dist/infra-templates/azure/upgrades/README.md +13 -0
- package/dist/infra-templates/cloudflare/COMMON.md +3 -1
- package/dist/infra-templates/cloudflare/app/provision-client-credential.mjs +5 -3
- package/dist/infra-templates/cloudflare/template.json +10 -10
- package/dist/infra-templates/cloudflare/upgrades/README.md +4 -0
- package/dist/infra-templates/cloudflare/worker/dist/README.md +1 -1
- package/dist/infra-templates/cloudflare/worker/dist/index.js +1 -1
- package/dist/infra-templates/cloudflare/worker/dist/index.js.map +2 -2
- package/dist/infra-templates/firebase/COMMON.md +3 -1
- package/dist/infra-templates/firebase/ENVIRONMENT.md +2 -1
- package/dist/infra-templates/firebase/SETUP.md +19 -8
- package/dist/infra-templates/firebase/app/hot-updater.config.bare.ts +1 -0
- package/dist/infra-templates/firebase/app/hot-updater.config.expo.ts +1 -0
- package/dist/infra-templates/firebase/app/hot-updater.config.rock.ts +1 -0
- package/dist/infra-templates/firebase/app/hotUpdater.ts +1 -0
- package/dist/infra-templates/firebase/app/migrate.ts +3 -2
- package/dist/infra-templates/firebase/app/provision-client-credential.mjs +5 -3
- package/dist/infra-templates/firebase/firebase/firebase.json +2 -1
- package/dist/infra-templates/firebase/firebase/functions/index.cjs +8 -4
- package/dist/infra-templates/firebase/template.json +17 -11
- package/dist/infra-templates/firebase/upgrades/README.md +4 -0
- package/dist/infra-templates/supabase/COMMON.md +3 -1
- package/dist/infra-templates/supabase/app/provision-client-credential.mjs +5 -3
- package/dist/infra-templates/supabase/supabase/functions/hot-updater-v1/_hot-updater/hot-updater-protocol/dist/index.cjs +5 -0
- package/dist/infra-templates/supabase/supabase/functions/hot-updater-v1/_hot-updater/hot-updater-protocol/dist/index.d.cts +14 -0
- package/dist/infra-templates/supabase/supabase/functions/hot-updater-v1/_hot-updater/hot-updater-protocol/dist/index.d.mts +14 -0
- package/dist/infra-templates/supabase/supabase/functions/hot-updater-v1/_hot-updater/hot-updater-protocol/dist/index.mjs +5 -0
- package/dist/infra-templates/supabase/supabase/functions/hot-updater-v1/_hot-updater/hot-updater-server/dist/package.cjs +1 -1
- package/dist/infra-templates/supabase/supabase/functions/hot-updater-v1/_hot-updater/hot-updater-server/dist/package.mjs +1 -1
- package/dist/infra-templates/supabase/template.json +10 -10
- package/dist/infra-templates/supabase/upgrades/README.md +4 -0
- package/dist/infra-templates/vercel/COMMON.md +310 -0
- package/dist/infra-templates/vercel/ENVIRONMENT.md +33 -0
- package/dist/infra-templates/vercel/SETUP.md +98 -0
- package/dist/infra-templates/vercel/app/hot-updater.config.bare.ts +26 -0
- package/dist/infra-templates/vercel/app/hot-updater.config.expo.ts +26 -0
- package/dist/infra-templates/vercel/app/hot-updater.config.rock.ts +26 -0
- package/dist/infra-templates/vercel/app/hotUpdater.ts +24 -0
- package/dist/infra-templates/vercel/app/migrate.ts +17 -0
- package/dist/infra-templates/vercel/app/provision-client-credential.mjs +89 -0
- package/dist/infra-templates/vercel/app/verify-server.mjs +381 -0
- package/dist/infra-templates/vercel/template.json +89 -0
- package/dist/infra-templates/vercel/upgrades/1.0.0.md +215 -0
- package/dist/infra-templates/vercel/upgrades/README.md +13 -0
- package/dist/infra-templates/vercel/vercel/api/index.mjs +53589 -0
- package/dist/infra-templates/vercel/vercel/vercel.json +14 -0
- package/package.json +30 -20
package/dist/index.mjs
CHANGED
|
@@ -3790,6 +3790,18 @@ const INIT_PROVIDER_PACKAGES = {
|
|
|
3790
3790
|
label: "Firebase",
|
|
3791
3791
|
load: () => import("@hot-updater/firebase/init"),
|
|
3792
3792
|
packageName: "@hot-updater/firebase"
|
|
3793
|
+
},
|
|
3794
|
+
vercel: {
|
|
3795
|
+
devDependencies: ["vercel", "@hot-updater/postgres"],
|
|
3796
|
+
label: "Vercel Blob + Functions + Postgres",
|
|
3797
|
+
load: () => import("@hot-updater/vercel/init"),
|
|
3798
|
+
packageName: "@hot-updater/vercel"
|
|
3799
|
+
},
|
|
3800
|
+
azure: {
|
|
3801
|
+
devDependencies: [],
|
|
3802
|
+
label: "Azure Blob Storage + Functions + Cosmos DB",
|
|
3803
|
+
load: () => import("@hot-updater/azure/init"),
|
|
3804
|
+
packageName: "@hot-updater/azure"
|
|
3793
3805
|
}
|
|
3794
3806
|
};
|
|
3795
3807
|
const isInitProvider = (value) => value !== void 0 && Object.hasOwn(INIT_PROVIDER_PACKAGES, value);
|
|
@@ -3870,26 +3882,112 @@ async function handleAgentInfra(operation, options) {
|
|
|
3870
3882
|
await handleInfraScaffold(operation, options);
|
|
3871
3883
|
}
|
|
3872
3884
|
//#endregion
|
|
3873
|
-
//#region src/utils/version/
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3885
|
+
//#region src/utils/version/appVersionCandidates.ts
|
|
3886
|
+
/** One version only when every candidate declares the same literal. */
|
|
3887
|
+
const resolveFromCandidates = (candidates) => {
|
|
3888
|
+
const versions = new Set(candidates.map((candidate) => candidate.version));
|
|
3889
|
+
const [version] = versions;
|
|
3890
|
+
return {
|
|
3891
|
+
version: versions.size === 1 && version ? version : null,
|
|
3892
|
+
candidates
|
|
3893
|
+
};
|
|
3881
3894
|
};
|
|
3882
|
-
|
|
3883
|
-
const
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
|
|
3895
|
+
/** Several candidates and no single version: the caller must pick one. */
|
|
3896
|
+
const isAmbiguousAppVersion = ({ version, candidates }) => version === null && candidates.length > 1;
|
|
3897
|
+
const formatAppVersionCandidates = (candidates) => candidates.map(({ label, version }) => `${label}: ${version ?? "unresolved"}`).join(", ");
|
|
3898
|
+
//#endregion
|
|
3899
|
+
//#region src/utils/version/getAndroidVersion.ts
|
|
3900
|
+
const stripComments = (source) => source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
|
|
3901
|
+
/** The body of the `{ ... }` block that opens at or after `from`. */
|
|
3902
|
+
const readBlock = (source, from) => {
|
|
3903
|
+
const open = source.indexOf("{", from);
|
|
3904
|
+
if (open === -1) return null;
|
|
3905
|
+
let depth = 0;
|
|
3906
|
+
for (let index = open; index < source.length; index++) {
|
|
3907
|
+
if (source[index] === "{") depth++;
|
|
3908
|
+
if (source[index] === "}" && --depth === 0) return {
|
|
3909
|
+
body: source.slice(open + 1, index),
|
|
3910
|
+
end: index + 1
|
|
3911
|
+
};
|
|
3890
3912
|
}
|
|
3891
3913
|
return null;
|
|
3892
3914
|
};
|
|
3915
|
+
/** The top-level `name { ... }` blocks in a block body. */
|
|
3916
|
+
const readNamedBlocks = (body) => {
|
|
3917
|
+
const blocks = [];
|
|
3918
|
+
const header = /(?:create\(\s*["']([\w-]+)["']\s*\)|([\w-]+))\s*\{/g;
|
|
3919
|
+
let match;
|
|
3920
|
+
while (match = header.exec(body)) {
|
|
3921
|
+
const block = readBlock(body, match.index);
|
|
3922
|
+
if (!block) break;
|
|
3923
|
+
blocks.push({
|
|
3924
|
+
name: match[1] ?? match[2],
|
|
3925
|
+
body: block.body
|
|
3926
|
+
});
|
|
3927
|
+
header.lastIndex = block.end;
|
|
3928
|
+
}
|
|
3929
|
+
return blocks;
|
|
3930
|
+
};
|
|
3931
|
+
/** The first block called `name`, in file order, at any depth. */
|
|
3932
|
+
const findBlock = (source, name) => readNamedBlocks(source).flatMap((block) => block.name === name ? [block] : [findBlock(block.body, name)]).find((block) => block !== void 0);
|
|
3933
|
+
/** A literal setting declared directly in a block body; null when not literal. */
|
|
3934
|
+
const readSetting = (body, setting) => {
|
|
3935
|
+
const match = readNamedBlocks(body).reduce((rest, block) => rest.replace(block.body, ""), body).match(new RegExp(`\\b${setting}\\b\\s*=?\\s*(?:(['"])([^'"]*)\\1|(\\S+))`));
|
|
3936
|
+
if (!match) return void 0;
|
|
3937
|
+
return match[2] || null;
|
|
3938
|
+
};
|
|
3939
|
+
const capitalize = (value) => value.charAt(0).toUpperCase() + value.slice(1);
|
|
3940
|
+
/**
|
|
3941
|
+
* Whether a variant builds a flavor: `stagingRelease` builds `staging`, and
|
|
3942
|
+
* with flavor dimensions `brandAStagingRelease` builds `brandA` and `staging`.
|
|
3943
|
+
*/
|
|
3944
|
+
const variantBuildsFlavor = (variant, flavor) => new RegExp(`${capitalize(flavor)}(?=[A-Z]|$)`).test(capitalize(variant));
|
|
3945
|
+
/**
|
|
3946
|
+
* Reads versionName from defaultConfig and from each product flavor, so an
|
|
3947
|
+
* app whose flavors (brands, environments) override the version is reported
|
|
3948
|
+
* as ambiguous instead of resolving to the first versionName in the file.
|
|
3949
|
+
*/
|
|
3950
|
+
const getAndroidVersionFromAppBuildGradle = async (scope = {}) => {
|
|
3951
|
+
const buildGradlePath = path.join(getCwd(), "android", scope.appModuleName ?? "app", "build.gradle");
|
|
3952
|
+
let buildGradle;
|
|
3953
|
+
try {
|
|
3954
|
+
buildGradle = stripComments(await fs$3.readFile(buildGradlePath, "utf8"));
|
|
3955
|
+
} catch {
|
|
3956
|
+
return {
|
|
3957
|
+
version: null,
|
|
3958
|
+
candidates: []
|
|
3959
|
+
};
|
|
3960
|
+
}
|
|
3961
|
+
const android = findBlock(buildGradle, "android")?.body ?? "";
|
|
3962
|
+
const defaultConfig = findBlock(android, "defaultConfig");
|
|
3963
|
+
const defaultVersion = defaultConfig ? readSetting(defaultConfig.body, "versionName") : void 0;
|
|
3964
|
+
const flavorsBlock = findBlock(android, "productFlavors");
|
|
3965
|
+
const flavors = (flavorsBlock ? readNamedBlocks(flavorsBlock.body) : []).map((flavor) => ({
|
|
3966
|
+
name: flavor.name,
|
|
3967
|
+
applicationId: readSetting(flavor.body, "applicationId"),
|
|
3968
|
+
versionName: readSetting(flavor.body, "versionName")
|
|
3969
|
+
}));
|
|
3970
|
+
const defaultCandidate = defaultVersion === void 0 ? [] : [{
|
|
3971
|
+
label: "defaultConfig",
|
|
3972
|
+
version: defaultVersion
|
|
3973
|
+
}];
|
|
3974
|
+
const flavorCandidates = (flavorList) => flavorList.flatMap((flavor) => flavor.versionName === void 0 ? [] : [{
|
|
3975
|
+
label: `${flavor.name} flavor`,
|
|
3976
|
+
version: flavor.versionName
|
|
3977
|
+
}]);
|
|
3978
|
+
const { variant, applicationId } = scope;
|
|
3979
|
+
if (variant || applicationId) {
|
|
3980
|
+
const builtFlavors = flavors.filter((flavor) => applicationId !== void 0 && flavor.applicationId === applicationId || variant !== void 0 && variantBuildsFlavor(variant, flavor.name));
|
|
3981
|
+
if (builtFlavors.length > 0) {
|
|
3982
|
+
const overrides = flavorCandidates(builtFlavors);
|
|
3983
|
+
return resolveFromCandidates(overrides.length > 0 ? overrides : defaultCandidate);
|
|
3984
|
+
}
|
|
3985
|
+
}
|
|
3986
|
+
const overrides = flavorCandidates(flavors);
|
|
3987
|
+
const everyFlavorOverrides = flavors.length > 0 && overrides.length === flavors.length;
|
|
3988
|
+
return resolveFromCandidates([...everyFlavorOverrides ? [] : defaultCandidate, ...overrides]);
|
|
3989
|
+
};
|
|
3990
|
+
const resolveAndroidAppVersion = (scope) => getAndroidVersionFromAppBuildGradle(scope);
|
|
3893
3991
|
//#endregion
|
|
3894
3992
|
//#region ../../node_modules/.pnpm/@bacons+xcode@1.0.0-alpha.33/node_modules/@bacons/xcode/build/api/utils/constants.js
|
|
3895
3993
|
var require_constants = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
@@ -19406,81 +19504,138 @@ const isFileExist = async (path) => {
|
|
|
19406
19504
|
return false;
|
|
19407
19505
|
}
|
|
19408
19506
|
};
|
|
19409
|
-
const
|
|
19507
|
+
const isResolved = (value) => typeof value === "string" && value.length > 0 && !value.includes("$(");
|
|
19508
|
+
const readInfoPlistVersion = async (plistPath) => {
|
|
19410
19509
|
try {
|
|
19411
|
-
const iosAppTargetDirectory = getIosAppTargetDirectoryName();
|
|
19412
|
-
if (!iosAppTargetDirectory) return null;
|
|
19413
|
-
const plistPath = path.join(getCwd(), "ios", iosAppTargetDirectory, "Info.plist");
|
|
19414
19510
|
if (!await isFileExist(plistPath)) return null;
|
|
19415
19511
|
const fileBuffer = await fs$3.readFile(plistPath);
|
|
19416
19512
|
const appVersion = parsePlist(fileBuffer)["CFBundleShortVersionString"];
|
|
19417
|
-
|
|
19418
|
-
return appVersion;
|
|
19513
|
+
return isResolved(appVersion) ? appVersion : null;
|
|
19419
19514
|
} catch {
|
|
19420
19515
|
return null;
|
|
19421
19516
|
}
|
|
19422
19517
|
};
|
|
19423
|
-
const
|
|
19518
|
+
const getIOSVersionFromInfoPlist = async () => {
|
|
19519
|
+
const iosAppTargetDirectory = getIosAppTargetDirectoryName();
|
|
19520
|
+
if (!iosAppTargetDirectory) return null;
|
|
19521
|
+
return readInfoPlistVersion(path.join(getCwd(), "ios", iosAppTargetDirectory, "Info.plist"));
|
|
19522
|
+
};
|
|
19523
|
+
const APPLICATION_PRODUCT_TYPE = "com.apple.product-type.application";
|
|
19524
|
+
/**
|
|
19525
|
+
* Reads MARKETING_VERSION per application target, so a project whose targets
|
|
19526
|
+
* (brands, environments) ship different versions is reported as ambiguous
|
|
19527
|
+
* instead of resolving to whichever configuration comes first in the file.
|
|
19528
|
+
*/
|
|
19529
|
+
const getIOSVersionFromXcodeProject = async (scope = {}) => {
|
|
19530
|
+
const notFound = {
|
|
19531
|
+
version: null,
|
|
19532
|
+
candidates: []
|
|
19533
|
+
};
|
|
19424
19534
|
try {
|
|
19535
|
+
const iosDirectory = path.join(getCwd(), "ios");
|
|
19425
19536
|
const [xcodeprojPath] = import_out.default.globSync("*.xcodeproj/project.pbxproj", {
|
|
19426
|
-
cwd:
|
|
19537
|
+
cwd: iosDirectory,
|
|
19427
19538
|
absolute: true,
|
|
19428
19539
|
onlyFiles: true
|
|
19429
19540
|
});
|
|
19430
|
-
if (!xcodeprojPath) return
|
|
19431
|
-
const
|
|
19432
|
-
|
|
19433
|
-
|
|
19434
|
-
|
|
19435
|
-
|
|
19436
|
-
|
|
19437
|
-
|
|
19438
|
-
|
|
19439
|
-
|
|
19440
|
-
|
|
19541
|
+
if (!xcodeprojPath) return notFound;
|
|
19542
|
+
const project = import_build.XcodeProject.open(xcodeprojPath).toJSON();
|
|
19543
|
+
const objects = project.objects ?? {};
|
|
19544
|
+
const configurationName = scope.configuration ?? "Release";
|
|
19545
|
+
const findConfiguration = (listId) => (listId ? objects[listId]?.buildConfigurations ?? [] : []).map((id) => objects[id]).find((config) => config?.name === configurationName);
|
|
19546
|
+
const rootProject = project.rootObject ? objects[project.rootObject] : void 0;
|
|
19547
|
+
const projectSettings = findConfiguration(rootProject?.buildConfigurationList)?.buildSettings ?? {};
|
|
19548
|
+
const appTargets = (rootProject?.targets ?? []).map((id) => objects[id]).filter((target) => target?.isa === "PBXNativeTarget" && target.productType === APPLICATION_PRODUCT_TYPE).map((target) => {
|
|
19549
|
+
const settings = {
|
|
19550
|
+
...projectSettings,
|
|
19551
|
+
...findConfiguration(target.buildConfigurationList)?.buildSettings
|
|
19552
|
+
};
|
|
19553
|
+
const infoPlistFile = typeof settings.INFOPLIST_FILE === "string" ? settings.INFOPLIST_FILE.replace("$(SRCROOT)", iosDirectory) : void 0;
|
|
19554
|
+
return {
|
|
19555
|
+
name: target.name ?? "unknown",
|
|
19556
|
+
bundleIdentifier: settings.PRODUCT_BUNDLE_IDENTIFIER,
|
|
19557
|
+
marketingVersion: settings.MARKETING_VERSION,
|
|
19558
|
+
infoPlistPath: isResolved(infoPlistFile) ? path.resolve(iosDirectory, infoPlistFile) : void 0
|
|
19559
|
+
};
|
|
19560
|
+
});
|
|
19561
|
+
if (appTargets.length === 0) {
|
|
19562
|
+
const candidates = Object.values(objects).filter((object) => object?.isa === "XCBuildConfiguration" && object.name === configurationName && object.buildSettings?.MARKETING_VERSION !== void 0).map((object) => ({
|
|
19563
|
+
label: configurationName,
|
|
19564
|
+
version: isResolved(object?.buildSettings?.MARKETING_VERSION) ? object.buildSettings.MARKETING_VERSION : null
|
|
19565
|
+
}));
|
|
19566
|
+
return resolveFromCandidates(candidates);
|
|
19567
|
+
}
|
|
19568
|
+
const scopedTargets = scope.scheme || scope.bundleIdentifier ? appTargets.filter((target) => target.name === scope.scheme || scope.bundleIdentifier !== void 0 && target.bundleIdentifier === scope.bundleIdentifier) : [];
|
|
19569
|
+
const targets = scopedTargets.length > 0 ? scopedTargets : appTargets;
|
|
19570
|
+
return resolveFromCandidates(await Promise.all(targets.map(async (target) => ({
|
|
19571
|
+
label: `${target.name} (${configurationName})`,
|
|
19572
|
+
version: (target.infoPlistPath ? await readInfoPlistVersion(target.infoPlistPath) : null) ?? (isResolved(target.marketingVersion) ? target.marketingVersion : null)
|
|
19573
|
+
}))));
|
|
19441
19574
|
} catch {
|
|
19442
|
-
return
|
|
19575
|
+
return notFound;
|
|
19443
19576
|
}
|
|
19444
19577
|
};
|
|
19445
|
-
|
|
19446
|
-
|
|
19447
|
-
|
|
19448
|
-
|
|
19449
|
-
|
|
19450
|
-
|
|
19451
|
-
|
|
19452
|
-
|
|
19453
|
-
|
|
19454
|
-
|
|
19455
|
-
|
|
19456
|
-
|
|
19457
|
-
|
|
19578
|
+
/**
|
|
19579
|
+
* The iOS app version: Info.plist first, project.pbxproj as a fallback.
|
|
19580
|
+
*
|
|
19581
|
+
* CFBundleShortVersionString is the version the built app actually reports,
|
|
19582
|
+
* so it is the more correct source (#84). It is also far cheaper: reading
|
|
19583
|
+
* Info.plist is a small file read, whereas the pbxproj parser is synchronous
|
|
19584
|
+
* and blocks the event loop for the length of the parse. On a large
|
|
19585
|
+
* project.pbxproj that can take minutes. The React Native template ships
|
|
19586
|
+
* Info.plist with CFBundleShortVersionString set to "$(MARKETING_VERSION)",
|
|
19587
|
+
* which the plist parser cannot resolve, so the real version only lives in
|
|
19588
|
+
* project.pbxproj for most projects.
|
|
19589
|
+
*/
|
|
19590
|
+
const resolveIOSAppVersion = async (scope) => {
|
|
19591
|
+
const plistVersion = await getIOSVersionFromInfoPlist();
|
|
19592
|
+
if (plistVersion) return {
|
|
19593
|
+
version: plistVersion,
|
|
19594
|
+
candidates: [{
|
|
19595
|
+
label: "Info.plist",
|
|
19596
|
+
version: plistVersion
|
|
19597
|
+
}]
|
|
19598
|
+
};
|
|
19599
|
+
return getIOSVersionFromXcodeProject(scope);
|
|
19458
19600
|
};
|
|
19459
19601
|
//#endregion
|
|
19460
19602
|
//#region src/utils/version/getNativeAppVersion.ts
|
|
19461
|
-
|
|
19603
|
+
/**
|
|
19604
|
+
* The version the native project builds, with every place it is declared.
|
|
19605
|
+
* A project that builds several apps (Xcode targets, product flavors) with
|
|
19606
|
+
* different versions resolves to null unless `scope` names the one built.
|
|
19607
|
+
*/
|
|
19608
|
+
const resolveNativeAppVersion = async (platform, scope = {}) => {
|
|
19462
19609
|
switch (platform) {
|
|
19463
|
-
case "ios": return
|
|
19464
|
-
case "android": return
|
|
19465
|
-
default: return
|
|
19610
|
+
case "ios": return resolveIOSAppVersion(scope.ios);
|
|
19611
|
+
case "android": return resolveAndroidAppVersion(scope.android);
|
|
19612
|
+
default: return {
|
|
19613
|
+
version: null,
|
|
19614
|
+
candidates: []
|
|
19615
|
+
};
|
|
19466
19616
|
}
|
|
19467
19617
|
};
|
|
19618
|
+
const getNativeAppVersion = async (platform, scope) => (await resolveNativeAppVersion(platform, scope)).version;
|
|
19468
19619
|
//#endregion
|
|
19469
19620
|
//#region src/commands/appVersion.ts
|
|
19470
19621
|
const readAppVersions = async () => {
|
|
19471
|
-
const [
|
|
19622
|
+
const [android, ios] = await Promise.all([resolveNativeAppVersion("android"), resolveNativeAppVersion("ios")]);
|
|
19472
19623
|
return {
|
|
19473
|
-
android
|
|
19474
|
-
ios
|
|
19624
|
+
android,
|
|
19625
|
+
ios
|
|
19475
19626
|
};
|
|
19476
19627
|
};
|
|
19628
|
+
const formatVersion = (resolved) => isAmbiguousAppVersion(resolved) ? ui.warning(`several (${formatAppVersionCandidates(resolved.candidates)}); deploy needs -t`) : ui.version(resolved.version);
|
|
19477
19629
|
const handleAppVersion = async (options = {}) => {
|
|
19478
19630
|
const versions = await readAppVersions();
|
|
19479
19631
|
if (options.json) {
|
|
19480
|
-
console.log(JSON.stringify(
|
|
19632
|
+
console.log(JSON.stringify({
|
|
19633
|
+
android: versions.android.version,
|
|
19634
|
+
ios: versions.ios.version
|
|
19635
|
+
}, null, 2));
|
|
19481
19636
|
return;
|
|
19482
19637
|
}
|
|
19483
|
-
p.log.message(ui.block("App version", [ui.kv("Android",
|
|
19638
|
+
p.log.message(ui.block("App version", [ui.kv("Android", formatVersion(versions.android)), ui.kv("iOS", formatVersion(versions.ios))]));
|
|
19484
19639
|
};
|
|
19485
19640
|
//#endregion
|
|
19486
19641
|
//#region ../../node_modules/.pnpm/is-plain-obj@4.1.0/node_modules/is-plain-obj/index.js
|
|
@@ -25767,11 +25922,25 @@ async function prepareNativeBuild(options) {
|
|
|
25767
25922
|
} else if (config.updateStrategy === "appVersion") {
|
|
25768
25923
|
const s = p.spinner();
|
|
25769
25924
|
s.start(`Get native app version (${platform})`);
|
|
25770
|
-
const
|
|
25925
|
+
const iosScheme = config.nativeBuild.ios[scheme];
|
|
25926
|
+
const androidScheme = config.nativeBuild.android[scheme];
|
|
25927
|
+
const resolved = await resolveNativeAppVersion(platform, {
|
|
25928
|
+
ios: iosScheme && {
|
|
25929
|
+
scheme: iosScheme.scheme,
|
|
25930
|
+
bundleIdentifier: iosScheme.bundleIdentifier,
|
|
25931
|
+
configuration: iosScheme.configuration
|
|
25932
|
+
},
|
|
25933
|
+
android: androidScheme && {
|
|
25934
|
+
variant: androidScheme.variant,
|
|
25935
|
+
applicationId: androidScheme.applicationId ?? androidScheme.packageName,
|
|
25936
|
+
appModuleName: androidScheme.appModuleName
|
|
25937
|
+
}
|
|
25938
|
+
});
|
|
25939
|
+
const appVersion = resolved.version;
|
|
25771
25940
|
s.stop(`App Version(${platform}): ${appVersion}`);
|
|
25772
25941
|
target.appVersion = appVersion;
|
|
25773
25942
|
if (!target.appVersion) {
|
|
25774
|
-
p.log.error(`Failed to retrieve native app version of ${platform}`);
|
|
25943
|
+
p.log.error(isAmbiguousAppVersion(resolved) ? platform === "ios" ? `nativeBuild.ios.${scheme} (scheme ${iosScheme?.scheme}, bundleIdentifier ${iosScheme?.bundleIdentifier}) does not pick one of the app targets in project.pbxproj, which declare different versions (${formatAppVersionCandidates(resolved.candidates)}). Make its scheme match the name of the app target it builds.` : `nativeBuild.android.${scheme} (variant ${androidScheme?.variant ?? "Release"}) does not pick one of the product flavors that set versionName, which declare different versions (${formatAppVersionCandidates(resolved.candidates)}). Set its variant to the flavor it builds, such as brandARelease.` : `Failed to retrieve native app version of ${platform}`);
|
|
25775
25944
|
return null;
|
|
25776
25945
|
}
|
|
25777
25946
|
}
|
|
@@ -26913,18 +27082,23 @@ async function validateSigningConfig(config, options = {}) {
|
|
|
26913
27082
|
}
|
|
26914
27083
|
//#endregion
|
|
26915
27084
|
//#region src/utils/version/getDefaultTargetAppVersion.ts
|
|
27085
|
+
/**
|
|
27086
|
+
* The target app version deploy defaults to, as a range. `version` is null
|
|
27087
|
+
* when the native files declare none, or several (see `candidates`).
|
|
27088
|
+
*/
|
|
26916
27089
|
const getDefaultTargetAppVersion = async (platform) => {
|
|
26917
|
-
|
|
26918
|
-
|
|
26919
|
-
|
|
26920
|
-
|
|
26921
|
-
|
|
26922
|
-
|
|
26923
|
-
}
|
|
26924
|
-
if (!version) return null;
|
|
26925
|
-
if (!(/^\d+\.\d+$/.test(version) || normalizeRange(version))) return null;
|
|
27090
|
+
const resolved = await resolveNativeAppVersion(platform);
|
|
27091
|
+
let version = resolved.version;
|
|
27092
|
+
if (!version) return resolved;
|
|
27093
|
+
if (!(/^\d+\.\d+$/.test(version) || normalizeRange(version))) return {
|
|
27094
|
+
...resolved,
|
|
27095
|
+
version: null
|
|
27096
|
+
};
|
|
26926
27097
|
if (version.split(".").length - 1 === 1) version = `${version}.x`;
|
|
26927
|
-
return
|
|
27098
|
+
return {
|
|
27099
|
+
...resolved,
|
|
27100
|
+
version
|
|
27101
|
+
};
|
|
26928
27102
|
};
|
|
26929
27103
|
//#endregion
|
|
26930
27104
|
//#region src/commands/deployTransaction.ts
|
|
@@ -27250,18 +27424,20 @@ const deployPlatform = async ({ config, core, database, deferAutoPatches, option
|
|
|
27250
27424
|
target.fingerprintHash = newFingerprint.hash;
|
|
27251
27425
|
s.stop(`Fingerprint(${platform}): ${newFingerprint.hash}`);
|
|
27252
27426
|
} else {
|
|
27253
|
-
const defaultTargetAppVersion = await getDefaultTargetAppVersion(platform);
|
|
27427
|
+
const defaultTargetAppVersion = options.targetAppVersion ? null : await getDefaultTargetAppVersion(platform);
|
|
27428
|
+
const ambiguousVersions = defaultTargetAppVersion && isAmbiguousAppVersion(defaultTargetAppVersion) ? formatAppVersionCandidates(defaultTargetAppVersion.candidates) : null;
|
|
27429
|
+
const defaultVersion = defaultTargetAppVersion?.version ?? null;
|
|
27254
27430
|
const targetAppVersion = options.targetAppVersion ?? (options.interactive ? await p.text({
|
|
27255
|
-
message: "Target app version",
|
|
27256
|
-
placeholder:
|
|
27257
|
-
initialValue:
|
|
27431
|
+
message: ambiguousVersions ? `Target app version (the native project builds several versions: ${ambiguousVersions})` : "Target app version",
|
|
27432
|
+
placeholder: defaultVersion ?? "1.0.0",
|
|
27433
|
+
initialValue: defaultVersion ?? (ambiguousVersions ? "" : "1.0.0"),
|
|
27258
27434
|
validate: (value) => {
|
|
27259
27435
|
if (!value || !normalizeRange(value)) return "Invalid semver format (e.g. 1.0.0, 1.x.x)";
|
|
27260
27436
|
}
|
|
27261
|
-
}) :
|
|
27437
|
+
}) : defaultVersion);
|
|
27262
27438
|
if (p.isCancel(targetAppVersion)) return null;
|
|
27263
27439
|
if (!targetAppVersion) {
|
|
27264
|
-
p.log.error("Target app version not found in native files (Info.plist for iOS, build.gradle for Android). Pass -t <targetAppVersion> explicitly, or check your native config.");
|
|
27440
|
+
p.log.error(ambiguousVersions ? `The native project builds several app versions (${ambiguousVersions}), so the target app version for this deploy cannot be read from it. Pass -t <targetAppVersion> for the app this deploy targets.` : "Target app version not found in native files (Info.plist or project.pbxproj for iOS, build.gradle for Android). Pass -t <targetAppVersion> explicitly, or check your native config.");
|
|
27265
27441
|
return null;
|
|
27266
27442
|
}
|
|
27267
27443
|
target.appVersion = targetAppVersion;
|
|
@@ -29639,7 +29815,7 @@ const isObject$2 = (value) => value !== null && typeof value === "object" && !Ar
|
|
|
29639
29815
|
const isText$1 = (value) => typeof value === "string" && value.trim().length > 0;
|
|
29640
29816
|
//#endregion
|
|
29641
29817
|
//#region src/commands/doctor/scaffold.ts
|
|
29642
|
-
const isDeploymentInput = (file) => /^(worker|supabase|lambda|cloudfront|dynamodb|iam|firebase)\//.test(file) && !/\.(md|map)$/.test(file);
|
|
29818
|
+
const isDeploymentInput = (file) => /^(worker|supabase|lambda|cloudfront|dynamodb|iam|firebase|vercel|azure)\//.test(file) && !/\.(md|map)$/.test(file);
|
|
29643
29819
|
const hasPlaceholder = (text) => /__HOT_UPDATER_[A-Z0-9_]+__|%%BUCKET_NAME%%/.test(text);
|
|
29644
29820
|
async function readScaffoldFile(root, file) {
|
|
29645
29821
|
const resolved = path$1.resolve(root, file);
|
|
@@ -29783,6 +29959,19 @@ async function checkScaffold(infraDir) {
|
|
|
29783
29959
|
const functions = json("firebase/firebase.json")["functions"];
|
|
29784
29960
|
if (!(Array.isArray(functions) ? functions : [functions]).some((entry) => isObject$2(entry) && entry["source"] === "functions")) fail("INFRA_FIREBASE_FUNCTION", "Firebase must deploy the supplied functions directory.", "firebase/firebase.json", "Restore the functions source from the supplied Firebase configuration.");
|
|
29785
29961
|
}
|
|
29962
|
+
if (manifest.provider === "vercel") {
|
|
29963
|
+
const file = "vercel/vercel.json";
|
|
29964
|
+
if (!items(json(file)["rewrites"]).some((entry) => entry["source"] === "/(.*)" && entry["destination"] === "/api")) fail("INFRA_VERCEL_ROUTES", "Vercel must rewrite every path to the supplied Function.", file, "Restore the rewrite { \"source\": \"/(.*)\", \"destination\": \"/api\" }.");
|
|
29965
|
+
try {
|
|
29966
|
+
await readScaffoldFile(infraDir, "vercel/api/index.mjs");
|
|
29967
|
+
} catch {
|
|
29968
|
+
fail("INFRA_RUNTIME_ENTRY", "The supplied Vercel Function is missing or unsafe.", "vercel/api/index.mjs", "Restore api/index.mjs from the supplied Vercel scaffold.");
|
|
29969
|
+
}
|
|
29970
|
+
}
|
|
29971
|
+
if (manifest.provider === "azure") {
|
|
29972
|
+
if (object(object(json("azure/function/host.json")["extensions"])["http"])["routePrefix"] !== "") fail("INFRA_AZURE_ROUTES", "Azure Functions must serve the supplied Function without a route prefix.", "azure/function/host.json", "Restore extensions.http.routePrefix to \"\" in host.json.");
|
|
29973
|
+
if (json("azure/function/package.json")["main"] !== "index.mjs") fail("INFRA_RUNTIME_ENTRY", "The Azure Function package must load the supplied index.mjs.", "azure/function/package.json", "Restore \"main\": \"index.mjs\" from the supplied Azure scaffold.");
|
|
29974
|
+
}
|
|
29786
29975
|
if (checks.every((check) => check.status === "pass")) checks.push({
|
|
29787
29976
|
code: "INFRA_SCAFFOLD",
|
|
29788
29977
|
status: "pass",
|
|
@@ -222,7 +222,9 @@ separate validation result.
|
|
|
222
222
|
bucket using the exact local storage plugin's credential chain and endpoint:
|
|
223
223
|
Cloudflare R2 S3 / AWS S3 `ListObjectsV2` with MaxKeys=1; Supabase Storage list
|
|
224
224
|
with limit=1; Firebase Admin Storage getFiles with maxResults=1 and
|
|
225
|
-
autoPaginate=false
|
|
225
|
+
autoPaginate=false; Vercel Blob `list({ limit: 1 })` with the local read-write
|
|
226
|
+
token; Azure Blob Storage `listBlobsFlat` with maxPageSize=1. Load
|
|
227
|
+
credentials privately in the probe process and report
|
|
226
228
|
only success/count, never keys or object contents. A provider MCP session or
|
|
227
229
|
the deployed runtime's access cannot substitute for local plugin access.
|
|
228
230
|
- Verify/record: config loads with the intended provider/build, the server check
|
|
@@ -21,7 +21,8 @@ if (
|
|
|
21
21
|
const { api, clientAuth, database, plugins } = hotUpdater;
|
|
22
22
|
|
|
23
23
|
const credentialPath = new URL("./client-credential.local", import.meta.url);
|
|
24
|
-
// Providers
|
|
24
|
+
// Providers whose schema comes from the server's migrator (Firestore, Cosmos
|
|
25
|
+
// DB, and Vercel's PostgreSQL) ship migrate.ts.
|
|
25
26
|
const migrationPath = new URL("./migrate.ts", import.meta.url);
|
|
26
27
|
try {
|
|
27
28
|
// The plugin that protects the deployed server's client routes, if any,
|
|
@@ -64,8 +65,9 @@ try {
|
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
67
|
if (existsSync(migrationPath)) {
|
|
67
|
-
// It
|
|
68
|
-
//
|
|
68
|
+
// It creates the tables of core and the deployed server's plugins where
|
|
69
|
+
// the database keeps tables, and writes their schema settings, which the
|
|
70
|
+
// database checks before its first read.
|
|
69
71
|
const { migrate } = await import(migrationPath.href);
|
|
70
72
|
await migrate(hotUpdater);
|
|
71
73
|
}
|
|
@@ -4550,7 +4550,7 @@ const createKvAdapter = ({ store, tablePrefix = "" }) => {
|
|
|
4550
4550
|
};
|
|
4551
4551
|
//#endregion
|
|
4552
4552
|
//#region ../../packages/server/dist/version.mjs
|
|
4553
|
-
const HOT_UPDATER_SERVER_VERSION = "1.0.
|
|
4553
|
+
const HOT_UPDATER_SERVER_VERSION = "1.0.2";
|
|
4554
4554
|
//#endregion
|
|
4555
4555
|
//#region ../../packages/server/dist/handlerVersionRoutes.mjs
|
|
4556
4556
|
/**
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"provider": "aws",
|
|
4
|
-
"cliVersion": "1.0.
|
|
5
|
-
"providerVersion": "1.0.
|
|
6
|
-
"serverVersion": "1.0.
|
|
4
|
+
"cliVersion": "1.0.2",
|
|
5
|
+
"providerVersion": "1.0.2",
|
|
6
|
+
"serverVersion": "1.0.2",
|
|
7
7
|
"infrastructureGeneration": 1,
|
|
8
8
|
"clientAuth": {
|
|
9
9
|
"plugin": "apiKeys",
|
|
@@ -65,13 +65,13 @@
|
|
|
65
65
|
}
|
|
66
66
|
],
|
|
67
67
|
"packages": {
|
|
68
|
-
"hot-updater": "1.0.
|
|
69
|
-
"@hot-updater/server": "1.0.
|
|
70
|
-
"@hot-updater/react-native": "1.0.
|
|
71
|
-
"@hot-updater/aws": "1.0.
|
|
72
|
-
"@hot-updater/bare": "1.0.
|
|
73
|
-
"@hot-updater/rock": "1.0.
|
|
74
|
-
"@hot-updater/expo": "1.0.
|
|
68
|
+
"hot-updater": "1.0.2",
|
|
69
|
+
"@hot-updater/server": "1.0.2",
|
|
70
|
+
"@hot-updater/react-native": "1.0.2",
|
|
71
|
+
"@hot-updater/aws": "1.0.2",
|
|
72
|
+
"@hot-updater/bare": "1.0.2",
|
|
73
|
+
"@hot-updater/rock": "1.0.2",
|
|
74
|
+
"@hot-updater/expo": "1.0.2",
|
|
75
75
|
"@aws-sdk/credential-providers": "3.1066.0"
|
|
76
76
|
},
|
|
77
77
|
"requiredInputs": {
|
|
@@ -4,6 +4,10 @@ Inspect the live server version, infrastructure generation and migration history
|
|
|
4
4
|
|
|
5
5
|
Each file records one required infrastructure version. Read the applicable baseline for the installed generation as context, then every subsequent file through the target requirement in ascending order. If the deployed generation predates the first file, start with that file. Read all relevant files before applying any changes; do not skip intermediate releases or read only the newest file.
|
|
6
6
|
|
|
7
|
+
Vercel was added after 1.0.0: files through 1.0.0 have no Vercel section. A Vercel deployment applies the Vercel sections of the files after 1.0.0; when none is listed, it needs no infrastructure change, and reconciling it with this scaffold follows the upgrade section of its SETUP.md.
|
|
8
|
+
|
|
9
|
+
Azure was added after 1.0.0: files through 1.0.0 have no Azure section. A Azure deployment applies the Azure sections of the files after 1.0.0; when none is listed, it needs no infrastructure change, and reconciling it with this scaffold follows the upgrade section of its SETUP.md.
|
|
10
|
+
|
|
7
11
|
Use the common sections and your provider's section to plan the full transition. Previously completed steps provide context and must not be replayed blindly. Inspect actual migration/resource state, preserve customizations and secrets, and record which version files and steps have been applied and verified. Apply pending changes in order using the target scaffold; investigate conflicting requirements before proceeding.
|
|
8
12
|
|
|
9
13
|
- [1.0.0](./1.0.0.md) — Release Catalog, manifest artifact protocol v1, and storage engine infrastructure generation
|