@tanstack/start-plugin-core 1.132.0-alpha.5 → 1.132.0-alpha.7

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.
Files changed (43) hide show
  1. package/dist/esm/plugin.js +2 -2
  2. package/dist/esm/plugin.js.map +1 -1
  3. package/dist/esm/schema.d.ts +20 -28
  4. package/dist/esm/schema.js +10 -14
  5. package/dist/esm/schema.js.map +1 -1
  6. package/dist/esm/start-compiler-plugin/compilers.d.ts +0 -6
  7. package/dist/esm/start-compiler-plugin/compilers.js +12 -271
  8. package/dist/esm/start-compiler-plugin/compilers.js.map +1 -1
  9. package/dist/esm/start-compiler-plugin/constants.d.ts +1 -1
  10. package/dist/esm/start-compiler-plugin/constants.js +2 -2
  11. package/dist/esm/start-compiler-plugin/constants.js.map +1 -1
  12. package/dist/esm/start-compiler-plugin/envOnly.d.ts +5 -0
  13. package/dist/esm/start-compiler-plugin/envOnly.js +41 -0
  14. package/dist/esm/start-compiler-plugin/envOnly.js.map +1 -0
  15. package/dist/esm/start-compiler-plugin/isomorphicFn.d.ts +4 -0
  16. package/dist/esm/start-compiler-plugin/isomorphicFn.js +49 -0
  17. package/dist/esm/start-compiler-plugin/isomorphicFn.js.map +1 -0
  18. package/dist/esm/start-compiler-plugin/middleware.d.ts +4 -0
  19. package/dist/esm/start-compiler-plugin/middleware.js +51 -0
  20. package/dist/esm/start-compiler-plugin/middleware.js.map +1 -0
  21. package/dist/esm/start-compiler-plugin/plugin.js +59 -26
  22. package/dist/esm/start-compiler-plugin/plugin.js.map +1 -1
  23. package/dist/esm/start-compiler-plugin/serverFileRoute.d.ts +4 -0
  24. package/dist/esm/start-compiler-plugin/serverFileRoute.js +38 -0
  25. package/dist/esm/start-compiler-plugin/serverFileRoute.js.map +1 -0
  26. package/dist/esm/start-compiler-plugin/serverFn.d.ts +4 -0
  27. package/dist/esm/start-compiler-plugin/serverFn.js +87 -0
  28. package/dist/esm/start-compiler-plugin/serverFn.js.map +1 -0
  29. package/dist/esm/start-compiler-plugin/utils.d.ts +13 -0
  30. package/dist/esm/start-compiler-plugin/utils.js +30 -0
  31. package/dist/esm/start-compiler-plugin/utils.js.map +1 -0
  32. package/package.json +2 -2
  33. package/src/plugin.ts +2 -2
  34. package/src/schema.ts +10 -16
  35. package/src/start-compiler-plugin/compilers.ts +16 -462
  36. package/src/start-compiler-plugin/constants.ts +2 -2
  37. package/src/start-compiler-plugin/envOnly.ts +58 -0
  38. package/src/start-compiler-plugin/isomorphicFn.ts +78 -0
  39. package/src/start-compiler-plugin/middleware.ts +79 -0
  40. package/src/start-compiler-plugin/plugin.ts +67 -36
  41. package/src/start-compiler-plugin/serverFileRoute.ts +59 -0
  42. package/src/start-compiler-plugin/serverFn.ts +163 -0
  43. package/src/start-compiler-plugin/utils.ts +41 -0
