hot-updater 1.0.1 → 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 +26 -1
- 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 +1 -1
- package/dist/infra-templates/firebase/COMMON.md +3 -1
- 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/functions/index.cjs +1 -1
- package/dist/infra-templates/firebase/template.json +11 -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-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
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Azure setup checklist
|
|
2
|
+
|
|
3
|
+
Read COMMON.md first. Follow these steps in order and record verified observations
|
|
4
|
+
in deployment.json. Use the Azure CLI (`az`) or other authorized Azure tools; paths
|
|
5
|
+
below are relative to this scaffold. The azure/function/ directory holds the same
|
|
6
|
+
bundled Function package as interactive init: `index.mjs`, `host.json` and
|
|
7
|
+
`package.json`, which need no install. Verify `az version` (Azure CLI 2.60.0 or
|
|
8
|
+
later, for the Flex Consumption commands) and an authenticated
|
|
9
|
+
`az account show` before remote provisioning.
|
|
10
|
+
|
|
11
|
+
## Upgrade or reconcile an existing deployment
|
|
12
|
+
|
|
13
|
+
Read upgrades/README.md and the Azure section of each applicable version file
|
|
14
|
+
first; a listed change applies before anything below. Then reconcile the
|
|
15
|
+
recorded resource group, storage account, Cosmos DB account and Function App
|
|
16
|
+
with this scaffold: every step is idempotent and reuses what exists. Repeat
|
|
17
|
+
az.database-credential, whose migration creates only what is missing and
|
|
18
|
+
refuses a container of another storage layout, then az.function, which sets
|
|
19
|
+
the Function App's settings and deploys this scaffold's package, and finish
|
|
20
|
+
with az.complete. Reuse the recorded resources and saved client credential;
|
|
21
|
+
never create replacements to upgrade. `hot-updater init` with the saved inputs
|
|
22
|
+
(`--from-env-file .env.hotupdater`) performs the same checks, migration and
|
|
23
|
+
deployment.
|
|
24
|
+
|
|
25
|
+
## Setup
|
|
26
|
+
|
|
27
|
+
- [ ] **az.scope — Select the subscription and resource group**
|
|
28
|
+
- Inputs: authenticated account, workspace configuration and established region.
|
|
29
|
+
- Run: list enabled subscriptions (`az account list`) and select the one the
|
|
30
|
+
user names or the only one. Register the resource providers
|
|
31
|
+
Microsoft.Storage, Microsoft.DocumentDB, Microsoft.Web, and
|
|
32
|
+
Microsoft.App (which the CLI requires to create a Flex Consumption app) when
|
|
33
|
+
`az provider show` reports them unregistered, as a new subscription does
|
|
34
|
+
(`az provider register --namespace <namespace> --wait`). List the regions
|
|
35
|
+
that host Flex Consumption
|
|
36
|
+
(`az functionapp list-flexconsumption-locations`): the Function App shares
|
|
37
|
+
the group's region. Query the resource group (`az group show`); reuse it
|
|
38
|
+
only when its region is in that list, or choose a listed region for a new
|
|
39
|
+
one and create it (`az group create`) only after every preflight below
|
|
40
|
+
passed. Stop before creating any resource in a group outside the list. Inspect
|
|
41
|
+
existing Hot Updater resources and the upgrade files before adopting them.
|
|
42
|
+
Preserve unrelated resources.
|
|
43
|
+
- Preflight, before creating the resource group or any storage, Cosmos DB
|
|
44
|
+
or Function App resource, as init does; stop on any failure:
|
|
45
|
+
- Storage account (`az storage account show`): an existing one must be a
|
|
46
|
+
general-purpose `StorageV2` (or `Storage`) account, since the Function
|
|
47
|
+
App's host storage needs Blob, Queue and Table services, must allow
|
|
48
|
+
shared key access, and must not restrict network access
|
|
49
|
+
(`publicNetworkAccess` not `Disabled`, `networkRuleSet.defaultAction`
|
|
50
|
+
not `Deny`): devices download from it, and the setup creates no virtual
|
|
51
|
+
network. A new name must pass `az storage account check-name`.
|
|
52
|
+
- Cosmos DB account (`az cosmosdb show`): an existing one must be API for
|
|
53
|
+
NoSQL (`kind` `GlobalDocumentDB`, no Mongo/Cassandra/Gremlin/Table
|
|
54
|
+
capability), keep key authentication (`disableLocalAuth` and
|
|
55
|
+
`disableKeyBasedMetadataWriteAccess` not true, since the migration
|
|
56
|
+
creates and updates the database and container with the key) and
|
|
57
|
+
write in one region (`enableMultipleWriteLocations` not true), and be
|
|
58
|
+
reachable from the Function App, which joins no virtual network:
|
|
59
|
+
`publicNetworkAccess` not `Disabled`, and with IP or virtual network
|
|
60
|
+
rules, an `0.0.0.0` rule that admits Azure datacenters and a rule that
|
|
61
|
+
admits the machine running the migration (reading the `hot-updater`
|
|
62
|
+
database with the key answers, or 404s, rather than 403). An existing
|
|
63
|
+
`hot_updater_v1` container in the `hot-updater` database must be
|
|
64
|
+
partitioned by `/partition` alone, have no unique keys, and either hold
|
|
65
|
+
a `schema.engine` setting of this version's layout or no items at all
|
|
66
|
+
(`az cosmosdb sql container show`, then a cross-partition
|
|
67
|
+
`SELECT TOP 1 c.id FROM c`). When its default consistency is not
|
|
68
|
+
`Strong`, get the user's approval to change it, since other apps on it
|
|
69
|
+
read under it too, and change it as the last preflight step, before any
|
|
70
|
+
resource is created (`az cosmosdb update --default-consistency-level
|
|
71
|
+
Strong`): Azure refuses it for an account whose regions lie more than
|
|
72
|
+
5,000 miles apart unless support enabled global strong consistency, and
|
|
73
|
+
then stop. A new name must be free
|
|
74
|
+
(`az cosmosdb check-name-exists` returns false).
|
|
75
|
+
- Function App (`az functionapp show`): an existing one must be a Flex
|
|
76
|
+
Consumption app on Node.js 22 or later (`properties.functionAppConfig.runtime`)
|
|
77
|
+
that is Hot Updater's: its app settings include
|
|
78
|
+
HOT_UPDATER_AZURE_COSMOS_CONNECTION_STRING, or it runs no functions yet
|
|
79
|
+
(`az functionapp function list`). Deploying replaces its whole package. A new name must be free across
|
|
80
|
+
azurewebsites.net (`az rest --method post --url
|
|
81
|
+
https://management.azure.com/subscriptions/<subscription-id>/providers/Microsoft.Web/checknameavailability?api-version=2024-04-01
|
|
82
|
+
--body '{"name":"<app>","type":"Microsoft.Web/sites"}'`).
|
|
83
|
+
- Verify/record: subscriptionId, resourceGroup, a location that hosts Flex
|
|
84
|
+
Consumption, and the preflight results.
|
|
85
|
+
- Retry: query the same group before another create request.
|
|
86
|
+
|
|
87
|
+
- [ ] **az.storage — Prepare the storage account**
|
|
88
|
+
- Requires: az.scope.
|
|
89
|
+
- Run: reuse the account az.scope checked, or create a StorageV2, Standard_LRS
|
|
90
|
+
account with TLS 1.2 and blob public access disabled. Shared key access
|
|
91
|
+
must stay allowed: the bundles' SAS URLs and the Function's settings use
|
|
92
|
+
the account key. Create the private `hot-updater` blob container when
|
|
93
|
+
missing, or make an existing one private (no anonymous `publicAccess`):
|
|
94
|
+
devices download bundles through SAS URLs.
|
|
95
|
+
- Verify/record: storageAccount, its region and the container.
|
|
96
|
+
- Retry: query the account and container; never create a second account to
|
|
97
|
+
resolve a naming conflict the user did not approve.
|
|
98
|
+
|
|
99
|
+
- [ ] **az.cosmos — Prepare the Cosmos DB account**
|
|
100
|
+
- Requires: az.scope.
|
|
101
|
+
- Run: reuse the account az.scope checked (and made `Strong` when the user
|
|
102
|
+
approved it there), or create one: API for NoSQL
|
|
103
|
+
(`--kind GlobalDocumentDB`), serverless (`--capabilities EnableServerless`
|
|
104
|
+
on the core CLI; `--capacity-mode Serverless` when `az cosmosdb create
|
|
105
|
+
--help` lists it, as the cosmosdb-preview extension does, whose preview
|
|
106
|
+
API refuses the capability) in
|
|
107
|
+
the group's region, with `Strong` default consistency. Account creation
|
|
108
|
+
takes several minutes.
|
|
109
|
+
- Verify/record: cosmosAccount, API, capacity mode and consistency.
|
|
110
|
+
- Retry: query the account's provisioning state instead of creating it again.
|
|
111
|
+
|
|
112
|
+
- [ ] **az.database-credential — Create the container, write the schema settings and register the client credential**
|
|
113
|
+
- Requires: az.storage and az.cosmos. Complete this before Function deployment, as init does.
|
|
114
|
+
- Run: read the connection strings (`az storage account
|
|
115
|
+
show-connection-string`, `az cosmosdb keys list --type
|
|
116
|
+
connection-strings`) into the app's ignored .env.hotupdater as
|
|
117
|
+
HOT_UPDATER_AZURE_STORAGE_CONNECTION_STRING and
|
|
118
|
+
HOT_UPDATER_AZURE_COSMOS_CONNECTION_STRING; never paste them into chat.
|
|
119
|
+
When the `hot_updater_v1` container of the `hot-updater` database exists,
|
|
120
|
+
read its settings item (`pk = private_hot_updater_settings`,
|
|
121
|
+
`row.key = schema.engine`); its `row.value` must be the string `"1"` when
|
|
122
|
+
present. A missing marker is acceptable only for an empty container. Stop
|
|
123
|
+
on any other value, a populated container without the marker, a container
|
|
124
|
+
not partitioned by `/partition`, or an unknown layout; preserve the data
|
|
125
|
+
for investigation. Then follow COMMON.md's Local CLI and client credential
|
|
126
|
+
steps. Run app/provision-client-credential.mjs from the app directory: the
|
|
127
|
+
helper loads app/hotUpdater.ts, the credential helper's server definition,
|
|
128
|
+
which stays in the scaffold and is never copied into the app. The helper
|
|
129
|
+
first runs app/migrate.ts, which creates the database and container when
|
|
130
|
+
they are missing and writes the schema settings of core and the plugins
|
|
131
|
+
app/hotUpdater.ts runs (the database answers 503 until they exist),
|
|
132
|
+
then registers the client API key.
|
|
133
|
+
- Verify/record: helper succeeds against the chosen account and the
|
|
134
|
+
API key is persisted privately. Do not create settings items
|
|
135
|
+
by hand.
|
|
136
|
+
- Retry: repeat the compatibility preflight, then rerun the helper with the
|
|
137
|
+
saved API key. Never edit settings to bypass a failed check or
|
|
138
|
+
rotate the API key.
|
|
139
|
+
|
|
140
|
+
- [ ] **az.function — Deploy the server**
|
|
141
|
+
- Requires: az.database-credential.
|
|
142
|
+
- Run: reuse the app az.scope checked, or create it on a Flex Consumption
|
|
143
|
+
plan (`az functionapp create --flexconsumption-location <location>
|
|
144
|
+
--runtime node --runtime-version 22 --storage-account <account>`). Set the
|
|
145
|
+
app settings HOT_UPDATER_AZURE_STORAGE_CONNECTION_STRING,
|
|
146
|
+
HOT_UPDATER_AZURE_BLOB_CONTAINER (`hot-updater`) and
|
|
147
|
+
HOT_UPDATER_AZURE_COSMOS_CONNECTION_STRING, preserving unrelated settings.
|
|
148
|
+
Zip the three files of azure/function/ at the archive root and deploy them
|
|
149
|
+
(`az functionapp deployment source config-zip --src <zip>`).
|
|
150
|
+
- Verify/record: functionApp, its default host name and baseUrl
|
|
151
|
+
(`https://<defaultHostName>`). host.json drops the `/api` prefix, so the
|
|
152
|
+
host name serves every route.
|
|
153
|
+
- Retry: inspect the deployment status and logs; reuse the same Function App.
|
|
154
|
+
|
|
155
|
+
- [ ] **az.complete — Verify and report**
|
|
156
|
+
- Requires: az.function.
|
|
157
|
+
- Run: check `<baseUrl>/version` and complete common.verify, common.local and
|
|
158
|
+
common.report in COMMON.md.
|
|
159
|
+
- Verify/record: target version/generation, catalog authentication and local
|
|
160
|
+
configuration pass. Resolve/download an existing artifact when available:
|
|
161
|
+
/version and an empty catalog do not exercise SAS download URLs. Report app
|
|
162
|
+
integration/OTA validation separately from server readiness.
|
|
163
|
+
- Retry: inspect the failing check and corresponding step, preserving verified resources.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { azureBlobStorage, cosmosDatabase } from "@hot-updater/azure";
|
|
2
|
+
import { bare } from "@hot-updater/bare";
|
|
3
|
+
import { defineConfig } from "hot-updater";
|
|
4
|
+
import { apiKeys, insights, remoteConfig } from "hot-updater/plugins";
|
|
5
|
+
import { existsSync } from "node:fs";
|
|
6
|
+
|
|
7
|
+
if (existsSync(".env.hotupdater")) {
|
|
8
|
+
process.loadEnvFile(".env.hotupdater");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default defineConfig({
|
|
12
|
+
build: bare({ enableHermes: true }),
|
|
13
|
+
storage: azureBlobStorage({
|
|
14
|
+
connectionString: process.env.HOT_UPDATER_AZURE_STORAGE_CONNECTION_STRING!,
|
|
15
|
+
containerName: "hot-updater",
|
|
16
|
+
}),
|
|
17
|
+
database: cosmosDatabase({
|
|
18
|
+
connectionString: process.env.HOT_UPDATER_AZURE_COSMOS_CONNECTION_STRING!,
|
|
19
|
+
}),
|
|
20
|
+
plugins: [apiKeys(), insights(), remoteConfig()],
|
|
21
|
+
updateStrategy: "appVersion", // or "fingerprint"
|
|
22
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { azureBlobStorage, cosmosDatabase } from "@hot-updater/azure";
|
|
2
|
+
import { expo } from "@hot-updater/expo";
|
|
3
|
+
import { defineConfig } from "hot-updater";
|
|
4
|
+
import { apiKeys, insights, remoteConfig } from "hot-updater/plugins";
|
|
5
|
+
import { existsSync } from "node:fs";
|
|
6
|
+
|
|
7
|
+
if (existsSync(".env.hotupdater")) {
|
|
8
|
+
process.loadEnvFile(".env.hotupdater");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default defineConfig({
|
|
12
|
+
build: expo(),
|
|
13
|
+
storage: azureBlobStorage({
|
|
14
|
+
connectionString: process.env.HOT_UPDATER_AZURE_STORAGE_CONNECTION_STRING!,
|
|
15
|
+
containerName: "hot-updater",
|
|
16
|
+
}),
|
|
17
|
+
database: cosmosDatabase({
|
|
18
|
+
connectionString: process.env.HOT_UPDATER_AZURE_COSMOS_CONNECTION_STRING!,
|
|
19
|
+
}),
|
|
20
|
+
plugins: [apiKeys(), insights(), remoteConfig()],
|
|
21
|
+
updateStrategy: "appVersion", // or "fingerprint"
|
|
22
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { azureBlobStorage, cosmosDatabase } from "@hot-updater/azure";
|
|
2
|
+
import { rock } from "@hot-updater/rock";
|
|
3
|
+
import { defineConfig } from "hot-updater";
|
|
4
|
+
import { apiKeys, insights, remoteConfig } from "hot-updater/plugins";
|
|
5
|
+
import { existsSync } from "node:fs";
|
|
6
|
+
|
|
7
|
+
if (existsSync(".env.hotupdater")) {
|
|
8
|
+
process.loadEnvFile(".env.hotupdater");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default defineConfig({
|
|
12
|
+
build: rock(),
|
|
13
|
+
storage: azureBlobStorage({
|
|
14
|
+
connectionString: process.env.HOT_UPDATER_AZURE_STORAGE_CONNECTION_STRING!,
|
|
15
|
+
containerName: "hot-updater",
|
|
16
|
+
}),
|
|
17
|
+
database: cosmosDatabase({
|
|
18
|
+
connectionString: process.env.HOT_UPDATER_AZURE_COSMOS_CONNECTION_STRING!,
|
|
19
|
+
}),
|
|
20
|
+
plugins: [apiKeys(), insights(), remoteConfig()],
|
|
21
|
+
updateStrategy: "appVersion", // or "fingerprint"
|
|
22
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { azureBlobStorage, cosmosDatabase } from "@hot-updater/azure";
|
|
2
|
+
import { createHotUpdater } from "@hot-updater/server";
|
|
3
|
+
import { apiKeys, insights, remoteConfig } from "@hot-updater/server/plugins";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The deployed server's database, storage, and plugins, on which
|
|
7
|
+
* provision-client-credential.mjs registers the app's client credential.
|
|
8
|
+
* It stays in the scaffold: the app's hot-updater.config.ts lists the same
|
|
9
|
+
* database, storage, and plugins.
|
|
10
|
+
*/
|
|
11
|
+
export const hotUpdater = createHotUpdater({
|
|
12
|
+
database: cosmosDatabase({
|
|
13
|
+
connectionString: process.env.HOT_UPDATER_AZURE_COSMOS_CONNECTION_STRING!,
|
|
14
|
+
}),
|
|
15
|
+
storage: azureBlobStorage({
|
|
16
|
+
connectionString: process.env.HOT_UPDATER_AZURE_STORAGE_CONNECTION_STRING!,
|
|
17
|
+
containerName: "hot-updater",
|
|
18
|
+
}),
|
|
19
|
+
plugins: [apiKeys(), insights(), remoteConfig()],
|
|
20
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { toolingTargetOf } from "@hot-updater/plugin-core";
|
|
2
|
+
import type { HotUpdaterAPI } from "@hot-updater/server";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Creates the tables of core and the plugins `hotUpdater` runs where the
|
|
6
|
+
* database keeps tables, or its store where it has one to prepare, and writes
|
|
7
|
+
* their schema settings, which the database checks before its first read.
|
|
8
|
+
*/
|
|
9
|
+
export const migrate = async ({ database, plugins }: HotUpdaterAPI) => {
|
|
10
|
+
if (database.createMigrator === undefined) {
|
|
11
|
+
throw new Error(`The ${database.name} database has no migrator.`);
|
|
12
|
+
}
|
|
13
|
+
const result = await database
|
|
14
|
+
.createMigrator(toolingTargetOf(plugins))
|
|
15
|
+
.migrateToLatest({ updateSettings: true });
|
|
16
|
+
await result.execute();
|
|
17
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
|
|
4
|
+
// The credential helper's server definition reads process.env when it loads,
|
|
5
|
+
// so the settings go first; a static import would load it before them.
|
|
6
|
+
if (existsSync(".env.hotupdater")) {
|
|
7
|
+
process.loadEnvFile(".env.hotupdater");
|
|
8
|
+
}
|
|
9
|
+
const { hotUpdater } = await import("./hotUpdater.ts");
|
|
10
|
+
if (
|
|
11
|
+
typeof hotUpdater !== "object" ||
|
|
12
|
+
hotUpdater === null ||
|
|
13
|
+
typeof hotUpdater.database !== "object" ||
|
|
14
|
+
!Array.isArray(hotUpdater.plugins) ||
|
|
15
|
+
typeof hotUpdater.api !== "object"
|
|
16
|
+
) {
|
|
17
|
+
throw new Error(
|
|
18
|
+
"app/hotUpdater.ts, the credential helper's server definition, must export `hotUpdater`: the server createHotUpdater from @hot-updater/server returns.",
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
const { api, clientAuth, database, plugins } = hotUpdater;
|
|
22
|
+
|
|
23
|
+
const credentialPath = new URL("./client-credential.local", import.meta.url);
|
|
24
|
+
// Providers whose schema comes from the server's migrator (Firestore, Cosmos
|
|
25
|
+
// DB, and Vercel's PostgreSQL) ship migrate.ts.
|
|
26
|
+
const migrationPath = new URL("./migrate.ts", import.meta.url);
|
|
27
|
+
try {
|
|
28
|
+
// The plugin that protects the deployed server's client routes, if any,
|
|
29
|
+
// and the credential an app sends it.
|
|
30
|
+
const clientCredential =
|
|
31
|
+
clientAuth === undefined
|
|
32
|
+
? undefined
|
|
33
|
+
: plugins.find(({ id }) => id === clientAuth.plugin)?.cli
|
|
34
|
+
?.clientCredential;
|
|
35
|
+
if (
|
|
36
|
+
clientAuth !== undefined &&
|
|
37
|
+
(typeof clientCredential !== "object" ||
|
|
38
|
+
clientCredential === null ||
|
|
39
|
+
typeof clientCredential.label !== "string" ||
|
|
40
|
+
typeof clientCredential.header !== "string" ||
|
|
41
|
+
typeof clientCredential.env !== "string" ||
|
|
42
|
+
typeof clientCredential.generate !== "function" ||
|
|
43
|
+
typeof clientCredential.provision !== "function")
|
|
44
|
+
) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
`Plugin "${clientAuth.plugin}" provides clientAuth but no cli.clientCredential with a label, header, env, generate, and provision, so the app cannot get its credential.`,
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
let credential;
|
|
50
|
+
if (clientCredential !== undefined) {
|
|
51
|
+
const { env, label } = clientCredential;
|
|
52
|
+
credential = await readFile(credentialPath, "utf8").catch((error) => {
|
|
53
|
+
if (error.code === "ENOENT") return undefined;
|
|
54
|
+
throw error;
|
|
55
|
+
});
|
|
56
|
+
const existing = process.env[env]?.trim();
|
|
57
|
+
if (credential && existing && credential.trim() !== existing) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
`Saved client credentials differ. Resolve the target ${label} before provisioning.`,
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
if (!credential) {
|
|
63
|
+
credential = existing || clientCredential.generate();
|
|
64
|
+
await writeFile(credentialPath, credential, { flag: "wx", mode: 0o600 });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (existsSync(migrationPath)) {
|
|
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.
|
|
71
|
+
const { migrate } = await import(migrationPath.href);
|
|
72
|
+
await migrate(hotUpdater);
|
|
73
|
+
}
|
|
74
|
+
if (clientCredential === undefined) {
|
|
75
|
+
console.log("Client routes are public: there is no client credential.");
|
|
76
|
+
} else {
|
|
77
|
+
// Registered again rather than replaced, through that plugin, on the
|
|
78
|
+
// tables the deployed server reads.
|
|
79
|
+
await clientCredential.provision(api[clientAuth.plugin], {
|
|
80
|
+
existing: credential.trim(),
|
|
81
|
+
name: "Agent infrastructure setup",
|
|
82
|
+
});
|
|
83
|
+
console.log(
|
|
84
|
+
`Client ${clientCredential.label} registered. It is saved in client-credential.local.`,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
} finally {
|
|
88
|
+
await database.dispose?.();
|
|
89
|
+
}
|