@helloleo/plugins 0.2.19 → 0.2.21
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/index.js +64 -8
- package/package.json +1 -1
- package/src/tools/setup-netsuite.ts +100 -36
package/dist/index.js
CHANGED
|
@@ -25561,6 +25561,17 @@ async function readScaffoldId2(dir) {
|
|
|
25561
25561
|
return null;
|
|
25562
25562
|
}
|
|
25563
25563
|
}
|
|
25564
|
+
async function isStartScaffold(dir) {
|
|
25565
|
+
const scaffoldId = await readScaffoldId2(dir);
|
|
25566
|
+
if (scaffoldId)
|
|
25567
|
+
return scaffoldId.startsWith("tanstack-start");
|
|
25568
|
+
try {
|
|
25569
|
+
const pkg = await Bun.file(path7.join(dir, "package.json")).json();
|
|
25570
|
+
return Boolean(pkg?.dependencies?.["@tanstack/react-start"] ?? pkg?.devDependencies?.["@tanstack/react-start"]);
|
|
25571
|
+
} catch {
|
|
25572
|
+
return false;
|
|
25573
|
+
}
|
|
25574
|
+
}
|
|
25564
25575
|
function schemaAppend() {
|
|
25565
25576
|
return `
|
|
25566
25577
|
// --- NetSuite OAuth (added by setup_netsuite) ---
|
|
@@ -25602,7 +25613,9 @@ function netsuiteServerTs() {
|
|
|
25602
25613
|
* Config is read from the Cloudflare env binding via @helloleo/runtime.
|
|
25603
25614
|
* Set NETSUITE_ACCOUNT_ID / _CLIENT_ID / _CLIENT_SECRET (secret optional for a
|
|
25604
25615
|
* PKCE public client) and optional PUBLIC_APP_URL as HelloLeo Cloud secrets
|
|
25605
|
-
* (prod) or dev vars (preview).
|
|
25616
|
+
* (prod) or dev vars (preview). NETSUITE_BROKER_CALLBACK (written by the
|
|
25617
|
+
* HelloLeo wizard) switches the redirect URI to the shared OAuth broker so
|
|
25618
|
+
* one URI serves every app/environment. They never reach the browser.
|
|
25606
25619
|
*/
|
|
25607
25620
|
import { eq } from 'drizzle-orm'
|
|
25608
25621
|
import { env as runtimeEnv } from '@helloleo/runtime'
|
|
@@ -25613,6 +25626,7 @@ interface NsEnv {
|
|
|
25613
25626
|
NETSUITE_ACCOUNT_ID?: string
|
|
25614
25627
|
NETSUITE_CLIENT_ID?: string
|
|
25615
25628
|
NETSUITE_CLIENT_SECRET?: string
|
|
25629
|
+
NETSUITE_BROKER_CALLBACK?: string
|
|
25616
25630
|
PUBLIC_APP_URL?: string
|
|
25617
25631
|
HELLOLEO_URL?: string
|
|
25618
25632
|
}
|
|
@@ -25673,8 +25687,47 @@ export function appOrigin(request: Request) {
|
|
|
25673
25687
|
url.host
|
|
25674
25688
|
return \`\${proto}://\${hostHeader}\`
|
|
25675
25689
|
}
|
|
25690
|
+
/**
|
|
25691
|
+
* HelloLeo's shared OAuth broker (integration-proxy Worker). When
|
|
25692
|
+
* NETSUITE_BROKER_CALLBACK is set (the platform writes it to the project env),
|
|
25693
|
+
* customers register that ONE stable URI on their Integration Record instead
|
|
25694
|
+
* of one URI per app/environment: the authorize redirect goes to the broker,
|
|
25695
|
+
* the broker 302s the callback back to THIS app (looked up by the state
|
|
25696
|
+
* nonce), and the token exchange passes the same broker URI. Unset (self-host
|
|
25697
|
+
* / standalone), the app falls back to its own origin's callback.
|
|
25698
|
+
*/
|
|
25699
|
+
export function brokerCallback(): string | null {
|
|
25700
|
+
const raw = nsEnv.NETSUITE_BROKER_CALLBACK
|
|
25701
|
+
if (!raw) return null
|
|
25702
|
+
try {
|
|
25703
|
+
const u = new URL(raw)
|
|
25704
|
+
return u.protocol === 'https:' ? u.toString() : null
|
|
25705
|
+
} catch {
|
|
25706
|
+
return null
|
|
25707
|
+
}
|
|
25708
|
+
}
|
|
25709
|
+
|
|
25676
25710
|
export function redirectUri(request: Request) {
|
|
25677
|
-
return \`\${appOrigin(request)}/auth/netsuite/callback\`
|
|
25711
|
+
return brokerCallback() ?? \`\${appOrigin(request)}/auth/netsuite/callback\`
|
|
25712
|
+
}
|
|
25713
|
+
|
|
25714
|
+
/**
|
|
25715
|
+
* Tell the broker where to send this flow's callback: nonce -> this app's
|
|
25716
|
+
* origin. MUST succeed before redirecting to NetSuite, otherwise the callback
|
|
25717
|
+
* dead-ends at the broker. No-op when the broker isn't configured.
|
|
25718
|
+
*/
|
|
25719
|
+
export async function registerWithBroker(request: Request, flowId: string): Promise<void> {
|
|
25720
|
+
const callback = brokerCallback()
|
|
25721
|
+
if (!callback) return
|
|
25722
|
+
const registerUrl = new URL('/auth/netsuite/register', callback)
|
|
25723
|
+
const res = await fetch(registerUrl.toString(), {
|
|
25724
|
+
method: 'POST',
|
|
25725
|
+
headers: { 'Content-Type': 'application/json' },
|
|
25726
|
+
body: JSON.stringify({ nonce: flowId, target: appOrigin(request) }),
|
|
25727
|
+
})
|
|
25728
|
+
if (!res.ok) {
|
|
25729
|
+
throw new Error(\`NetSuite broker registration failed (HTTP \${res.status}) -- cannot start sign-in\`)
|
|
25730
|
+
}
|
|
25678
25731
|
}
|
|
25679
25732
|
|
|
25680
25733
|
// Coerce ?return_to= to our OWN origin. The callback appends the session bearer
|
|
@@ -25838,6 +25891,7 @@ import {
|
|
|
25838
25891
|
authorizeUrl,
|
|
25839
25892
|
pkce,
|
|
25840
25893
|
redirectUri,
|
|
25894
|
+
registerWithBroker,
|
|
25841
25895
|
requireConfig,
|
|
25842
25896
|
safeReturnTo,
|
|
25843
25897
|
SCOPE,
|
|
@@ -25855,6 +25909,10 @@ export const Route = createFileRoute('/auth/netsuite/start')({
|
|
|
25855
25909
|
|
|
25856
25910
|
const { verifier, challenge } = await pkce()
|
|
25857
25911
|
const flowId = crypto.randomUUID()
|
|
25912
|
+
// With the shared broker, NetSuite redirects to the broker's fixed
|
|
25913
|
+
// callback and the broker forwards here -- it must know this flow's
|
|
25914
|
+
// target before the user leaves for NetSuite.
|
|
25915
|
+
await registerWithBroker(request, flowId)
|
|
25858
25916
|
await db.insert(netsuiteFlows).values({ flowId, codeVerifier: verifier, returnTo })
|
|
25859
25917
|
|
|
25860
25918
|
const authorize = new URL(authorizeUrl(accountId))
|
|
@@ -26717,8 +26775,8 @@ import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
|
|
|
26717
26775
|
"CONFIG -- NETSUITE_ACCOUNT_ID, NETSUITE_CLIENT_ID, NETSUITE_CLIENT_SECRET (secret optional for PKCE public clients). Never hardcode; never put in code. Same var names in every environment, different SOURCE:",
|
|
26718
26776
|
"- DEV (Fly preview): read from the project .env. HelloLeo writes them there (the setup wizard, or the env tooling). In dev the app uses .env -- do NOT tell the user to use HelloLeo Cloud here.",
|
|
26719
26777
|
"- PROD (published Worker): the SAME names live as HelloLeo Cloud secrets; the publish step promotes them from .env onto the Worker automatically. Same code path (@helloleo/runtime env), just a different source per environment.",
|
|
26720
|
-
"- PUBLIC_APP_URL / HELLOLEO_URL are set by the platform per environment; don't hardcode them.",
|
|
26721
|
-
"- REDIRECT URI -- never guess or hardcode it.
|
|
26778
|
+
"- PUBLIC_APP_URL / HELLOLEO_URL / NETSUITE_BROKER_CALLBACK are set by the platform per environment; don't hardcode them.",
|
|
26779
|
+
"- REDIRECT URI -- never guess or hardcode it. When NETSUITE_BROKER_CALLBACK is set (the HelloLeo wizard writes it), the redirect URI is HelloLeo's shared OAuth broker: ONE stable URI the user registers ONCE on Integration Record #2 (scope: rest_webservices), valid for both the preview and the published app -- the broker routes each callback back to this app. Without it, the app falls back to its own origin's callback. Either way, read the exact value from GET /auth/netsuite/config (or getNetSuiteRedirectUri() on the client) and tell the user to register THAT.",
|
|
26722
26780
|
"- Never replace <AuthLayout> with a password gate. Real OAuth is the only supported login."
|
|
26723
26781
|
].join(`
|
|
26724
26782
|
`);
|
|
@@ -26773,13 +26831,11 @@ async function executeSpa(dir) {
|
|
|
26773
26831
|
`);
|
|
26774
26832
|
}
|
|
26775
26833
|
var setupNetsuite = tool({
|
|
26776
|
-
description: "Add NetSuite runtime OAuth (end-user LOGIN) to an existing project. " + "Call scaffold first, then this. Works on both scaffold types
|
|
26834
|
+
description: "Add NetSuite runtime OAuth (end-user LOGIN) to an existing project. " + "Call scaffold first, then this. Works on both scaffold types and " + "picks the variant automatically: on tanstack-start the app hosts " + "its own OAuth routes (/auth/netsuite/* + /api/me, same-origin " + "client); on tanstack-router (SPA) it writes the client + auth " + "components and the OAuth backend is the project's HelloLeo Cloud " + "Worker (VITE_WORKER_URL). This login uses NetSuite Integration " + "Record #2 (scope: rest_webservices) -- a SEPARATE record from the " + "MCP one (scope: mcp) that was used only to BUILD the app; do not " + "confuse them. Real OAuth from day one -- never replace <AuthLayout> " + "with a password placeholder. Setup guide (link the user to it): " + "https://docs.helloleo.dev/integrations/netsuite",
|
|
26777
26835
|
args: {},
|
|
26778
26836
|
async execute(_args, context) {
|
|
26779
26837
|
const dir = context.directory;
|
|
26780
|
-
|
|
26781
|
-
const inAppOptIn = process.env.PLUGINS_NETSUITE_IN_APP === "1";
|
|
26782
|
-
if (inAppOptIn && scaffoldId?.startsWith("tanstack-start")) {
|
|
26838
|
+
if (await isStartScaffold(dir)) {
|
|
26783
26839
|
return executeStart(dir);
|
|
26784
26840
|
}
|
|
26785
26841
|
return executeSpa(dir);
|
package/package.json
CHANGED
|
@@ -4,19 +4,20 @@ import type { Plugin } from "@opencode-ai/plugin";
|
|
|
4
4
|
import { tool } from "@opencode-ai/plugin";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* NetSuite runtime OAuth (end-user LOGIN). Two variants
|
|
7
|
+
* NetSuite runtime OAuth (end-user LOGIN). Two variants, picked automatically
|
|
8
|
+
* from the project's scaffold -- never from env:
|
|
8
9
|
*
|
|
9
|
-
* -
|
|
10
|
-
*
|
|
10
|
+
* - tanstack-start (SSR): the app IS the backend. Emits server routes
|
|
11
|
+
* (/auth/netsuite/* + /api/me), netsuite_flows + netsuite_sessions drizzle
|
|
12
|
+
* tables, and a same-origin client. The redirect URI registered on
|
|
13
|
+
* Integration Record #2 must be the APP's own /auth/netsuite/callback
|
|
14
|
+
* (read it from GET /auth/netsuite/config).
|
|
15
|
+
* - tanstack-router (SPA): client-only files; the OAuth backend is the
|
|
16
|
+
* per-project HelloLeo Cloud Worker (VITE_WORKER_URL), sign-in runs in
|
|
11
17
|
* a popup + postMessage so it works inside the HelloLeo preview iframe.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* - IN-APP (opt-in: PLUGINS_NETSUITE_IN_APP=1 + tanstack-start scaffold):
|
|
16
|
-
* the app IS the backend. Emits server routes (/auth/netsuite/* + /api/me),
|
|
17
|
-
* netsuite_flows + netsuite_sessions drizzle tables, and a same-origin
|
|
18
|
-
* client. Only valid when the deployment registers the APP's own callback
|
|
19
|
-
* on the Integration Record instead of the Worker's.
|
|
18
|
+
* The wizard registers the Worker's fixed /auth/netsuite/callback as the
|
|
19
|
+
* ONE redirect URI on Integration Record #2 (provision-first, see webapp
|
|
20
|
+
* netsuiteRuntimeSetup).
|
|
20
21
|
*
|
|
21
22
|
* Both variants share the same component names (AuthLayout / LoginPage /
|
|
22
23
|
* SignInWithNetSuite) and the same wiring step: wrap the root route's
|
|
@@ -42,6 +43,26 @@ async function readScaffoldId(dir: string): Promise<string | null> {
|
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Is this project a tanstack-start (SSR) app? Primary signal: the scaffold id
|
|
48
|
+
* in .leo/project.json. Fallback for pre-project.json workspaces: only the
|
|
49
|
+
* start scaffold depends on @tanstack/react-start -- the router SPA ships
|
|
50
|
+
* @tanstack/react-router alone.
|
|
51
|
+
*/
|
|
52
|
+
async function isStartScaffold(dir: string): Promise<boolean> {
|
|
53
|
+
const scaffoldId = await readScaffoldId(dir);
|
|
54
|
+
if (scaffoldId) return scaffoldId.startsWith("tanstack-start");
|
|
55
|
+
try {
|
|
56
|
+
const pkg = await Bun.file(path.join(dir, "package.json")).json();
|
|
57
|
+
return Boolean(
|
|
58
|
+
pkg?.dependencies?.["@tanstack/react-start"] ??
|
|
59
|
+
pkg?.devDependencies?.["@tanstack/react-start"],
|
|
60
|
+
);
|
|
61
|
+
} catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
45
66
|
// --- emitted: src/db/schema.ts (appended) --------------------------------
|
|
46
67
|
function schemaAppend(): string {
|
|
47
68
|
return `
|
|
@@ -86,7 +107,9 @@ function netsuiteServerTs(): string {
|
|
|
86
107
|
* Config is read from the Cloudflare env binding via @helloleo/runtime.
|
|
87
108
|
* Set NETSUITE_ACCOUNT_ID / _CLIENT_ID / _CLIENT_SECRET (secret optional for a
|
|
88
109
|
* PKCE public client) and optional PUBLIC_APP_URL as HelloLeo Cloud secrets
|
|
89
|
-
* (prod) or dev vars (preview).
|
|
110
|
+
* (prod) or dev vars (preview). NETSUITE_BROKER_CALLBACK (written by the
|
|
111
|
+
* HelloLeo wizard) switches the redirect URI to the shared OAuth broker so
|
|
112
|
+
* one URI serves every app/environment. They never reach the browser.
|
|
90
113
|
*/
|
|
91
114
|
import { eq } from 'drizzle-orm'
|
|
92
115
|
import { env as runtimeEnv } from '@helloleo/runtime'
|
|
@@ -97,6 +120,7 @@ interface NsEnv {
|
|
|
97
120
|
NETSUITE_ACCOUNT_ID?: string
|
|
98
121
|
NETSUITE_CLIENT_ID?: string
|
|
99
122
|
NETSUITE_CLIENT_SECRET?: string
|
|
123
|
+
NETSUITE_BROKER_CALLBACK?: string
|
|
100
124
|
PUBLIC_APP_URL?: string
|
|
101
125
|
HELLOLEO_URL?: string
|
|
102
126
|
}
|
|
@@ -157,8 +181,47 @@ export function appOrigin(request: Request) {
|
|
|
157
181
|
url.host
|
|
158
182
|
return \`\${proto}://\${hostHeader}\`
|
|
159
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* HelloLeo's shared OAuth broker (integration-proxy Worker). When
|
|
186
|
+
* NETSUITE_BROKER_CALLBACK is set (the platform writes it to the project env),
|
|
187
|
+
* customers register that ONE stable URI on their Integration Record instead
|
|
188
|
+
* of one URI per app/environment: the authorize redirect goes to the broker,
|
|
189
|
+
* the broker 302s the callback back to THIS app (looked up by the state
|
|
190
|
+
* nonce), and the token exchange passes the same broker URI. Unset (self-host
|
|
191
|
+
* / standalone), the app falls back to its own origin's callback.
|
|
192
|
+
*/
|
|
193
|
+
export function brokerCallback(): string | null {
|
|
194
|
+
const raw = nsEnv.NETSUITE_BROKER_CALLBACK
|
|
195
|
+
if (!raw) return null
|
|
196
|
+
try {
|
|
197
|
+
const u = new URL(raw)
|
|
198
|
+
return u.protocol === 'https:' ? u.toString() : null
|
|
199
|
+
} catch {
|
|
200
|
+
return null
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
160
204
|
export function redirectUri(request: Request) {
|
|
161
|
-
return \`\${appOrigin(request)}/auth/netsuite/callback\`
|
|
205
|
+
return brokerCallback() ?? \`\${appOrigin(request)}/auth/netsuite/callback\`
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Tell the broker where to send this flow's callback: nonce -> this app's
|
|
210
|
+
* origin. MUST succeed before redirecting to NetSuite, otherwise the callback
|
|
211
|
+
* dead-ends at the broker. No-op when the broker isn't configured.
|
|
212
|
+
*/
|
|
213
|
+
export async function registerWithBroker(request: Request, flowId: string): Promise<void> {
|
|
214
|
+
const callback = brokerCallback()
|
|
215
|
+
if (!callback) return
|
|
216
|
+
const registerUrl = new URL('/auth/netsuite/register', callback)
|
|
217
|
+
const res = await fetch(registerUrl.toString(), {
|
|
218
|
+
method: 'POST',
|
|
219
|
+
headers: { 'Content-Type': 'application/json' },
|
|
220
|
+
body: JSON.stringify({ nonce: flowId, target: appOrigin(request) }),
|
|
221
|
+
})
|
|
222
|
+
if (!res.ok) {
|
|
223
|
+
throw new Error(\`NetSuite broker registration failed (HTTP \${res.status}) -- cannot start sign-in\`)
|
|
224
|
+
}
|
|
162
225
|
}
|
|
163
226
|
|
|
164
227
|
// Coerce ?return_to= to our OWN origin. The callback appends the session bearer
|
|
@@ -324,6 +387,7 @@ import {
|
|
|
324
387
|
authorizeUrl,
|
|
325
388
|
pkce,
|
|
326
389
|
redirectUri,
|
|
390
|
+
registerWithBroker,
|
|
327
391
|
requireConfig,
|
|
328
392
|
safeReturnTo,
|
|
329
393
|
SCOPE,
|
|
@@ -341,6 +405,10 @@ export const Route = createFileRoute('/auth/netsuite/start')({
|
|
|
341
405
|
|
|
342
406
|
const { verifier, challenge } = await pkce()
|
|
343
407
|
const flowId = crypto.randomUUID()
|
|
408
|
+
// With the shared broker, NetSuite redirects to the broker's fixed
|
|
409
|
+
// callback and the broker forwards here -- it must know this flow's
|
|
410
|
+
// target before the user leaves for NetSuite.
|
|
411
|
+
await registerWithBroker(request, flowId)
|
|
344
412
|
await db.insert(netsuiteFlows).values({ flowId, codeVerifier: verifier, returnTo })
|
|
345
413
|
|
|
346
414
|
const authorize = new URL(authorizeUrl(accountId))
|
|
@@ -1239,8 +1307,8 @@ async function executeStart(dir: string): Promise<string> {
|
|
|
1239
1307
|
"CONFIG -- NETSUITE_ACCOUNT_ID, NETSUITE_CLIENT_ID, NETSUITE_CLIENT_SECRET (secret optional for PKCE public clients). Never hardcode; never put in code. Same var names in every environment, different SOURCE:",
|
|
1240
1308
|
"- DEV (Fly preview): read from the project .env. HelloLeo writes them there (the setup wizard, or the env tooling). In dev the app uses .env -- do NOT tell the user to use HelloLeo Cloud here.",
|
|
1241
1309
|
"- PROD (published Worker): the SAME names live as HelloLeo Cloud secrets; the publish step promotes them from .env onto the Worker automatically. Same code path (@helloleo/runtime env), just a different source per environment.",
|
|
1242
|
-
"- PUBLIC_APP_URL / HELLOLEO_URL are set by the platform per environment; don't hardcode them.",
|
|
1243
|
-
"- REDIRECT URI -- never guess or hardcode it.
|
|
1310
|
+
"- PUBLIC_APP_URL / HELLOLEO_URL / NETSUITE_BROKER_CALLBACK are set by the platform per environment; don't hardcode them.",
|
|
1311
|
+
"- REDIRECT URI -- never guess or hardcode it. When NETSUITE_BROKER_CALLBACK is set (the HelloLeo wizard writes it), the redirect URI is HelloLeo's shared OAuth broker: ONE stable URI the user registers ONCE on Integration Record #2 (scope: rest_webservices), valid for both the preview and the published app -- the broker routes each callback back to this app. Without it, the app falls back to its own origin's callback. Either way, read the exact value from GET /auth/netsuite/config (or getNetSuiteRedirectUri() on the client) and tell the user to register THAT.",
|
|
1244
1312
|
"- Never replace <AuthLayout> with a password gate. Real OAuth is the only supported login.",
|
|
1245
1313
|
].join("\n");
|
|
1246
1314
|
}
|
|
@@ -1302,31 +1370,27 @@ async function executeSpa(dir: string): Promise<string> {
|
|
|
1302
1370
|
export const setupNetsuite = tool({
|
|
1303
1371
|
description:
|
|
1304
1372
|
"Add NetSuite runtime OAuth (end-user LOGIN) to an existing project. " +
|
|
1305
|
-
"Call scaffold first, then this. Works on both scaffold types
|
|
1306
|
-
"
|
|
1307
|
-
"
|
|
1308
|
-
"
|
|
1309
|
-
"
|
|
1310
|
-
"This login uses NetSuite Integration
|
|
1311
|
-
"(scope: rest_webservices) -- a SEPARATE record from the
|
|
1312
|
-
"(scope: mcp) that was used only to BUILD the app; do not
|
|
1313
|
-
"them. Real OAuth from day one -- never replace <AuthLayout>
|
|
1314
|
-
"password placeholder. Setup guide (link the user to it): " +
|
|
1373
|
+
"Call scaffold first, then this. Works on both scaffold types and " +
|
|
1374
|
+
"picks the variant automatically: on tanstack-start the app hosts " +
|
|
1375
|
+
"its own OAuth routes (/auth/netsuite/* + /api/me, same-origin " +
|
|
1376
|
+
"client); on tanstack-router (SPA) it writes the client + auth " +
|
|
1377
|
+
"components and the OAuth backend is the project's HelloLeo Cloud " +
|
|
1378
|
+
"Worker (VITE_WORKER_URL). This login uses NetSuite Integration " +
|
|
1379
|
+
"Record #2 (scope: rest_webservices) -- a SEPARATE record from the " +
|
|
1380
|
+
"MCP one (scope: mcp) that was used only to BUILD the app; do not " +
|
|
1381
|
+
"confuse them. Real OAuth from day one -- never replace <AuthLayout> " +
|
|
1382
|
+
"with a password placeholder. Setup guide (link the user to it): " +
|
|
1315
1383
|
"https://docs.helloleo.dev/integrations/netsuite",
|
|
1316
1384
|
args: {},
|
|
1317
1385
|
async execute(_args, context) {
|
|
1318
1386
|
const dir = context.directory;
|
|
1319
|
-
//
|
|
1320
|
-
//
|
|
1321
|
-
//
|
|
1322
|
-
// Worker
|
|
1323
|
-
//
|
|
1324
|
-
//
|
|
1325
|
-
|
|
1326
|
-
// tanstack-start scaffold.
|
|
1327
|
-
const scaffoldId = await readScaffoldId(dir);
|
|
1328
|
-
const inAppOptIn = process.env.PLUGINS_NETSUITE_IN_APP === "1";
|
|
1329
|
-
if (inAppOptIn && scaffoldId?.startsWith("tanstack-start")) {
|
|
1387
|
+
// The variant follows the scaffold, never an env flag: a start app has
|
|
1388
|
+
// a server (TanStack Start routes + D1) so it hosts the OAuth itself;
|
|
1389
|
+
// the router SPA has no server, so the per-project HelloLeo Cloud
|
|
1390
|
+
// Worker is the backend. The redirect URI registered on Integration
|
|
1391
|
+
// Record #2 must match: the app's own /auth/netsuite/callback for
|
|
1392
|
+
// start, the Worker's fixed callback for the SPA.
|
|
1393
|
+
if (await isStartScaffold(dir)) {
|
|
1330
1394
|
return executeStart(dir);
|
|
1331
1395
|
}
|
|
1332
1396
|
return executeSpa(dir);
|