@@ -0,0 +1,58 @@
1
+ import * as t from '@babel/types'
2
+ import type * as babel from '@babel/core'
3
+
4
+ import type { CompileOptions } from './compilers'
5
+
6
+ function capitalize(str: string) {
7
+ if (!str) return ''
8
+ return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase()
9
+ }
10
+
11
+ function buildEnvOnlyCallExpressionHandler(env: 'client' | 'server') {
12
+ return function envOnlyCallExpressionHandler(
13
+ path: babel.NodePath<t.CallExpression>,
14
+ opts: CompileOptions,
15
+ ) {
16
+ // if (debug)
17
+ // console.info(`Handling ${env}Only call expression:`, path.toString())
18
+
19
+ const isEnvMatch =
20
+ env === 'client' ? opts.env === 'client' : opts.env === 'server'
21
+
22
+ if (isEnvMatch) {
23
+ // extract the inner function from the call expression
24
+ const innerInputExpression = path.node.arguments[0]
25
+
26
+ if (!t.isExpression(innerInputExpression)) {
27
+ throw new Error(
28
+ `${env}Only() functions must be called with a function!`,
29
+ )
30
+ }
31
+
32
+ path.replaceWith(innerInputExpression)
33
+ return
34
+ }
35
+
36
+ // If we're on the wrong environment, replace the call expression
37
+ // with a function that always throws an error.
38
+ path.replaceWith(
39
+ t.arrowFunctionExpression(
40
+ [],
41
+ t.blockStatement([
42
+ t.throwStatement(
43
+ t.newExpression(t.identifier('Error'), [
44
+ t.stringLiteral(
45
+ `create${capitalize(env)}OnlyFn() functions can only be called on the ${env}!`,
46
+ ),
47
+ ]),
48
+ ),
49
+ ]),
50
+ ),
51
+ )
52
+ }
53
+ }
54
+
55
+ export const handleCreateServerOnlyFnCallExpression =
56
+ buildEnvOnlyCallExpressionHandler('server')
57
+ export const handleCreateClientOnlyFnCallExpression =
58
+ buildEnvOnlyCallExpressionHandler('client')
@@ -0,0 +1,78 @@
1
+ import * as t from '@babel/types'
2
+ import { getRootCallExpression } from './utils'
3
+ import type * as babel from '@babel/core'
4
+
5
+ import type { CompileOptions } from './compilers'
6
+
7
+ export function handleCreateIsomorphicFnCallExpression(
8
+ path: babel.NodePath<t.CallExpression>,
9
+ opts: CompileOptions,
10
+ ) {
11
+ const rootCallExpression = getRootCallExpression(path)
12
+
13
+ // if (debug)
14
+ // console.info(
15
+ // 'Handling createIsomorphicFn call expression:',
16
+ // rootCallExpression.toString(),
17
+ // )
18
+
19
+ const callExpressionPaths = {
20
+ client: null as babel.NodePath<t.CallExpression> | null,
21
+ server: null as babel.NodePath<t.CallExpression> | null,
22
+ }
23
+
24
+ const validMethods = Object.keys(callExpressionPaths)
25
+
26
+ rootCallExpression.traverse({
27
+ MemberExpression(memberExpressionPath) {
28
+ if (t.isIdentifier(memberExpressionPath.node.property)) {
29
+ const name = memberExpressionPath.node.property
30
+ .name as keyof typeof callExpressionPaths
31
+
32
+ if (
33
+ validMethods.includes(name) &&
34
+ memberExpressionPath.parentPath.isCallExpression()
35
+ ) {
36
+ callExpressionPaths[name] = memberExpressionPath.parentPath
37
+ }
38
+ }
39
+ },
40
+ })
41
+
42
+ if (
43
+ validMethods.every(
44
+ (method) =>
45
+ !callExpressionPaths[method as keyof typeof callExpressionPaths],
46
+ )
47
+ ) {
48
+ const variableId = rootCallExpression.parentPath.isVariableDeclarator()
49
+ ? rootCallExpression.parentPath.node.id
50
+ : null
51
+ console.warn(
52
+ 'createIsomorphicFn called without a client or server implementation!',
53
+ 'This will result in a no-op function.',
54
+ 'Variable name:',
55
+ t.isIdentifier(variableId) ? variableId.name : 'unknown',
56
+ )
57
+ }
58
+
59
+ const envCallExpression = callExpressionPaths[opts.env]
60
+
61
+ if (!envCallExpression) {
62
+ // if we don't have an implementation for this environment, default to a no-op
63
+ rootCallExpression.replaceWith(
64
+ t.arrowFunctionExpression([], t.blockStatement([])),
65
+ )
66
+ return
67
+ }
68
+
69
+ const innerInputExpression = envCallExpression.node.arguments[0]
70
+
71
+ if (!t.isExpression(innerInputExpression)) {
72
+ throw new Error(
73
+ `createIsomorphicFn().${opts.env}(func) must be called with a function!`,
74
+ )
75
+ }
76
+
77
+ rootCallExpression.replaceWith(innerInputExpression)
78
+ }
@@ -0,0 +1,79 @@
1
+ import * as t from '@babel/types'
2
+ import { getRootCallExpression } from './utils'
3
+ import type * as babel from '@babel/core'
4
+
5
+ import type { CompileOptions } from './compilers'
6
+
7
+ export function handleCreateMiddlewareCallExpression(
8
+ path: babel.NodePath<t.CallExpression>,
9
+ opts: CompileOptions,
10
+ ) {
11
+ const rootCallExpression = getRootCallExpression(path)
12
+
13
+ // if (debug)
14
+ // console.info(
15
+ // 'Handling createMiddleware call expression:',
16
+ // rootCallExpression.toString(),
17
+ // )
18
+
19
+ const callExpressionPaths = {
20
+ middleware: null as babel.NodePath<t.CallExpression> | null,
21
+ validator: null as babel.NodePath<t.CallExpression> | null,
22
+ client: null as babel.NodePath<t.CallExpression> | null,
23
+ server: null as babel.NodePath<t.CallExpression> | null,
24
+ }
25
+
26
+ const validMethods = Object.keys(callExpressionPaths)
27
+
28
+ rootCallExpression.traverse({
29
+ MemberExpression(memberExpressionPath) {
30
+ if (t.isIdentifier(memberExpressionPath.node.property)) {
31
+ const name = memberExpressionPath.node.property
32
+ .name as keyof typeof callExpressionPaths
33
+
34
+ if (
35
+ validMethods.includes(name) &&
36
+ memberExpressionPath.parentPath.isCallExpression()
37
+ ) {
38
+ callExpressionPaths[name] = memberExpressionPath.parentPath
39
+ }
40
+ }
41
+ },
42
+ })
43
+
44
+ if (callExpressionPaths.validator) {
45
+ const innerInputExpression = callExpressionPaths.validator.node.arguments[0]
46
+
47
+ if (!innerInputExpression) {
48
+ throw new Error(
49
+ 'createMiddleware().validator() must be called with a validator!',
50
+ )
51
+ }
52
+
53
+ // If we're on the client, remove the validator call expression
54
+ if (opts.env === 'client') {
55
+ if (t.isMemberExpression(callExpressionPaths.validator.node.callee)) {
56
+ callExpressionPaths.validator.replaceWith(
57
+ callExpressionPaths.validator.node.callee.object,
58
+ )
59
+ }
60
+ }
61
+ }
62
+
63
+ const serverFnPath = callExpressionPaths.server?.get(
64
+ 'arguments.0',
65
+ ) as babel.NodePath<any>
66
+
67
+ if (
68
+ callExpressionPaths.server &&
69
+ serverFnPath.node &&
70
+ opts.env === 'client'
71
+ ) {
72
+ // If we're on the client, remove the server call expression
73
+ if (t.isMemberExpression(callExpressionPaths.server.node.callee)) {
74
+ callExpressionPaths.server.replaceWith(
75
+ callExpressionPaths.server.node.callee.object,
76
+ )
77
+ }
78
+ }
79
+ }
@@ -1,7 +1,11 @@
1
1
  import { fileURLToPath, pathToFileURL } from 'node:url'
