@hot-updater/vercel 1.0.3 → 1.0.5
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/function/index.mjs +4 -10
- package/dist/index.cjs +12 -5
- package/dist/index.d.cts +6 -7
- package/dist/index.d.mts +6 -7
- package/dist/index.mjs +7 -6
- package/dist/init/index.cjs +15 -38
- package/dist/init/index.d.cts +0 -10
- package/dist/init/index.d.mts +0 -10
- package/dist/init/index.mjs +15 -38
- package/package.json +7 -7
package/dist/function/index.mjs
CHANGED
|
@@ -17558,7 +17558,7 @@ const postgres = (config) => {
|
|
|
17558
17558
|
};
|
|
17559
17559
|
//#endregion
|
|
17560
17560
|
//#region ../../packages/server/dist/version.mjs
|
|
17561
|
-
const HOT_UPDATER_SERVER_VERSION = "1.0.
|
|
17561
|
+
const HOT_UPDATER_SERVER_VERSION = "1.0.5";
|
|
17562
17562
|
//#endregion
|
|
17563
17563
|
//#region ../../packages/server/dist/handlerVersionRoutes.mjs
|
|
17564
17564
|
const createVersionRouteHandlers = (plugins = []) => {
|
|
@@ -53404,10 +53404,10 @@ const storeIdOf = (token) => {
|
|
|
53404
53404
|
if (prefix !== "vercel" || kind !== "blob" || rw !== "rw" || !id) throw new Error("vercelBlobStorage needs the store's read-write token (vercel_blob_rw_...).");
|
|
53405
53405
|
return `store_${id}`;
|
|
53406
53406
|
};
|
|
53407
|
+
const access = "private";
|
|
53407
53408
|
const vercelBlobStorage = (config) => {
|
|
53408
|
-
const { token
|
|
53409
|
+
const { token } = config;
|
|
53409
53410
|
const storeId = storeIdOf(token);
|
|
53410
|
-
const host = `${storeId.slice(6).toLowerCase()}.${access}.blob.vercel-storage.com`;
|
|
53411
53411
|
const normalizedBasePath = config.basePath?.replace(/^\/+|\/+$/g, "") ?? "";
|
|
53412
53412
|
const getStorageKey = createStorageKeyBuilder(normalizedBasePath);
|
|
53413
53413
|
const expiresIn = config.signedUrlExpiresIn ?? 3600;
|
|
@@ -53477,7 +53477,6 @@ const vercelBlobStorage = (config) => {
|
|
|
53477
53477
|
async getDownloadUrl({ storageUri }) {
|
|
53478
53478
|
const { key } = parseAndValidate(storageUri);
|
|
53479
53479
|
const pathname = toPathname(key);
|
|
53480
|
-
if (access === "public") return { url: `https://${host}/${pathname.split("/").map(encodeURIComponent).join("/")}` };
|
|
53481
53480
|
const validUntil = Date.now() + expiresInMs;
|
|
53482
53481
|
const { presignedUrl } = await presignUrl(await delegationUntil(validUntil), {
|
|
53483
53482
|
access,
|
|
@@ -53564,8 +53563,6 @@ const requireEnv = (name) => {
|
|
|
53564
53563
|
if (!value) throw new Error(`${name} is not set on the Vercel project. Run \`hot-updater init\` again to configure it.`);
|
|
53565
53564
|
return value;
|
|
53566
53565
|
};
|
|
53567
|
-
const access = requireEnv("HOT_UPDATER_VERCEL_BLOB_ACCESS");
|
|
53568
|
-
if (access !== "public" && access !== "private") throw new Error("HOT_UPDATER_VERCEL_BLOB_ACCESS must be \"public\" or \"private\".");
|
|
53569
53566
|
const pool = new import_lib$1.default.Pool({
|
|
53570
53567
|
connectionString: requireEnv("HOT_UPDATER_VERCEL_DATABASE_URL"),
|
|
53571
53568
|
idleTimeoutMillis: 5e3,
|
|
@@ -53579,10 +53576,7 @@ const hotUpdater = createHotUpdater({
|
|
|
53579
53576
|
apiKeys(),
|
|
53580
53577
|
remoteConfig()
|
|
53581
53578
|
],
|
|
53582
|
-
storage: vercelBlobStorage({
|
|
53583
|
-
access,
|
|
53584
|
-
token: requireEnv("HOT_UPDATER_VERCEL_BLOB_READ_WRITE_TOKEN")
|
|
53585
|
-
})
|
|
53579
|
+
storage: vercelBlobStorage({ token: requireEnv("HOT_UPDATER_VERCEL_BLOB_READ_WRITE_TOKEN") })
|
|
53586
53580
|
});
|
|
53587
53581
|
const handler = async (request) => withoutEncodingVary(await hotUpdater.handlers.client(request));
|
|
53588
53582
|
//#endregion
|
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
let _hot_updater_plugin_core = require("@hot-updater/plugin-core");
|
|
3
3
|
let _vercel_blob = require("@vercel/blob");
|
|
4
|
+
let _hot_updater_postgres = require("@hot-updater/postgres");
|
|
4
5
|
//#region src/vercelBlobStorage.ts
|
|
5
6
|
const PROTOCOL = "vercel-blob";
|
|
6
7
|
const ONE_YEAR_IN_SECONDS = 31536e3;
|
|
@@ -61,14 +62,15 @@ const storeIdOf = (token) => {
|
|
|
61
62
|
if (prefix !== "vercel" || kind !== "blob" || rw !== "rw" || !id) throw new Error("vercelBlobStorage needs the store's read-write token (vercel_blob_rw_...).");
|
|
62
63
|
return `store_${id}`;
|
|
63
64
|
};
|
|
65
|
+
/** A private store's objects: only an API key holder's signed URL reaches them. */
|
|
66
|
+
const access = "private";
|
|
64
67
|
/**
|
|
65
|
-
* Bundles in a Vercel Blob store.
|
|
66
|
-
*
|
|
68
|
+
* Bundles in a private Vercel Blob store. Devices download from presigned URLs
|
|
69
|
+
* that expire, so the store's objects never sit at a public address.
|
|
67
70
|
*/
|
|
68
71
|
const vercelBlobStorage = (config) => {
|
|
69
|
-
const { token
|
|
72
|
+
const { token } = config;
|
|
70
73
|
const storeId = storeIdOf(token);
|
|
71
|
-
const host = `${storeId.slice(6).toLowerCase()}.${access}.blob.vercel-storage.com`;
|
|
72
74
|
const normalizedBasePath = config.basePath?.replace(/^\/+|\/+$/g, "") ?? "";
|
|
73
75
|
const getStorageKey = (0, _hot_updater_plugin_core.createStorageKeyBuilder)(normalizedBasePath);
|
|
74
76
|
const expiresIn = config.signedUrlExpiresIn ?? 3600;
|
|
@@ -139,7 +141,6 @@ const vercelBlobStorage = (config) => {
|
|
|
139
141
|
async getDownloadUrl({ storageUri }) {
|
|
140
142
|
const { key } = parseAndValidate(storageUri);
|
|
141
143
|
const pathname = toPathname(key);
|
|
142
|
-
if (access === "public") return { url: `https://${host}/${pathname.split("/").map(encodeURIComponent).join("/")}` };
|
|
143
144
|
const validUntil = Date.now() + expiresInMs;
|
|
144
145
|
const { presignedUrl } = await (0, _vercel_blob.presignUrl)(await delegationUntil(validUntil), {
|
|
145
146
|
access,
|
|
@@ -205,4 +206,10 @@ const vercelBlobStorage = (config) => {
|
|
|
205
206
|
});
|
|
206
207
|
};
|
|
207
208
|
//#endregion
|
|
209
|
+
Object.defineProperty(exports, "postgres", {
|
|
210
|
+
enumerable: true,
|
|
211
|
+
get: function() {
|
|
212
|
+
return _hot_updater_postgres.postgres;
|
|
213
|
+
}
|
|
214
|
+
});
|
|
208
215
|
exports.vercelBlobStorage = vercelBlobStorage;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { StorageAdapterWith } from "@hot-updater/plugin-core";
|
|
2
|
+
import { PostgresConfig, postgres } from "@hot-updater/postgres";
|
|
2
3
|
//#region src/vercelBlobStorage.d.ts
|
|
3
4
|
interface VercelBlobStorageConfig {
|
|
4
5
|
/**
|
|
@@ -6,21 +7,19 @@ interface VercelBlobStorageConfig {
|
|
|
6
7
|
* the `BLOB_READ_WRITE_TOKEN` Vercel adds to a project the store connects to.
|
|
7
8
|
*/
|
|
8
9
|
token: string;
|
|
9
|
-
/** The store's access, which Vercel fixes when it creates the store. */
|
|
10
|
-
access: "public" | "private";
|
|
11
10
|
/** Base path where bundles will be stored in the store. */
|
|
12
11
|
basePath?: string;
|
|
13
12
|
/**
|
|
14
|
-
* Lifetime of
|
|
15
|
-
*
|
|
13
|
+
* Lifetime of the download URLs in seconds, at most seven days, Vercel's
|
|
14
|
+
* limit. @default 3600
|
|
16
15
|
*/
|
|
17
16
|
signedUrlExpiresIn?: number;
|
|
18
17
|
}
|
|
19
18
|
type VercelBlobStorageOperations = "put" | "get" | "getDownloadUrl" | "exists" | "delete" | "listObjects" | "deleteObjects";
|
|
20
19
|
/**
|
|
21
|
-
* Bundles in a Vercel Blob store.
|
|
22
|
-
*
|
|
20
|
+
* Bundles in a private Vercel Blob store. Devices download from presigned URLs
|
|
21
|
+
* that expire, so the store's objects never sit at a public address.
|
|
23
22
|
*/
|
|
24
23
|
declare const vercelBlobStorage: (config: VercelBlobStorageConfig) => StorageAdapterWith<VercelBlobStorageOperations>;
|
|
25
24
|
//#endregion
|
|
26
|
-
export { VercelBlobStorageConfig, vercelBlobStorage };
|
|
25
|
+
export { type PostgresConfig, VercelBlobStorageConfig, postgres, vercelBlobStorage };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { StorageAdapterWith } from "@hot-updater/plugin-core";
|
|
2
|
+
import { PostgresConfig, postgres } from "@hot-updater/postgres";
|
|
2
3
|
//#region src/vercelBlobStorage.d.ts
|
|
3
4
|
interface VercelBlobStorageConfig {
|
|
4
5
|
/**
|
|
@@ -6,21 +7,19 @@ interface VercelBlobStorageConfig {
|
|
|
6
7
|
* the `BLOB_READ_WRITE_TOKEN` Vercel adds to a project the store connects to.
|
|
7
8
|
*/
|
|
8
9
|
token: string;
|
|
9
|
-
/** The store's access, which Vercel fixes when it creates the store. */
|
|
10
|
-
access: "public" | "private";
|
|
11
10
|
/** Base path where bundles will be stored in the store. */
|
|
12
11
|
basePath?: string;
|
|
13
12
|
/**
|
|
14
|
-
* Lifetime of
|
|
15
|
-
*
|
|
13
|
+
* Lifetime of the download URLs in seconds, at most seven days, Vercel's
|
|
14
|
+
* limit. @default 3600
|
|
16
15
|
*/
|
|
17
16
|
signedUrlExpiresIn?: number;
|
|
18
17
|
}
|
|
19
18
|
type VercelBlobStorageOperations = "put" | "get" | "getDownloadUrl" | "exists" | "delete" | "listObjects" | "deleteObjects";
|
|
20
19
|
/**
|
|
21
|
-
* Bundles in a Vercel Blob store.
|
|
22
|
-
*
|
|
20
|
+
* Bundles in a private Vercel Blob store. Devices download from presigned URLs
|
|
21
|
+
* that expire, so the store's objects never sit at a public address.
|
|
23
22
|
*/
|
|
24
23
|
declare const vercelBlobStorage: (config: VercelBlobStorageConfig) => StorageAdapterWith<VercelBlobStorageOperations>;
|
|
25
24
|
//#endregion
|
|
26
|
-
export { VercelBlobStorageConfig, vercelBlobStorage };
|
|
25
|
+
export { type PostgresConfig, VercelBlobStorageConfig, postgres, vercelBlobStorage };
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createStorageAdapter, createStorageKeyBuilder, createStorageUri, parseStorageUri } from "@hot-updater/plugin-core";
|
|
2
2
|
import { BlobNotFoundError, del, get, head, issueSignedToken, list, presignUrl, put } from "@vercel/blob";
|
|
3
|
+
import { postgres } from "@hot-updater/postgres";
|
|
3
4
|
//#region src/vercelBlobStorage.ts
|
|
4
5
|
const PROTOCOL = "vercel-blob";
|
|
5
6
|
const ONE_YEAR_IN_SECONDS = 31536e3;
|
|
@@ -60,14 +61,15 @@ const storeIdOf = (token) => {
|
|
|
60
61
|
if (prefix !== "vercel" || kind !== "blob" || rw !== "rw" || !id) throw new Error("vercelBlobStorage needs the store's read-write token (vercel_blob_rw_...).");
|
|
61
62
|
return `store_${id}`;
|
|
62
63
|
};
|
|
64
|
+
/** A private store's objects: only an API key holder's signed URL reaches them. */
|
|
65
|
+
const access = "private";
|
|
63
66
|
/**
|
|
64
|
-
* Bundles in a Vercel Blob store.
|
|
65
|
-
*
|
|
67
|
+
* Bundles in a private Vercel Blob store. Devices download from presigned URLs
|
|
68
|
+
* that expire, so the store's objects never sit at a public address.
|
|
66
69
|
*/
|
|
67
70
|
const vercelBlobStorage = (config) => {
|
|
68
|
-
const { token
|
|
71
|
+
const { token } = config;
|
|
69
72
|
const storeId = storeIdOf(token);
|
|
70
|
-
const host = `${storeId.slice(6).toLowerCase()}.${access}.blob.vercel-storage.com`;
|
|
71
73
|
const normalizedBasePath = config.basePath?.replace(/^\/+|\/+$/g, "") ?? "";
|
|
72
74
|
const getStorageKey = createStorageKeyBuilder(normalizedBasePath);
|
|
73
75
|
const expiresIn = config.signedUrlExpiresIn ?? 3600;
|
|
@@ -138,7 +140,6 @@ const vercelBlobStorage = (config) => {
|
|
|
138
140
|
async getDownloadUrl({ storageUri }) {
|
|
139
141
|
const { key } = parseAndValidate(storageUri);
|
|
140
142
|
const pathname = toPathname(key);
|
|
141
|
-
if (access === "public") return { url: `https://${host}/${pathname.split("/").map(encodeURIComponent).join("/")}` };
|
|
142
143
|
const validUntil = Date.now() + expiresInMs;
|
|
143
144
|
const { presignedUrl } = await presignUrl(await delegationUntil(validUntil), {
|
|
144
145
|
access,
|
|
@@ -204,4 +205,4 @@ const vercelBlobStorage = (config) => {
|
|
|
204
205
|
});
|
|
205
206
|
};
|
|
206
207
|
//#endregion
|
|
207
|
-
export { vercelBlobStorage };
|
|
208
|
+
export { postgres, vercelBlobStorage };
|
package/dist/init/index.cjs
CHANGED
|
@@ -6276,14 +6276,10 @@ const getConfigScaffold = (build) => {
|
|
|
6276
6276
|
}],
|
|
6277
6277
|
configString: `vercelBlobStorage({
|
|
6278
6278
|
token: process.env.HOT_UPDATER_VERCEL_BLOB_READ_WRITE_TOKEN!,
|
|
6279
|
-
access:
|
|
6280
|
-
process.env.HOT_UPDATER_VERCEL_BLOB_ACCESS === "public"
|
|
6281
|
-
? "public"
|
|
6282
|
-
: "private",
|
|
6283
6279
|
})`
|
|
6284
6280
|
}).setDatabase({
|
|
6285
6281
|
imports: [{
|
|
6286
|
-
pkg: "@hot-updater/
|
|
6282
|
+
pkg: "@hot-updater/vercel",
|
|
6287
6283
|
named: ["postgres"]
|
|
6288
6284
|
}],
|
|
6289
6285
|
configString: `postgres({
|
|
@@ -6310,7 +6306,6 @@ const getConfigScaffold = (build) => {
|
|
|
6310
6306
|
const isVercelProjectName = (value) => value !== void 0 && /^[a-z0-9][a-z0-9._-]{0,99}$/.test(value) && !value.includes("---");
|
|
6311
6307
|
/** A Blob store name: letters, digits, `_`, and `-`. */
|
|
6312
6308
|
const isVercelBlobStoreName = (value) => value !== void 0 && /^[A-Za-z0-9][A-Za-z0-9_-]{0,31}$/.test(value);
|
|
6313
|
-
const isVercelBlobAccess = (value) => value === "public" || value === "private";
|
|
6314
6309
|
const isPostgresUrl = (value) => {
|
|
6315
6310
|
if (value === void 0) return false;
|
|
6316
6311
|
try {
|
|
@@ -6363,16 +6358,6 @@ const initProvider = {
|
|
|
6363
6358
|
help: "Blob store ID (store_...), saved by init",
|
|
6364
6359
|
optional: true
|
|
6365
6360
|
},
|
|
6366
|
-
blobAccess: {
|
|
6367
|
-
envKey: "HOT_UPDATER_VERCEL_BLOB_ACCESS",
|
|
6368
|
-
help: "The Blob store's access: private (the default) or public",
|
|
6369
|
-
optional: true,
|
|
6370
|
-
prompt: {
|
|
6371
|
-
message: "Make the new Blob store private?",
|
|
6372
|
-
type: "confirm"
|
|
6373
|
-
},
|
|
6374
|
-
validate: isVercelBlobAccess
|
|
6375
|
-
},
|
|
6376
6361
|
blobReadWriteToken: {
|
|
6377
6362
|
envKey: "HOT_UPDATER_VERCEL_BLOB_READ_WRITE_TOKEN",
|
|
6378
6363
|
help: "The Blob store's read-write token, which init reads from the project",
|
|
@@ -6791,7 +6776,6 @@ const inputs = initProvider.inputs;
|
|
|
6791
6776
|
const BLOB_ENV_PREFIX = "HOT_UPDATER_VERCEL_BLOB";
|
|
6792
6777
|
const BLOB_TOKEN_ENV = inputs.blobReadWriteToken.envKey;
|
|
6793
6778
|
const DATABASE_URL_ENV = inputs.databaseUrl.envKey;
|
|
6794
|
-
const BLOB_ACCESS_ENV = inputs.blobAccess.envKey;
|
|
6795
6779
|
/** Project env vars a Marketplace Postgres database adds, such as Neon's `DATABASE_URL`. */
|
|
6796
6780
|
const DATABASE_URL_ENV_PATTERN = /(?:^|_)(?:DATABASE_URL|POSTGRES_URL)$/;
|
|
6797
6781
|
const cancelled = (value) => _hot_updater_cli_tools.p.isCancel(value);
|
|
@@ -6849,9 +6833,13 @@ const resolveProjectName = async (savedName, nonInteractive) => {
|
|
|
6849
6833
|
validate: (value) => isVercelProjectName(value) ? void 0 : "Use up to 100 lowercase letters, digits, '.', '_', or '-', without '---'."
|
|
6850
6834
|
}));
|
|
6851
6835
|
};
|
|
6852
|
-
/**
|
|
6836
|
+
/**
|
|
6837
|
+
* The saved private store, or one the user selects or creates. Bundles
|
|
6838
|
+
* download only through signed URLs, so a public store is never used.
|
|
6839
|
+
*/
|
|
6853
6840
|
const selectBlobStore = async ({ nonInteractive, savedId, savedName, stores }) => {
|
|
6854
|
-
const
|
|
6841
|
+
const privateStores = stores.filter((store) => store.access === "private");
|
|
6842
|
+
const saved = privateStores.find((store) => store.id === savedId) ?? (savedId === void 0 ? privateStores.find((store) => store.name === savedName) : void 0);
|
|
6855
6843
|
if (nonInteractive) {
|
|
6856
6844
|
if (saved) return {
|
|
6857
6845
|
kind: "existing",
|
|
@@ -6867,16 +6855,16 @@ const selectBlobStore = async ({ nonInteractive, savedId, savedName, stores }) =
|
|
|
6867
6855
|
const createKey = "\0create";
|
|
6868
6856
|
const selected = exitIfCancelled(await _hot_updater_cli_tools.p.select({
|
|
6869
6857
|
message: "Select the Blob store for bundles",
|
|
6870
|
-
initialValue: saved?.id ??
|
|
6871
|
-
options: [...
|
|
6858
|
+
initialValue: saved?.id ?? privateStores[0]?.id ?? createKey,
|
|
6859
|
+
options: [...privateStores.map((store) => ({
|
|
6872
6860
|
value: store.id,
|
|
6873
|
-
label: `${store.name}
|
|
6861
|
+
label: `${store.name}${store.region ? ` (${store.region})` : ""}`
|
|
6874
6862
|
})), {
|
|
6875
6863
|
value: createKey,
|
|
6876
6864
|
label: "Create a new Blob store"
|
|
6877
6865
|
}]
|
|
6878
6866
|
}));
|
|
6879
|
-
const store =
|
|
6867
|
+
const store = privateStores.find((candidate) => candidate.id === selected);
|
|
6880
6868
|
if (store) return {
|
|
6881
6869
|
kind: "existing",
|
|
6882
6870
|
store
|
|
@@ -6891,14 +6879,6 @@ const selectBlobStore = async ({ nonInteractive, savedId, savedName, stores }) =
|
|
|
6891
6879
|
}))
|
|
6892
6880
|
};
|
|
6893
6881
|
};
|
|
6894
|
-
const resolveNewStoreAccess = async (savedAccess, nonInteractive) => {
|
|
6895
|
-
if (isVercelBlobAccess(savedAccess) && nonInteractive) return savedAccess;
|
|
6896
|
-
if (nonInteractive) return "private";
|
|
6897
|
-
return exitIfCancelled(await _hot_updater_cli_tools.p.confirm({
|
|
6898
|
-
message: inputs.blobAccess.prompt.message,
|
|
6899
|
-
initialValue: savedAccess !== "public"
|
|
6900
|
-
})) ? "private" : "public";
|
|
6901
|
-
};
|
|
6902
6882
|
/**
|
|
6903
6883
|
* The store's read-write token: from its connection to the project, which
|
|
6904
6884
|
* init makes when there is none.
|
|
@@ -6917,6 +6897,7 @@ const resolveBlobToken = async (api, project, store) => {
|
|
|
6917
6897
|
const env = (await api.listProjectEnv(project.id)).find((candidate) => candidate.key === tokenEnv && candidate.target.includes("production"));
|
|
6918
6898
|
const token = env === void 0 ? void 0 : await api.getProjectEnvValue(project.id, env.id);
|
|
6919
6899
|
if (!token?.startsWith("vercel_blob_rw_")) throw new _hot_updater_cli_tools.InitError(`Blob store ${store.name} is connected to project ${project.name} without a read-write token for production. Reconnect it from the project's Storage tab with a read-write token, then rerun init.`);
|
|
6900
|
+
if (`store_${token.split("_")[3]}` !== store.id) throw new _hot_updater_cli_tools.InitError(`The production ${tokenEnv} of project ${project.name} belongs to another Blob store, not ${store.name}. Disconnect that store from the project's Storage tab, then rerun init.`);
|
|
6920
6901
|
return token;
|
|
6921
6902
|
};
|
|
6922
6903
|
/**
|
|
@@ -7051,7 +7032,6 @@ const runInit = async ({ build, envFile }) => {
|
|
|
7051
7032
|
...saved.blobStoreId ? { savedId: saved.blobStoreId } : {},
|
|
7052
7033
|
...saved.blobStoreName ? { savedName: saved.blobStoreName } : {}
|
|
7053
7034
|
});
|
|
7054
|
-
const newStoreAccess = storeSelection.kind === "create" ? await resolveNewStoreAccess(saved.blobAccess, nonInteractive) : void 0;
|
|
7055
7035
|
let databaseUrl = nonInteractive ? saved.databaseUrl : void 0;
|
|
7056
7036
|
if (nonInteractive && !isPostgresUrl(databaseUrl)) throw new _hot_updater_cli_tools.InitError(`${DATABASE_URL_ENV} must be a postgres:// or postgresql:// URL.`);
|
|
7057
7037
|
const project = existingProject ?? await (async () => {
|
|
@@ -7073,23 +7053,21 @@ const runInit = async ({ build, envFile }) => {
|
|
|
7073
7053
|
store = storeSelection.store;
|
|
7074
7054
|
_hot_updater_cli_tools.p.log.info(`Selected Blob store: ${store.name}`);
|
|
7075
7055
|
} else {
|
|
7076
|
-
const access = newStoreAccess ?? "private";
|
|
7077
7056
|
const { id } = await api.createBlobStore({
|
|
7078
|
-
access,
|
|
7057
|
+
access: "private",
|
|
7079
7058
|
name: storeSelection.name
|
|
7080
7059
|
});
|
|
7081
7060
|
store = {
|
|
7082
|
-
access,
|
|
7061
|
+
access: "private",
|
|
7083
7062
|
connections: [],
|
|
7084
7063
|
id,
|
|
7085
7064
|
name: storeSelection.name,
|
|
7086
7065
|
region: void 0
|
|
7087
7066
|
};
|
|
7088
|
-
_hot_updater_cli_tools.p.log.info(`Created Blob store: ${store.name} (
|
|
7067
|
+
_hot_updater_cli_tools.p.log.info(`Created Blob store: ${store.name} (private)`);
|
|
7089
7068
|
}
|
|
7090
7069
|
const blobToken = await resolveBlobToken(api, project, store);
|
|
7091
7070
|
const resolvedInputs = {
|
|
7092
|
-
blobAccess: store.access,
|
|
7093
7071
|
blobReadWriteToken: blobToken,
|
|
7094
7072
|
blobStoreId: store.id,
|
|
7095
7073
|
blobStoreName: store.name,
|
|
@@ -7124,7 +7102,6 @@ const runInit = async ({ build, envFile }) => {
|
|
|
7124
7102
|
title: "Setting the Function's environment variables...",
|
|
7125
7103
|
task: async () => {
|
|
7126
7104
|
await api.setProductionEnv(project.id, DATABASE_URL_ENV, databaseUrl);
|
|
7127
|
-
await api.setProductionEnv(project.id, BLOB_ACCESS_ENV, store.access);
|
|
7128
7105
|
await syncFunctionBlobToken(api, project, store, blobToken);
|
|
7129
7106
|
return "Set the Function's environment variables";
|
|
7130
7107
|
}
|
package/dist/init/index.d.cts
CHANGED
|
@@ -43,16 +43,6 @@ declare const initProvider: {
|
|
|
43
43
|
readonly help: "Blob store ID (store_...), saved by init";
|
|
44
44
|
readonly optional: true;
|
|
45
45
|
};
|
|
46
|
-
readonly blobAccess: {
|
|
47
|
-
readonly envKey: "HOT_UPDATER_VERCEL_BLOB_ACCESS";
|
|
48
|
-
readonly help: "The Blob store's access: private (the default) or public";
|
|
49
|
-
readonly optional: true;
|
|
50
|
-
readonly prompt: {
|
|
51
|
-
readonly message: "Make the new Blob store private?";
|
|
52
|
-
readonly type: "confirm";
|
|
53
|
-
};
|
|
54
|
-
readonly validate: (value: string | undefined) => value is "public" | "private";
|
|
55
|
-
};
|
|
56
46
|
readonly blobReadWriteToken: {
|
|
57
47
|
readonly envKey: "HOT_UPDATER_VERCEL_BLOB_READ_WRITE_TOKEN";
|
|
58
48
|
readonly help: "The Blob store's read-write token, which init reads from the project";
|
package/dist/init/index.d.mts
CHANGED
|
@@ -43,16 +43,6 @@ declare const initProvider: {
|
|
|
43
43
|
readonly help: "Blob store ID (store_...), saved by init";
|
|
44
44
|
readonly optional: true;
|
|
45
45
|
};
|
|
46
|
-
readonly blobAccess: {
|
|
47
|
-
readonly envKey: "HOT_UPDATER_VERCEL_BLOB_ACCESS";
|
|
48
|
-
readonly help: "The Blob store's access: private (the default) or public";
|
|
49
|
-
readonly optional: true;
|
|
50
|
-
readonly prompt: {
|
|
51
|
-
readonly message: "Make the new Blob store private?";
|
|
52
|
-
readonly type: "confirm";
|
|
53
|
-
};
|
|
54
|
-
readonly validate: (value: string | undefined) => value is "public" | "private";
|
|
55
|
-
};
|
|
56
46
|
readonly blobReadWriteToken: {
|
|
57
47
|
readonly envKey: "HOT_UPDATER_VERCEL_BLOB_READ_WRITE_TOKEN";
|
|
58
48
|
readonly help: "The Blob store's read-write token, which init reads from the project";
|
package/dist/init/index.mjs
CHANGED
|
@@ -6270,14 +6270,10 @@ const getConfigScaffold = (build) => {
|
|
|
6270
6270
|
}],
|
|
6271
6271
|
configString: `vercelBlobStorage({
|
|
6272
6272
|
token: process.env.HOT_UPDATER_VERCEL_BLOB_READ_WRITE_TOKEN!,
|
|
6273
|
-
access:
|
|
6274
|
-
process.env.HOT_UPDATER_VERCEL_BLOB_ACCESS === "public"
|
|
6275
|
-
? "public"
|
|
6276
|
-
: "private",
|
|
6277
6273
|
})`
|
|
6278
6274
|
}).setDatabase({
|
|
6279
6275
|
imports: [{
|
|
6280
|
-
pkg: "@hot-updater/
|
|
6276
|
+
pkg: "@hot-updater/vercel",
|
|
6281
6277
|
named: ["postgres"]
|
|
6282
6278
|
}],
|
|
6283
6279
|
configString: `postgres({
|
|
@@ -6304,7 +6300,6 @@ const getConfigScaffold = (build) => {
|
|
|
6304
6300
|
const isVercelProjectName = (value) => value !== void 0 && /^[a-z0-9][a-z0-9._-]{0,99}$/.test(value) && !value.includes("---");
|
|
6305
6301
|
/** A Blob store name: letters, digits, `_`, and `-`. */
|
|
6306
6302
|
const isVercelBlobStoreName = (value) => value !== void 0 && /^[A-Za-z0-9][A-Za-z0-9_-]{0,31}$/.test(value);
|
|
6307
|
-
const isVercelBlobAccess = (value) => value === "public" || value === "private";
|
|
6308
6303
|
const isPostgresUrl = (value) => {
|
|
6309
6304
|
if (value === void 0) return false;
|
|
6310
6305
|
try {
|
|
@@ -6357,16 +6352,6 @@ const initProvider = {
|
|
|
6357
6352
|
help: "Blob store ID (store_...), saved by init",
|
|
6358
6353
|
optional: true
|
|
6359
6354
|
},
|
|
6360
|
-
blobAccess: {
|
|
6361
|
-
envKey: "HOT_UPDATER_VERCEL_BLOB_ACCESS",
|
|
6362
|
-
help: "The Blob store's access: private (the default) or public",
|
|
6363
|
-
optional: true,
|
|
6364
|
-
prompt: {
|
|
6365
|
-
message: "Make the new Blob store private?",
|
|
6366
|
-
type: "confirm"
|
|
6367
|
-
},
|
|
6368
|
-
validate: isVercelBlobAccess
|
|
6369
|
-
},
|
|
6370
6355
|
blobReadWriteToken: {
|
|
6371
6356
|
envKey: "HOT_UPDATER_VERCEL_BLOB_READ_WRITE_TOKEN",
|
|
6372
6357
|
help: "The Blob store's read-write token, which init reads from the project",
|
|
@@ -6785,7 +6770,6 @@ const inputs = initProvider.inputs;
|
|
|
6785
6770
|
const BLOB_ENV_PREFIX = "HOT_UPDATER_VERCEL_BLOB";
|
|
6786
6771
|
const BLOB_TOKEN_ENV = inputs.blobReadWriteToken.envKey;
|
|
6787
6772
|
const DATABASE_URL_ENV = inputs.databaseUrl.envKey;
|
|
6788
|
-
const BLOB_ACCESS_ENV = inputs.blobAccess.envKey;
|
|
6789
6773
|
/** Project env vars a Marketplace Postgres database adds, such as Neon's `DATABASE_URL`. */
|
|
6790
6774
|
const DATABASE_URL_ENV_PATTERN = /(?:^|_)(?:DATABASE_URL|POSTGRES_URL)$/;
|
|
6791
6775
|
const cancelled = (value) => p.isCancel(value);
|
|
@@ -6843,9 +6827,13 @@ const resolveProjectName = async (savedName, nonInteractive) => {
|
|
|
6843
6827
|
validate: (value) => isVercelProjectName(value) ? void 0 : "Use up to 100 lowercase letters, digits, '.', '_', or '-', without '---'."
|
|
6844
6828
|
}));
|
|
6845
6829
|
};
|
|
6846
|
-
/**
|
|
6830
|
+
/**
|
|
6831
|
+
* The saved private store, or one the user selects or creates. Bundles
|
|
6832
|
+
* download only through signed URLs, so a public store is never used.
|
|
6833
|
+
*/
|
|
6847
6834
|
const selectBlobStore = async ({ nonInteractive, savedId, savedName, stores }) => {
|
|
6848
|
-
const
|
|
6835
|
+
const privateStores = stores.filter((store) => store.access === "private");
|
|
6836
|
+
const saved = privateStores.find((store) => store.id === savedId) ?? (savedId === void 0 ? privateStores.find((store) => store.name === savedName) : void 0);
|
|
6849
6837
|
if (nonInteractive) {
|
|
6850
6838
|
if (saved) return {
|
|
6851
6839
|
kind: "existing",
|
|
@@ -6861,16 +6849,16 @@ const selectBlobStore = async ({ nonInteractive, savedId, savedName, stores }) =
|
|
|
6861
6849
|
const createKey = "\0create";
|
|
6862
6850
|
const selected = exitIfCancelled(await p.select({
|
|
6863
6851
|
message: "Select the Blob store for bundles",
|
|
6864
|
-
initialValue: saved?.id ??
|
|
6865
|
-
options: [...
|
|
6852
|
+
initialValue: saved?.id ?? privateStores[0]?.id ?? createKey,
|
|
6853
|
+
options: [...privateStores.map((store) => ({
|
|
6866
6854
|
value: store.id,
|
|
6867
|
-
label: `${store.name}
|
|
6855
|
+
label: `${store.name}${store.region ? ` (${store.region})` : ""}`
|
|
6868
6856
|
})), {
|
|
6869
6857
|
value: createKey,
|
|
6870
6858
|
label: "Create a new Blob store"
|
|
6871
6859
|
}]
|
|
6872
6860
|
}));
|
|
6873
|
-
const store =
|
|
6861
|
+
const store = privateStores.find((candidate) => candidate.id === selected);
|
|
6874
6862
|
if (store) return {
|
|
6875
6863
|
kind: "existing",
|
|
6876
6864
|
store
|
|
@@ -6885,14 +6873,6 @@ const selectBlobStore = async ({ nonInteractive, savedId, savedName, stores }) =
|
|
|
6885
6873
|
}))
|
|
6886
6874
|
};
|
|
6887
6875
|
};
|
|
6888
|
-
const resolveNewStoreAccess = async (savedAccess, nonInteractive) => {
|
|
6889
|
-
if (isVercelBlobAccess(savedAccess) && nonInteractive) return savedAccess;
|
|
6890
|
-
if (nonInteractive) return "private";
|
|
6891
|
-
return exitIfCancelled(await p.confirm({
|
|
6892
|
-
message: inputs.blobAccess.prompt.message,
|
|
6893
|
-
initialValue: savedAccess !== "public"
|
|
6894
|
-
})) ? "private" : "public";
|
|
6895
|
-
};
|
|
6896
6876
|
/**
|
|
6897
6877
|
* The store's read-write token: from its connection to the project, which
|
|
6898
6878
|
* init makes when there is none.
|
|
@@ -6911,6 +6891,7 @@ const resolveBlobToken = async (api, project, store) => {
|
|
|
6911
6891
|
const env = (await api.listProjectEnv(project.id)).find((candidate) => candidate.key === tokenEnv && candidate.target.includes("production"));
|
|
6912
6892
|
const token = env === void 0 ? void 0 : await api.getProjectEnvValue(project.id, env.id);
|
|
6913
6893
|
if (!token?.startsWith("vercel_blob_rw_")) throw new InitError(`Blob store ${store.name} is connected to project ${project.name} without a read-write token for production. Reconnect it from the project's Storage tab with a read-write token, then rerun init.`);
|
|
6894
|
+
if (`store_${token.split("_")[3]}` !== store.id) throw new InitError(`The production ${tokenEnv} of project ${project.name} belongs to another Blob store, not ${store.name}. Disconnect that store from the project's Storage tab, then rerun init.`);
|
|
6914
6895
|
return token;
|
|
6915
6896
|
};
|
|
6916
6897
|
/**
|
|
@@ -7045,7 +7026,6 @@ const runInit = async ({ build, envFile }) => {
|
|
|
7045
7026
|
...saved.blobStoreId ? { savedId: saved.blobStoreId } : {},
|
|
7046
7027
|
...saved.blobStoreName ? { savedName: saved.blobStoreName } : {}
|
|
7047
7028
|
});
|
|
7048
|
-
const newStoreAccess = storeSelection.kind === "create" ? await resolveNewStoreAccess(saved.blobAccess, nonInteractive) : void 0;
|
|
7049
7029
|
let databaseUrl = nonInteractive ? saved.databaseUrl : void 0;
|
|
7050
7030
|
if (nonInteractive && !isPostgresUrl(databaseUrl)) throw new InitError(`${DATABASE_URL_ENV} must be a postgres:// or postgresql:// URL.`);
|
|
7051
7031
|
const project = existingProject ?? await (async () => {
|
|
@@ -7067,23 +7047,21 @@ const runInit = async ({ build, envFile }) => {
|
|
|
7067
7047
|
store = storeSelection.store;
|
|
7068
7048
|
p.log.info(`Selected Blob store: ${store.name}`);
|
|
7069
7049
|
} else {
|
|
7070
|
-
const access = newStoreAccess ?? "private";
|
|
7071
7050
|
const { id } = await api.createBlobStore({
|
|
7072
|
-
access,
|
|
7051
|
+
access: "private",
|
|
7073
7052
|
name: storeSelection.name
|
|
7074
7053
|
});
|
|
7075
7054
|
store = {
|
|
7076
|
-
access,
|
|
7055
|
+
access: "private",
|
|
7077
7056
|
connections: [],
|
|
7078
7057
|
id,
|
|
7079
7058
|
name: storeSelection.name,
|
|
7080
7059
|
region: void 0
|
|
7081
7060
|
};
|
|
7082
|
-
p.log.info(`Created Blob store: ${store.name} (
|
|
7061
|
+
p.log.info(`Created Blob store: ${store.name} (private)`);
|
|
7083
7062
|
}
|
|
7084
7063
|
const blobToken = await resolveBlobToken(api, project, store);
|
|
7085
7064
|
const resolvedInputs = {
|
|
7086
|
-
blobAccess: store.access,
|
|
7087
7065
|
blobReadWriteToken: blobToken,
|
|
7088
7066
|
blobStoreId: store.id,
|
|
7089
7067
|
blobStoreName: store.name,
|
|
@@ -7118,7 +7096,6 @@ const runInit = async ({ build, envFile }) => {
|
|
|
7118
7096
|
title: "Setting the Function's environment variables...",
|
|
7119
7097
|
task: async () => {
|
|
7120
7098
|
await api.setProductionEnv(project.id, DATABASE_URL_ENV, databaseUrl);
|
|
7121
|
-
await api.setProductionEnv(project.id, BLOB_ACCESS_ENV, store.access);
|
|
7122
7099
|
await syncFunctionBlobToken(api, project, store, blobToken);
|
|
7123
7100
|
return "Set the Function's environment variables";
|
|
7124
7101
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hot-updater/vercel",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.5",
|
|
5
5
|
"description": "React Native OTA solution for self-hosted",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
7
7
|
"module": "dist/index.mjs",
|
|
@@ -41,11 +41,11 @@
|
|
|
41
41
|
"@vercel/cli-auth": "0.3.10",
|
|
42
42
|
"@vercel/cli-config": "0.3.1",
|
|
43
43
|
"pg": "8.13.1",
|
|
44
|
-
"@hot-updater/
|
|
45
|
-
"@hot-updater/
|
|
46
|
-
"@hot-updater/
|
|
47
|
-
"@hot-updater/
|
|
48
|
-
"@hot-updater/server": "1.0.
|
|
44
|
+
"@hot-updater/cli-tools": "1.0.5",
|
|
45
|
+
"@hot-updater/postgres": "1.0.5",
|
|
46
|
+
"@hot-updater/protocol": "1.0.5",
|
|
47
|
+
"@hot-updater/plugin-core": "1.0.5",
|
|
48
|
+
"@hot-updater/server": "1.0.5"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@types/node": "^20",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"execa": "9.5.2",
|
|
55
55
|
"kysely": "0.28.17",
|
|
56
56
|
"vitest": "4.1.4",
|
|
57
|
-
"@hot-updater/test-utils": "1.0.
|
|
57
|
+
"@hot-updater/test-utils": "1.0.5"
|
|
58
58
|
},
|
|
59
59
|
"scripts": {
|
|
60
60
|
"build": "tsdown",
|