@locale-labs/miniapp-builder 0.1.9 → 0.2.0-dev.54.7fe6f45

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@locale-labs/miniapp-builder",
3
- "version": "0.1.9",
3
+ "version": "0.2.0-dev.54.7fe6f45",
4
4
  "description": "Build tool for Localé Mini-Apps",
5
5
  "repository": {
6
6
  "type": "git",
@@ -11,28 +11,34 @@
11
11
  "locale-miniapp-builder": "./dist/index.js"
12
12
  },
13
13
  "files": [
14
- "dist"
14
+ "dist",
15
+ "configs",
16
+ "templates"
15
17
  ],
16
18
  "scripts": {
17
- "build": "esbuild src/index.ts --bundle --platform=node --target=node18 --outfile=dist/index.js --format=cjs --banner:js=\"#!/usr/bin/env node\" --external:esbuild",
19
+ "build": "esbuild src/index.ts --bundle --platform=node --target=node18 --outfile=dist/index.js --format=cjs --banner:js=\"#!/usr/bin/env node\" --external:esbuild --external:tailwindcss",
18
20
  "prepublishOnly": "bun run build",
19
21
  "security-check": "bunx @socketsecurity/cli ci"
20
22
  },
21
23
  "dependencies": {
24
+ "@eslint/js": "^9.0.0",
22
25
  "@locale-labs/miniapp-sdk": "0.2.0-dev.2.095bf24",
23
- "@types/html-minifier-terser": "7.0.2",
24
26
  "autoprefixer": "10.4.23",
27
+ "esbuild": "0.27.2",
28
+ "eslint-config-prettier": "^9.1.0",
29
+ "globals": "^15.0.0",
25
30
  "html-minifier-terser": "7.2.0",
26
31
  "postcss": "8.5.6",
27
32
  "posthtml": "0.16.7",
28
33
  "posthtml-include": "2.0.1",
29
- "tailwindcss": "4.1.18"
34
+ "tailwindcss": "^3.4.3",
35
+ "typescript-eslint": "^8.0.0"
30
36
  },
31
37
  "devDependencies": {
32
38
  "@semantic-release/changelog": "6.0.3",
39
+ "@types/html-minifier-terser": "7.0.2",
33
40
  "@semantic-release/git": "10.0.1",
34
41
  "@types/node": "25.0.10",
35
- "esbuild": "0.27.2",
36
42
  "semantic-release": "25.0.2",
37
43
  "typescript": "^5.0.0"
38
44
  },
