@remixhq/core 0.1.38 → 0.1.39

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.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { CoreConfig } from './config.js';
2
- import { T as TokenProvider } from './tokenProvider-B9so5Pm3.js';
2
+ import { T as TokenProvider } from './tokenProvider-BP3YfJHm.js';
3
3
  import { T as TurnUsage } from './contracts-DTeSJnoC.js';
4
4
  import 'zod';
5
5
 
package/dist/auth.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { W as WithRefreshLock, S as SessionStore, a as StoredSession } from './tokenProvider-B9so5Pm3.js';
2
- export { R as RefreshLockOptions, e as RefreshStoredSession, f as ResolvedAuthToken, T as TokenProvider, c as createRefreshLock, b as createStoredSessionTokenProvider, s as shouldRefreshSoon, d as storedSessionSchema } from './tokenProvider-B9so5Pm3.js';
1
+ import { W as WithRefreshLock, S as SessionStore, a as StoredSession } from './tokenProvider-BP3YfJHm.js';
2
+ export { R as RefreshLockOptions, e as RefreshStoredSession, f as ResolvedAuthToken, T as TokenProvider, c as createRefreshLock, b as createStoredSessionTokenProvider, s as shouldRefreshSoon, d as storedSessionSchema } from './tokenProvider-BP3YfJHm.js';
3
3
  import { CoreConfig } from './config.js';
4
4
  import 'zod';
5
5
 
@@ -31,10 +31,10 @@ declare function createDefaultRefreshLock(filePath?: string): WithRefreshLock;
31
31
  * - `isNetworkError`: transient — DNS / TCP / TLS / undici fetch
