@sanity/runtime-cli 4.3.5-bundle.0 → 4.4.0
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/README.md +17 -17
- package/dist/actions/blueprints/assets.d.ts +0 -1
- package/dist/actions/blueprints/assets.js +4 -21
- package/dist/actions/functions/test.d.ts +2 -2
- package/dist/actions/functions/test.js +2 -2
- package/dist/commands/functions/test.js +6 -3
- package/dist/server/app.js +12 -80
- package/dist/server/static/api.js +3 -24
- package/dist/server/static/components/app.css +117 -0
- package/dist/server/static/components/console-panel.d.ts +1 -0
- package/dist/server/static/components/console-panel.js +53 -0
- package/dist/server/static/components/filters.js +4 -3
- package/dist/server/static/components/function-list.js +4 -4
- package/dist/server/static/components/response-panel.js +25 -28
- package/dist/server/static/index.html +4 -2
- package/dist/server/static/vendor/vendor.bundle.d.ts +2 -2
- package/dist/utils/build-payload.d.ts +1 -1
- package/dist/utils/build-payload.js +3 -3
- package/dist/utils/child-process-wrapper.js +6 -8
- package/dist/utils/invoke-local.d.ts +2 -2
- package/dist/utils/invoke-local.js +7 -48
- package/dist/utils/is-json.d.ts +1 -0
- package/dist/utils/is-json.js +12 -0
- package/dist/utils/types.d.ts +1 -3
- package/oclif.manifest.json +1 -1
- package/package.json +1 -4
- package/dist/utils/bundle/bundle-function.d.ts +0 -8
- package/dist/utils/bundle/bundle-function.js +0 -125
- package/dist/utils/bundle/cleanup-source-maps.d.ts +0 -10
- package/dist/utils/bundle/cleanup-source-maps.js +0 -53
- package/dist/utils/bundle/find-up.d.ts +0 -16
- package/dist/utils/bundle/find-up.js +0 -39
- package/dist/utils/bundle/verify-handler.d.ts +0 -2
- package/dist/utils/bundle/verify-handler.js +0 -13
- package/dist/utils/functions/find-entry-point.d.ts +0 -11
- package/dist/utils/functions/find-entry-point.js +0 -75
- package/dist/utils/functions/should-bundle.d.ts +0 -2
- package/dist/utils/functions/should-bundle.js +0 -23
- package/dist/utils/is-record.d.ts +0 -1
- package/dist/utils/is-record.js +0 -3
- package/dist/utils/parse-json-object.d.ts +0 -1
- package/dist/utils/parse-json-object.js +0 -10
|
@@ -10,9 +10,9 @@ import {
|
|
|
10
10
|
} from '../vendor/vendor.bundle.js'
|
|
11
11
|
import {ApiBaseElement} from './api-base.js'
|
|
12
12
|
|
|
13
|
-
const template = `<div class="border-left border-top border-top-none-l h-100 gutter-gradient" style='height: 100%; max-height: 100%; overflow: hidden; display:grid;grid-template-rows:
|
|
13
|
+
const template = `<div class="border-left border-top border-top-none-l h-100 gutter-gradient" style='height: 100%; max-height: 100%; overflow: hidden; display:grid;grid-template-rows: auto 1fr; grid-template-columns: 1fr;' >
|
|
14
14
|
<!-- Response Section -->
|
|
15
|
-
<div style='overflow-y:scroll;'>
|
|
15
|
+
<div style='overflow-y:scroll; min-height: 0;'>
|
|
16
16
|
<div style='padding-bottom: var(--space-6)'>
|
|
17
17
|
<h3 class="config-label" style='display: none; margin-top: 0;'>Response</h3>
|
|
18
18
|
<header class='flex space-between'>
|
|
@@ -28,17 +28,13 @@ const template = `<div class="border-left border-top border-top-none-l h-100 gut
|
|
|
28
28
|
<div id="response" name="response" class="cm-s-dracula"></div>
|
|
29
29
|
</div>
|
|
30
30
|
</div>
|
|
31
|
-
|
|
32
|
-
<!-- Console Section -->
|
|
33
|
-
<div style='position: relative; border-top: 1px solid var(--card-border-color); background: var(--base-background-color); padding: var(--space-0) var(--space-3) var(--space-6) var(--space-4);overflow-y:scroll;'>
|
|
34
|
-
<h3 class="config-label" style="padding-top: var(--space-3); padding-bottom: var(--space-3); z-index: 32; background: var(--base-background-color); position: sticky; top: 0; left: 0; right: 0; margin-top:0;margin-bottom:0;">Console</h3>
|
|
35
|
-
<pre style="padding: 0; margin: 0; white-space: pre-wrap; word-wrap: break-word;"></pre>
|
|
36
|
-
</div>
|
|
37
31
|
</div>
|
|
38
32
|
`
|
|
39
33
|
class ResponsePanel extends ApiBaseElement {
|
|
40
34
|
updateResponse = ({result}) => {
|
|
41
|
-
|
|
35
|
+
if (!result) return
|
|
36
|
+
|
|
37
|
+
const {error, json, time} = result
|
|
42
38
|
if (!error) {
|
|
43
39
|
const transaction = this.api.store.response.state.update({
|
|
44
40
|
changes: {
|
|
@@ -50,23 +46,16 @@ class ResponsePanel extends ApiBaseElement {
|
|
|
50
46
|
this.api.store.response.dispatch(transaction)
|
|
51
47
|
|
|
52
48
|
this.size.innerText = json ? prettyBytes(JSON.stringify(json).length) : ''
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const bundleTime = prettyMilliseconds(timings.bundle)
|
|
56
|
-
const executeTime = prettyMilliseconds(timings.execute)
|
|
57
|
-
this.time.innerText = `${executeTime} (+${bundleTime} bundle time)`
|
|
58
|
-
this.time.dateTime = `PT${executeTime / 1000}S`
|
|
59
|
-
} else if (timings && 'execute' in timings) {
|
|
60
|
-
this.time.innerText = prettyMilliseconds(timings.execute)
|
|
61
|
-
this.time.dateTime = `PT${timings.execute / 1000}S`
|
|
62
|
-
} else {
|
|
63
|
-
this.time.innerText = prettyMilliseconds(time)
|
|
64
|
-
this.time.dateTime = `PT${time / 1000}S`
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
this.consoleTab.innerText = logs
|
|
49
|
+
this.time.innerText = prettyMilliseconds(time)
|
|
50
|
+
this.time.dateTime = `PT${time / 1000}S`
|
|
68
51
|
} else {
|
|
69
|
-
this.
|
|
52
|
+
const transaction = this.api.store.response.state.update({
|
|
53
|
+
changes: {from: 0, to: this.api.store.response.state.doc.length, insert: ''},
|
|
54
|
+
})
|
|
55
|
+
this.api.store.response.dispatch(transaction)
|
|
56
|
+
this.size.innerText = ''
|
|
57
|
+
this.time.innerText = ''
|
|
58
|
+
this.time.removeAttribute('datetime')
|
|
70
59
|
}
|
|
71
60
|
}
|
|
72
61
|
|
|
@@ -75,8 +64,14 @@ class ResponsePanel extends ApiBaseElement {
|
|
|
75
64
|
this.response = this.querySelector('#response')
|
|
76
65
|
this.size = this.querySelector('#size')
|
|
77
66
|
this.time = this.querySelector('#time')
|
|
78
|
-
|
|
79
|
-
|
|
67
|
+
if (this.api) {
|
|
68
|
+
this.api.subscribe(this.updateResponse, ['result'])
|
|
69
|
+
if (this.api.store.result) {
|
|
70
|
+
this.updateResponse({result: this.api.store.result})
|
|
71
|
+
}
|
|
72
|
+
} else {
|
|
73
|
+
console.error('API context not available for response-panel on connect.')
|
|
74
|
+
}
|
|
80
75
|
|
|
81
76
|
this.api.store.response = new EditorView({
|
|
82
77
|
doc: '\n\n\n\n',
|
|
@@ -86,7 +81,9 @@ class ResponsePanel extends ApiBaseElement {
|
|
|
86
81
|
}
|
|
87
82
|
|
|
88
83
|
disconnectedCallback() {
|
|
89
|
-
this.api
|
|
84
|
+
if (this.api) {
|
|
85
|
+
this.api.unsubscribe(this.updateResponse)
|
|
86
|
+
}
|
|
90
87
|
}
|
|
91
88
|
}
|
|
92
89
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html>
|
|
3
3
|
<head>
|
|
4
|
-
<
|
|
4
|
+
<title>Sanity Functions</title>
|
|
5
5
|
<link href="https://unpkg.com/m-@3.2.0/dist/m-.woff2" rel="preload" as="font" crossorigin>
|
|
6
6
|
<link href="https://unpkg.com/m-@3.2.0/dist/m-.css" rel="stylesheet">
|
|
7
7
|
<link href="./components/app.css" rel="stylesheet">
|
|
@@ -21,9 +21,10 @@
|
|
|
21
21
|
<h4 class="config-label mar-t-0" style="padding-left: var(--space-3); margin-bottom:var(--space-3);">Functions</h4>
|
|
22
22
|
<function-list></function-list>
|
|
23
23
|
</nav>
|
|
24
|
-
<main style='
|
|
24
|
+
<main id="main-content" style='max-height: 100%; overflow: hidden;'>
|
|
25
25
|
<payload-panel></payload-panel>
|
|
26
26
|
<response-panel></response-panel>
|
|
27
|
+
<console-panel></console-panel>
|
|
27
28
|
</main>
|
|
28
29
|
|
|
29
30
|
|
|
@@ -34,5 +35,6 @@
|
|
|
34
35
|
<script src="./components/network-spinner.js" type="module"></script>
|
|
35
36
|
<script src="./components/payload-panel.js" type="module"></script>
|
|
36
37
|
<script src="./components/response-panel.js" type="module"></script>
|
|
38
|
+
<script src="./components/console-panel.js" type="module"></script>
|
|
37
39
|
</body>
|
|
38
40
|
</html>
|
|
@@ -1018,9 +1018,9 @@ declare class ViewState {
|
|
|
1018
1018
|
viewport: Viewport | undefined;
|
|
1019
1019
|
lineGaps: any[];
|
|
1020
1020
|
lineGapDeco: any;
|
|
1021
|
-
updateForViewport():
|
|
1021
|
+
updateForViewport(): 0 | 2;
|
|
1022
1022
|
viewports: (Viewport | undefined)[] | undefined;
|
|
1023
|
-
updateScaler():
|
|
1023
|
+
updateScaler(): 0 | 2;
|
|
1024
1024
|
updateViewportLines(): void;
|
|
1025
1025
|
viewportLines: any[] | undefined;
|
|
1026
1026
|
update(update: any, scrollTarget?: null): void;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { InvokePayloadOptions } from './types.js';
|
|
2
|
-
export default function buildPayload(options: InvokePayloadOptions):
|
|
2
|
+
export default function buildPayload(options: InvokePayloadOptions): object | null;
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { cwd } from 'node:process';
|
|
4
|
-
import
|
|
4
|
+
import isJson from './is-json.js';
|
|
5
5
|
export default function buildPayload(options) {
|
|
6
6
|
const { data, file } = options;
|
|
7
7
|
let payload = {};
|
|
8
8
|
if (data) {
|
|
9
|
-
payload =
|
|
9
|
+
payload = isJson(data);
|
|
10
10
|
}
|
|
11
11
|
else if (file) {
|
|
12
|
-
payload =
|
|
12
|
+
payload = isJson(readFileSync(join(cwd(), file), 'utf8'));
|
|
13
13
|
}
|
|
14
14
|
return payload;
|
|
15
15
|
}
|
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
import {existsSync, statSync} from 'node:fs'
|
|
2
|
-
import {
|
|
2
|
+
import {join} from 'node:path'
|
|
3
3
|
import process from 'node:process'
|
|
4
4
|
|
|
5
5
|
export function getFunctionSource(src) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
if (statSync(pathToCheck).isDirectory()) {
|
|
9
|
-
const indexPath = join(pathToCheck, 'index.js')
|
|
6
|
+
if (statSync(src).isDirectory()) {
|
|
7
|
+
const indexPath = join(src, 'index.js')
|
|
10
8
|
if (!existsSync(indexPath)) {
|
|
11
|
-
throw Error(`Function directory ${
|
|
9
|
+
throw Error(`Function directory ${src} has no index.js`)
|
|
12
10
|
}
|
|
13
11
|
return indexPath
|
|
14
12
|
}
|
|
15
|
-
return
|
|
13
|
+
return src
|
|
16
14
|
}
|
|
17
15
|
|
|
18
16
|
// Start when payload data arrives from parent process
|
|
@@ -30,7 +28,7 @@ process.on('message', async (data) => {
|
|
|
30
28
|
let json = null
|
|
31
29
|
|
|
32
30
|
// Import the function code
|
|
33
|
-
const {handler} = await import(getFunctionSource(srcPath))
|
|
31
|
+
const {handler} = await import(getFunctionSource(join(process.cwd(), srcPath)))
|
|
34
32
|
|
|
35
33
|
// backup stdout
|
|
36
34
|
const originalStdoutWrite = process.stdout.write.bind(process.stdout)
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { InvocationResponse, InvokeContextOptions
|
|
1
|
+
import type { InvocationResponse, InvokeContextOptions } from './types.js';
|
|
2
2
|
export declare function sanitizeLogs(logs: string): string;
|
|
3
|
-
export default function invoke(
|
|
3
|
+
export default function invoke(srcPath: string, data: null | object, context: InvokeContextOptions, timeout?: number): Promise<InvocationResponse>;
|
|
@@ -1,80 +1,42 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
|
-
import { performance } from 'node:perf_hooks';
|
|
3
2
|
import { cwd } from 'node:process';
|
|
4
3
|
import { setTimeout } from 'node:timers';
|
|
5
4
|
import config from '../config.js';
|
|
6
|
-
import { bundleFunction } from './bundle/bundle-function.js';
|
|
7
|
-
import { findFunctionEntryPoint } from './functions/find-entry-point.js';
|
|
8
|
-
import { shouldBundleFunction } from './functions/should-bundle.js';
|
|
9
5
|
function getChildProcessWrapperPath() {
|
|
10
6
|
return new URL('./child-process-wrapper.js', import.meta.url).pathname;
|
|
11
7
|
}
|
|
12
8
|
export function sanitizeLogs(logs) {
|
|
13
9
|
return logs.replace(/([a-zA-Z0-9]{10})[a-zA-Z0-9]{65,}/g, '$1**********');
|
|
14
10
|
}
|
|
15
|
-
export default async function invoke(
|
|
16
|
-
if (!resource.src) {
|
|
17
|
-
throw new Error(`Function resource "${resource.name}" is missing the 'src' property.`);
|
|
18
|
-
}
|
|
19
|
-
let cleanupBundle = async () => { };
|
|
20
|
-
let functionPath = '';
|
|
21
|
-
let bundleTimings = undefined;
|
|
22
|
-
if (await shouldBundleFunction(resource)) {
|
|
23
|
-
const bundleResult = await bundleFunction(resource);
|
|
24
|
-
functionPath = await findFunctionEntryPoint(bundleResult.outputDir);
|
|
25
|
-
bundleTimings = bundleResult.timings;
|
|
26
|
-
cleanupBundle = bundleResult.cleanup;
|
|
27
|
-
}
|
|
28
|
-
else {
|
|
29
|
-
functionPath = await findFunctionEntryPoint(resource.src, resource.displayName ?? resource.name);
|
|
30
|
-
}
|
|
11
|
+
export default async function invoke(srcPath, data, context, timeout = 5) {
|
|
31
12
|
return new Promise((resolve, reject) => {
|
|
32
13
|
let child;
|
|
33
14
|
let timer;
|
|
34
|
-
let executionStart;
|
|
35
15
|
function start() {
|
|
36
|
-
executionStart = performance.now();
|
|
37
16
|
child = spawn('node', [getChildProcessWrapperPath()], {
|
|
38
17
|
cwd: cwd(),
|
|
39
18
|
stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
|
|
40
19
|
});
|
|
20
|
+
// Note: start a timeout so child process doesn't run forever
|
|
41
21
|
child.on('message', (data) => {
|
|
42
|
-
const executionTimeMs = performance.now() - executionStart;
|
|
43
22
|
const { json, logs } = JSON.parse(data.toString());
|
|
44
23
|
shutdown();
|
|
45
|
-
resolve({
|
|
46
|
-
json,
|
|
47
|
-
logs: sanitizeLogs(logs),
|
|
48
|
-
error: undefined,
|
|
49
|
-
timings: {
|
|
50
|
-
...bundleTimings,
|
|
51
|
-
execute: executionTimeMs,
|
|
52
|
-
},
|
|
53
|
-
});
|
|
24
|
+
resolve({ json, logs: sanitizeLogs(logs), error: '' });
|
|
54
25
|
});
|
|
55
26
|
child.on('error', (error) => {
|
|
56
|
-
shutdown();
|
|
57
27
|
reject(new Error(`encountered error ${error.message}`));
|
|
58
28
|
});
|
|
59
29
|
child.on('exit', (code) => {
|
|
60
|
-
const executionTimeMs = performance.now() - executionStart;
|
|
61
30
|
shutdown();
|
|
62
31
|
if (code !== 0) {
|
|
63
32
|
reject(new Error(`exited with code ${code}`));
|
|
64
33
|
}
|
|
65
34
|
else {
|
|
66
|
-
resolve({
|
|
67
|
-
json: {},
|
|
68
|
-
logs: '',
|
|
69
|
-
error: undefined,
|
|
70
|
-
timings: {
|
|
71
|
-
...bundleTimings,
|
|
72
|
-
execute: executionTimeMs,
|
|
73
|
-
},
|
|
74
|
-
});
|
|
35
|
+
resolve({ json: {}, logs: '', error: '' });
|
|
75
36
|
}
|
|
76
37
|
});
|
|
77
38
|
timer = setTimeout(() => {
|
|
39
|
+
// timedOut = true
|
|
78
40
|
shutdown();
|
|
79
41
|
reject(new Error(`Timed out after hitting its ${timeout}s timeout!`));
|
|
80
42
|
}, timeout * 1000);
|
|
@@ -89,14 +51,11 @@ export default async function invoke(resource, data, context, timeout = 5) {
|
|
|
89
51
|
},
|
|
90
52
|
},
|
|
91
53
|
};
|
|
92
|
-
child.send(JSON.stringify({ srcPath
|
|
54
|
+
child.send(JSON.stringify({ srcPath, payload }, null, 2));
|
|
93
55
|
}
|
|
94
56
|
function shutdown() {
|
|
95
57
|
clearTimeout(timer);
|
|
96
|
-
|
|
97
|
-
child.kill();
|
|
98
|
-
}
|
|
99
|
-
cleanupBundle().catch((err) => console.warn('Bundle cleanup failed:', err));
|
|
58
|
+
child.kill();
|
|
100
59
|
}
|
|
101
60
|
start();
|
|
102
61
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default function isJson(jsonString: string): null | object;
|
package/dist/utils/types.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export interface LocalBlueprint {
|
|
|
12
12
|
/** @link https://github.com/sanity-io/blueprints-rfc/blob/main/readme.md#outputs */
|
|
13
13
|
outputs?: Array<Record<string, unknown>>;
|
|
14
14
|
/** @link https://github.com/sanity-io/blueprints-rfc/blob/main/readme.md#resources */
|
|
15
|
-
resources?: Array<LocalResource
|
|
15
|
+
resources?: Array<LocalResource>;
|
|
16
16
|
/** @link https://github.com/sanity-io/blueprints-rfc/blob/main/readme.md#parameters */
|
|
17
17
|
parameters?: Array<{
|
|
18
18
|
name: string;
|
|
@@ -51,7 +51,6 @@ export declare function isLocalFunctionResource(r: LocalResource): r is LocalFun
|
|
|
51
51
|
/** @internal */
|
|
52
52
|
export interface LocalFunctionResource extends LocalResource {
|
|
53
53
|
src?: string;
|
|
54
|
-
bundle?: boolean;
|
|
55
54
|
memory?: number;
|
|
56
55
|
timeout?: number;
|
|
57
56
|
env?: Record<string, string>;
|
|
@@ -116,7 +115,6 @@ export interface InvocationResponse {
|
|
|
116
115
|
error: undefined | unknown;
|
|
117
116
|
json: object | undefined;
|
|
118
117
|
logs: string | undefined;
|
|
119
|
-
timings?: Record<string, number>;
|
|
120
118
|
}
|
|
121
119
|
/** @internal */
|
|
122
120
|
export interface BlueprintLog {
|
package/oclif.manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/runtime-cli",
|
|
3
3
|
"description": "Sanity's Runtime CLI for Blueprints and Functions",
|
|
4
|
-
"version": "4.
|
|
4
|
+
"version": "4.4.0",
|
|
5
5
|
"author": "Sanity Runtime Team",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "MIT",
|
|
@@ -47,7 +47,6 @@
|
|
|
47
47
|
"postbuild": "oclif manifest && oclif readme",
|
|
48
48
|
"build:ts": "shx rm -rf dist *.tsbuildinfo && tsc -b",
|
|
49
49
|
"build:static": "npm run copy:wrapper && npm run copy:server",
|
|
50
|
-
"clean": "shx rm -rf dist oclif.manifest.json",
|
|
51
50
|
"copy:server": "shx cp -r ./src/server/static ./dist/server",
|
|
52
51
|
"copy:wrapper": "shx cp ./src/utils/child-process-wrapper.js ./dist/utils/child-process-wrapper.js",
|
|
53
52
|
"lint": "biome ci",
|
|
@@ -69,8 +68,6 @@
|
|
|
69
68
|
"eventsource": "^3.0.6",
|
|
70
69
|
"inquirer": "^12.5.2",
|
|
71
70
|
"mime-types": "^3.0.1",
|
|
72
|
-
"vite": "^6.3.3",
|
|
73
|
-
"vite-tsconfig-paths": "^5.1.4",
|
|
74
71
|
"xdg-basedir": "^5.1.0",
|
|
75
72
|
"yocto-spinner": "^0.2.1"
|
|
76
73
|
},
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { LocalFunctionResource } from '../types.js';
|
|
2
|
-
export declare function bundleFunction(resource: LocalFunctionResource): Promise<{
|
|
3
|
-
type: string;
|
|
4
|
-
outputDir: string;
|
|
5
|
-
warnings: string[];
|
|
6
|
-
cleanup: () => Promise<void>;
|
|
7
|
-
timings: Record<string, number>;
|
|
8
|
-
}>;
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import { mkdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { performance } from 'node:perf_hooks';
|
|
4
|
-
import { cwd } from 'node:process';
|
|
5
|
-
import { build as viteBuild } from 'vite';
|
|
6
|
-
import tsConfigPaths from 'vite-tsconfig-paths';
|
|
7
|
-
import { findFunctionEntryPoint } from '../functions/find-entry-point.js';
|
|
8
|
-
import { cleanupSourceMaps } from './cleanup-source-maps.js';
|
|
9
|
-
import { findDirUp } from './find-up.js';
|
|
10
|
-
import { verifyHandler } from './verify-handler.js';
|
|
11
|
-
export async function bundleFunction(resource) {
|
|
12
|
-
if (!resource.src)
|
|
13
|
-
throw new Error('Resource src is required');
|
|
14
|
-
if (!resource.name)
|
|
15
|
-
throw new Error('Resource name is required');
|
|
16
|
-
const timings = {};
|
|
17
|
-
const bundleStart = performance.now();
|
|
18
|
-
const sourcePath = path.resolve(cwd(), resource.src);
|
|
19
|
-
const stats = await stat(sourcePath);
|
|
20
|
-
const fnDisplayName = resource.displayName ?? resource.name;
|
|
21
|
-
const findEntryStart = performance.now();
|
|
22
|
-
const entry = await findFunctionEntryPoint(sourcePath, fnDisplayName);
|
|
23
|
-
timings['bundle:findEntry'] = performance.now() - findEntryStart;
|
|
24
|
-
const entryDir = stats.isFile() ? path.dirname(sourcePath) : sourcePath;
|
|
25
|
-
const outputPathStart = performance.now();
|
|
26
|
-
const outputDir = await getBundleOutputPath(entryDir, resource.name);
|
|
27
|
-
const outputFile = path.join(outputDir, getOutputFilename(entry));
|
|
28
|
-
const fnRootDir = (await findDirUp('node_modules', entryDir)) || entryDir;
|
|
29
|
-
timings['bundle:setupOutput'] = performance.now() - outputPathStart;
|
|
30
|
-
async function cleanupTmpDir() {
|
|
31
|
-
// Feel a certain way about leaving things uncleaned, but helps with debugging for now
|
|
32
|
-
// await rm(outputDir, {recursive: true, force: true}).catch(logCleanupFailure)
|
|
33
|
-
}
|
|
34
|
-
try {
|
|
35
|
-
const viteStart = performance.now();
|
|
36
|
-
const result = await viteBuild({
|
|
37
|
-
root: fnRootDir,
|
|
38
|
-
logLevel: 'silent',
|
|
39
|
-
build: {
|
|
40
|
-
target: 'node20',
|
|
41
|
-
outDir: outputDir,
|
|
42
|
-
emptyOutDir: false,
|
|
43
|
-
minify: false,
|
|
44
|
-
sourcemap: true,
|
|
45
|
-
ssr: true,
|
|
46
|
-
rollupOptions: {
|
|
47
|
-
input: entry,
|
|
48
|
-
output: {
|
|
49
|
-
format: 'esm',
|
|
50
|
-
entryFileNames: getOutputFilename(entry),
|
|
51
|
-
},
|
|
52
|
-
},
|
|
53
|
-
},
|
|
54
|
-
ssr: {
|
|
55
|
-
noExternal: true,
|
|
56
|
-
resolve: {
|
|
57
|
-
conditions: ['sanity-function', 'vite'],
|
|
58
|
-
},
|
|
59
|
-
},
|
|
60
|
-
plugins: [tsConfigPaths()],
|
|
61
|
-
});
|
|
62
|
-
timings['bundle:build'] = performance.now() - viteStart;
|
|
63
|
-
const verifyStart = performance.now();
|
|
64
|
-
await verifyHandler(result);
|
|
65
|
-
timings['bundle:verify'] = performance.now() - verifyStart;
|
|
66
|
-
const pkgStart = performance.now();
|
|
67
|
-
await writeBundledPackageJson(entryDir, outputFile);
|
|
68
|
-
timings['bundle:writePackage'] = performance.now() - pkgStart;
|
|
69
|
-
const cleanupStart = performance.now();
|
|
70
|
-
await cleanupSourceMaps(sourcePath, outputDir);
|
|
71
|
-
timings['bundle:cleanupMaps'] = performance.now() - cleanupStart;
|
|
72
|
-
timings.bundle = performance.now() - bundleStart;
|
|
73
|
-
return {
|
|
74
|
-
type: 'success',
|
|
75
|
-
outputDir,
|
|
76
|
-
warnings: [],
|
|
77
|
-
cleanup: cleanupTmpDir,
|
|
78
|
-
timings,
|
|
79
|
-
};
|
|
80
|
-
}
|
|
81
|
-
catch (err) {
|
|
82
|
-
await cleanupTmpDir();
|
|
83
|
-
throw new Error(`Bundling of function failed: ${err instanceof Error ? err.message : err}`, {
|
|
84
|
-
cause: err,
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
async function writeBundledPackageJson(inputDir, outputFilePath) {
|
|
89
|
-
const baseName = path.basename(outputFilePath);
|
|
90
|
-
let original;
|
|
91
|
-
try {
|
|
92
|
-
const pkgJsonPath = path.join(inputDir, 'package.json');
|
|
93
|
-
original = JSON.parse(await readFile(pkgJsonPath, 'utf-8'));
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
original = undefined;
|
|
97
|
-
}
|
|
98
|
-
const bundled = {
|
|
99
|
-
// One could argue that we should strip this down significantly.
|
|
100
|
-
// Theoretically though, a function may reach into it to get dependency versions
|
|
101
|
-
// and whatnot, so maybe it makes more sense to keep it as close to the original
|
|
102
|
-
// as possible?
|
|
103
|
-
...original,
|
|
104
|
-
main: baseName, // This should never be the input file, always the built output name
|
|
105
|
-
type: 'module', // We explicitly create ESM output
|
|
106
|
-
};
|
|
107
|
-
const pkgJsonOutputPath = path.join(path.dirname(outputFilePath), 'package.json');
|
|
108
|
-
await writeFile(pkgJsonOutputPath, JSON.stringify(bundled, null, 2));
|
|
109
|
-
}
|
|
110
|
-
async function getBundleOutputPath(entryDir, fnName) {
|
|
111
|
-
const tmpPath = path.resolve(entryDir, '.build', `function-${fnName}`);
|
|
112
|
-
await rm(tmpPath, { recursive: true, force: true }).catch(logCleanupFailure);
|
|
113
|
-
await mkdir(tmpPath, { recursive: true });
|
|
114
|
-
return tmpPath;
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* Minor convenience/niceness to keep the same input filename, but change the extension
|
|
118
|
-
*/
|
|
119
|
-
function getOutputFilename(entryFileName) {
|
|
120
|
-
const baseName = path.basename(entryFileName, path.extname(entryFileName));
|
|
121
|
-
return baseName ? `${baseName}.js` : 'index.js';
|
|
122
|
-
}
|
|
123
|
-
function logCleanupFailure(err) {
|
|
124
|
-
console.warn(`[warn] Failed to clean up temporary files: ${err instanceof Error ? err.message : err}`);
|
|
125
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* "Clean up" source maps by removing absolute paths and making paths relative to the
|
|
3
|
-
* _input_ (eg source) rather than the _output_ (eg bundle) directory. Note that this
|
|
4
|
-
* process is not critical since the source content is inlined, but it helps with
|
|
5
|
-
* debugging to have (approximate) paths to the original source files.
|
|
6
|
-
*
|
|
7
|
-
* @param inputDir - The directory where the source files are located
|
|
8
|
-
* @param outputDir - The directory where the bundled files are located
|
|
9
|
-
*/
|
|
10
|
-
export declare function cleanupSourceMaps(inputDir: string, outputDir: string): Promise<void>;
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { promises as fs } from 'node:fs';
|
|
2
|
-
import { isAbsolute, join, relative } from 'node:path';
|
|
3
|
-
/**
|
|
4
|
-
* "Clean up" source maps by removing absolute paths and making paths relative to the
|
|
5
|
-
* _input_ (eg source) rather than the _output_ (eg bundle) directory. Note that this
|
|
6
|
-
* process is not critical since the source content is inlined, but it helps with
|
|
7
|
-
* debugging to have (approximate) paths to the original source files.
|
|
8
|
-
*
|
|
9
|
-
* @param inputDir - The directory where the source files are located
|
|
10
|
-
* @param outputDir - The directory where the bundled files are located
|
|
11
|
-
*/
|
|
12
|
-
export async function cleanupSourceMaps(inputDir, outputDir) {
|
|
13
|
-
const entries = await fs.readdir(outputDir, { withFileTypes: true });
|
|
14
|
-
const sourceMaps = entries.filter((entry) => entry.isFile() && entry.name.endsWith('.map'));
|
|
15
|
-
for (const entry of sourceMaps) {
|
|
16
|
-
const filePath = join(outputDir, entry.name);
|
|
17
|
-
let raw;
|
|
18
|
-
try {
|
|
19
|
-
raw = await fs.readFile(filePath, 'utf8');
|
|
20
|
-
}
|
|
21
|
-
catch {
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
let map;
|
|
25
|
-
try {
|
|
26
|
-
const json = JSON.parse(raw);
|
|
27
|
-
map = isRelevantSourceMap(json) ? json : undefined;
|
|
28
|
-
}
|
|
29
|
-
catch {
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
if (!map) {
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
map.sources = map.sources.map((source) => {
|
|
36
|
-
const fullPath = isAbsolute(source) ? source : join(outputDir, source);
|
|
37
|
-
return relative(inputDir, fullPath);
|
|
38
|
-
});
|
|
39
|
-
try {
|
|
40
|
-
await fs.writeFile(filePath, JSON.stringify(map));
|
|
41
|
-
}
|
|
42
|
-
catch {
|
|
43
|
-
// ignore write errors
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
function isRelevantSourceMap(map) {
|
|
48
|
-
return (typeof map === 'object' &&
|
|
49
|
-
map !== null &&
|
|
50
|
-
'sources' in map &&
|
|
51
|
-
Array.isArray(map.sources) &&
|
|
52
|
-
map.sources.every((source) => typeof source === 'string'));
|
|
53
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Walks up the directory tree from a starting point to find a given file/directory.
|
|
3
|
-
*
|
|
4
|
-
* @param fileName - The name of the file/directory to find.
|
|
5
|
-
* @param startDir - The directory to start searching from (default is the current working directory).
|
|
6
|
-
* @returns The path to the file if found, otherwise undefined.
|
|
7
|
-
*/
|
|
8
|
-
export declare function findUp(fileName: string, startDir?: string): Promise<string | undefined>;
|
|
9
|
-
/**
|
|
10
|
-
* Finds the directory containing a specific file/directory by walking up the directory tree.
|
|
11
|
-
*
|
|
12
|
-
* @param fileName - The name of the file/directory to find.
|
|
13
|
-
* @param startDir - The directory to start searching from (default is the current working directory).
|
|
14
|
-
* @returns The directory containing the file if found, otherwise undefined.
|
|
15
|
-
*/
|
|
16
|
-
export declare function findDirUp(fileName: string, startDir?: string): Promise<string | undefined>;
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { access } from 'node:fs/promises';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
/**
|
|
4
|
-
* Walks up the directory tree from a starting point to find a given file/directory.
|
|
5
|
-
*
|
|
6
|
-
* @param fileName - The name of the file/directory to find.
|
|
7
|
-
* @param startDir - The directory to start searching from (default is the current working directory).
|
|
8
|
-
* @returns The path to the file if found, otherwise undefined.
|
|
9
|
-
*/
|
|
10
|
-
export async function findUp(fileName, startDir = process.cwd()) {
|
|
11
|
-
let dir = path.resolve(startDir);
|
|
12
|
-
while (true) {
|
|
13
|
-
const candidate = path.join(dir, fileName);
|
|
14
|
-
try {
|
|
15
|
-
await access(candidate);
|
|
16
|
-
return candidate;
|
|
17
|
-
}
|
|
18
|
-
catch {
|
|
19
|
-
const parent = path.dirname(dir);
|
|
20
|
-
if (parent === dir)
|
|
21
|
-
break; // Reached root
|
|
22
|
-
dir = parent;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
return undefined;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Finds the directory containing a specific file/directory by walking up the directory tree.
|
|
29
|
-
*
|
|
30
|
-
* @param fileName - The name of the file/directory to find.
|
|
31
|
-
* @param startDir - The directory to start searching from (default is the current working directory).
|
|
32
|
-
* @returns The directory containing the file if found, otherwise undefined.
|
|
33
|
-
*/
|
|
34
|
-
export async function findDirUp(fileName, startDir = process.cwd()) {
|
|
35
|
-
const filePath = await findUp(fileName, startDir);
|
|
36
|
-
if (!filePath)
|
|
37
|
-
return undefined;
|
|
38
|
-
return path.dirname(filePath);
|
|
39
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export async function verifyHandler(result) {
|
|
2
|
-
if ('close' in result) {
|
|
3
|
-
throw new Error('Incorrect build output, got watcher');
|
|
4
|
-
}
|
|
5
|
-
const outputs = (Array.isArray(result) ? result : [result]).flatMap(({ output }) => output);
|
|
6
|
-
const bundledIndex = outputs.find((output) => output.type === 'chunk' && output.isEntry && output.name === 'index');
|
|
7
|
-
if (!bundledIndex || bundledIndex.type !== 'chunk') {
|
|
8
|
-
throw new Error('Unexpected build output, no bundled index found');
|
|
9
|
-
}
|
|
10
|
-
if (!bundledIndex.exports.includes('handler')) {
|
|
11
|
-
throw new Error('Unexpected build output, no `handler` export found');
|
|
12
|
-
}
|
|
13
|
-
}
|