@@ -0,0 +1,42 @@
1
+ // Follow this setup guide to integrate the Deno language server with your editor:
2
+ // https://deno.land/manual/getting_started/setup_your_environment
3
+
4
+ // Setup type definitions for built-in Supabase Runtime APIs
5
+ // deno-lint-ignore no-unversioned-import no-import-prefix
6
+ import "jsr:@supabase/functions-js/edge-runtime.d.ts";
7
+ import { createClient, SupabaseClient } from "@supabase/supabase-js";
8
+
9
+ import { response500_internal_server_error } from "./responses.ts";
10
+
11
+ export const createSupabaseAdminClient = ():
12
+ | {
13
+ error: null;
14
+ client: SupabaseClient;
15
+ }
16
+ | {
17
+ error: Response;
18
+ client: null;
19
+ } => {
20
+ try {
21
+ const supabaseUrl = Deno.env.get("SUPABASE_URL") ?? "";
22
+ const serviceRoleKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? "";
23
+
24
+ if (!supabaseUrl || !serviceRoleKey) {
25
+ return {
26
+ error: response500_internal_server_error(
27
+ new Error("Missing Supabase environment variables")
28
+ ),
29
+ client: null,
30
+ };
31
+ }
32
+
33
+ return {
34
+ error: null,
35
+ client: createClient(supabaseUrl, serviceRoleKey, {
36
+ auth: { persistSession: false, autoRefreshToken: false },
37
+ }),
38
+ };
39
+ } catch (error) {
40
+ return { error: response500_internal_server_error(error), client: null };
41
+ }
42
+ };
@@ -0,0 +1,5 @@
1
+ export const corsHeaders = {
2
+ "Access-Control-Allow-Origin": "*",
3
+ "Access-Control-Allow-Headers":
4
+ "authorization, x-client-info, apikey, content-type",
5
+ };
@@ -0,0 +1,45 @@
1
+ // Nota: aca solo se usaba el TYPE `PostgrestError` (union `PostgrestError | unknown`),
2
+ // union que colapsa a `unknown` (sin efecto). La dependencia @supabase/supabase-js
3
+ // SI es real y sigue en uso: client.ts la importa (createClient) y se resuelve via
4
+ // el import map de deno.json (jsr:@supabase/supabase-js@2).
5
+ import { corsHeaders } from "./cors.ts";
6
+
7
+ const headers = { ...corsHeaders, "Content-Type": "application/json" };
8
+
9
+ const errorMessageOrUnknownError = (error: unknown) =>
10
+ error instanceof Error ? error.message : "Unknown error";
11
+
12
+ export const response200 = (data: unknown) =>
13
+ new Response(JSON.stringify(data), {
14
+ headers,
15
+ status: 200,
16
+ });
17
+
18
+ export const response400_bad_request = (error: unknown) =>
19
+ new Response(JSON.stringify({ error: errorMessageOrUnknownError(error) }), {
20
+ headers,
21
+ status: 400,
22
+ });
23
+
24
+ export const response401_unauthorized = () =>
25
+ new Response(JSON.stringify({ error: "Unauthorized" }), {
26
+ headers,
27
+ status: 401,
28
+ });
29
+
30
+ export const response405_method_not_allowed = () =>
31
+ new Response(JSON.stringify({ error: "Method not allowed" }), {
32
+ headers,
33
+ status: 405,
34
+ });
35
+
36
+ export const response500_internal_server_error = (error: unknown) =>
37
+ new Response(JSON.stringify({ error: errorMessageOrUnknownError(error) }), {
38
+ headers,
39
+ status: 500,
40
+ });
41
+
42
+ // ─────────────────────────────────────────────────────────────────────
43
+
44
+ export const response_cors_preflight = () =>
45
+ new Response("ok", { headers: corsHeaders });
@@ -0,0 +1,5 @@
1
+ {
2
+ "imports": {
3
+ "@supabase/supabase-js": "jsr:@supabase/supabase-js@2"
4
+ }
5
+ }
@@ -0,0 +1,169 @@
1
+ // TODO: next step -> extract all of this code to outside miniapps (easy to maintain and error prone code)
2
+
3
+ import {
4
+ response400_bad_request,
5
+ response500_internal_server_error,
6
+ response401_unauthorized,
7
+ } from "../_shared/responses.ts";
8
+ import { createSupabaseAdminClient } from "../_shared/client.ts";
9
+
10
+ interface DeployMiniappParams {
11
+ version: string;
12
+ git_sha?: string | null;
13
+ html_content: string;
14
+ env?: "dev" | "prod";
15
+ }
16
+
17
+ export const deployMiniapp = async ({
18
+ req,
19
+ }: {
20
+ req: Request;
21
+ }): Promise<
22
+ | {
23
+ error: null;
24
+ data: { success: boolean; message: string; url: string };
25
+ }
26
+ | {
27
+ error: Response;
28
+ data: null;
29
+ }
30
+ > => {
31
+ // ── Get request body ─────────────────────────────────────────────────────
32
+ let body: DeployMiniappParams;
33
+ try {
34
+ body = await req.json();
35
+ } catch (_e) {
36
+ return {
37
+ data: null,
38
+ error: response400_bad_request(new Error("Invalid JSON body")),
39
+ };
40
+ }
41
+
42
+ const { version, git_sha, html_content, env = "prod" } = body;
43
+ const registeredVersion = git_sha ? `${version}+${git_sha}` : version;
44
+
45
+ if (!version || !html_content) {
46
+ return {
47
+ data: null,
48
+ error: response400_bad_request(
49
+ new Error("Missing version or html_content")
50
+ ),
51
+ };
52
+ }
53
+
54
+ // ── Validate API Key (Deploy Token) ──────────────────────────────────────
55
+ const apiKey = req.headers.get("x-miniapp-api-key");
56
+ const validApiKey = Deno.env.get("MINIAPP_API_KEY");
57
+
58
+ if (!apiKey || !validApiKey || apiKey !== validApiKey) {
59
+ return {
60
+ data: null,
61
+ error: response401_unauthorized(),
62
+ };
63
+ }
64
+
65
+ // ── Get Supabase admin client (MiniApp) ──────────────────────────────────
66
+ const { error: adminError, client: supabaseAdmin } =
67
+ createSupabaseAdminClient();
68
+ if (adminError) {
69
+ return { data: null, error: adminError };
70
+ }
71
+
72
+ // ── Upload to Storage ────────────────────────────────────────────────────
73
+ const storagePath = `builds/${registeredVersion}/index.html`;
74
+ const { error: uploadError } = await supabaseAdmin.storage
75
+ .from("miniapp-builds")
76
+ .upload(storagePath, html_content, {
77
+ contentType: "text/html",
78
+ upsert: true,
79
+ });
80
+
81
+ if (uploadError) {
82
+ return {
83
+ data: null,
84
+ error: response500_internal_server_error(uploadError),
85
+ };
86
+ }
87
+
88
+ const {
89
+ data: { publicUrl },
90
+ } = supabaseAdmin.storage.from("miniapp-builds").getPublicUrl(storagePath);
91
+
92
+ // ── Register with both Cores ────────────────────────────────────────────
93
+ const coreDevUrl = Deno.env.get("CORE_DEV_SUPABASE_URL");
94
+ const coreDevAnonKey = Deno.env.get("CORE_DEV_SUPABASE_ANON_PUBLIC");
95
+ const coreProdUrl = Deno.env.get("CORE_PROD_SUPABASE_URL");
96
+ const coreProdAnonKey = Deno.env.get("CORE_PROD_SUPABASE_ANON_PUBLIC");
97
+ const coreDeploySecret = Deno.env.get("MINIAPP_DEPLOY_SECRET");
98
+ const slug = Deno.env.get("MINIAPP_SLUG");
99
+ const name = Deno.env.get("MINIAPP_NAME") ?? slug ?? "Mini-app";
100
+
101
+ const cores = [
102
+ { name: "core-dev", url: coreDevUrl, anonKey: coreDevAnonKey },
103
+ { name: "core-prod", url: coreProdUrl, anonKey: coreProdAnonKey },
104
+ ];
105
+
106
+ const registerFailures: string[] = [];
107
+
108
+ for (const core of cores) {
109
+ if (!core.url || !core.anonKey) {
110
+ registerFailures.push(`${core.name}: missing URL or anon key`);
111
+ continue;
112
+ }
113
+
114
+ try {
115
+ const response = await fetch(
116
+ `${core.url}/functions/v1/register_miniapp_version`,
117
+ {
118
+ method: "POST",
119
+ headers: {
120
+ "Content-Type": "application/json",
121
+ Authorization: `Bearer ${core.anonKey}`,
122
+ "x-deploy-secret": coreDeploySecret || "",
123
+ },
124
+ body: JSON.stringify({
125
+ slug,
126
+ version: registeredVersion,
127
+ url: publicUrl,
128
+ name,
129
+ env,
130
+ }),
131
+ }
132
+ );
133
+
134
+ if (!response.ok) {
135
+ const text = await response.text();
136
+ registerFailures.push(`${core.name}: HTTP ${response.status} ${text}`);
137
+ }
138
+ } catch (err) {
139
+ registerFailures.push(
140
+ `${core.name}: ${err instanceof Error ? err.message : String(err)}`
141
+ );
142
+ }
143
+ }
144
+
145
+ // Fail the deploy if registration with any core failed. The HTML is already
146
+ // in storage, but a green deploy that didn't update the core DB is a silent
147
+ // failure — surface it so the CI job fails.
148
+ if (registerFailures.length > 0) {
149
+ return {
150
+ data: null,
151
+ error: response500_internal_server_error(
152
+ new Error(
153
+ `Uploaded to storage but failed to register version with core(s): ${registerFailures.join(
154
+ "; "
155
+ )}`
156
+ )
157
+ ),
158
+ };
159
+ }
160
+
161
+ return {
162
+ data: {
163
+ success: true,
164
+ message: "Miniapp deployed successfully",
165
+ url: publicUrl,
166
+ },
167
+ error: null,
168
+ };
169
+ };
@@ -0,0 +1,31 @@
1
+ import {
2
+ response_cors_preflight,
3
+ response200,
4
+ response405_method_not_allowed,
5
+ response500_internal_server_error,
6
+ } from "../_shared/responses.ts";
7
+
8
+ import { deployMiniapp } from "./deploy_miniapp.ts";
9
+
10
+ // Setup type definitions for built-in Supabase Runtime APIs
11
+ // deno-lint-ignore no-unversioned-import no-import-prefix
12
+ import "jsr:@supabase/functions-js/edge-runtime.d.ts";
13
+
14
+ Deno.serve(async (req) => {
15
+ if (req.method === "OPTIONS") {
16
+ return response_cors_preflight();
17
+ }
18
+
19
+ if (req.method !== "POST") {
20
+ return response405_method_not_allowed();
21
+ }
22
+
23
+ try {
24
+ const { data, error } = await deployMiniapp({ req });
25
+ if (error) return error;
26
+
27
+ return response200(data);
28
+ } catch (error) {
29
+ return response500_internal_server_error(error);
30
+ }
31
+ });