@proveanything/smartlinks 2.0.0-alpha.6 → 2.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/api/authKit.d.ts +18 -0
- package/dist/api/authKit.js +26 -0
- package/dist/docs/API_SUMMARY.md +4 -1
- package/dist/docs/deploying-apps.md +34 -54
- package/dist/openapi.yaml +26 -0
- package/docs/API_SUMMARY.md +4 -1
- package/docs/deploying-apps.md +34 -54
- package/openapi.yaml +26 -0
- package/package.json +5 -1
- package/scripts/register-release.mjs +91 -0
package/dist/api/authKit.d.ts
CHANGED
|
@@ -54,6 +54,24 @@ export declare namespace authKit {
|
|
|
54
54
|
function googleLogin(clientId: string, idToken: string, trustedDeviceToken?: string): Promise<AuthLoginResponse>;
|
|
55
55
|
/** Google OAuth login via server-side authorization code (public). */
|
|
56
56
|
function googleCodeLogin(clientId: string, code: string, redirectUri: string): Promise<AuthLoginResponse>;
|
|
57
|
+
/**
|
|
58
|
+
* Sign in with LinkedIn (OpenID Connect) via a server-side authorization code (public).
|
|
59
|
+
*
|
|
60
|
+
* LinkedIn has no client-side ID-token SDK, so this is always the authorization-code flow:
|
|
61
|
+
* redirect the browser to LinkedIn's authorize endpoint, then pass the returned one-time
|
|
62
|
+
* `code` (and the exact `redirectUri` used) here — the server exchanges it (with the app's
|
|
63
|
+
* client secret) and verifies the id_token. Mirrors {@link googleCodeLogin}: on success the
|
|
64
|
+
* bearer token is stored and the cache invalidated.
|
|
65
|
+
*
|
|
66
|
+
* Notable error codes (thrown as `SmartlinksApiError`, read via `err.errorCode`):
|
|
67
|
+
* - `MISSING_LINKEDIN_CODE` (400), `LINKEDIN_AUTH_NOT_CONFIGURED` (400),
|
|
68
|
+
* `INVALID_LINKEDIN_TOKEN` (400), `LINKEDIN_AUTH_FAILED` (500)
|
|
69
|
+
* - `ACCOUNT_EXISTS_UNVERIFIED` (409) — same shared verified-to-verified linking policy as
|
|
70
|
+
* {@link googleLogin}/{@link appleLogin}.
|
|
71
|
+
*
|
|
72
|
+
* Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape.
|
|
73
|
+
*/
|
|
74
|
+
function linkedInLogin(clientId: string, code: string, redirectUri: string, trustedDeviceToken?: string): Promise<AuthLoginResponse>;
|
|
57
75
|
/**
|
|
58
76
|
* Sign in with Apple via an Apple identity token (public).
|
|
59
77
|
*
|
package/dist/api/authKit.js
CHANGED
|
@@ -83,6 +83,32 @@ export var authKit;
|
|
|
83
83
|
return res;
|
|
84
84
|
}
|
|
85
85
|
authKit.googleCodeLogin = googleCodeLogin;
|
|
86
|
+
/**
|
|
87
|
+
* Sign in with LinkedIn (OpenID Connect) via a server-side authorization code (public).
|
|
88
|
+
*
|
|
89
|
+
* LinkedIn has no client-side ID-token SDK, so this is always the authorization-code flow:
|
|
90
|
+
* redirect the browser to LinkedIn's authorize endpoint, then pass the returned one-time
|
|
91
|
+
* `code` (and the exact `redirectUri` used) here — the server exchanges it (with the app's
|
|
92
|
+
* client secret) and verifies the id_token. Mirrors {@link googleCodeLogin}: on success the
|
|
93
|
+
* bearer token is stored and the cache invalidated.
|
|
94
|
+
*
|
|
95
|
+
* Notable error codes (thrown as `SmartlinksApiError`, read via `err.errorCode`):
|
|
96
|
+
* - `MISSING_LINKEDIN_CODE` (400), `LINKEDIN_AUTH_NOT_CONFIGURED` (400),
|
|
97
|
+
* `INVALID_LINKEDIN_TOKEN` (400), `LINKEDIN_AUTH_FAILED` (500)
|
|
98
|
+
* - `ACCOUNT_EXISTS_UNVERIFIED` (409) — same shared verified-to-verified linking policy as
|
|
99
|
+
* {@link googleLogin}/{@link appleLogin}.
|
|
100
|
+
*
|
|
101
|
+
* Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape.
|
|
102
|
+
*/
|
|
103
|
+
async function linkedInLogin(clientId, code, redirectUri, trustedDeviceToken) {
|
|
104
|
+
const res = await post(`/authkit/${encodeURIComponent(clientId)}/auth/linkedin`, { code, redirectUri, trustedDeviceToken });
|
|
105
|
+
if (res.token) {
|
|
106
|
+
setBearerToken(res.token);
|
|
107
|
+
invalidateCache();
|
|
108
|
+
}
|
|
109
|
+
return res;
|
|
110
|
+
}
|
|
111
|
+
authKit.linkedInLogin = linkedInLogin;
|
|
86
112
|
/**
|
|
87
113
|
* Sign in with Apple via an Apple identity token (public).
|
|
88
114
|
*
|
package/dist/docs/API_SUMMARY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Smartlinks API Summary
|
|
2
2
|
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.2 | Generated: 2026-09-19T12:10:30.440Z
|
|
4
4
|
|
|
5
5
|
This is a concise summary of all available API functions and types.
|
|
6
6
|
|
|
@@ -9353,6 +9353,9 @@ Google OAuth login via ID token (public). Gated by step-up MFA — see {@link lo
|
|
|
9353
9353
|
**googleCodeLogin**(clientId: string, code: string, redirectUri: string) → `Promise<AuthLoginResponse>`
|
|
9354
9354
|
Google OAuth login via server-side authorization code (public).
|
|
9355
9355
|
|
|
9356
|
+
**linkedInLogin**(clientId: string, code: string, redirectUri: string, trustedDeviceToken?: string) → `Promise<AuthLoginResponse>`
|
|
9357
|
+
Sign in with LinkedIn (OpenID Connect) via a server-side authorization code (public). LinkedIn has no client-side ID-token SDK, so this is always the authorization-code flow: redirect the browser to LinkedIn's authorize endpoint, then pass the returned one-time `code` (and the exact `redirectUri` used) here — the server exchanges it (with the app's client secret) and verifies the id_token. Mirrors {@link googleCodeLogin}: on success the bearer token is stored and the cache invalidated. Notable error codes (thrown as `SmartlinksApiError`, read via `err.errorCode`): - `MISSING_LINKEDIN_CODE` (400), `LINKEDIN_AUTH_NOT_CONFIGURED` (400), `INVALID_LINKEDIN_TOKEN` (400), `LINKEDIN_AUTH_FAILED` (500) - `ACCOUNT_EXISTS_UNVERIFIED` (409) — same shared verified-to-verified linking policy as {@link googleLogin}/{@link appleLogin}. Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape.
|
|
9358
|
+
|
|
9356
9359
|
**appleLogin**(clientId: string, identityToken: string, opts?: AppleLoginOptions) → `Promise<AuthLoginResponse>`
|
|
9357
9360
|
Sign in with Apple via an Apple identity token (public). Mirrors {@link googleLogin}. On success the returned bearer token is stored automatically and the cache is invalidated. Notable error codes (thrown as `SmartlinksApiError`, read via `err.errorCode`): - `MISSING_APPLE_TOKEN` (400), `APPLE_AUTH_NOT_CONFIGURED` (400), `INVALID_APPLE_TOKEN` (401), `APPLE_AUTH_FAILED` (500) - `ACCOUNT_EXISTS_UNVERIFIED` (409) — an unverified account already owns this email; the server refuses to silently link. `err.details.requiresEmailVerification` is `true`. Recoverable: the user should sign in with their password (or reset it), then link Apple from settings. **The same 409 can now come back from {@link googleLogin}** under the shared verified-to-verified linking policy. Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape. Pass `opts.trustedDeviceToken` to skip the challenge on a recognized device.
|
|
9358
9361
|
|
|
@@ -71,9 +71,13 @@ A release is registered on one **channel**, matching how you deploy:
|
|
|
71
71
|
|
|
72
72
|
| Channel | Deployed from | Typical use |
|
|
73
73
|
|---|---|---|
|
|
74
|
-
| `dev`
|
|
75
|
-
| `
|
|
76
|
-
| `
|
|
74
|
+
| `dev` | **Lovable "Publish"** | Live development / testing |
|
|
75
|
+
| `alpha` | your pipeline | Early internal testing |
|
|
76
|
+
| `beta` | your pipeline | Staging / preview |
|
|
77
|
+
| `stable`| your **Cloud Build** toolset (from git) | Production |
|
|
78
|
+
|
|
79
|
+
The canonical channels are **`dev` · `alpha` · `beta` · `stable`**. Legacy `prod`/`production`/
|
|
80
|
+
`live` are accepted and normalise to **`stable`** (there is no separate "prod" channel).
|
|
77
81
|
|
|
78
82
|
A collection chooses which channel it follows, so you can point a test collection at `dev`
|
|
79
83
|
and exercise a build before it reaches `prod`.
|
|
@@ -155,7 +159,11 @@ today.)
|
|
|
155
159
|
|
|
156
160
|
### What gets validated
|
|
157
161
|
|
|
158
|
-
-
|
|
162
|
+
- The **app id is the `{appId}` in the URL** — the platform-assigned id, which is authoritative
|
|
163
|
+
(it's what every collection's config + the CDN path already bind to). `manifest.meta.appId` is
|
|
164
|
+
**ignored** for identity: if it differs, the release still registers under the URL id and you get
|
|
165
|
+
a non-blocking `APPID_IGNORED` warning. So pass your **platform id** in the URL (the script below
|
|
166
|
+
reads it from `SMARTLINKS_APP_ID`), and don't rely on the manifest to declare it.
|
|
159
167
|
- The **`functions` block** is validated with the *same* rules the runtime enforces
|
|
160
168
|
(names, triggers, `visibility`/`authority`, the capability grammar, duplicate names) — so
|
|
161
169
|
a malformed server function is caught **at deploy time**, not at runtime. See
|
|
@@ -169,70 +177,42 @@ Registration is the **last step of your build** — after bundles are built and
|
|
|
169
177
|
small script that reads your built manifest and POSTs it, and **exits non-zero on failure**
|
|
170
178
|
so a bad install fails the publish.
|
|
171
179
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
import { readFileSync } from 'node:fs'
|
|
175
|
-
import { execSync } from 'node:child_process'
|
|
176
|
-
|
|
177
|
-
const API = process.env.SMARTLINKS_API || 'https://smartlinks.app' // a VPC env: set to its host
|
|
178
|
-
const KEY = process.env.SMARTLINKS_DEPLOY_KEY // Lovable Build Secret (dev) / CI secret (prod)
|
|
179
|
-
const CHANNEL = process.env.SMARTLINKS_CHANNEL // 'dev' | 'beta' | 'prod'; UNSET ⇒ don't register
|
|
180
|
-
|
|
181
|
-
// --- Gate: only register when this build is meant to ---
|
|
182
|
-
// A preview / live-edit build (no channel) skips quietly so it never fails. A build that
|
|
183
|
-
// declares a channel but has no key is a hard error IF it's prod; dev skips quietly.
|
|
184
|
-
if (!CHANNEL) {
|
|
185
|
-
console.log('ℹ︎ SmartLinks: SMARTLINKS_CHANNEL unset — skipping release registration (preview build).')
|
|
186
|
-
process.exit(0)
|
|
187
|
-
}
|
|
188
|
-
if (!KEY) {
|
|
189
|
-
if (CHANNEL === 'prod') { console.error('❌ prod build but SMARTLINKS_DEPLOY_KEY is missing'); process.exit(1) }
|
|
190
|
-
console.log(`ℹ︎ SmartLinks: no deploy key for "${CHANNEL}" — skipping registration.`)
|
|
191
|
-
process.exit(0)
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const manifest = JSON.parse(readFileSync('dist/app.manifest.json', 'utf8'))
|
|
195
|
-
const appId = manifest.meta.appId
|
|
196
|
-
const version = manifest.meta.version
|
|
197
|
-
const gitHash = (() => { try { return execSync('git rev-parse --short HEAD').toString().trim() } catch { return null } })()
|
|
198
|
-
// dev: your published Lovable URL; prod: the SmartLinks CDN base. Set SMARTLINKS_BUNDLE_BASE_URL.
|
|
199
|
-
const bundleBaseUrl = process.env.SMARTLINKS_BUNDLE_BASE_URL || `https://smartlinks.app/apps/${appId}/${version}`
|
|
200
|
-
|
|
201
|
-
const res = await fetch(`${API}/api/v1/apps/${appId}/releases`, {
|
|
202
|
-
method: 'POST',
|
|
203
|
-
headers: { 'content-type': 'application/json', 'x-smartlinks-deploy-key': KEY },
|
|
204
|
-
body: JSON.stringify({
|
|
205
|
-
channel: CHANNEL, version, manifest, bundleBaseUrl,
|
|
206
|
-
build: { at: new Date().toISOString(), gitHash, builder: CHANNEL === 'dev' ? 'lovable' : 'ci' },
|
|
207
|
-
}),
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
const body = await res.json().catch(() => ({}))
|
|
211
|
-
if (!res.ok || !body.ok) {
|
|
212
|
-
console.error(`❌ SmartLinks registration failed (${res.status}):`)
|
|
213
|
-
for (const e of body.errors || []) console.error(` • ${e.path}: ${e.message}`)
|
|
214
|
-
process.exit(1) // fail the build
|
|
215
|
-
}
|
|
216
|
-
console.log(`✅ Registered ${appId}@${version} on "${CHANNEL}" — functions: ${(body.functions || []).join(', ') || 'none'}`)
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
Wire it after your bundle build/hash step:
|
|
180
|
+
The SDK ships the script, so you don't copy-paste it — run it as your postbuild step. It reads
|
|
181
|
+
your built manifest, POSTs it, prints any warnings, and exits non-zero on failure.
|
|
220
182
|
|
|
221
183
|
```jsonc
|
|
222
184
|
// package.json
|
|
223
185
|
"scripts": {
|
|
224
186
|
"build": "vite build && … && node scripts/hash-bundles.mjs",
|
|
225
|
-
"postbuild": "
|
|
187
|
+
"postbuild": "smartlinks-register-release" // ships in @proveanything/smartlinks
|
|
226
188
|
}
|
|
227
189
|
```
|
|
228
190
|
|
|
191
|
+
It is driven entirely by env vars, so the same command works for dev (Lovable) and prod (CI):
|
|
192
|
+
|
|
193
|
+
| Env var | Required | What it is |
|
|
194
|
+
|---|---|---|
|
|
195
|
+
| `SMARTLINKS_APP_ID` | **yes** | your **platform** app id — authoritative, assigned by SmartLinks (per-project). Not the manifest's `meta.appId`. |
|
|
196
|
+
| `SMARTLINKS_DEPLOY_KEY` | yes* | channel-scoped deploy key. *dev builds skip quietly if unset; `beta`/`stable` hard-fail. |
|
|
197
|
+
| `SMARTLINKS_CHANNEL` | the gate | `dev` \| `alpha` \| `beta` \| `stable` (legacy `prod` → `stable`). **UNSET ⇒ skip** (preview builds never register). |
|
|
198
|
+
| `SMARTLINKS_BUNDLE_BASE_URL` | recommended | where the files are served — **dev:** your Lovable URL; **prod:** the CDN base. |
|
|
199
|
+
| `SMARTLINKS_API` | no | API host; default `https://smartlinks.app`. A VPC env uses its own. |
|
|
200
|
+
| `SMARTLINKS_MANIFEST` | no | manifest path; default `dist/app.manifest.json`. |
|
|
201
|
+
|
|
202
|
+
> The full source is at `scripts/register-release.mjs` in the SDK package if you'd rather vendor it.
|
|
203
|
+
|
|
229
204
|
Registration is gated by **`SMARTLINKS_CHANNEL`**, so the three Lovable build types behave correctly:
|
|
230
205
|
|
|
231
206
|
| Build | `SMARTLINKS_CHANNEL` | Result |
|
|
232
207
|
|---|---|---|
|
|
233
208
|
| **Preview / live-edit** | unset | **skips quietly** — never registers, never fails |
|
|
234
209
|
| **Dev (Publish)** | `dev` | registers to `dev` with the workspace Build-Secret key + your Lovable `SMARTLINKS_BUNDLE_BASE_URL` |
|
|
235
|
-
| **Prod (CI)** | `
|
|
210
|
+
| **Prod (CI)** | `stable` | registers to `stable` with the prod/master key; a missing key **hard-fails** |
|
|
211
|
+
|
|
212
|
+
**Two secrets, two scopes:** the **deploy key** is channel-scoped and can be a *workspace-level*
|
|
213
|
+
Lovable Build Secret shared by every app (a dev key only writes `dev`, so sharing it is safe). The
|
|
214
|
+
**`SMARTLINKS_APP_ID` is per-app** — set it as a *project-level* variable on each app, so each
|
|
215
|
+
registers under its own authoritative platform id.
|
|
236
216
|
|
|
237
217
|
The gate is `SMARTLINKS_CHANNEL`, so **set it only where you want a release** — i.e. on the
|
|
238
218
|
Publish/CI build, not on preview. If your host exposes a publish-only signal (an env var it sets
|
package/dist/openapi.yaml
CHANGED
|
@@ -8842,6 +8842,32 @@ paths:
|
|
|
8842
8842
|
description: Unauthorized
|
|
8843
8843
|
404:
|
|
8844
8844
|
description: Not found
|
|
8845
|
+
/authkit/{clientId}/auth/linkedin:
|
|
8846
|
+
post:
|
|
8847
|
+
tags:
|
|
8848
|
+
- authKit
|
|
8849
|
+
summary: authKit.linkedInLogin
|
|
8850
|
+
operationId: authKit_linkedInLogin
|
|
8851
|
+
security: []
|
|
8852
|
+
parameters:
|
|
8853
|
+
- name: clientId
|
|
8854
|
+
in: path
|
|
8855
|
+
required: true
|
|
8856
|
+
schema:
|
|
8857
|
+
type: string
|
|
8858
|
+
responses:
|
|
8859
|
+
200:
|
|
8860
|
+
description: Success
|
|
8861
|
+
content:
|
|
8862
|
+
application/json:
|
|
8863
|
+
schema:
|
|
8864
|
+
$ref: "#/components/schemas/AuthLoginResponse"
|
|
8865
|
+
400:
|
|
8866
|
+
description: Bad request
|
|
8867
|
+
401:
|
|
8868
|
+
description: Unauthorized
|
|
8869
|
+
404:
|
|
8870
|
+
description: Not found
|
|
8845
8871
|
/authkit/{clientId}/auth/login:
|
|
8846
8872
|
post:
|
|
8847
8873
|
tags:
|
package/docs/API_SUMMARY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Smartlinks API Summary
|
|
2
2
|
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.2 | Generated: 2026-09-19T12:10:30.440Z
|
|
4
4
|
|
|
5
5
|
This is a concise summary of all available API functions and types.
|
|
6
6
|
|
|
@@ -9353,6 +9353,9 @@ Google OAuth login via ID token (public). Gated by step-up MFA — see {@link lo
|
|
|
9353
9353
|
**googleCodeLogin**(clientId: string, code: string, redirectUri: string) → `Promise<AuthLoginResponse>`
|
|
9354
9354
|
Google OAuth login via server-side authorization code (public).
|
|
9355
9355
|
|
|
9356
|
+
**linkedInLogin**(clientId: string, code: string, redirectUri: string, trustedDeviceToken?: string) → `Promise<AuthLoginResponse>`
|
|
9357
|
+
Sign in with LinkedIn (OpenID Connect) via a server-side authorization code (public). LinkedIn has no client-side ID-token SDK, so this is always the authorization-code flow: redirect the browser to LinkedIn's authorize endpoint, then pass the returned one-time `code` (and the exact `redirectUri` used) here — the server exchanges it (with the app's client secret) and verifies the id_token. Mirrors {@link googleCodeLogin}: on success the bearer token is stored and the cache invalidated. Notable error codes (thrown as `SmartlinksApiError`, read via `err.errorCode`): - `MISSING_LINKEDIN_CODE` (400), `LINKEDIN_AUTH_NOT_CONFIGURED` (400), `INVALID_LINKEDIN_TOKEN` (400), `LINKEDIN_AUTH_FAILED` (500) - `ACCOUNT_EXISTS_UNVERIFIED` (409) — same shared verified-to-verified linking policy as {@link googleLogin}/{@link appleLogin}. Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape.
|
|
9358
|
+
|
|
9356
9359
|
**appleLogin**(clientId: string, identityToken: string, opts?: AppleLoginOptions) → `Promise<AuthLoginResponse>`
|
|
9357
9360
|
Sign in with Apple via an Apple identity token (public). Mirrors {@link googleLogin}. On success the returned bearer token is stored automatically and the cache is invalidated. Notable error codes (thrown as `SmartlinksApiError`, read via `err.errorCode`): - `MISSING_APPLE_TOKEN` (400), `APPLE_AUTH_NOT_CONFIGURED` (400), `INVALID_APPLE_TOKEN` (401), `APPLE_AUTH_FAILED` (500) - `ACCOUNT_EXISTS_UNVERIFIED` (409) — an unverified account already owns this email; the server refuses to silently link. `err.details.requiresEmailVerification` is `true`. Recoverable: the user should sign in with their password (or reset it), then link Apple from settings. **The same 409 can now come back from {@link googleLogin}** under the shared verified-to-verified linking policy. Gated by step-up MFA — see {@link login} for the `MFA_REQUIRED` error shape. Pass `opts.trustedDeviceToken` to skip the challenge on a recognized device.
|
|
9358
9361
|
|
package/docs/deploying-apps.md
CHANGED
|
@@ -71,9 +71,13 @@ A release is registered on one **channel**, matching how you deploy:
|
|
|
71
71
|
|
|
72
72
|
| Channel | Deployed from | Typical use |
|
|
73
73
|
|---|---|---|
|
|
74
|
-
| `dev`
|
|
75
|
-
| `
|
|
76
|
-
| `
|
|
74
|
+
| `dev` | **Lovable "Publish"** | Live development / testing |
|
|
75
|
+
| `alpha` | your pipeline | Early internal testing |
|
|
76
|
+
| `beta` | your pipeline | Staging / preview |
|
|
77
|
+
| `stable`| your **Cloud Build** toolset (from git) | Production |
|
|
78
|
+
|
|
79
|
+
The canonical channels are **`dev` · `alpha` · `beta` · `stable`**. Legacy `prod`/`production`/
|
|
80
|
+
`live` are accepted and normalise to **`stable`** (there is no separate "prod" channel).
|
|
77
81
|
|
|
78
82
|
A collection chooses which channel it follows, so you can point a test collection at `dev`
|
|
79
83
|
and exercise a build before it reaches `prod`.
|
|
@@ -155,7 +159,11 @@ today.)
|
|
|
155
159
|
|
|
156
160
|
### What gets validated
|
|
157
161
|
|
|
158
|
-
-
|
|
162
|
+
- The **app id is the `{appId}` in the URL** — the platform-assigned id, which is authoritative
|
|
163
|
+
(it's what every collection's config + the CDN path already bind to). `manifest.meta.appId` is
|
|
164
|
+
**ignored** for identity: if it differs, the release still registers under the URL id and you get
|
|
165
|
+
a non-blocking `APPID_IGNORED` warning. So pass your **platform id** in the URL (the script below
|
|
166
|
+
reads it from `SMARTLINKS_APP_ID`), and don't rely on the manifest to declare it.
|
|
159
167
|
- The **`functions` block** is validated with the *same* rules the runtime enforces
|
|
160
168
|
(names, triggers, `visibility`/`authority`, the capability grammar, duplicate names) — so
|
|
161
169
|
a malformed server function is caught **at deploy time**, not at runtime. See
|
|
@@ -169,70 +177,42 @@ Registration is the **last step of your build** — after bundles are built and
|
|
|
169
177
|
small script that reads your built manifest and POSTs it, and **exits non-zero on failure**
|
|
170
178
|
so a bad install fails the publish.
|
|
171
179
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
import { readFileSync } from 'node:fs'
|
|
175
|
-
import { execSync } from 'node:child_process'
|
|
176
|
-
|
|
177
|
-
const API = process.env.SMARTLINKS_API || 'https://smartlinks.app' // a VPC env: set to its host
|
|
178
|
-
const KEY = process.env.SMARTLINKS_DEPLOY_KEY // Lovable Build Secret (dev) / CI secret (prod)
|
|
179
|
-
const CHANNEL = process.env.SMARTLINKS_CHANNEL // 'dev' | 'beta' | 'prod'; UNSET ⇒ don't register
|
|
180
|
-
|
|
181
|
-
// --- Gate: only register when this build is meant to ---
|
|
182
|
-
// A preview / live-edit build (no channel) skips quietly so it never fails. A build that
|
|
183
|
-
// declares a channel but has no key is a hard error IF it's prod; dev skips quietly.
|
|
184
|
-
if (!CHANNEL) {
|
|
185
|
-
console.log('ℹ︎ SmartLinks: SMARTLINKS_CHANNEL unset — skipping release registration (preview build).')
|
|
186
|
-
process.exit(0)
|
|
187
|
-
}
|
|
188
|
-
if (!KEY) {
|
|
189
|
-
if (CHANNEL === 'prod') { console.error('❌ prod build but SMARTLINKS_DEPLOY_KEY is missing'); process.exit(1) }
|
|
190
|
-
console.log(`ℹ︎ SmartLinks: no deploy key for "${CHANNEL}" — skipping registration.`)
|
|
191
|
-
process.exit(0)
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const manifest = JSON.parse(readFileSync('dist/app.manifest.json', 'utf8'))
|
|
195
|
-
const appId = manifest.meta.appId
|
|
196
|
-
const version = manifest.meta.version
|
|
197
|
-
const gitHash = (() => { try { return execSync('git rev-parse --short HEAD').toString().trim() } catch { return null } })()
|
|
198
|
-
// dev: your published Lovable URL; prod: the SmartLinks CDN base. Set SMARTLINKS_BUNDLE_BASE_URL.
|
|
199
|
-
const bundleBaseUrl = process.env.SMARTLINKS_BUNDLE_BASE_URL || `https://smartlinks.app/apps/${appId}/${version}`
|
|
200
|
-
|
|
201
|
-
const res = await fetch(`${API}/api/v1/apps/${appId}/releases`, {
|
|
202
|
-
method: 'POST',
|
|
203
|
-
headers: { 'content-type': 'application/json', 'x-smartlinks-deploy-key': KEY },
|
|
204
|
-
body: JSON.stringify({
|
|
205
|
-
channel: CHANNEL, version, manifest, bundleBaseUrl,
|
|
206
|
-
build: { at: new Date().toISOString(), gitHash, builder: CHANNEL === 'dev' ? 'lovable' : 'ci' },
|
|
207
|
-
}),
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
const body = await res.json().catch(() => ({}))
|
|
211
|
-
if (!res.ok || !body.ok) {
|
|
212
|
-
console.error(`❌ SmartLinks registration failed (${res.status}):`)
|
|
213
|
-
for (const e of body.errors || []) console.error(` • ${e.path}: ${e.message}`)
|
|
214
|
-
process.exit(1) // fail the build
|
|
215
|
-
}
|
|
216
|
-
console.log(`✅ Registered ${appId}@${version} on "${CHANNEL}" — functions: ${(body.functions || []).join(', ') || 'none'}`)
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
Wire it after your bundle build/hash step:
|
|
180
|
+
The SDK ships the script, so you don't copy-paste it — run it as your postbuild step. It reads
|
|
181
|
+
your built manifest, POSTs it, prints any warnings, and exits non-zero on failure.
|
|
220
182
|
|
|
221
183
|
```jsonc
|
|
222
184
|
// package.json
|
|
223
185
|
"scripts": {
|
|
224
186
|
"build": "vite build && … && node scripts/hash-bundles.mjs",
|
|
225
|
-
"postbuild": "
|
|
187
|
+
"postbuild": "smartlinks-register-release" // ships in @proveanything/smartlinks
|
|
226
188
|
}
|
|
227
189
|
```
|
|
228
190
|
|
|
191
|
+
It is driven entirely by env vars, so the same command works for dev (Lovable) and prod (CI):
|
|
192
|
+
|
|
193
|
+
| Env var | Required | What it is |
|
|
194
|
+
|---|---|---|
|
|
195
|
+
| `SMARTLINKS_APP_ID` | **yes** | your **platform** app id — authoritative, assigned by SmartLinks (per-project). Not the manifest's `meta.appId`. |
|
|
196
|
+
| `SMARTLINKS_DEPLOY_KEY` | yes* | channel-scoped deploy key. *dev builds skip quietly if unset; `beta`/`stable` hard-fail. |
|
|
197
|
+
| `SMARTLINKS_CHANNEL` | the gate | `dev` \| `alpha` \| `beta` \| `stable` (legacy `prod` → `stable`). **UNSET ⇒ skip** (preview builds never register). |
|
|
198
|
+
| `SMARTLINKS_BUNDLE_BASE_URL` | recommended | where the files are served — **dev:** your Lovable URL; **prod:** the CDN base. |
|
|
199
|
+
| `SMARTLINKS_API` | no | API host; default `https://smartlinks.app`. A VPC env uses its own. |
|
|
200
|
+
| `SMARTLINKS_MANIFEST` | no | manifest path; default `dist/app.manifest.json`. |
|
|
201
|
+
|
|
202
|
+
> The full source is at `scripts/register-release.mjs` in the SDK package if you'd rather vendor it.
|
|
203
|
+
|
|
229
204
|
Registration is gated by **`SMARTLINKS_CHANNEL`**, so the three Lovable build types behave correctly:
|
|
230
205
|
|
|
231
206
|
| Build | `SMARTLINKS_CHANNEL` | Result |
|
|
232
207
|
|---|---|---|
|
|
233
208
|
| **Preview / live-edit** | unset | **skips quietly** — never registers, never fails |
|
|
234
209
|
| **Dev (Publish)** | `dev` | registers to `dev` with the workspace Build-Secret key + your Lovable `SMARTLINKS_BUNDLE_BASE_URL` |
|
|
235
|
-
| **Prod (CI)** | `
|
|
210
|
+
| **Prod (CI)** | `stable` | registers to `stable` with the prod/master key; a missing key **hard-fails** |
|
|
211
|
+
|
|
212
|
+
**Two secrets, two scopes:** the **deploy key** is channel-scoped and can be a *workspace-level*
|
|
213
|
+
Lovable Build Secret shared by every app (a dev key only writes `dev`, so sharing it is safe). The
|
|
214
|
+
**`SMARTLINKS_APP_ID` is per-app** — set it as a *project-level* variable on each app, so each
|
|
215
|
+
registers under its own authoritative platform id.
|
|
236
216
|
|
|
237
217
|
The gate is `SMARTLINKS_CHANNEL`, so **set it only where you want a release** — i.e. on the
|
|
238
218
|
Publish/CI build, not on preview. If your host exposes a publish-only signal (an env var it sets
|
package/openapi.yaml
CHANGED
|
@@ -8842,6 +8842,32 @@ paths:
|
|
|
8842
8842
|
description: Unauthorized
|
|
8843
8843
|
404:
|
|
8844
8844
|
description: Not found
|
|
8845
|
+
/authkit/{clientId}/auth/linkedin:
|
|
8846
|
+
post:
|
|
8847
|
+
tags:
|
|
8848
|
+
- authKit
|
|
8849
|
+
summary: authKit.linkedInLogin
|
|
8850
|
+
operationId: authKit_linkedInLogin
|
|
8851
|
+
security: []
|
|
8852
|
+
parameters:
|
|
8853
|
+
- name: clientId
|
|
8854
|
+
in: path
|
|
8855
|
+
required: true
|
|
8856
|
+
schema:
|
|
8857
|
+
type: string
|
|
8858
|
+
responses:
|
|
8859
|
+
200:
|
|
8860
|
+
description: Success
|
|
8861
|
+
content:
|
|
8862
|
+
application/json:
|
|
8863
|
+
schema:
|
|
8864
|
+
$ref: "#/components/schemas/AuthLoginResponse"
|
|
8865
|
+
400:
|
|
8866
|
+
description: Bad request
|
|
8867
|
+
401:
|
|
8868
|
+
description: Unauthorized
|
|
8869
|
+
404:
|
|
8870
|
+
description: Not found
|
|
8845
8871
|
/authkit/{clientId}/auth/login:
|
|
8846
8872
|
post:
|
|
8847
8873
|
tags:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@proveanything/smartlinks",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "Official JavaScript/TypeScript SDK for the Smartlinks API",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -14,9 +14,13 @@
|
|
|
14
14
|
"default": "./dist/testing/index.js"
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
|
+
"bin": {
|
|
18
|
+
"smartlinks-register-release": "scripts/register-release.mjs"
|
|
19
|
+
},
|
|
17
20
|
"files": [
|
|
18
21
|
"dist/",
|
|
19
22
|
"docs/",
|
|
23
|
+
"scripts/register-release.mjs",
|
|
20
24
|
"openapi.yaml",
|
|
21
25
|
"README.md"
|
|
22
26
|
],
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* register-release — the post-build "register on publish" step for a SmartLinks app.
|
|
4
|
+
*
|
|
5
|
+
* Runs at the END of a build (after the bundles are built + hashed): reads the built
|
|
6
|
+
* app.manifest.json and POSTs it to the SmartLinks install endpoint so the release is
|
|
7
|
+
* validated + recorded in the registry (push model). Exits non-zero on failure so a bad
|
|
8
|
+
* manifest / function fails the publish.
|
|
9
|
+
*
|
|
10
|
+
* ONE script for both paths:
|
|
11
|
+
* - dev from Lovable → SMARTLINKS_CHANNEL=dev, dev deploy key, bundleBaseUrl = your Lovable URL
|
|
12
|
+
* - prod/beta via CI → SMARTLINKS_CHANNEL=stable|beta, prod key, bundleBaseUrl = the CDN base
|
|
13
|
+
*
|
|
14
|
+
* IDENTITY (important): the SmartLinks app id is assigned by the PLATFORM, not self-declared in
|
|
15
|
+
* the manifest. Pass it as SMARTLINKS_APP_ID. The manifest's meta.appId is ignored by the
|
|
16
|
+
* backend (the installed id is authoritative); if they differ the backend just warns. If
|
|
17
|
+
* SMARTLINKS_APP_ID is unset we fall back to manifest.meta.appId with a warning — set it.
|
|
18
|
+
*
|
|
19
|
+
* Env:
|
|
20
|
+
* SMARTLINKS_APP_ID the app's platform id (REQUIRED for correctness; per-project)
|
|
21
|
+
* SMARTLINKS_DEPLOY_KEY channel-scoped deploy key (dev key is safe to keep as a Lovable
|
|
22
|
+
* workspace Build Secret — its scope is the dev channel only)
|
|
23
|
+
* SMARTLINKS_CHANNEL dev | alpha | beta | stable (also accepts legacy 'prod' → stable);
|
|
24
|
+
* UNSET ⇒ skip quietly (preview/live-edit builds never register)
|
|
25
|
+
* SMARTLINKS_API API host (default https://smartlinks.app); a VPC env uses its own
|
|
26
|
+
* SMARTLINKS_BUNDLE_BASE_URL where the built files are served (dev: your Lovable URL;
|
|
27
|
+
* prod: https://smartlinks.app/apps/<appId>/<version>)
|
|
28
|
+
* SMARTLINKS_MANIFEST manifest path (default dist/app.manifest.json)
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { readFileSync } from 'node:fs'
|
|
32
|
+
import { execSync } from 'node:child_process'
|
|
33
|
+
|
|
34
|
+
const API = process.env.SMARTLINKS_API || 'https://smartlinks.app'
|
|
35
|
+
const KEY = process.env.SMARTLINKS_DEPLOY_KEY
|
|
36
|
+
const CHANNEL = process.env.SMARTLINKS_CHANNEL
|
|
37
|
+
const MANIFEST = process.env.SMARTLINKS_MANIFEST || 'dist/app.manifest.json'
|
|
38
|
+
|
|
39
|
+
// --- Gate: only register when this build is meant to ship a release ---
|
|
40
|
+
if (!CHANNEL) {
|
|
41
|
+
console.log('ℹ︎ SmartLinks: SMARTLINKS_CHANNEL unset — skipping release registration (preview build).')
|
|
42
|
+
process.exit(0)
|
|
43
|
+
}
|
|
44
|
+
if (!KEY) {
|
|
45
|
+
// A prod/beta build with no key is a real misconfiguration; dev skips quietly.
|
|
46
|
+
if (CHANNEL !== 'dev') { console.error(`❌ SmartLinks: "${CHANNEL}" build but SMARTLINKS_DEPLOY_KEY is missing`); process.exit(1) }
|
|
47
|
+
console.log(`ℹ︎ SmartLinks: no deploy key for "${CHANNEL}" — skipping registration.`)
|
|
48
|
+
process.exit(0)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
let manifest
|
|
52
|
+
try {
|
|
53
|
+
manifest = JSON.parse(readFileSync(MANIFEST, 'utf8'))
|
|
54
|
+
} catch (e) {
|
|
55
|
+
console.error(`❌ SmartLinks: could not read manifest at ${MANIFEST} — ${e.message}`)
|
|
56
|
+
process.exit(1)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// The platform id is authoritative. Fall back to the manifest's declared id only if it wasn't
|
|
60
|
+
// injected — and say so, because that self-declared id is exactly what drifts.
|
|
61
|
+
let appId = process.env.SMARTLINKS_APP_ID
|
|
62
|
+
if (!appId) {
|
|
63
|
+
appId = manifest?.meta?.appId
|
|
64
|
+
console.warn(`⚠︎ SmartLinks: SMARTLINKS_APP_ID not set — falling back to manifest.meta.appId "${appId}". Set SMARTLINKS_APP_ID to the platform id to be safe.`)
|
|
65
|
+
}
|
|
66
|
+
if (!appId) { console.error('❌ SmartLinks: no app id (set SMARTLINKS_APP_ID)'); process.exit(1) }
|
|
67
|
+
|
|
68
|
+
const version = String(manifest?.meta?.version || '')
|
|
69
|
+
if (!version) { console.error('❌ SmartLinks: manifest.meta.version is required'); process.exit(1) }
|
|
70
|
+
|
|
71
|
+
const gitHash = (() => { try { return execSync('git rev-parse --short HEAD').toString().trim() } catch { return null } })()
|
|
72
|
+
const bundleBaseUrl = process.env.SMARTLINKS_BUNDLE_BASE_URL || `${API}/apps/${appId}/${version}`
|
|
73
|
+
|
|
74
|
+
const res = await fetch(`${API}/api/v1/apps/${appId}/releases`, {
|
|
75
|
+
method: 'POST',
|
|
76
|
+
headers: { 'content-type': 'application/json', 'x-smartlinks-deploy-key': KEY },
|
|
77
|
+
body: JSON.stringify({
|
|
78
|
+
channel: CHANNEL, version, manifest, bundleBaseUrl,
|
|
79
|
+
build: { at: new Date().toISOString(), gitHash, builder: CHANNEL === 'dev' ? 'lovable' : 'ci' },
|
|
80
|
+
}),
|
|
81
|
+
}).catch((e) => { console.error(`❌ SmartLinks: request failed — ${e.message}`); process.exit(1) })
|
|
82
|
+
|
|
83
|
+
const body = await res.json().catch(() => ({}))
|
|
84
|
+
if (!res.ok || !body.ok) {
|
|
85
|
+
console.error(`❌ SmartLinks registration failed (${res.status}):`)
|
|
86
|
+
for (const e of body.errors || []) console.error(` • ${e.path}: ${e.message}`)
|
|
87
|
+
process.exit(1) // fail the build
|
|
88
|
+
}
|
|
89
|
+
// Non-blocking warnings (e.g. the manifest declared a different id, which the platform ignores).
|
|
90
|
+
for (const w of body.warnings || []) console.warn(` ⚠︎ ${w.path}: ${w.message}`)
|
|
91
|
+
console.log(`✅ Registered ${appId}@${version} on "${body.channel || CHANNEL}" — functions: ${(body.functions || []).join(', ') || 'none'}`)
|