32
32
  * dropped mid-flight. Keep the existing session (it may already
33
33
  * be unusable but the *refresh token* is most likely still alive
34
- * server-side, especially within Supabase's reuse interval). The
34
+ * server-side, especially within the backend's refresh-token reuse interval). The
35
35
  * next attempt will retry.
36
36
  *
37
- * - `isInvalidGrantError`: terminal — Supabase explicitly said the
37
+ * - `isInvalidGrantError`: terminal — the backend explicitly said the
38
38
  * refresh token is dead (revoked, reused outside the grace
39
39
  * window, user signed out elsewhere). No amount of retrying will
40
40
  * fix it; the user must re-authenticate.
@@ -46,19 +46,22 @@ declare function createDefaultRefreshLock(filePath?: string): WithRefreshLock;
46
46
  declare function isNetworkError(err: unknown): boolean;
47
47
  declare function isInvalidGrantError(err: unknown): boolean;
48
48
 
49
- type SupabaseAuthHelpers = {
49
+ type RemixAuthHelpers = {
50
50
  startGoogleLogin(params: {
51
51
  redirectTo: string;
52
+ state?: string;
52
53
  }): Promise<{
53
54
  url: string;
55
+ codeVerifier?: string;
54
56
  }>;
55
57
  exchangeCode(params: {
56
58
  code: string;
59
+ codeVerifier?: string;
57
60
  }): Promise<StoredSession>;
58
61
  refreshWithStoredSession(params: {
59
62
  session: StoredSession;
60
63
  }): Promise<StoredSession>;
61
64
  };
62
- declare function createSupabaseAuthHelpers(config: CoreConfig): SupabaseAuthHelpers;
65
+ declare function createRemixAuthHelpers(config: CoreConfig): RemixAuthHelpers;
63
66
 
64
- export { SessionStore, StoredSession, type SupabaseAuthHelpers, WithRefreshLock, createDefaultRefreshLock, createLocalSessionStore, createSupabaseAuthHelpers, defaultSessionFilePath, isInvalidGrantError, isNetworkError };
67
+ export { type RemixAuthHelpers, SessionStore, StoredSession, WithRefreshLock, createDefaultRefreshLock, createLocalSessionStore, createRemixAuthHelpers, defaultSessionFilePath, isInvalidGrantError, isNetworkError };
package/dist/auth.js CHANGED
@@ -2,21 +2,21 @@ import {
2
2
  createDefaultRefreshLock,
3
3
  createLocalSessionStore,
4
4
  createRefreshLock,
5
+ createRemixAuthHelpers,
5
6
  createStoredSessionTokenProvider,
6
- createSupabaseAuthHelpers,
7
7
  defaultSessionFilePath,
8
8
  isInvalidGrantError,
9
9
  isNetworkError,
10
10
  shouldRefreshSoon,
11
11
  storedSessionSchema
12
- } from "./chunk-OMJ4JDME.js";
12
+ } from "./chunk-QCRTVR5N.js";
13
13
  import "./chunk-7XJGOKEO.js";
14
14
  export {
15
15
  createDefaultRefreshLock,
16
16
  createLocalSessionStore,
17
17
  createRefreshLock,
18
+ createRemixAuthHelpers,
18
19
  createStoredSessionTokenProvider,
19
- createSupabaseAuthHelpers,
20
20
  defaultSessionFilePath,
21
21
  isInvalidGrantError,
22
22
  isNetworkError,
@@ -209,7 +209,7 @@ function isNetworkError(err) {
209
209
  if (e.cause) return isNetworkError(e.cause);
210
210
  return false;
211
211
  }
212
- var INVALID_GRANT_MESSAGE_PATTERN = /invalid[_ ]grant|invalid refresh token|refresh[_ ]token[_ ]not[_ ]found|token has expired or (been )?revoked|already used|user from sub claim in jwt does not exist/i;
212
+ var INVALID_GRANT_MESSAGE_PATTERN = /invalid[_ ]grant|invalid refresh token|refresh[_ ]token[_ ]not[_ ]found|refresh token (has )?(been )?(expired|revoked)|token has expired or (been )?revoked|already used|user from sub claim in jwt does not exist/i;
213
213
  function isInvalidGrantError(err) {
214
214
  if (!err || typeof err !== "object") return false;
215
215
  const e = err;
@@ -222,6 +222,12 @@ function isInvalidGrantError(err) {
222
222
  }
223
223
 
224
224
  // src/auth/tokenProvider.ts
225
+ function firstNonEmptyEnvToken() {
226
+ for (const value of [process.env.REMIX_ACCESS_TOKEN, process.env.COMERGE_ACCESS_TOKEN]) {
227
+ if (typeof value === "string" && value.trim().length > 0) return value.trim();
228
+ }
229
+ return null;
230
+ }
225
231
  function shouldRefreshSoon(session, skewSeconds = 60) {
226
232
  const nowSec = Math.floor(Date.now() / 1e3);
227
233
  return session.expires_at <= nowSec + skewSeconds;
@@ -230,15 +236,15 @@ function createStoredSessionTokenProvider(params) {
230
236
  const withLock = params.withRefreshLock ?? (async (fn) => fn());
231
237
  return async (opts) => {
232
238
  const forceRefresh = Boolean(opts?.forceRefresh);
233
- const envToken = process.env.COMERGE_ACCESS_TOKEN;
234
- if (typeof envToken === "string" && envToken.trim().length > 0) {
235
- return { token: envToken.trim(), session: null, fromEnv: true };
239
+ const envToken = firstNonEmptyEnvToken();
240
+ if (envToken) {
241
+ return { token: envToken, session: null, fromEnv: true };
236
242
  }
237
243
  let session = await params.sessionStore.getSession();
238
244
  if (!session) {
239
245
  throw new RemixError("Not signed in.", {
240
246
  exitCode: 2,
241
- hint: "Run `remix login`, or set COMERGE_ACCESS_TOKEN for CI."
247
+ hint: "Run `remix login`, or set REMIX_ACCESS_TOKEN for CI."
242
248
  });
243
249
  }
244
250
  if (forceRefresh || shouldRefreshSoon(session)) {
@@ -250,7 +256,7 @@ function createStoredSessionTokenProvider(params) {
250
256
  if (!base) {
251
257
  throw new RemixError("Not signed in.", {
252
258
  exitCode: 2,
253
- hint: "Run `remix login`, or set COMERGE_ACCESS_TOKEN for CI."
259
+ hint: "Run `remix login`, or set REMIX_ACCESS_TOKEN for CI."
254
260
  });
255
261
  }
256
262
  const siblingRefreshed = latest != null && latest.access_token !== sessionAtEntry.access_token;
@@ -281,80 +287,72 @@ function createStoredSessionTokenProvider(params) {
281
287
  }
282
288
 
283
289
  // src/auth/supabase.ts
284
- import { createClient } from "@supabase/supabase-js";
285
- function createInMemoryStorage() {
286
- const map = /* @__PURE__ */ new Map();
287
- return {
288
- getItem: (k) => map.get(k) ?? null,
289
- setItem: (k, v) => {
290
- map.set(k, v);
291
- },
292
- removeItem: (k) => {
293
- map.delete(k);
294
- }
295
- };
296
- }
297
- function createSupabaseClient(config, storage) {
298
- return createClient(config.supabaseUrl, config.supabaseAnonKey, {
299
- auth: {
300
- flowType: "pkce",
301
- persistSession: false,
302
- autoRefreshToken: false,
303
- detectSessionInUrl: false,
304
- storage
305
- },
306
- global: {
307
- headers: {
308
- "X-Requested-By": "comerge-cli"
309
- }
310
- }
290
+ import crypto from "crypto";
291
+ async function postBackend(config, path3, body, options) {
292
+ const response = await fetch(new URL(path3, config.apiUrl), {
293
+ method: "POST",
294
+ headers: { "Content-Type": "application/json", "X-Requested-By": "remix-cli" },
295
+ ...options?.credentials ? { credentials: options.credentials } : {},
296
+ body: JSON.stringify(body)
311
297
  });
298
+ const payload = await response.json();
299
+ if (!response.ok || !payload.success || payload.responseObject == null) {
300
+ throw new RemixError(payload.message || `Remix backend request failed with status ${response.status}.`, {
301
+ exitCode: 1
302
+ });
303
+ }
304
+ return payload.responseObject;
312
305
  }
313
- function toStoredSession(session) {
314
- if (!session.access_token || !session.refresh_token || !session.expires_at) {
315
- throw new RemixError("Supabase session is missing required fields.", { exitCode: 1 });
306
+ function toStoredSession(payload) {
307
+ if (!payload.accessToken || !payload.refreshToken) {
308
+ throw new RemixError("Remix backend did not return a token pair.", { exitCode: 1 });
316
309
  }
310
+ const expiresIn = Number.isFinite(payload.expiresIn) && payload.expiresIn ? payload.expiresIn : 15 * 60;
317
311
  return {
318
- access_token: session.access_token,
319
- refresh_token: session.refresh_token,
320
- expires_at: session.expires_at,
321
- token_type: session.token_type ?? void 0,
322
- user: session.user ? { id: session.user.id, email: session.user.email ?? null } : void 0
312
+ access_token: payload.accessToken,
313
+ refresh_token: payload.refreshToken,
314
+ expires_at: Math.floor(Date.now() / 1e3) + expiresIn,
315
+ token_type: payload.tokenType ?? "Bearer",
316
+ user: payload.user?.id ? { id: payload.user.id, email: payload.user.email ?? null } : void 0
323
317
  };
324
318
  }
325
- function createSupabaseAuthHelpers(config) {
326
- const storage = createInMemoryStorage();
327
- const supabase = createSupabaseClient(config, storage);
319
+ function createPkcePair() {
320
+ const codeVerifier = crypto.randomBytes(32).toString("base64url");
321
+ const codeChallenge = crypto.createHash("sha256").update(codeVerifier).digest("base64url");
322
+ return { codeVerifier, codeChallenge };
323
+ }
324
+ function normalizeBrowserReturnTo(redirectTo) {
325
+ try {
326
+ const url = new URL(redirectTo);
327
+ if (url.protocol === "http:" || url.protocol === "https:") {
328
+ return `${url.pathname}${url.search}${url.hash}`;
329
+ }
330
+ } catch {
331
+ }
332
+ return redirectTo;
333
+ }
334
+ function createRemixAuthHelpers(config) {
328
335
  return {
329
336
  async startGoogleLogin(params) {
330
- const { data, error } = await supabase.auth.signInWithOAuth({
331
- provider: "google",
332
- options: {
333
- redirectTo: params.redirectTo,
334
- skipBrowserRedirect: true,
335
- queryParams: {
336
- access_type: "offline",
337
- prompt: "consent"
338
- }
339
- }
340
- });
341
- if (error) throw error;
342
- if (!data?.url) throw new RemixError("Supabase did not return an OAuth URL.", { exitCode: 1 });
343
- return { url: data.url };
337
+ const path3 = params.state ? "/v1/auth/cli/login-url" : "/v1/auth/login-url";
338
+ const pkce = params.state ? createPkcePair() : null;
339
+ const body = params.state ? { returnTo: params.redirectTo, state: params.state, codeChallenge: pkce?.codeChallenge } : { returnTo: normalizeBrowserReturnTo(params.redirectTo) };
340
+ const result = await postBackend(config, path3, body, params.state ? void 0 : { credentials: "include" });
341
+ if (!result.url) throw new RemixError("Remix backend did not return an auth URL.", { exitCode: 1 });
342
+ return pkce ? { url: result.url, codeVerifier: pkce.codeVerifier } : { url: result.url };
344
343
  },
345
344
  async exchangeCode(params) {
346
- const { data, error } = await supabase.auth.exchangeCodeForSession(params.code);
347
- if (error) throw error;
348
- if (!data?.session) throw new RemixError("Supabase did not return a session.", { exitCode: 1 });
349
- return toStoredSession(data.session);
345
+ const result = await postBackend(config, "/v1/auth/cli/exchange", {
346
+ code: params.code,
347
+ ...params.codeVerifier ? { codeVerifier: params.codeVerifier } : {}
348
+ });
349
+ return toStoredSession(result);
350
350
  },
351
351
  async refreshWithStoredSession(params) {
352
- const { data, error } = await supabase.auth.refreshSession({
353
- refresh_token: params.session.refresh_token
352
+ const result = await postBackend(config, "/v1/auth/token/refresh", {
353
+ refreshToken: params.session.refresh_token
354
354
  });
355
- if (error) throw error;
356
- if (!data?.session) throw new RemixError("No session returned after refresh.", { exitCode: 1 });
357
- return toStoredSession(data.session);
355
+ return toStoredSession(result);
358
356
  }
359
357
  };
360
358
  }
@@ -369,5 +367,5 @@ export {
369
367
  isInvalidGrantError,
370
368
  shouldRefreshSoon,
371
369
  createStoredSessionTokenProvider,
372
- createSupabaseAuthHelpers
370
+ createRemixAuthHelpers
373
371
  };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  export { CliError, REMIX_ERROR_CODES, CliError as RemixError, RemixErrorCode } from './errors.js';
2
2
  export { CoreConfig, ResolveConfigOptions, configSchema, resolveConfig } from './config.js';
3
- export { S as SessionStore, a as StoredSession, W as WithRefreshLock, c as createRefreshLock, b as createStoredSessionTokenProvider, s as shouldRefreshSoon, d as storedSessionSchema } from './tokenProvider-B9so5Pm3.js';
4
- export { createDefaultRefreshLock, createLocalSessionStore, createSupabaseAuthHelpers, defaultSessionFilePath, isInvalidGrantError, isNetworkError } from './auth.js';
3
+ export { S as SessionStore, a as StoredSession, W as WithRefreshLock, c as createRefreshLock, b as createStoredSessionTokenProvider, s as shouldRefreshSoon, d as storedSessionSchema } from './tokenProvider-BP3YfJHm.js';
4
+ export { createDefaultRefreshLock, createLocalSessionStore, createRemixAuthHelpers, defaultSessionFilePath, isInvalidGrantError, isNetworkError } from './auth.js';
5
5
  export { AgentMemoryItem, AgentMemoryKind, AgentMemorySearchItem, AgentMemorySearchResponse, AgentMemorySummary, AgentMemoryTimelineResponse, ApiClient, AppContext, AppContextAccessPath, AppPreviewExpectedKind, AppPreviewProcessStatus, AppPreviewQuery, AppPreviewResponse, AppReconcileResponse, AppSandboxCommandRun, AppShareLinkSummary, Bundle, BundlePlatform, BundleStatus, ChangeStepDiffResponse, CreateAppShareLinkPayload, DetectProjectRuntimeTargetsPayload, ImportProjectRuntimeEnvPayload, InitiateBundleRequest, InvitationRecord, MergeRequest, MergeRequestReview, MergeRequestStatus, ProjectRuntimeEnvMetadata, ProjectRuntimeEnvScope, ProjectRuntimeTargetMetadata, ProjectRuntimeTargetScope, ProjectTrigger, ProjectTriggerPayload, ProjectTriggerScope, ProjectTriggerStep, ProjectTriggerStepPayload, ReconcilePreflightResponse, ResolveProjectRuntimeEnvForLocalPullResponse, RunAppRuntimeTargetPayload, RunAppSandboxCommandPayload, RunAppTriggerEventPayload, RunAppTriggerPayload, RuntimeCommandKind, RuntimeTargetKind, SetProjectRuntimeEnvPayload, SetProjectRuntimeTargetPayload, SyncLocalResponse, SyncUpstreamResponse, TriggerEventMetadata, TriggerEventSource, TriggerIntegrationStatus, TriggerMode, TriggerRun, TriggerStepRun, TriggerStepType, UpdateProjectTriggerPayload, createApiClient } from './api.js';
6
6
  import 'zod';
7
7
  import './contracts-DTeSJnoC.js';
package/dist/index.js CHANGED
@@ -5,14 +5,14 @@ import {
5
5
  createDefaultRefreshLock,
6
6
  createLocalSessionStore,
7
7
  createRefreshLock,
8
+ createRemixAuthHelpers,
8
9
  createStoredSessionTokenProvider,
9
- createSupabaseAuthHelpers,
10
10
  defaultSessionFilePath,
11
11
  isInvalidGrantError,
12
12
  isNetworkError,
13
13
  shouldRefreshSoon,
14
14
  storedSessionSchema
15
- } from "./chunk-OMJ4JDME.js";
15
+ } from "./chunk-QCRTVR5N.js";
16
16
  import {
17
17
  configSchema,
18
18
  resolveConfig
@@ -33,8 +33,8 @@ export {
33
33
  createDefaultRefreshLock,
34
34
  createLocalSessionStore,
35
35
  createRefreshLock,
36
+ createRemixAuthHelpers,
36
37
  createStoredSessionTokenProvider,
37
- createSupabaseAuthHelpers,
38
38
  defaultSessionFilePath,
39
39
  isInvalidGrantError,
40
40
  isNetworkError,
@@ -69,12 +69,12 @@ type TokenProvider = (opts?: {
69
69
  declare function shouldRefreshSoon(session: StoredSession, skewSeconds?: number): boolean;
70
70
  /**
71
71
  * Token provider that resolves a fresh access token from a stored
72
- * Supabase session, refreshing it through `refreshStoredSession` when
72
+ * Remix app session, refreshing it through `refreshStoredSession` when
73
73
  * the cached token is at/near expiry.
74
74
  *
75
75
  * # Why this code is more careful than it looks
76
76
  *
77
- * Supabase rotates refresh tokens: every successful refresh server-
77
+ * Remix rotates refresh tokens: every successful refresh server-
78
78
  * side invalidates the old refresh token and issues a new one. If we
79
79
  * lose the new token before persisting it (network drop after the
80
80
  * server processed our request; multiple processes refreshing the
@@ -101,7 +101,7 @@ declare function shouldRefreshSoon(session: StoredSession, skewSeconds?: number)
101
101
  * 3. **Network-vs-auth error classification** — `fetch failed`
102
102
  * keeps the existing session (the refresh may or may not have
103
103
  * landed server-side; we'll find out on the next attempt, ideally
104
- * within Supabase's reuse interval). Only `invalid_grant` /
104
+ * within the backend's reuse interval). Only `invalid_grant` /
105
105
  * "refresh token not found" clears state and surfaces a clean
106
106
  * sign-in prompt — by then the token is definitively dead and
107
107
  * retrying won't help.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remixhq/core",
3
- "version": "0.1.38",
3
+ "version": "0.1.39",
4
4
  "description": "Remix core library",
5
5
  "homepage": "https://github.com/RemixDotOne/remix-core",
6
6
  "license": "MIT",