@ruvyxa/adapter-netlify 1.0.19 → 1.0.20
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.d.ts.map +1 -1
- package/dist/index.js +36 -12
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/src/index.ts +276 -0
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAgD,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAgD,MAAM,cAAc,CAAA;AAWzF;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,qFAAqF;IACrF,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB;AAmFD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,cAAc,CAAC,OAAO,GAAE,qBAA0B,GAAG,OAAO,CAwI3E;eAEc,cAAc"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { clientBuildOutput, projectRelativeOutDir, validateBuildContext } from '@ruvyxa/core';
|
|
1
|
+
import { CLIENT_BUNDLE_PREFIX, clientBuildOutput, IMMUTABLE_CACHE_CONTROL, projectRelativeOutDir, PUBLIC_ASSET_CACHE_CONTROL, publicAssetGlobs, validateBuildContext, } from '@ruvyxa/core';
|
|
2
2
|
/**
|
|
3
3
|
* Netlify Functions v2 handler source code.
|
|
4
4
|
*
|
|
@@ -9,12 +9,18 @@ import { clientBuildOutput, projectRelativeOutDir, validateBuildContext } from '
|
|
|
9
9
|
function netlifyHandlerSource() {
|
|
10
10
|
return `import { createHandler, prerenderRelativePath } from './serverless-handler.mjs';
|
|
11
11
|
import { loadRouteModule } from './route-modules.mjs';
|
|
12
|
+
// Netlify bundles the function with esbuild, so anything the deployed code
|
|
13
|
+
// needs must be reachable through the module graph. A sibling manifest.json
|
|
14
|
+
// read from import.meta.dirname is not, and never reaches /var/task.
|
|
15
|
+
import manifest from './manifest.mjs';
|
|
12
16
|
import { readFileSync, writeFileSync, mkdirSync, statSync } from 'node:fs';
|
|
13
17
|
import os from 'node:os';
|
|
14
18
|
import path from 'node:path';
|
|
15
19
|
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
// Deploy-time prerender output is likewise dropped by the bundler unless the
|
|
21
|
+
// site declares included_files. Reads are best-effort: a miss falls through to
|
|
22
|
+
// an on-demand render, and Netlify serves SSG pages from the publish directory
|
|
23
|
+
// before the function is ever invoked (config.preferStatic).
|
|
18
24
|
const prerenderDir = path.join(import.meta.dirname, 'prerender');
|
|
19
25
|
// The function bundle directory is read-only at runtime; only the platform
|
|
20
26
|
// tmp directory accepts writes. ISR revalidations land there and are read
|
|
@@ -109,8 +115,20 @@ export function netlifyAdapter(options = {}) {
|
|
|
109
115
|
const relativeOutDir = projectRelativeOutDir(ctx);
|
|
110
116
|
// Hashed client bundles are served under /__ruvyxa/client/ (see the
|
|
111
117
|
// chunk manifest src fields); the immutable header must match that URL.
|
|
112
|
-
|
|
113
|
-
|
|
118
|
+
// `public/` assets are not hashed and otherwise inherit Netlify's
|
|
119
|
+
// revalidate-every-request default, so they get the same lifetime the
|
|
120
|
+
// Rust server sends for the same files.
|
|
121
|
+
const headerRules = [
|
|
122
|
+
{ for: `${CLIENT_BUNDLE_PREFIX}*`, cacheControl: IMMUTABLE_CACHE_CONTROL },
|
|
123
|
+
...publicAssetGlobs().map((glob) => ({
|
|
124
|
+
for: glob,
|
|
125
|
+
cacheControl: PUBLIC_ASSET_CACHE_CONTROL,
|
|
126
|
+
})),
|
|
127
|
+
];
|
|
128
|
+
const immutableHeaderToml = headerRules
|
|
129
|
+
.map((rule) => `[[headers]]\n for = "${rule.for}"\n [headers.values]\n` +
|
|
130
|
+
` Cache-Control = "${rule.cacheControl}"\n`)
|
|
131
|
+
.join('');
|
|
114
132
|
// Netlify.toml for the deploy directory
|
|
115
133
|
const netlifyToml = '[build]\n publish = "publish"\n functions = "functions"\n\n' + immutableHeaderToml;
|
|
116
134
|
// Project-root netlify.toml pointing at the build output (opt-in)
|
|
@@ -121,12 +139,10 @@ export function netlifyAdapter(options = {}) {
|
|
|
121
139
|
immutableHeaderToml;
|
|
122
140
|
// Frameworks API deploy configuration (.netlify/v1/config.json)
|
|
123
141
|
const frameworksApiConfig = JSON.stringify({
|
|
124
|
-
headers:
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
},
|
|
129
|
-
],
|
|
142
|
+
headers: headerRules.map((rule) => ({
|
|
143
|
+
for: rule.for,
|
|
144
|
+
values: { 'Cache-Control': rule.cacheControl },
|
|
145
|
+
})),
|
|
130
146
|
}, null, 2);
|
|
131
147
|
return {
|
|
132
148
|
name: 'netlify',
|
|
@@ -142,7 +158,15 @@ export function netlifyAdapter(options = {}) {
|
|
|
142
158
|
// an API-only or all-SSR app has no prerendered pages; the function
|
|
143
159
|
// still serves every route, so the missing prerender directory must
|
|
144
160
|
// not fail the build.
|
|
145
|
-
|
|
161
|
+
// `preferStatic: true` means a published page is served without ever
|
|
162
|
+
// reaching the function, so ISR/PPR pages must stay unpublished for
|
|
163
|
+
// revalidation to happen at all.
|
|
164
|
+
{
|
|
165
|
+
kind: 'static-site',
|
|
166
|
+
path: 'deploy/netlify/publish',
|
|
167
|
+
optional: true,
|
|
168
|
+
excludeStrategies: ['isr', 'ppr'],
|
|
169
|
+
},
|
|
146
170
|
// Serverless function bundle
|
|
147
171
|
{
|
|
148
172
|
kind: 'function',
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ruvyxa/adapter-netlify",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.20",
|
|
4
4
|
"description": "Shape Ruvyxa builds for Netlify functions and assets with a small typed adapter contract.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
9
|
"files": [
|
|
10
10
|
"dist",
|
|
11
|
+
"src",
|
|
11
12
|
"README.md"
|
|
12
13
|
],
|
|
13
14
|
"keywords": [
|
|
@@ -26,7 +27,7 @@
|
|
|
26
27
|
},
|
|
27
28
|
"homepage": "https://github.com/thirawat27/ruvyxa#readme",
|
|
28
29
|
"engines": {
|
|
29
|
-
"node": ">=22.
|
|
30
|
+
"node": ">=22.12.0"
|
|
30
31
|
},
|
|
31
32
|
"publishConfig": {
|
|
32
33
|
"access": "public"
|
|
@@ -38,7 +39,7 @@
|
|
|
38
39
|
}
|
|
39
40
|
},
|
|
40
41
|
"dependencies": {
|
|
41
|
-
"@ruvyxa/core": "^1.0.
|
|
42
|
+
"@ruvyxa/core": "^1.0.20"
|
|
42
43
|
},
|
|
43
44
|
"scripts": {
|
|
44
45
|
"build": "tsc -p tsconfig.json",
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import type { Adapter, AdapterArtifact, AdapterOutput, BuildContext } from '@ruvyxa/core'
|
|
2
|
+
import {
|
|
3
|
+
CLIENT_BUNDLE_PREFIX,
|
|
4
|
+
clientBuildOutput,
|
|
5
|
+
IMMUTABLE_CACHE_CONTROL,
|
|
6
|
+
projectRelativeOutDir,
|
|
7
|
+
PUBLIC_ASSET_CACHE_CONTROL,
|
|
8
|
+
publicAssetGlobs,
|
|
9
|
+
validateBuildContext,
|
|
10
|
+
} from '@ruvyxa/core'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Options for Netlify deployment.
|
|
14
|
+
*/
|
|
15
|
+
export interface NetlifyAdapterOptions {
|
|
16
|
+
/** Custom Netlify functions directory. Defaults to `${outDir}/netlify/functions`. */
|
|
17
|
+
functionsDir?: string
|
|
18
|
+
/**
|
|
19
|
+
* Also emit a `netlify.toml` at the project root pointing Netlify at the
|
|
20
|
+
* generated publish directory and functions. An existing project
|
|
21
|
+
* `netlify.toml` is never overwritten.
|
|
22
|
+
*
|
|
23
|
+
* Off by default: the adapter already emits the serverless function and
|
|
24
|
+
* cache headers through Netlify's Frameworks API (`.netlify/v1/`, a
|
|
25
|
+
* gitignored build artifact), so the only remaining setup is the publish
|
|
26
|
+
* directory — set once in the Netlify dashboard, or opt into this file.
|
|
27
|
+
* @default false
|
|
28
|
+
*/
|
|
29
|
+
projectConfig?: boolean
|
|
30
|
+
/**
|
|
31
|
+
* Emit the Netlify Frameworks API directory (`.netlify/v1/`) at the project
|
|
32
|
+
* root during `ruvyxa build`: the SSR/API function and the immutable cache
|
|
33
|
+
* headers for hashed client bundles. Netlify picks the directory up
|
|
34
|
+
* automatically on the next deploy — no config file at the project root.
|
|
35
|
+
* @default true
|
|
36
|
+
*/
|
|
37
|
+
frameworksApi?: boolean
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Netlify Functions v2 handler source code.
|
|
42
|
+
*
|
|
43
|
+
* Wraps the generic Ruvyxa serverless handler into a Netlify Functions v2
|
|
44
|
+
* handler using the Web-standard Request/Response API. Reads the route
|
|
45
|
+
* manifest and handles SSR/API/ISR/PPR requests.
|
|
46
|
+
*/
|
|
47
|
+
function netlifyHandlerSource(): string {
|
|
48
|
+
return `import { createHandler, prerenderRelativePath } from './serverless-handler.mjs';
|
|
49
|
+
import { loadRouteModule } from './route-modules.mjs';
|
|
50
|
+
// Netlify bundles the function with esbuild, so anything the deployed code
|
|
51
|
+
// needs must be reachable through the module graph. A sibling manifest.json
|
|
52
|
+
// read from import.meta.dirname is not, and never reaches /var/task.
|
|
53
|
+
import manifest from './manifest.mjs';
|
|
54
|
+
import { readFileSync, writeFileSync, mkdirSync, statSync } from 'node:fs';
|
|
55
|
+
import os from 'node:os';
|
|
56
|
+
import path from 'node:path';
|
|
57
|
+
|
|
58
|
+
// Deploy-time prerender output is likewise dropped by the bundler unless the
|
|
59
|
+
// site declares included_files. Reads are best-effort: a miss falls through to
|
|
60
|
+
// an on-demand render, and Netlify serves SSG pages from the publish directory
|
|
61
|
+
// before the function is ever invoked (config.preferStatic).
|
|
62
|
+
const prerenderDir = path.join(import.meta.dirname, 'prerender');
|
|
63
|
+
// The function bundle directory is read-only at runtime; only the platform
|
|
64
|
+
// tmp directory accepts writes. ISR revalidations land there and are read
|
|
65
|
+
// back before the bundled deploy-time prerender output.
|
|
66
|
+
const isrCacheDir = path.join(os.tmpdir(), 'ruvyxa-isr-cache');
|
|
67
|
+
|
|
68
|
+
const readEntry = (htmlPath, revalidate) => {
|
|
69
|
+
const html = readFileSync(htmlPath, 'utf8');
|
|
70
|
+
const stale = Date.now() - statSync(htmlPath).mtimeMs >= revalidate * 1000;
|
|
71
|
+
return { html, stale };
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const handler = createHandler({
|
|
75
|
+
routes: manifest.routes,
|
|
76
|
+
importPage: loadRouteModule,
|
|
77
|
+
importApi: loadRouteModule,
|
|
78
|
+
readPrerendered: (pathname, revalidate = 60) => {
|
|
79
|
+
// prerenderRelativePath rejects any request path that cannot be mapped to a
|
|
80
|
+
// location inside the cache directories, so reads can never escape them.
|
|
81
|
+
const relative = prerenderRelativePath(pathname);
|
|
82
|
+
if (relative === null) return null;
|
|
83
|
+
try {
|
|
84
|
+
return readEntry(path.join(isrCacheDir, relative), revalidate);
|
|
85
|
+
} catch {
|
|
86
|
+
// fall through to the bundled prerender output
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
return readEntry(path.join(prerenderDir, relative), revalidate);
|
|
90
|
+
} catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
writePrerendered: (pathname, html, revalidate) => {
|
|
95
|
+
const relative = prerenderRelativePath(pathname);
|
|
96
|
+
if (relative === null) return;
|
|
97
|
+
const htmlPath = path.join(isrCacheDir, relative);
|
|
98
|
+
try {
|
|
99
|
+
mkdirSync(path.dirname(htmlPath), { recursive: true });
|
|
100
|
+
writeFileSync(htmlPath, html, 'utf8');
|
|
101
|
+
} catch {
|
|
102
|
+
// ISR cache write failures are non-fatal
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
supportedStrategies: ['ssr', 'ssg', 'csr', 'isr', 'ppr', 'api'],
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Netlify Functions v2 — Web-standard Request/Response
|
|
109
|
+
export default async function(request, context) {
|
|
110
|
+
return handler(request, context);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Netlify Functions v2 config
|
|
114
|
+
export const config = {
|
|
115
|
+
path: '/*',
|
|
116
|
+
preferStatic: true,
|
|
117
|
+
};
|
|
118
|
+
`
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Create a Netlify deployment adapter for Ruvyxa.
|
|
123
|
+
*
|
|
124
|
+
* Produces a Functions v2 handler (Web-standard Request/Response) and static
|
|
125
|
+
* assets for deployment via Netlify CLI. Supports SSR, API routes, ISR, PPR,
|
|
126
|
+
* SSG, and CSR.
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```ts
|
|
130
|
+
* import { config } from "ruvyxa/config"
|
|
131
|
+
* import { netlifyAdapter } from "@ruvyxa/adapter-netlify"
|
|
132
|
+
*
|
|
133
|
+
* export default config({
|
|
134
|
+
* adapter: netlifyAdapter()
|
|
135
|
+
* })
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
export function netlifyAdapter(options: NetlifyAdapterOptions = {}): Adapter {
|
|
139
|
+
if (options.functionsDir !== undefined && typeof options.functionsDir !== 'string') {
|
|
140
|
+
throw new Error(
|
|
141
|
+
`[RUV2001] netlifyAdapter: "functionsDir" must be a string, got ${typeof options.functionsDir}`,
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (options.functionsDir !== undefined && options.functionsDir.trim() === '') {
|
|
146
|
+
throw new Error(`[RUV2001] netlifyAdapter: "functionsDir" must not be an empty string`)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
name: 'netlify',
|
|
151
|
+
target: 'serverless',
|
|
152
|
+
supports: ['ssr', 'ssg', 'csr', 'isr', 'ppr', 'api'],
|
|
153
|
+
build(ctx: BuildContext): AdapterOutput {
|
|
154
|
+
validateBuildContext(ctx, 'netlifyAdapter')
|
|
155
|
+
const functionsDir = options.functionsDir ?? `${ctx.outDir}/netlify/functions`
|
|
156
|
+
// Config files are committed or read on other machines; never embed the
|
|
157
|
+
// absolute build-machine outDir in them.
|
|
158
|
+
const relativeOutDir = projectRelativeOutDir(ctx)
|
|
159
|
+
|
|
160
|
+
// Hashed client bundles are served under /__ruvyxa/client/ (see the
|
|
161
|
+
// chunk manifest src fields); the immutable header must match that URL.
|
|
162
|
+
// `public/` assets are not hashed and otherwise inherit Netlify's
|
|
163
|
+
// revalidate-every-request default, so they get the same lifetime the
|
|
164
|
+
// Rust server sends for the same files.
|
|
165
|
+
const headerRules: Array<{ for: string; cacheControl: string }> = [
|
|
166
|
+
{ for: `${CLIENT_BUNDLE_PREFIX}*`, cacheControl: IMMUTABLE_CACHE_CONTROL },
|
|
167
|
+
...publicAssetGlobs().map((glob) => ({
|
|
168
|
+
for: glob,
|
|
169
|
+
cacheControl: PUBLIC_ASSET_CACHE_CONTROL,
|
|
170
|
+
})),
|
|
171
|
+
]
|
|
172
|
+
|
|
173
|
+
const immutableHeaderToml = headerRules
|
|
174
|
+
.map(
|
|
175
|
+
(rule) =>
|
|
176
|
+
`[[headers]]\n for = "${rule.for}"\n [headers.values]\n` +
|
|
177
|
+
` Cache-Control = "${rule.cacheControl}"\n`,
|
|
178
|
+
)
|
|
179
|
+
.join('')
|
|
180
|
+
|
|
181
|
+
// Netlify.toml for the deploy directory
|
|
182
|
+
const netlifyToml =
|
|
183
|
+
'[build]\n publish = "publish"\n functions = "functions"\n\n' + immutableHeaderToml
|
|
184
|
+
|
|
185
|
+
// Project-root netlify.toml pointing at the build output (opt-in)
|
|
186
|
+
const projectNetlifyToml =
|
|
187
|
+
'[build]\n' +
|
|
188
|
+
' command = "npx --no-install ruvyxa build"\n' +
|
|
189
|
+
` publish = "${relativeOutDir}/deploy/netlify/publish"\n` +
|
|
190
|
+
` functions = "${relativeOutDir}/deploy/netlify/functions"\n\n` +
|
|
191
|
+
immutableHeaderToml
|
|
192
|
+
|
|
193
|
+
// Frameworks API deploy configuration (.netlify/v1/config.json)
|
|
194
|
+
const frameworksApiConfig = JSON.stringify(
|
|
195
|
+
{
|
|
196
|
+
headers: headerRules.map((rule) => ({
|
|
197
|
+
for: rule.for,
|
|
198
|
+
values: { 'Cache-Control': rule.cacheControl },
|
|
199
|
+
})),
|
|
200
|
+
},
|
|
201
|
+
null,
|
|
202
|
+
2,
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
name: 'netlify',
|
|
207
|
+
target: 'serverless',
|
|
208
|
+
platform: 'netlify',
|
|
209
|
+
entry: `${ctx.outDir}/server/app`,
|
|
210
|
+
assetsDir: `${ctx.outDir}/assets`,
|
|
211
|
+
...clientBuildOutput(ctx),
|
|
212
|
+
functionsDir,
|
|
213
|
+
configFiles: ['netlify.toml'],
|
|
214
|
+
artifacts: [
|
|
215
|
+
// Static assets (pre-rendered pages + client bundles). `optional`:
|
|
216
|
+
// an API-only or all-SSR app has no prerendered pages; the function
|
|
217
|
+
// still serves every route, so the missing prerender directory must
|
|
218
|
+
// not fail the build.
|
|
219
|
+
// `preferStatic: true` means a published page is served without ever
|
|
220
|
+
// reaching the function, so ISR/PPR pages must stay unpublished for
|
|
221
|
+
// revalidation to happen at all.
|
|
222
|
+
{
|
|
223
|
+
kind: 'static-site',
|
|
224
|
+
path: 'deploy/netlify/publish',
|
|
225
|
+
optional: true,
|
|
226
|
+
excludeStrategies: ['isr', 'ppr'],
|
|
227
|
+
},
|
|
228
|
+
// Serverless function bundle
|
|
229
|
+
{
|
|
230
|
+
kind: 'function',
|
|
231
|
+
path: 'deploy/netlify/functions/ruvyxa-handler',
|
|
232
|
+
handlerSource: netlifyHandlerSource(),
|
|
233
|
+
},
|
|
234
|
+
// Netlify config for the deploy directory
|
|
235
|
+
{
|
|
236
|
+
kind: 'file',
|
|
237
|
+
path: 'deploy/netlify/netlify.toml',
|
|
238
|
+
contents: netlifyToml,
|
|
239
|
+
},
|
|
240
|
+
// Frameworks API artifacts: Netlify discovers .netlify/v1/ at the
|
|
241
|
+
// project root automatically, so a fresh site needs only the publish
|
|
242
|
+
// directory set once in the dashboard — no committed config file.
|
|
243
|
+
...(options.frameworksApi === false
|
|
244
|
+
? []
|
|
245
|
+
: [
|
|
246
|
+
{
|
|
247
|
+
kind: 'function',
|
|
248
|
+
path: '.netlify/v1/functions/ruvyxa-handler',
|
|
249
|
+
scope: 'project',
|
|
250
|
+
handlerSource: netlifyHandlerSource(),
|
|
251
|
+
} satisfies AdapterArtifact,
|
|
252
|
+
{
|
|
253
|
+
kind: 'file',
|
|
254
|
+
path: '.netlify/v1/config.json',
|
|
255
|
+
scope: 'project',
|
|
256
|
+
contents: frameworksApiConfig + '\n',
|
|
257
|
+
} satisfies AdapterArtifact,
|
|
258
|
+
]),
|
|
259
|
+
...(options.projectConfig === true
|
|
260
|
+
? [
|
|
261
|
+
{
|
|
262
|
+
kind: 'file',
|
|
263
|
+
path: 'netlify.toml',
|
|
264
|
+
scope: 'project',
|
|
265
|
+
skipIfExists: true,
|
|
266
|
+
contents: projectNetlifyToml,
|
|
267
|
+
} satisfies AdapterArtifact,
|
|
268
|
+
]
|
|
269
|
+
: []),
|
|
270
|
+
],
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export default netlifyAdapter
|