@hot-updater/supabase 0.12.7 → 0.13.1
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/iac/index.cjs +7704 -0
- package/dist/iac/index.d.cts +26 -0
- package/dist/iac/index.d.ts +26 -0
- package/dist/iac/index.js +7707 -0
- package/dist/index.cjs +259 -3473
- package/dist/index.d.cts +17 -0
- package/dist/index.d.ts +17 -3
- package/dist/index.js +225 -3430
- package/package.json +27 -7
- package/supabase/functions/update-server/index.ts +97 -10
- package/supabase/migrations/20250314000000_hot-updater_0.13.0.sql +134 -0
- package/dist/supabaseDatabase.d.ts +0 -6
- package/dist/supabaseStorage.d.ts +0 -7
- package/dist/types.d.ts +0 -17
- package/dist/utils/index.d.ts +0 -2
- package/dist/utils/supabaseApi.d.ts +0 -14
- package/dist/utils/templates.d.ts +0 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hot-updater/supabase",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.13.1",
|
|
5
5
|
"description": "React Native OTA solution for self-hosted",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
7
7
|
"module": "dist/index.js",
|
|
@@ -10,6 +10,15 @@
|
|
|
10
10
|
".": {
|
|
11
11
|
"import": "./dist/index.js",
|
|
12
12
|
"require": "./dist/index.cjs"
|
|
13
|
+
},
|
|
14
|
+
"./edge-functions": {
|
|
15
|
+
"import": "./supabase/index.ts",
|
|
16
|
+
"require": "./supabase/index.ts"
|
|
17
|
+
},
|
|
18
|
+
"./iac": {
|
|
19
|
+
"types": "./dist/iac/index.d.ts",
|
|
20
|
+
"import": "./dist/iac/index.js",
|
|
21
|
+
"require": "./dist/iac/index.cjs"
|
|
13
22
|
}
|
|
14
23
|
},
|
|
15
24
|
"license": "MIT",
|
|
@@ -30,18 +39,29 @@
|
|
|
30
39
|
"package.json"
|
|
31
40
|
],
|
|
32
41
|
"dependencies": {
|
|
33
|
-
"@hot-updater/core": "0.
|
|
34
|
-
"@hot-updater/plugin-core": "0.
|
|
42
|
+
"@hot-updater/core": "0.13.1",
|
|
43
|
+
"@hot-updater/plugin-core": "0.13.1",
|
|
35
44
|
"@supabase/supabase-js": "^2.47.10"
|
|
36
45
|
},
|
|
37
46
|
"devDependencies": {
|
|
38
|
-
"
|
|
39
|
-
"@hot-updater/postgres": "0.12.7",
|
|
47
|
+
"@hot-updater/postgres": "0.13.1",
|
|
40
48
|
"dayjs": "^1.11.13",
|
|
41
|
-
"
|
|
49
|
+
"es-toolkit": "^1.32.0",
|
|
50
|
+
"execa": "^9.5.2",
|
|
51
|
+
"mime": "^4.0.4",
|
|
52
|
+
"picocolors": "^1.0.0",
|
|
53
|
+
"@clack/prompts": "^0.10.0"
|
|
54
|
+
},
|
|
55
|
+
"peerDependencies": {
|
|
56
|
+
"@clack/prompts": "*"
|
|
57
|
+
},
|
|
58
|
+
"peerDependenciesMeta": {
|
|
59
|
+
"@clack/prompts": {
|
|
60
|
+
"optional": true
|
|
61
|
+
}
|
|
42
62
|
},
|
|
43
63
|
"scripts": {
|
|
44
|
-
"build": "
|
|
64
|
+
"build": "tsup",
|
|
45
65
|
"test:type": "tsc --noEmit",
|
|
46
66
|
"make-migrations": "node --experimental-strip-types ./scripts/make-migrations.ts"
|
|
47
67
|
}
|
|
@@ -1,6 +1,37 @@
|
|
|
1
1
|
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
|
|
2
2
|
import camelcaseKeys from "npm:camelcase-keys@9.1.3";
|
|
3
|
-
import
|
|
3
|
+
import semver from "npm:semver@7.7.1";
|
|
4
|
+
import { createClient } from "jsr:@supabase/supabase-js@2.49.1";
|
|
5
|
+
|
|
6
|
+
const NIL_UUID = "00000000-0000-0000-0000-000000000000";
|
|
7
|
+
|
|
8
|
+
const semverSatisfies = (targetAppVersion: string, currentVersion: string) => {
|
|
9
|
+
const currentCoerce = semver.coerce(currentVersion);
|
|
10
|
+
if (!currentCoerce) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return semver.satisfies(currentCoerce.version, targetAppVersion);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Filters target app versions that are compatible with the current app version.
|
|
19
|
+
* Returns only versions that are compatible with the current version according to semver rules.
|
|
20
|
+
*
|
|
21
|
+
* @param targetAppVersionList - List of target app versions to filter
|
|
22
|
+
* @param currentVersion - Current app version
|
|
23
|
+
* @returns Array of target app versions compatible with the current version
|
|
24
|
+
*/
|
|
25
|
+
export const filterCompatibleAppVersions = (
|
|
26
|
+
targetAppVersionList: string[],
|
|
27
|
+
currentVersion: string,
|
|
28
|
+
) => {
|
|
29
|
+
const compatibleAppVersionList = targetAppVersionList.filter((version) =>
|
|
30
|
+
semverSatisfies(version, currentVersion),
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
return compatibleAppVersionList.sort((a, b) => b.localeCompare(a));
|
|
34
|
+
};
|
|
4
35
|
|
|
5
36
|
const createErrorResponse = (message: string, statusCode: number) => {
|
|
6
37
|
return new Response(JSON.stringify({ code: statusCode, message }), {
|
|
@@ -9,21 +40,29 @@ const createErrorResponse = (message: string, statusCode: number) => {
|
|
|
9
40
|
});
|
|
10
41
|
};
|
|
11
42
|
|
|
43
|
+
declare global {
|
|
44
|
+
var HotUpdater: {
|
|
45
|
+
BUCKET_NAME: string;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
12
49
|
Deno.serve(async (req) => {
|
|
13
50
|
try {
|
|
14
51
|
const supabase = createClient(
|
|
15
52
|
Deno.env.get("SUPABASE_URL") ?? "",
|
|
16
|
-
Deno.env.get("
|
|
53
|
+
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? "",
|
|
17
54
|
{
|
|
18
|
-
|
|
19
|
-
headers: { Authorization: req.headers.get("Authorization")! },
|
|
20
|
-
},
|
|
55
|
+
auth: { autoRefreshToken: false, persistSession: false },
|
|
21
56
|
},
|
|
22
57
|
);
|
|
23
58
|
|
|
24
59
|
const bundleId = req.headers.get("x-bundle-id") as string;
|
|
25
60
|
const appPlatform = req.headers.get("x-app-platform") as "ios" | "android";
|
|
26
61
|
const appVersion = req.headers.get("x-app-version") as string;
|
|
62
|
+
const minBundleId = req.headers.get("x-min-bundle-id") as
|
|
63
|
+
| string
|
|
64
|
+
| undefined;
|
|
65
|
+
const channel = req.headers.get("x-channel") as string | undefined;
|
|
27
66
|
|
|
28
67
|
if (!bundleId || !appPlatform || !appVersion) {
|
|
29
68
|
return createErrorResponse(
|
|
@@ -32,10 +71,25 @@ Deno.serve(async (req) => {
|
|
|
32
71
|
);
|
|
33
72
|
}
|
|
34
73
|
|
|
74
|
+
const { data: appVersionList } = await supabase.rpc(
|
|
75
|
+
"get_target_app_version_list",
|
|
76
|
+
{
|
|
77
|
+
app_platform: appPlatform,
|
|
78
|
+
min_bundle_id: minBundleId || NIL_UUID,
|
|
79
|
+
},
|
|
80
|
+
);
|
|
81
|
+
const compatibleAppVersionList = filterCompatibleAppVersions(
|
|
82
|
+
appVersionList?.map((group) => group.target_app_version) ?? [],
|
|
83
|
+
appVersion,
|
|
84
|
+
);
|
|
85
|
+
|
|
35
86
|
const { data, error } = await supabase.rpc("get_update_info", {
|
|
36
87
|
app_platform: appPlatform,
|
|
37
88
|
app_version: appVersion,
|
|
38
89
|
bundle_id: bundleId,
|
|
90
|
+
min_bundle_id: minBundleId || NIL_UUID,
|
|
91
|
+
target_channel: channel || "production",
|
|
92
|
+
target_app_version_list: compatibleAppVersionList,
|
|
39
93
|
});
|
|
40
94
|
|
|
41
95
|
if (error) {
|
|
@@ -43,11 +97,44 @@ Deno.serve(async (req) => {
|
|
|
43
97
|
}
|
|
44
98
|
|
|
45
99
|
const response = data[0] ? camelcaseKeys(data[0]) : null;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
100
|
+
if (!response) {
|
|
101
|
+
return new Response(JSON.stringify(null), {
|
|
102
|
+
headers: { "Content-Type": "application/json" },
|
|
103
|
+
status: 200,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (response.id === NIL_UUID) {
|
|
108
|
+
return new Response(
|
|
109
|
+
JSON.stringify({
|
|
110
|
+
...response,
|
|
111
|
+
fileUrl: null,
|
|
112
|
+
}),
|
|
113
|
+
{
|
|
114
|
+
headers: { "Content-Type": "application/json" },
|
|
115
|
+
status: 200,
|
|
116
|
+
},
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const { data: signedUrlData } = await supabase.storage
|
|
121
|
+
.from(HotUpdater.BUCKET_NAME)
|
|
122
|
+
.createSignedUrl([response.id, "bundle.zip"].join("/"), 60);
|
|
123
|
+
|
|
124
|
+
return new Response(
|
|
125
|
+
JSON.stringify({
|
|
126
|
+
...response,
|
|
127
|
+
fileUrl: signedUrlData?.signedUrl ?? null,
|
|
128
|
+
}),
|
|
129
|
+
{
|
|
130
|
+
headers: { "Content-Type": "application/json" },
|
|
131
|
+
status: 200,
|
|
132
|
+
},
|
|
133
|
+
);
|
|
50
134
|
} catch (err: unknown) {
|
|
51
|
-
return createErrorResponse(
|
|
135
|
+
return createErrorResponse(
|
|
136
|
+
err instanceof Error ? err.message : "Unknown error",
|
|
137
|
+
500,
|
|
138
|
+
);
|
|
52
139
|
}
|
|
53
140
|
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
-- HotUpdater.semver_satisfies
|
|
2
|
+
DROP FUNCTION IF EXISTS semver_satisfies;
|
|
3
|
+
|
|
4
|
+
-- HotUpdater.get_update_info
|
|
5
|
+
DROP FUNCTION IF EXISTS get_update_info;
|
|
6
|
+
|
|
7
|
+
-- HotUpdater.get_update_info
|
|
8
|
+
CREATE OR REPLACE FUNCTION get_update_info (
|
|
9
|
+
app_platform platforms,
|
|
10
|
+
app_version text,
|
|
11
|
+
bundle_id uuid,
|
|
12
|
+
min_bundle_id uuid,
|
|
13
|
+
target_channel text,
|
|
14
|
+
target_app_version_list text[]
|
|
15
|
+
)
|
|
16
|
+
RETURNS TABLE (
|
|
17
|
+
id uuid,
|
|
18
|
+
should_force_update boolean,
|
|
19
|
+
message text,
|
|
20
|
+
status text
|
|
21
|
+
)
|
|
22
|
+
LANGUAGE plpgsql
|
|
23
|
+
AS
|
|
24
|
+
$$
|
|
25
|
+
DECLARE
|
|
26
|
+
NIL_UUID CONSTANT uuid := '00000000-0000-0000-0000-000000000000';
|
|
27
|
+
BEGIN
|
|
28
|
+
RETURN QUERY
|
|
29
|
+
WITH update_candidate AS (
|
|
30
|
+
SELECT
|
|
31
|
+
b.id,
|
|
32
|
+
b.should_force_update,
|
|
33
|
+
b.message,
|
|
34
|
+
'UPDATE' AS status
|
|
35
|
+
FROM bundles b
|
|
36
|
+
WHERE b.enabled = TRUE
|
|
37
|
+
AND b.platform = app_platform
|
|
38
|
+
AND b.id >= bundle_id
|
|
39
|
+
AND b.id > min_bundle_id
|
|
40
|
+
AND b.target_app_version IN (SELECT unnest(target_app_version_list))
|
|
41
|
+
AND b.channel = target_channel
|
|
42
|
+
ORDER BY b.id DESC
|
|
43
|
+
LIMIT 1
|
|
44
|
+
),
|
|
45
|
+
rollback_candidate AS (
|
|
46
|
+
SELECT
|
|
47
|
+
b.id,
|
|
48
|
+
TRUE AS should_force_update,
|
|
49
|
+
b.message,
|
|
50
|
+
'ROLLBACK' AS status
|
|
51
|
+
FROM bundles b
|
|
52
|
+
WHERE b.enabled = TRUE
|
|
53
|
+
AND b.platform = app_platform
|
|
54
|
+
AND b.id < bundle_id
|
|
55
|
+
AND b.id > min_bundle_id
|
|
56
|
+
ORDER BY b.id DESC
|
|
57
|
+
LIMIT 1
|
|
58
|
+
),
|
|
59
|
+
final_result AS (
|
|
60
|
+
SELECT * FROM update_candidate
|
|
61
|
+
UNION ALL
|
|
62
|
+
SELECT * FROM rollback_candidate
|
|
63
|
+
WHERE NOT EXISTS (SELECT 1 FROM update_candidate)
|
|
64
|
+
)
|
|
65
|
+
SELECT *
|
|
66
|
+
FROM final_result
|
|
67
|
+
WHERE final_result.id != bundle_id
|
|
68
|
+
|
|
69
|
+
UNION ALL
|
|
70
|
+
|
|
71
|
+
SELECT
|
|
72
|
+
NIL_UUID AS id,
|
|
73
|
+
TRUE AS should_force_update,
|
|
74
|
+
NULL AS message,
|
|
75
|
+
'ROLLBACK' AS status
|
|
76
|
+
WHERE (SELECT COUNT(*) FROM final_result) = 0
|
|
77
|
+
AND bundle_id != NIL_UUID
|
|
78
|
+
AND bundle_id > min_bundle_id
|
|
79
|
+
AND NOT EXISTS (
|
|
80
|
+
SELECT 1
|
|
81
|
+
FROM bundles b
|
|
82
|
+
WHERE b.id = bundle_id
|
|
83
|
+
AND b.enabled = TRUE
|
|
84
|
+
AND b.platform = app_platform
|
|
85
|
+
);
|
|
86
|
+
END;
|
|
87
|
+
$$;
|
|
88
|
+
|
|
89
|
+
-- HotUpdater.bundles
|
|
90
|
+
ALTER TABLE bundles
|
|
91
|
+
ADD COLUMN channel text NOT NULL DEFAULT 'production';
|
|
92
|
+
|
|
93
|
+
ALTER TABLE bundles
|
|
94
|
+
DROP COLUMN file_url;
|
|
95
|
+
|
|
96
|
+
-- HotUpdater.get_target_app_version_list
|
|
97
|
+
|
|
98
|
+
CREATE OR REPLACE FUNCTION get_target_app_version_list (
|
|
99
|
+
app_platform platforms,
|
|
100
|
+
min_bundle_id uuid
|
|
101
|
+
)
|
|
102
|
+
RETURNS TABLE (
|
|
103
|
+
target_app_version text
|
|
104
|
+
)
|
|
105
|
+
LANGUAGE plpgsql
|
|
106
|
+
AS
|
|
107
|
+
$$
|
|
108
|
+
BEGIN
|
|
109
|
+
RETURN QUERY
|
|
110
|
+
SELECT b.target_app_version
|
|
111
|
+
FROM bundles b
|
|
112
|
+
WHERE b.platform = app_platform
|
|
113
|
+
AND b.id >= min_bundle_id
|
|
114
|
+
GROUP BY b.target_app_version;
|
|
115
|
+
END;
|
|
116
|
+
$$;
|
|
117
|
+
|
|
118
|
+
-- HotUpdater.get_channels
|
|
119
|
+
CREATE OR REPLACE FUNCTION get_channels ()
|
|
120
|
+
RETURNS TABLE (
|
|
121
|
+
channel text
|
|
122
|
+
)
|
|
123
|
+
LANGUAGE plpgsql
|
|
124
|
+
AS
|
|
125
|
+
$$
|
|
126
|
+
BEGIN
|
|
127
|
+
RETURN QUERY
|
|
128
|
+
SELECT b.channel
|
|
129
|
+
FROM bundles b
|
|
130
|
+
GROUP BY b.channel;
|
|
131
|
+
END;
|
|
132
|
+
$$;
|
|
133
|
+
|
|
134
|
+
CREATE INDEX bundles_channel_idx ON bundles(channel);
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { BasePluginArgs, DatabasePlugin, DatabasePluginHooks } from "@hot-updater/plugin-core";
|
|
2
|
-
export interface SupabaseDatabaseConfig {
|
|
3
|
-
supabaseUrl: string;
|
|
4
|
-
supabaseAnonKey: string;
|
|
5
|
-
}
|
|
6
|
-
export declare const supabaseDatabase: (config: SupabaseDatabaseConfig, hooks?: DatabasePluginHooks) => (_: BasePluginArgs) => DatabasePlugin;
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { BasePluginArgs, StoragePlugin, StoragePluginHooks } from "@hot-updater/plugin-core";
|
|
2
|
-
export interface SupabaseStorageConfig {
|
|
3
|
-
supabaseUrl: string;
|
|
4
|
-
supabaseAnonKey: string;
|
|
5
|
-
bucketName: string;
|
|
6
|
-
}
|
|
7
|
-
export declare const supabaseStorage: (config: SupabaseStorageConfig, hooks?: StoragePluginHooks) => (_: BasePluginArgs) => StoragePlugin;
|
package/dist/types.d.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { SnakeCaseBundle } from "@hot-updater/core";
|
|
2
|
-
export type Database = {
|
|
3
|
-
public: {
|
|
4
|
-
Tables: {
|
|
5
|
-
bundles: {
|
|
6
|
-
Row: SnakeCaseBundle;
|
|
7
|
-
Insert: SnakeCaseBundle;
|
|
8
|
-
Update: SnakeCaseBundle;
|
|
9
|
-
Relationships: [];
|
|
10
|
-
};
|
|
11
|
-
};
|
|
12
|
-
Views: {
|
|
13
|
-
[_ in never]: never;
|
|
14
|
-
};
|
|
15
|
-
Functions: any;
|
|
16
|
-
};
|
|
17
|
-
};
|
package/dist/utils/index.d.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export interface SupabaseApi {
|
|
2
|
-
listBuckets: () => Promise<{
|
|
3
|
-
id: string;
|
|
4
|
-
name: string;
|
|
5
|
-
isPublic: boolean;
|
|
6
|
-
createdAt: string;
|
|
7
|
-
}[]>;
|
|
8
|
-
createBucket: (bucketName: string, options: {
|
|
9
|
-
public: boolean;
|
|
10
|
-
}) => Promise<{
|
|
11
|
-
name: string;
|
|
12
|
-
}>;
|
|
13
|
-
}
|
|
14
|
-
export declare const supabaseApi: (supabaseUrl: string, supabaseAnonKey: string) => SupabaseApi;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const supabaseConfigTomlTemplate = "\nproject_id = \"%%projectId%%\"\n\n[db.seed]\nenabled = false\n";
|