2
+ import { createRequire } from 'node:module'
2
3
  import { logDiff } from '@tanstack/router-utils'
3
4
 
4
5
  import { VIRTUAL_MODULES } from '@tanstack/start-server-core'
6
+ import { normalizePath } from 'vite'
7
+ import path from 'pathe'
8
+ import { VITE_ENVIRONMENT_NAMES } from '../constants'
5
9
  import { compileStartOutputFactory } from './compilers'
6
10
  import { transformFuncs } from './constants'
7
11
  import type { Plugin } from 'vite'
@@ -17,6 +21,20 @@ export type TanStackStartViteOptions = {
17
21
 
18
22
  const tokenRegex = new RegExp(transformFuncs.join('|'))
19
23
 
24
+ const require = createRequire(import.meta.url)
25
+
26
+ function resolveRuntimeFiles(opts: { package: string; files: Array<string> }) {
27
+ const pkgRoot = resolvePackage(opts.package)
28
+ const basePath = path.join(pkgRoot, 'dist', 'esm')
29
+
30
+ return opts.files.map((file) => normalizePath(path.join(basePath, file)))
31
+ }
32
+
33
+ function resolvePackage(packageName: string): string {
34
+ const pkgRoot = path.dirname(require.resolve(packageName + '/package.json'))
35
+ return pkgRoot
36
+ }
37
+
20
38
  export function startCompilerPlugin(
21
39
  framework: CompileStartFrameworkOptions,
22
40
  inputOpts?: {
@@ -30,15 +48,17 @@ export function startCompilerPlugin(
30
48
  ): Plugin {
31
49
  const opts = {
32
50
  client: {
33
- envName: 'client',
51
+ envName: VITE_ENVIRONMENT_NAMES.client,
34
52
  ...inputOpts?.client,
35
53
  },
36
54
  server: {
37
- envName: 'server',
55
+ envName: VITE_ENVIRONMENT_NAMES.server,
38
56
  ...inputOpts?.server,
39
57
  },
40
58
  }
41
59
 
60
+ const compileStartOutput = compileStartOutputFactory(framework)
61
+
42
62
  return {
43
63
  name: 'tanstack-start-core:compiler',
44
64
  enforce: 'pre',
@@ -49,7 +69,36 @@ export function startCompilerPlugin(
49
69
  filter: {
50
70
  code: tokenRegex,
51
71
  id: {
52
- exclude: VIRTUAL_MODULES.serverFnManifest,
72
+ exclude: [
73
+ VIRTUAL_MODULES.serverFnManifest,
74
+ // N.B. the following files either just re-export or provide the runtime implementation of those functions
75
+ // we do not want to include them in the transformation
76
+ // however, those packages (especially start-client-core ATM) also USE these functions
77
+ // (namely `createIsomorphicFn` in `packages/start-client-core/src/getRouterInstance.ts`) and thus need to be transformed
78
+ ...resolveRuntimeFiles({
79
+ package: '@tanstack/start-client-core',
80
+ files: [
81
+ 'index.js',
82
+ 'createIsomorphicFn.js',
83
+ 'envOnly.js',
84
+ 'createServerFn.js',
85
+ 'createMiddleware.js',
86
+ 'serverFnFetcher.js',
87
+ ],
88
+ }),
89
+ ...resolveRuntimeFiles({
90
+ package: '@tanstack/start-server-core',
91
+ files: [
92
+ 'index.js',
93
+ 'server-functions-handler.js',
94
+ 'serverRoute.js',
95
+ ],
96
+ }),
97
+ ...resolveRuntimeFiles({
98
+ package: `@tanstack/${framework}-start-client`,
99
+ files: ['index.js'],
100
+ }),
101
+ ],
53
102
  },
54
103
  },
55
104
  handler(code, id) {
@@ -64,43 +113,25 @@ export function startCompilerPlugin(
64
113
  )
65
114
  })()
66
115
 
67
- return transformCode({
116
+ const url = pathToFileURL(id)
117
+ url.searchParams.delete('v')
118
+ id = fileURLToPath(url).replace(/\\/g, '/')
119
+
120
+ if (debug) console.info(`${env} Compiling Start: `, id)
121
+
122
+ const compiled = compileStartOutput({
68
123
  code,
69
- id,
124
+ filename: id,
70
125
  env,
71
- framework,
72
126
  })
73
- },
74
- },
75
- }
76
- }
77
-
78
- function transformCode(opts: {
79
- code: string
80
- id: string
81
- env: 'server' | 'client'
82
- framework: CompileStartFrameworkOptions
83
- }) {
84
- const { code, env, framework } = opts
85
- let { id } = opts
86
127
 
87
- const url = pathToFileURL(id)
88
- url.searchParams.delete('v')
89
- id = fileURLToPath(url).replace(/\\/g, '/')
128
+ if (debug) {
129
+ logDiff(code, compiled.code)
130
+ console.log('Output:\n', compiled.code + '\n\n')
131
+ }
90
132
 
91
- if (debug) console.info(`${env} Compiling Start: `, id)
92
-
93
- const compileStartOutput = compileStartOutputFactory(framework)
94
- const compiled = compileStartOutput({
95
- code,
96
- filename: id,
97
- env,
98
- })
99
-
100
- if (debug) {
101
- logDiff(code, compiled.code)
102
- console.log('Output:\n', compiled.code + '\n\n')
133
+ return compiled
134
+ },
135
+ },
103
136
  }
104
-
105
- return compiled
106
137
  }
@@ -0,0 +1,59 @@
1
+ import * as t from '@babel/types'
2
+ import type * as babel from '@babel/core'
3
+
4
+ import type { CompileOptions, CompileStartFrameworkOptions } from './compilers'
5
+
6
+ export function handleCreateServerFileRouteCallExpressionFactory(
7
+ framework: CompileStartFrameworkOptions,
8
+ method:
9
+ | 'createServerFileRoute'
10
+ | 'createServerRoute'
11
+ | 'createServerRootRoute',
12
+ ) {
13
+ return function handleCreateServerFileRouteCallExpression(
14
+ path: babel.NodePath<t.CallExpression>,
15
+ opts: CompileOptions,
16
+ ) {
17
+ const PACKAGES = { start: `@tanstack/${framework}-start/server` }
18
+
19
+ let highestParent: babel.NodePath<any> = path
20
+
21
+ while (highestParent.parentPath && !highestParent.parentPath.isProgram()) {
22
+ highestParent = highestParent.parentPath
23
+ }
24
+
25
+ const programPath = highestParent.parentPath as babel.NodePath<t.Program>
26
+
27
+ // If we're on the client, remove the entire variable
28
+ if (opts.env === 'client') {
29
+ highestParent.remove()
30
+ return
31
+ }
32
+
33
+ let isCreateServerFileRouteImported = false as boolean
34
+
35
+ programPath.traverse({
36
+ ImportDeclaration(importPath) {
37
+ const importSource = importPath.node.source.value
38
+ if (importSource === PACKAGES.start) {
39
+ const specifiers = importPath.node.specifiers
40
+ isCreateServerFileRouteImported ||= specifiers.some((specifier) => {
41
+ return (
42
+ t.isImportSpecifier(specifier) &&
43
+ t.isIdentifier(specifier.imported) &&
44
+ specifier.imported.name === method
45
+ )
46
+ })
47
+ }
48
+ },
49
+ })
50
+
51
+ if (!isCreateServerFileRouteImported) {
52
+ const importDeclaration = t.importDeclaration(
53
+ [t.importSpecifier(t.identifier(method), t.identifier(method))],
54
+ t.stringLiteral(PACKAGES.start),
55
+ )
56
+ programPath.node.body.unshift(importDeclaration)
57
+ }
58
+ }
59
+ }
@@ -0,0 +1,163 @@
1
+ import * as t from '@babel/types'
2
+ import { codeFrameError, getRootCallExpression } from './utils'
3
+ import type * as babel from '@babel/core'
4
+
5
+ import type { CompileOptions } from './compilers'
6
+
7
+ export function handleCreateServerFnCallExpression(
8
+ path: babel.NodePath<t.CallExpression>,
9
+ opts: CompileOptions,
10
+ ) {
11
+ // Traverse the member expression and find the call expressions for
12
+ // the validator, handler, and middleware methods. Check to make sure they
13
+ // are children of the createServerFn call expression.
14
+
15
+ const calledOptions = path.node.arguments[0]
16
+ ? (path.get('arguments.0') as babel.NodePath<t.ObjectExpression>)
17
+ : null
18
+
19
+ const shouldValidateClient = !!calledOptions?.node.properties.find((prop) => {
20
+ return (
21
+ t.isObjectProperty(prop) &&
22
+ t.isIdentifier(prop.key) &&
23
+ prop.key.name === 'validateClient' &&
24
+ t.isBooleanLiteral(prop.value) &&
25
+ prop.value.value === true
26
+ )
27
+ })
28
+
29
+ const callExpressionPaths = {
30
+ middleware: null as babel.NodePath<t.CallExpression> | null,
31
+ validator: null as babel.NodePath<t.CallExpression> | null,
32
+ handler: null as babel.NodePath<t.CallExpression> | null,
33
+ }
34
+
35
+ const validMethods = Object.keys(callExpressionPaths)
36
+
37
+ const rootCallExpression = getRootCallExpression(path)
38
+
39
+ // if (debug)
40
+ // console.info(
41
+ // 'Handling createServerFn call expression:',
42
+ // rootCallExpression.toString(),
43
+ // )
44
+
45
+ // Check if the call is assigned to a variable
46
+ if (!rootCallExpression.parentPath.isVariableDeclarator()) {
47
+ throw new Error('createServerFn must be assigned to a variable!')
48
+ }
49
+
50
+ // Get the identifier name of the variable
51
+ const variableDeclarator = rootCallExpression.parentPath.node
52
+ const existingVariableName = (variableDeclarator.id as t.Identifier).name
53
+
54
+ rootCallExpression.traverse({
55
+ MemberExpression(memberExpressionPath) {
56
+ if (t.isIdentifier(memberExpressionPath.node.property)) {
57
+ const name = memberExpressionPath.node.property
58
+ .name as keyof typeof callExpressionPaths
59
+
60
+ if (
61
+ validMethods.includes(name) &&
62
+ memberExpressionPath.parentPath.isCallExpression()
63
+ ) {
64
+ callExpressionPaths[name] = memberExpressionPath.parentPath
65
+ }
66
+ }
67
+ },
68
+ })
69
+
70
+ if (callExpressionPaths.validator) {
71
+ const innerInputExpression = callExpressionPaths.validator.node.arguments[0]
72
+
73
+ if (!innerInputExpression) {
74
+ throw new Error(
75
+ 'createServerFn().validator() must be called with a validator!',
76
+ )
77
+ }
78
+
79
+ // If we're on the client, and we're not validating the client, remove the validator call expression
80
+ if (
81
+ opts.env === 'client' &&
82
+ !shouldValidateClient &&
83
+ t.isMemberExpression(callExpressionPaths.validator.node.callee)
84
+ ) {
85
+ callExpressionPaths.validator.replaceWith(
86
+ callExpressionPaths.validator.node.callee.object,
87
+ )
88
+ }
89
+ }
90
+
91
+ // First, we need to move the handler function to a nested function call
92
+ // that is applied to the arguments passed to the server function.
93
+
94
+ const handlerFnPath = callExpressionPaths.handler?.get(
95
+ 'arguments.0',
96
+ ) as babel.NodePath<any>
97
+
98
+ if (!callExpressionPaths.handler || !handlerFnPath.node) {
99
+ throw codeFrameError(
100
+ opts.code,
101
+ path.node.callee.loc!,
102
+ `createServerFn must be called with a "handler" property!`,
103
+ )
104
+ }
105
+
106
+ const handlerFn = handlerFnPath.node
107
+
108
+ // So, the way we do this is we give the handler function a way
109
+ // to access the serverFn ctx on the server via function scope.
110
+ // The 'use server' extracted function will be called with the
111
+ // payload from the client, then use the scoped serverFn ctx
112
+ // to execute the handler function.
113
+ // This way, we can do things like data and middleware validation
114
+ // in the __execute function without having to AST transform the
115
+ // handler function too much itself.
116
+
117
+ // .handler((optsOut, ctx) => {
118
+ // return ((optsIn) => {
119
+ // 'use server'
120
+ // ctx.__execute(handlerFn, optsIn)
121
+ // })(optsOut)
122
+ // })
123
+
124
+ // If the handler function is an identifier and we're on the client, we need to
125
+ // remove the bound function from the file.
126
+ // If we're on the server, you can leave it, since it will get referenced
127
+ // as a second argument.
128
+
129
+ if (t.isIdentifier(handlerFn)) {
130
+ if (opts.env === 'client') {
131
+ // Find the binding for the handler function
132
+ const binding = handlerFnPath.scope.getBinding(handlerFn.name)
133
+ // Remove it
134
+ if (binding) {
135
+ binding.path.remove()
136
+ }
137
+ }
138
+ // If the env is server, just leave it alone
139
+ }
140
+
141
+ handlerFnPath.replaceWith(
142
+ t.arrowFunctionExpression(
143
+ [t.identifier('opts'), t.identifier('signal')],
144
+ t.blockStatement(
145
+ // Everything in here is server-only, since the client
146
+ // will strip out anything in the 'use server' directive.
147
+ [
148
+ t.returnStatement(
149
+ t.callExpression(
150
+ t.identifier(`${existingVariableName}.__executeServer`),
151
+ [t.identifier('opts'), t.identifier('signal')],
152
+ ),
153
+ ),
154
+ ],
155
+ [t.directive(t.directiveLiteral('use server'))],
156
+ ),
157
+ ),
158
+ )
159
+
160
+ if (opts.env === 'server') {
161
+ callExpressionPaths.handler.node.arguments.push(handlerFn)
162
+ }
163
+ }
@@ -0,0 +1,41 @@
1
+ import { codeFrameColumns } from '@babel/code-frame'
2
+ import type * as t from '@babel/types'
3
+ import type * as babel from '@babel/core'
4
+
5
+ export function getRootCallExpression(path: babel.NodePath<t.CallExpression>) {
6
+ // Find the highest callExpression parent
7
+ let rootCallExpression: babel.NodePath<t.CallExpression> = path
8
+
9
+ // Traverse up the chain of CallExpressions
10
+ while (rootCallExpression.parentPath.isMemberExpression()) {
11
+ const parent = rootCallExpression.parentPath
12
+ if (parent.parentPath.isCallExpression()) {
13
+ rootCallExpression = parent.parentPath
14
+ }
15
+ }
16
+
17
+ return rootCallExpression
18
+ }
19
+
20
+ export function codeFrameError(
21
+ code: string,
22
+ loc: {
23
+ start: { line: number; column: number }
24
+ end: { line: number; column: number }
25
+ },
26
+ message: string,
27
+ ) {
28
+ const frame = codeFrameColumns(
29
+ code,
30
+ {
31
+ start: loc.start,
32
+ end: loc.end,
33
+ },
34
+ {
35
+ highlightCode: true,
36
+ message,
37
+ },
38
+ )
39
+
40
+ return new Error(frame)
41
+ }