Add THIS_LINE_PLATFORM macro (#537)

<!--

Make sure you've read the CONTRIBUTING.md guidelines:
https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md

-->

<!-- ELLIPSIS_HIDDEN -->


----

> [!IMPORTANT]
> Add `THIS_LINE_PLATFORM` macro for inline platform-specific comments,
replacing `NEXT_LINE_PLATFORM` across the codebase.
> 
>   - **Macro Addition**:
> - Introduces `THIS_LINE_PLATFORM` macro to replace
`NEXT_LINE_PLATFORM` for inline platform-specific comments.
> - Applied in `stack-handler.tsx`, `link.tsx`, `cookie.ts`, and 10
other files.
>   - **Behavior**:
> - `THIS_LINE_PLATFORM` allows inline comments to specify
platform-specific code inclusion.
>     - Ensures platform-specific code clarity and separation.
>   - **Documentation**:
> - Updated documentation files to use `THIS_LINE_PLATFORM` for
platform-specific sections.
>   - **Scripts**:
> - Updated `processMacros` function in `utils.ts` to handle
`THIS_LINE_PLATFORM`.
>     - Adjusted `generate-docs.ts` to process new macro correctly.
> 
> <sup>This description was created by </sup>[<img alt="Ellipsis"
src="https://img.shields.io/badge/Ellipsis-blue?color=175173">](https://www.ellipsis.dev?ref=stack-auth%2Fstack-auth&utm_source=github&utm_medium=referral)<sup>
for e3a283aa65. It will automatically
update as commits are pushed.</sup>


<!-- ELLIPSIS_HIDDEN -->
This commit is contained in:
Zai Shi 2025-03-13 02:35:17 +01:00 committed by GitHub
parent 5425b3e2dd
commit ab8b88c747
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 58 additions and 81 deletions

View File

@ -20,8 +20,7 @@ On this page:
Basic information about a contact channel, as seen by a user themselves.
Usually obtained by calling [`user.listContactChannels()`](../types/user.mdx#currentuserlistcontactchannels)
{/* NEXT_LINE_PLATFORM react-like */}
or [`user.useContactChannels()`](../types/user.mdx#currentuserusecontactchannels)
or [`user.useContactChannels()`](../types/user.mdx#currentuserusecontactchannels) {/* THIS_LINE_PLATFORM react-like */}
.
### Table of Contents
@ -279,8 +278,7 @@ await contactChannel.delete();
Like `ContactChannel`, but includes additional methods and properties that require the `SECRET_SERVER_KEY`.
Usually obtained by calling [`serverUser.listContactChannels()`](../types/user.mdx#serveruserlistcontactchannels)
{/* NEXT_LINE_PLATFORM react-like */}
or [`serverUser.useContactChannels()`](../types/user.mdx#serveruserusecontactchannels)
or [`serverUser.useContactChannels()`](../types/user.mdx#serveruserusecontactchannels) {/* THIS_LINE_PLATFORM react-like */}
.
### Table of Contents

View File

@ -8,8 +8,7 @@ slug: sdk/types/project
The `Project` object contains the information and configuration of a project, such as the name, description, and enabled authentication methods.
Each [Stack app](../../concepts/stack-app.mdx) corresponds to a project. You can obtain its `Project` object by calling [`stackApp.getProject()`](../objects/stack-app.mdx#stackappgetproject)
{/* NEXT_LINE_PLATFORM react-like */}
or [`stackApp.useProject()`](../objects/stack-app.mdx#stackappuseproject)
or [`stackApp.useProject()`](../objects/stack-app.mdx#stackappuseproject) {/* THIS_LINE_PLATFORM react-like */}
.
### Table of Contents

View File

@ -19,8 +19,7 @@ On this page:
The `TeamUser` object is used on the client side to represent a user in the context of a team, providing minimal information about the user, including their ID and team-specific profile.
It is usually obtained by calling
{/* NEXT_LINE_PLATFORM react-like */}
`team.useUsers()` or
`team.useUsers()` or {/* THIS_LINE_PLATFORM react-like */}
`team.listUsers()` on a [`Team` object](../types/team.mdx#team).
### Table of Contents

View File

@ -21,8 +21,7 @@ On this page:
A `Team` object contains basic information and functions about a team, to the extent of which a member of the team would have access to it.
You can get `Team` objects with the
{/* NEXT_LINE_PLATFORM react-like */}
`user.useTeams()` or
`user.useTeams()` or {/* THIS_LINE_PLATFORM react-like */}
`user.listTeams()` functions. The created team will then inherit the permissions of that user; for example, the `team.update(...)` function can only succeed if the user is allowed to make updates to the team.
### Table of Contents

View File

@ -1,8 +1,7 @@
import { StackAssertionError } from "@stackframe/stack-shared/dist/utils/errors";
import { FilterUndefined, filterUndefined, pick } from "@stackframe/stack-shared/dist/utils/objects";
import { getRelativePart } from "@stackframe/stack-shared/dist/utils/urls";
// NEXT_LINE_PLATFORM next
import { RedirectType, notFound, redirect } from 'next/navigation';
import { RedirectType, notFound, redirect } from 'next/navigation'; // THIS_LINE_PLATFORM next
import { useMemo } from 'react';
import { SignIn, SignUp, StackServerApp } from "..";
import { IframePreventer } from "../components/iframe-preventer";

View File

@ -1,8 +1,7 @@
'use client';
import { cn } from "@stackframe/stack-ui";
// NEXT_LINE_PLATFORM next
import NextLink from 'next/link';
import NextLink from 'next/link'; // THIS_LINE_PLATFORM next
type LinkProps = {
href: string,

View File

@ -1,5 +1,4 @@
// NEXT_LINE_PLATFORM next
import { cookies as rscCookies, headers as rscHeaders } from '@stackframe/stack-sc/force-react-server';
import { cookies as rscCookies, headers as rscHeaders } from '@stackframe/stack-sc/force-react-server'; // THIS_LINE_PLATFORM next
import { isBrowserLike } from '@stackframe/stack-shared/dist/utils/env';
import { StackAssertionError } from '@stackframe/stack-shared/dist/utils/errors';
import Cookies from "js-cookie";

View File

@ -7,8 +7,8 @@ import { InternalProjectsCrud } from "@stackframe/stack-shared/dist/interface/cr
import { StackAssertionError, throwErr } from "@stackframe/stack-shared/dist/utils/errors";
import { pick } from "@stackframe/stack-shared/dist/utils/objects";
import { Result } from "@stackframe/stack-shared/dist/utils/results";
import { InternalEmailsCrud } from "@stackframe/stack-shared/dist/interface/crud/emails";
import { useMemo } from "react"; // THIS_LINE_PLATFORM react-like
import { AdminSentEmail } from "../..";
import { ApiKey, ApiKeyBase, ApiKeyBaseCrudRead, ApiKeyCreateOptions, ApiKeyFirstView, apiKeyCreateOptionsToCrud } from "../../api-keys";
import { EmailConfig, stackAppInternalsSymbol } from "../../common";
import { AdminEmailTemplate, AdminEmailTemplateUpdateOptions, adminEmailTemplateUpdateOptionsToCrud } from "../../email-templates";
@ -18,12 +18,8 @@ import { StackAdminApp, StackAdminAppConstructorOptions } from "../interfaces/ad
import { clientVersion, createCache, getBaseUrl, getDefaultProjectId, getDefaultPublishableClientKey, getDefaultSecretServerKey, getDefaultSuperSecretAdminKey } from "./common";
import { _StackServerAppImplIncomplete } from "./server-app-impl";
// NEXT_LINE_PLATFORM react-like
import { useMemo } from "react";
// NEXT_LINE_PLATFORM react-like
import { useAsyncCache } from "./common";
import { AdminSentEmail } from "../../email";
export class _StackAdminAppImplIncomplete<HasTokenStore extends boolean, ProjectId extends string> extends _StackServerAppImplIncomplete<HasTokenStore, ProjectId>
{

View File

@ -24,10 +24,8 @@ import { deindent, mergeScopeStrings } from "@stackframe/stack-shared/dist/utils
import { getRelativePart, isRelative } from "@stackframe/stack-shared/dist/utils/urls";
import { generateUuid } from "@stackframe/stack-shared/dist/utils/uuids";
import * as cookie from "cookie";
// NEXT_LINE_PLATFORM next
import * as NextNavigationUnscrambled from "next/navigation"; // import the entire module to get around some static compiler warnings emitted by Next.js in some cases
// NEXT_LINE_PLATFORM react-like
import React, { useCallback, useMemo } from "react";
import * as NextNavigationUnscrambled from "next/navigation"; // import the entire module to get around some static compiler warnings emitted by Next.js in some cases | THIS_LINE_PLATFORM next
import React, { useCallback, useMemo } from "react"; // THIS_LINE_PLATFORM react-like
import { constructRedirectUrl } from "../../../../utils/url";
import { addNewOAuthProviderOrScope, callOAuthCallback, signInWithOAuth } from "../../../auth";
import { CookieHelper, createBrowserCookieHelper, createCookieHelper, createPlaceholderCookieHelper, deleteCookieClient, getCookieClient, setOrDeleteCookie, setOrDeleteCookieClient } from "../../../cookie";
@ -41,8 +39,8 @@ import { ActiveSession, Auth, BaseUser, CurrentUser, InternalUserExtra, ProjectC
import { StackClientApp, StackClientAppConstructorOptions, StackClientAppJson } from "../interfaces/client-app";
import { _StackAdminAppImplIncomplete } from "./admin-app-impl";
import { TokenObject, clientVersion, createCache, createCacheBySession, createEmptyTokenStore, getBaseUrl, getDefaultProjectId, getDefaultPublishableClientKey, getUrls } from "./common";
// NEXT_LINE_PLATFORM react-like
import { useAsyncCache } from "./common";
import { useAsyncCache } from "./common"; // THIS_LINE_PLATFORM react-like
let isReactServer = false;
// IF_PLATFORM next
@ -55,8 +53,7 @@ const NextNavigation = scrambleDuringCompileTime(NextNavigationUnscrambled);
// END_PLATFORM
// hack to make sure process is defined in non-node environments
// NEXT_LINE_PLATFORM js react
const process = (globalThis as any).process ?? { env: {} };
const process = (globalThis as any).process ?? { env: {} }; // THIS_LINE_PLATFORM js react
const allClientApps = new Map<string, [checkString: string, app: StackClientApp<any, any>]>();
@ -267,16 +264,14 @@ export class _StackClientAppImplIncomplete<HasTokenStore extends boolean, Projec
clientVersion,
publishableClientKey: _options.publishableClientKey ?? getDefaultPublishableClientKey(),
prepareRequest: async () => {
// NEXT_LINE_PLATFORM next
await cookies?.();
await cookies?.(); // THIS_LINE_PLATFORM next
}
});
}
this._tokenStoreInit = _options.tokenStore;
this._redirectMethod = _options.redirectMethod || "none";
// NEXT_LINE_PLATFORM next
this._redirectMethod = _options.redirectMethod || "nextjs";
this._redirectMethod = _options.redirectMethod || "nextjs"; // THIS_LINE_PLATFORM next
this._urlOptions = _options.urls ?? {};
this._oauthScopesOnSignIn = _options.oauthScopesOnSignIn ?? {};
@ -832,8 +827,7 @@ export class _StackClientAppImplIncomplete<HasTokenStore extends boolean, Projec
await this.update({ selectedTeamId: team?.id ?? null });
},
getConnectedAccount,
// NEXT_LINE_PLATFORM react-like
useConnectedAccount,
useConnectedAccount, // THIS_LINE_PLATFORM react-like
async getTeam(teamId: string) {
const teams = await this.listTeams();
return teams.find((t) => t.id === teamId) ?? null;

View File

@ -7,13 +7,11 @@ import { ReactPromise } from "@stackframe/stack-shared/dist/utils/promises";
import { suspendIfSsr } from "@stackframe/stack-shared/dist/utils/react";
import { Result } from "@stackframe/stack-shared/dist/utils/results";
import { Store } from "@stackframe/stack-shared/dist/utils/stores";
import React, { useCallback } from "react"; // THIS_LINE_PLATFORM react-like
import { HandlerUrls } from "../../common";
// NEXT_LINE_PLATFORM react-like
import React, { useCallback } from "react";
// hack to make sure process is defined in non-node environments
// NEXT_LINE_PLATFORM js react
const process = (globalThis as any).process ?? { env: {} };
const process = (globalThis as any).process ?? { env: {} }; // THIS_LINE_PLATFORM js react
export const clientVersion = "STACK_COMPILE_TIME_CLIENT_PACKAGE_VERSION_SENTINEL";
if (clientVersion.startsWith("STACK_COMPILE_TIME")) {

View File

@ -11,8 +11,7 @@ import { ProviderType } from "@stackframe/stack-shared/dist/utils/oauth";
import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises";
import { suspend } from "@stackframe/stack-shared/dist/utils/react";
import { Result } from "@stackframe/stack-shared/dist/utils/results";
// NEXT_LINE_PLATFORM react-like
import { useMemo } from "react";
import { useMemo } from "react"; // THIS_LINE_PLATFORM react-like
import { constructRedirectUrl } from "../../../../utils/url";
import { GetUserOptions, HandlerUrls, OAuthScopesOnSignIn, TokenStoreInit } from "../../common";
import { OAuthConnection } from "../../connected-accounts";
@ -23,10 +22,10 @@ import { ProjectCurrentServerUser, ServerUser, ServerUserCreateOptions, ServerUs
import { StackServerAppConstructorOptions } from "../interfaces/server-app";
import { _StackClientAppImplIncomplete } from "./client-app-impl";
import { clientVersion, createCache, createCacheBySession, getBaseUrl, getDefaultProjectId, getDefaultPublishableClientKey, getDefaultSecretServerKey } from "./common";
// NEXT_LINE_PLATFORM react-like
import { useAsyncCache } from "./common";
export class _StackServerAppImplIncomplete<HasTokenStore extends boolean, ProjectId extends string> extends _StackClientAppImplIncomplete<HasTokenStore, ProjectId>
{
declare protected _interface: StackServerInterface;
@ -269,8 +268,7 @@ export class _StackServerAppImplIncomplete<HasTokenStore extends boolean, Projec
return await this.update({ selectedTeamId: team?.id ?? null });
},
getConnectedAccount,
// NEXT_LINE_PLATFORM react-like
useConnectedAccount,
useConnectedAccount, // THIS_LINE_PLATFORM react-like
selectedTeam: crud.selected_team ? app._serverTeamFromCrud(crud.selected_team) : null,
async getTeam(teamId: string) {
const teams = await this.listTeams();

View File

@ -32,8 +32,7 @@ export type StackAdminApp<HasTokenStore extends boolean = boolean, ProjectId ext
& AsyncStoreProperty<"apiKeys", [], ApiKey[], true>
& AsyncStoreProperty<"teamPermissionDefinitions", [], AdminTeamPermissionDefinition[], true>
& {
// NEXT_LINE_PLATFORM react-like
useEmailTemplates(): AdminEmailTemplate[],
useEmailTemplates(): AdminEmailTemplate[], // THIS_LINE_PLATFORM react-like
listEmailTemplates(): Promise<AdminEmailTemplate[]>,
updateEmailTemplate(type: EmailTemplateType, data: AdminEmailTemplateUpdateOptions): Promise<void>,
resetEmailTemplate(type: EmailTemplateType): Promise<void>,
@ -44,8 +43,7 @@ export type StackAdminApp<HasTokenStore extends boolean = boolean, ProjectId ext
updateTeamPermissionDefinition(permissionId: string, data: AdminTeamPermissionDefinitionUpdateOptions): Promise<void>,
deleteTeamPermissionDefinition(permissionId: string): Promise<void>,
// NEXT_LINE_PLATFORM react-like
useSvixToken(): string,
useSvixToken(): string, // THIS_LINE_PLATFORM react-like
sendTestEmail(options: {
recipientEmail: string,

View File

@ -63,8 +63,7 @@ export type StackClientApp<HasTokenStore extends boolean = boolean, ProjectId ex
getUser(options: GetUserOptions<HasTokenStore> & { or: 'throw' }): Promise<ProjectCurrentUser<ProjectId>>,
getUser(options?: GetUserOptions<HasTokenStore>): Promise<ProjectCurrentUser<ProjectId> | null>,
// NEXT_LINE_PLATFORM react-like
useNavigate(): (to: string) => void,
useNavigate(): (to: string) => void, // THIS_LINE_PLATFORM react-like
[stackAppInternalsSymbol]: {
toClientJson(): StackClientAppJson<HasTokenStore, ProjectId>,

View File

@ -29,8 +29,7 @@ export type StackServerApp<HasTokenStore extends boolean = boolean, ProjectId ex
getUser(options: GetUserOptions<HasTokenStore> & { or: 'throw' }): Promise<ProjectCurrentServerUser<ProjectId>>,
getUser(options?: GetUserOptions<HasTokenStore>): Promise<ProjectCurrentServerUser<ProjectId> | null>,
// NEXT_LINE_PLATFORM react-like
useUsers(options?: ServerListUsersOptions): ServerUser[] & { nextCursor: string | null },
useUsers(options?: ServerListUsersOptions): ServerUser[] & { nextCursor: string | null }, // THIS_LINE_PLATFORM react-like
listUsers(options?: ServerListUsersOptions): Promise<ServerUser[] & { nextCursor: string | null }>,
}
& AsyncStoreProperty<"user", [id: string], ServerUser | null, false>

View File

@ -7,8 +7,7 @@ export type RedirectToOptions = {
export type AsyncStoreProperty<Name extends string, Args extends any[], Value, IsMultiple extends boolean> =
& { [key in `${IsMultiple extends true ? "list" : "get"}${Capitalize<Name>}`]: (...args: Args) => Promise<Value> }
// NEXT_LINE_PLATFORM react-like
& { [key in `use${Capitalize<Name>}`]: (...args: Args) => Value }
& { [key in `use${Capitalize<Name>}`]: (...args: Args) => Value } // THIS_LINE_PLATFORM react-like
export type EmailConfig = {
host: string,
@ -20,8 +19,7 @@ export type EmailConfig = {
}
export type RedirectMethod = "window"
// NEXT_LINE_PLATFORM next
| "nextjs"
| "nextjs" // THIS_LINE_PLATFORM next
| "none"
| {
useNavigate: () => (to: string) => void,

View File

@ -6,6 +6,5 @@ export type Connection = {
export type OAuthConnection = {
getAccessToken(): Promise<{ accessToken: string }>,
// NEXT_LINE_PLATFORM react-like
useAccessToken(): { accessToken: string },
useAccessToken(): { accessToken: string }, // THIS_LINE_PLATFORM react-like
} & Connection;

View File

@ -38,11 +38,9 @@ export type Team = {
clientReadOnlyMetadata: any,
inviteUser(options: { email: string, callbackUrl?: string }): Promise<void>,
listUsers(): Promise<TeamUser[]>,
// NEXT_LINE_PLATFORM react-like
useUsers(): TeamUser[],
useUsers(): TeamUser[], // THIS_LINE_PLATFORM react-like
listInvitations(): Promise<TeamInvitation[]>,
// NEXT_LINE_PLATFORM react-like
useInvitations(): TeamInvitation[],
useInvitations(): TeamInvitation[], // THIS_LINE_PLATFORM react-like
update(update: TeamUpdateOptions): Promise<void>,
delete(): Promise<void>,
};
@ -83,8 +81,7 @@ export type ServerTeam = {
createdAt: Date,
serverMetadata: any,
listUsers(): Promise<ServerTeamUser[]>,
// NEXT_LINE_PLATFORM react-like
useUsers(): ServerUser[],
useUsers(): ServerUser[], // THIS_LINE_PLATFORM react-like
update(update: ServerTeamUpdateOptions): Promise<void>,
delete(): Promise<void>,
addUser(userId: string): Promise<void>,

View File

@ -172,8 +172,7 @@ export type UserExtra = {
*/
update(update: UserUpdateOptions): Promise<void>,
// NEXT_LINE_PLATFORM react-like
useContactChannels(): ContactChannel[],
useContactChannels(): ContactChannel[], // THIS_LINE_PLATFORM react-like
listContactChannels(): Promise<ContactChannel[]>,
createContactChannel(data: ContactChannelCreateOptions): Promise<ContactChannel>,
@ -197,8 +196,7 @@ export type UserExtra = {
getActiveSessions(): Promise<ActiveSession[]>,
revokeSession(sessionId: string): Promise<void>,
getTeamProfile(team: Team): Promise<EditableTeamMemberProfile>,
// NEXT_LINE_PLATFORM react-like
useTeamProfile(team: Team): EditableTeamMemberProfile,
useTeamProfile(team: Team): EditableTeamMemberProfile, // THIS_LINE_PLATFORM react-like
}
& AsyncStoreProperty<"team", [id: string], Team | null, false>
& AsyncStoreProperty<"teams", [], Team[], true>
@ -263,8 +261,7 @@ export type ServerBaseUser = {
createTeam(data: Omit<ServerTeamCreateOptions, "creatorUserId">): Promise<ServerTeam>,
// NEXT_LINE_PLATFORM react-like
useContactChannels(): ServerContactChannel[],
useContactChannels(): ServerContactChannel[], // THIS_LINE_PLATFORM react-like
listContactChannels(): Promise<ServerContactChannel[]>,
createContactChannel(data: ServerContactChannelCreateOptions): Promise<ServerContactChannel>,

View File

@ -1,5 +1,4 @@
// NEXT_LINE_PLATFORM next
import "client-only";
import "client-only"; // THIS_LINE_PLATFORM next
import React from "react";
import { TranslationContext } from "../providers/translation-provider-client";

View File

@ -65,8 +65,6 @@ function processDocObject(obj: any, platforms: string[]): { result: any, validPa
}
}
withGeneratorLock(async () => {
const docsDir = path.resolve(__dirname, "..", "docs", "fern");
const templateDir = path.join(docsDir, "docs", "pages-template");

View File

@ -139,18 +139,20 @@ export function processMacros(content: string, platforms: string[]): string {
* - ELSE_PLATFORM
* - END_PLATFORM
* - NEXT_LINE_PLATFORM
* - THIS_LINE_PLATFORM
*
* And then capture everything after that keyword up to the end of the line.
*
* Examples:
* "blah blah IF_PLATFORM platform1 platform2 ???" => captures "platform platform2 ???"
* "blah blah IF_PLATFORM platform1 platform2 ???" => captures "platform1 platform2 ???"
* "adsfasdf ELSE_PLATFORM blabla" => captures "blabla"
*/
const reBeginOnly = /\bIF_PLATFORM:?\s+(.+)/i;
const reElseIf = /\bELSE_IF_PLATFORM:?\s+(.+)/i;
const reElse = /\bELSE_PLATFORM\b/i;
const reEndOnly = /\bEND_PLATFORM\b/i;
const reNextLine = /\bNEXT_LINE_PLATFORM:?\s+(.+)/i;
const reBeginOnly = /\bIF_PLATFORM:?\s+(.+)/i;
const reElseIf = /\bELSE_IF_PLATFORM:?\s+(.+)/i;
const reElse = /\bELSE_PLATFORM\b/i;
const reEndOnly = /\bEND_PLATFORM\b/i;
const reNextLine = /\bNEXT_LINE_PLATFORM:?\s+(.+)/i;
const reThisLine = /\bTHIS_LINE_PLATFORM:?\s+(.+)/i; // New regex
for (const line of lines) {
// 1) Try detecting IF_PLATFORM ...
@ -259,6 +261,19 @@ export function processMacros(content: string, platforms: string[]): string {
continue;
}
// 6) Try detecting THIS_LINE_PLATFORM ... (new behavior)
const thisLineMatch = line.match(reThisLine);
if (thisLineMatch) {
const platformList = parsePlatformList(thisLineMatch[1]);
const matched = platformList.some((e) => platforms.includes(e));
if (matched) {
// Output the line unchanged (including the directive itself)
result.push(line);
}
// Whether matched or not, skip further processing of this line
continue;
}
// If it's a normal line:
if (shouldOutputLine()) {
result.push(line);