@intelligems/sst 2.49.8-ig.2 → 2.49.8-ig.3
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 +60 -0
- package/cli/commands/dev.js +1 -1
- package/cli/local/server.js +9 -1
- package/cli/sst.js +0 -0
- package/package.json +1 -1
- package/runtime/dev-env-overrides.d.ts +20 -0
- package/runtime/dev-env-overrides.js +32 -0
- package/runtime/handlers/node.js +2 -1
- package/runtime/server.js +3 -1
- package/runtime/worker-config.d.ts +1 -1
- package/runtime/worker-config.js +3 -3
- package/runtime/worker-pool.d.ts +6 -3
- package/runtime/worker-pool.js +7 -4
- package/runtime/workers.d.ts +9 -5
- package/runtime/workers.js +52 -8
- package/stacks/build.js +19 -2
package/README.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# sst
|
|
2
|
+
|
|
3
|
+
[SST](https://sst.dev) makes it easy to build modern full-stack applications on AWS.
|
|
4
|
+
|
|
5
|
+
The `sst` package is made up of the following.
|
|
6
|
+
|
|
7
|
+
- [`sst`](https://docs.sst.dev/packages/sst) CLI
|
|
8
|
+
- [`sst/node`](https://docs.sst.dev/clients) Node.js client
|
|
9
|
+
- [`sst/constructs`](https://docs.sst.dev/constructs) CDK constructs
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
Install the `sst` package in your project root.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install sst --save-exact
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
Once installed, you can run the CLI commands using.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npx sst <command>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Import the Node.js client in your functions. For example, you can import the `Bucket` client.
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { Bucket } from "sst/node/bucket";
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
And import the constructs you need in your stacks code. For example, you can add an API.
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { Api } from "sst/constructs";
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
For more details, [head over to our docs](https://docs.sst.dev).
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
**Join our community** [Discord](https://sst.dev/discord) | [YouTube](https://www.youtube.com/c/sst-dev) | [Twitter](https://twitter.com/SST_dev)
|
|
44
|
+
|
|
45
|
+
## Dev mode tuning (Intelligems fork)
|
|
46
|
+
|
|
47
|
+
`sst dev` runs Node functions as worker threads inside the CLI process. In mono-build mode every worker loads
|
|
48
|
+
the whole `.mono-build` bundle, so the number of live workers is what decides memory use. These variables
|
|
49
|
+
control it (defaults in `src/runtime/worker-config.ts`):
|
|
50
|
+
|
|
51
|
+
| Variable | Default | Meaning |
|
|
52
|
+
| --- | --- | --- |
|
|
53
|
+
| `SST_WORKER_POOL_SIZE` | `4` | Max live workers per pool (one shared pool for all mono-build Node functions). Requests beyond that wait for a free worker instead of spawning a new one. |
|
|
54
|
+
| `SST_WORKER_CONCURRENCY` | `10` | Invocations one Node worker runs at the same time. Each invocation gets its own `process.env`. Set to `1` to fall back to one request per worker. |
|
|
55
|
+
| `SST_WORKER_IDLE_TIMEOUT` | `300000` | Milliseconds an idle worker is kept before it is terminated. |
|
|
56
|
+
| `SST_WARMUP_COUNT` | pool size | Warm pings sent at startup (capped at the pool size). `0` disables warmup. |
|
|
57
|
+
| `SST_WORKER_MAX_HEAP_MB` | `1024` | V8 old-space cap per worker. A worker that exceeds it exits and its in-flight requests fail; the dev session keeps running. `0` removes the cap. |
|
|
58
|
+
| `SST_SOURCE_MAPS` | unset | `true` runs workers with `--enable-source-maps` (costs memory per worker). |
|
|
59
|
+
| `SST_DEBUG_MEMORY` | unset | `true` samples process and worker memory to `.sst/memory.log` every 10s and adds peaks to the pool session summary. |
|
|
60
|
+
| `SST_DEBUG_POOL` | unset | `true` logs pool events (`CREATE`, `REUSE`, `POOL_WAIT`, `TERMINATE`, …) to `.sst/worker-pool.log`. |
|
package/cli/commands/dev.js
CHANGED
|
@@ -401,7 +401,7 @@ export const dev = (program) => program.command(["dev", "start"], "Work on your
|
|
|
401
401
|
import("./plugins/warmer.js").then((mod) => mod.useRDSWarmer()),
|
|
402
402
|
useFunctionLogger(),
|
|
403
403
|
]);
|
|
404
|
-
// Warm
|
|
404
|
+
// Warm SST_WARMUP_COUNT workers through the real request flow. 0
|
|
405
405
|
// turns this off; the count is capped at the pool size so warmup can
|
|
406
406
|
// never hold more isolates than steady state would.
|
|
407
407
|
const { WARMUP_COUNT } = await import("../../runtime/worker-config.js");
|
package/cli/local/server.js
CHANGED
|
@@ -103,11 +103,19 @@ export async function useLocalServer(opts) {
|
|
|
103
103
|
const wss = new WebSocketServer({ noServer: true });
|
|
104
104
|
const wss2 = new WebSocketServer({ noServer: true });
|
|
105
105
|
const sockets = new Set();
|
|
106
|
+
// Replayed to console clients on connect. Bounded: every entry keeps its
|
|
107
|
+
// full event payload and every log line, so an unbounded list grows for the
|
|
108
|
+
// life of the dev session.
|
|
109
|
+
const MAX_INVOCATIONS = 200;
|
|
106
110
|
let invocations = [];
|
|
107
111
|
function publish(invocation) {
|
|
108
112
|
const index = invocations.findLastIndex((i) => i.id === invocation.id);
|
|
109
|
-
if (index < 0)
|
|
113
|
+
if (index < 0) {
|
|
110
114
|
invocations.push(invocation);
|
|
115
|
+
if (invocations.length > MAX_INVOCATIONS) {
|
|
116
|
+
invocations.splice(0, invocations.length - MAX_INVOCATIONS);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
111
119
|
else
|
|
112
120
|
invocations[index] = invocation;
|
|
113
121
|
const json = JSON.stringify({
|
package/cli/sst.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
/**
|
|
3
|
+
* Keys of the `sst dev` process environment that override the environment a
|
|
4
|
+
* deployed stub forwards with each invocation.
|
|
5
|
+
*
|
|
6
|
+
* The stub Lambda snapshots ITS OWN `process.env` and sends it along
|
|
7
|
+
* (`support/bridge/live-lambda.ts`), and the local worker runs with exactly
|
|
8
|
+
* that map. So anything a developer wants to change per session — which
|
|
9
|
+
* database, which frontend — either had to be on the stub's function config
|
|
10
|
+
* (a CloudFormation update of every stub each time it changed) or could not
|
|
11
|
+
* reach the handler at all.
|
|
12
|
+
*
|
|
13
|
+
* `SST_DEV_ENV_OVERRIDES=KEY1,KEY2` names the keys the dev process supplies
|
|
14
|
+
* instead. Only listed keys that are set in the dev process are applied; the
|
|
15
|
+
* rest of the stub's environment (AWS credentials, function metadata) stays
|
|
16
|
+
* as forwarded.
|
|
17
|
+
*/
|
|
18
|
+
export declare const SST_DEV_ENV_OVERRIDES_KEY = "SST_DEV_ENV_OVERRIDES";
|
|
19
|
+
export declare function devEnvOverrides(env?: NodeJS.ProcessEnv): Record<string, string>;
|
|
20
|
+
export declare function applyDevEnvOverrides(forwarded: Record<string, string> | undefined, env?: NodeJS.ProcessEnv): Record<string, string>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keys of the `sst dev` process environment that override the environment a
|
|
3
|
+
* deployed stub forwards with each invocation.
|
|
4
|
+
*
|
|
5
|
+
* The stub Lambda snapshots ITS OWN `process.env` and sends it along
|
|
6
|
+
* (`support/bridge/live-lambda.ts`), and the local worker runs with exactly
|
|
7
|
+
* that map. So anything a developer wants to change per session — which
|
|
8
|
+
* database, which frontend — either had to be on the stub's function config
|
|
9
|
+
* (a CloudFormation update of every stub each time it changed) or could not
|
|
10
|
+
* reach the handler at all.
|
|
11
|
+
*
|
|
12
|
+
* `SST_DEV_ENV_OVERRIDES=KEY1,KEY2` names the keys the dev process supplies
|
|
13
|
+
* instead. Only listed keys that are set in the dev process are applied; the
|
|
14
|
+
* rest of the stub's environment (AWS credentials, function metadata) stays
|
|
15
|
+
* as forwarded.
|
|
16
|
+
*/
|
|
17
|
+
export const SST_DEV_ENV_OVERRIDES_KEY = "SST_DEV_ENV_OVERRIDES";
|
|
18
|
+
export function devEnvOverrides(env = process.env) {
|
|
19
|
+
const list = env[SST_DEV_ENV_OVERRIDES_KEY];
|
|
20
|
+
if (!list)
|
|
21
|
+
return {};
|
|
22
|
+
const overrides = {};
|
|
23
|
+
for (const key of list.split(",").map((k) => k.trim()).filter(Boolean)) {
|
|
24
|
+
const value = env[key];
|
|
25
|
+
if (value !== undefined)
|
|
26
|
+
overrides[key] = value;
|
|
27
|
+
}
|
|
28
|
+
return overrides;
|
|
29
|
+
}
|
|
30
|
+
export function applyDevEnvOverrides(forwarded, env = process.env) {
|
|
31
|
+
return { ...(forwarded ?? {}), ...devEnvOverrides(env) };
|
|
32
|
+
}
|
package/runtime/handlers/node.js
CHANGED
|
@@ -14,6 +14,7 @@ import { findAbove } from "../../util/fs.js";
|
|
|
14
14
|
import { useMonoBuildConfig } from "../mono-build-config.js";
|
|
15
15
|
import { SOURCE_MAPS, WORKER_MAX_HEAP_MB } from "../worker-config.js";
|
|
16
16
|
import { forgetWorkerMemory, recordWorkerMemory } from "../memory-logging.js";
|
|
17
|
+
import { applyDevEnvOverrides } from "../dev-env-overrides.js";
|
|
17
18
|
export const useNodeHandler = () => {
|
|
18
19
|
const rebuildCache = {};
|
|
19
20
|
process.on("exit", () => {
|
|
@@ -39,7 +40,7 @@ export const useNodeHandler = () => {
|
|
|
39
40
|
const workers = await useRuntimeWorkers();
|
|
40
41
|
const worker = new Worker(url.fileURLToPath(new URL("../../support/nodejs-runtime/index.mjs", import.meta.url)), {
|
|
41
42
|
env: {
|
|
42
|
-
...input.environment,
|
|
43
|
+
...applyDevEnvOverrides(input.environment),
|
|
43
44
|
IS_LOCAL: "true",
|
|
44
45
|
},
|
|
45
46
|
// Source maps cost memory in every isolate; opt in with SST_SOURCE_MAPS=true
|
package/runtime/server.js
CHANGED
|
@@ -9,6 +9,7 @@ import { lazy } from "../util/lazy.js";
|
|
|
9
9
|
import { getRequestPath } from "./request-utils.js";
|
|
10
10
|
import { logServer } from "./debug-bridge-logging.js";
|
|
11
11
|
import { logEventTrace } from "./event-trace-logging.js";
|
|
12
|
+
import { applyDevEnvOverrides } from "./dev-env-overrides.js";
|
|
12
13
|
export const useRuntimeServerConfig = lazy(async () => {
|
|
13
14
|
const port = await getPort({
|
|
14
15
|
port: 12557,
|
|
@@ -128,7 +129,8 @@ export const useRuntimeServer = lazy(async () => {
|
|
|
128
129
|
// This prevents env leakage when workers are reused across different functions
|
|
129
130
|
res.json({
|
|
130
131
|
event: payload.event,
|
|
131
|
-
|
|
132
|
+
// The dev process's own axes win over what the stub forwarded.
|
|
133
|
+
env: applyDevEnvOverrides(payload.env),
|
|
132
134
|
});
|
|
133
135
|
});
|
|
134
136
|
app.post(`/:workerID/${cfg.API_VERSION}/runtime/invocation/:awsRequestId/response`, express.json({
|
|
@@ -8,7 +8,7 @@ export declare const POOL_SIZE: number;
|
|
|
8
8
|
export declare const IDLE_TIMEOUT: number;
|
|
9
9
|
/** Invocations one Node worker may run at the same time. */
|
|
10
10
|
export declare const WORKER_CONCURRENCY: number;
|
|
11
|
-
/**
|
|
11
|
+
/** Workers to warm at dev start. 0 disables warmup. Capped at POOL_SIZE. */
|
|
12
12
|
export declare const WARMUP_COUNT: number;
|
|
13
13
|
/** V8 old-space cap for each Node worker thread, in MB. 0 leaves it unbounded. */
|
|
14
14
|
export declare const WORKER_MAX_HEAP_MB: number;
|
package/runtime/worker-config.js
CHANGED
|
@@ -14,9 +14,9 @@ export const POOL_SIZE = int("SST_WORKER_POOL_SIZE", 4);
|
|
|
14
14
|
/** How long an idle worker is kept before it is terminated. */
|
|
15
15
|
export const IDLE_TIMEOUT = int("SST_WORKER_IDLE_TIMEOUT", 5 * 60 * 1000);
|
|
16
16
|
/** Invocations one Node worker may run at the same time. */
|
|
17
|
-
export const WORKER_CONCURRENCY = Math.max(1, int("SST_WORKER_CONCURRENCY",
|
|
18
|
-
/**
|
|
19
|
-
export const WARMUP_COUNT = Math.min(POOL_SIZE, Math.max(0, int("SST_WARMUP_COUNT",
|
|
17
|
+
export const WORKER_CONCURRENCY = Math.max(1, int("SST_WORKER_CONCURRENCY", 10));
|
|
18
|
+
/** Workers to warm at dev start. 0 disables warmup. Capped at POOL_SIZE. */
|
|
19
|
+
export const WARMUP_COUNT = Math.min(POOL_SIZE, Math.max(0, int("SST_WARMUP_COUNT", 1)));
|
|
20
20
|
/** V8 old-space cap for each Node worker thread, in MB. 0 leaves it unbounded. */
|
|
21
21
|
export const WORKER_MAX_HEAP_MB = int("SST_WORKER_MAX_HEAP_MB", 1024);
|
|
22
22
|
/** Run Node workers with --enable-source-maps (costs memory per worker). */
|
package/runtime/worker-pool.d.ts
CHANGED
|
@@ -52,9 +52,12 @@ export declare class WorkerPool {
|
|
|
52
52
|
liveCount(poolKey: string): number;
|
|
53
53
|
canCreate(poolKey: string): boolean;
|
|
54
54
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
55
|
+
* The busiest live worker that still has spare capacity, or undefined.
|
|
56
|
+
* Packing invocations into as few workers as possible is what keeps memory
|
|
57
|
+
* down: every worker that serves traffic grows to hold the handlers it has
|
|
58
|
+
* loaded, so an idle spare is a few hundred MB doing nothing. Workers whose
|
|
59
|
+
* bundle is older than `currentMtime` are retired on the way: idle ones
|
|
60
|
+
* now, busy ones once they drain.
|
|
58
61
|
*/
|
|
59
62
|
pick(poolKey: string, currentMtime?: number): PoolWorker | undefined;
|
|
60
63
|
/** Hand an invocation to a worker. */
|
package/runtime/worker-pool.js
CHANGED
|
@@ -44,9 +44,12 @@ export class WorkerPool {
|
|
|
44
44
|
return this.liveCount(poolKey) < this.opts.maxWorkers;
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
47
|
+
* The busiest live worker that still has spare capacity, or undefined.
|
|
48
|
+
* Packing invocations into as few workers as possible is what keeps memory
|
|
49
|
+
* down: every worker that serves traffic grows to hold the handlers it has
|
|
50
|
+
* loaded, so an idle spare is a few hundred MB doing nothing. Workers whose
|
|
51
|
+
* bundle is older than `currentMtime` are retired on the way: idle ones
|
|
52
|
+
* now, busy ones once they drain.
|
|
50
53
|
*/
|
|
51
54
|
pick(poolKey, currentMtime) {
|
|
52
55
|
const pool = this.pools.get(poolKey);
|
|
@@ -66,7 +69,7 @@ export class WorkerPool {
|
|
|
66
69
|
continue;
|
|
67
70
|
if (worker.inFlight >= worker.maxConcurrency)
|
|
68
71
|
continue;
|
|
69
|
-
if (!best || worker.inFlight
|
|
72
|
+
if (!best || worker.inFlight > best.inFlight)
|
|
70
73
|
best = worker;
|
|
71
74
|
}
|
|
72
75
|
return best;
|
package/runtime/workers.d.ts
CHANGED
|
@@ -71,16 +71,20 @@ export declare const useRuntimeWorkers: () => Promise<{
|
|
|
71
71
|
cb: (payload: any) => void;
|
|
72
72
|
};
|
|
73
73
|
/**
|
|
74
|
-
* Warm
|
|
74
|
+
* Warm `workersToWarm` pooled workers by invoking a Node function with
|
|
75
|
+
* concurrent warm pings.
|
|
75
76
|
*
|
|
76
77
|
* Each ping is marked as a fan-out *child* (`__WARMER_INVOCATION__ > 1`)
|
|
77
78
|
* so the app's lambda-warmer preloads its handlers and returns instead of
|
|
78
79
|
* fanning out to `concurrency` more Lambdas, which is what turned the old
|
|
79
|
-
* 30 pings into ~900 worker creations.
|
|
80
|
-
*
|
|
81
|
-
*
|
|
80
|
+
* 30 pings into ~900 worker creations. Pings go through the normal pool
|
|
81
|
+
* path, so warmup can never hold more isolates than steady state.
|
|
82
|
+
*
|
|
83
|
+
* In mono-build mode this waits for the bundle to settle first: dev start
|
|
84
|
+
* kicks off the bundle's first watch build, and a worker loaded before it
|
|
85
|
+
* lands is retired as stale seconds later, which wasted every warm worker.
|
|
82
86
|
*/
|
|
83
|
-
triggerWarmup(
|
|
87
|
+
triggerWarmup(workersToWarm: number): Promise<{
|
|
84
88
|
warmed: number;
|
|
85
89
|
elapsed?: undefined;
|
|
86
90
|
} | {
|
package/runtime/workers.js
CHANGED
|
@@ -137,6 +137,44 @@ function isPoolableRuntime(runtime, env) {
|
|
|
137
137
|
return (POOLABLE_RUNTIMES.has(runtime) ||
|
|
138
138
|
[...POOLABLE_RUNTIMES].some((r) => runtime.startsWith(r)));
|
|
139
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Resolve once `.mono-build/.last-rebuild` exists and has not changed for
|
|
142
|
+
* `quietMs`. No-op when mono-build is off. Gives up after `maxWaitMs`.
|
|
143
|
+
*
|
|
144
|
+
* Dev start writes the timestamp twice about 7s apart (the app's initial
|
|
145
|
+
* rebuild, then the watcher's first pass), so the quiet window has to be
|
|
146
|
+
* longer than that gap or the warm worker is retired as stale right away.
|
|
147
|
+
*/
|
|
148
|
+
async function waitForStableBundle(quietMs = 10_000, maxWaitMs = 120_000) {
|
|
149
|
+
const config = useMonoBuildConfig();
|
|
150
|
+
const timestampFile = path.join(config.dir, ".last-rebuild");
|
|
151
|
+
const started = Date.now();
|
|
152
|
+
let last;
|
|
153
|
+
let lastChange = Date.now();
|
|
154
|
+
while (Date.now() - started < maxWaitMs) {
|
|
155
|
+
if (!config.enabled) {
|
|
156
|
+
// Not (yet) a mono-build project; nothing to wait for unless it appears
|
|
157
|
+
if (Date.now() - started > quietMs)
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
let current;
|
|
162
|
+
try {
|
|
163
|
+
current = fs.readFileSync(timestampFile, "utf-8");
|
|
164
|
+
}
|
|
165
|
+
catch { }
|
|
166
|
+
if (current !== last) {
|
|
167
|
+
last = current;
|
|
168
|
+
lastChange = Date.now();
|
|
169
|
+
}
|
|
170
|
+
else if (current !== undefined && Date.now() - lastChange >= quietMs) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
175
|
+
}
|
|
176
|
+
logPool("WARMUP_BUNDLE_TIMEOUT", { waitedMs: Date.now() - started });
|
|
177
|
+
}
|
|
140
178
|
/** Only the Node runtime shim knows how to run invocations side by side. */
|
|
141
179
|
function concurrencyFor(runtime) {
|
|
142
180
|
return runtime.startsWith("nodejs") ? WORKER_CONCURRENCY : 1;
|
|
@@ -689,19 +727,25 @@ export const useRuntimeWorkers = lazy(async () => {
|
|
|
689
727
|
stats,
|
|
690
728
|
subscribe: bus.forward("worker.started", "worker.stopped", "worker.exited", "worker.stdout", "worker.reused"),
|
|
691
729
|
/**
|
|
692
|
-
* Warm
|
|
730
|
+
* Warm `workersToWarm` pooled workers by invoking a Node function with
|
|
731
|
+
* concurrent warm pings.
|
|
693
732
|
*
|
|
694
733
|
* Each ping is marked as a fan-out *child* (`__WARMER_INVOCATION__ > 1`)
|
|
695
734
|
* so the app's lambda-warmer preloads its handlers and returns instead of
|
|
696
735
|
* fanning out to `concurrency` more Lambdas, which is what turned the old
|
|
697
|
-
* 30 pings into ~900 worker creations.
|
|
698
|
-
*
|
|
699
|
-
*
|
|
736
|
+
* 30 pings into ~900 worker creations. Pings go through the normal pool
|
|
737
|
+
* path, so warmup can never hold more isolates than steady state.
|
|
738
|
+
*
|
|
739
|
+
* In mono-build mode this waits for the bundle to settle first: dev start
|
|
740
|
+
* kicks off the bundle's first watch build, and a worker loaded before it
|
|
741
|
+
* lands is retired as stale seconds later, which wasted every warm worker.
|
|
700
742
|
*/
|
|
701
|
-
async triggerWarmup(
|
|
702
|
-
|
|
703
|
-
if (
|
|
743
|
+
async triggerWarmup(workersToWarm) {
|
|
744
|
+
workersToWarm = Math.min(workersToWarm, POOL_SIZE);
|
|
745
|
+
if (workersToWarm <= 0)
|
|
704
746
|
return { warmed: 0 };
|
|
747
|
+
const count = workersToWarm * WORKER_CONCURRENCY;
|
|
748
|
+
await waitForStableBundle();
|
|
705
749
|
const functions = useFunctions();
|
|
706
750
|
const allFunctions = functions.all;
|
|
707
751
|
// Find a nodejs function to use as the warmup target
|
|
@@ -721,7 +765,7 @@ export const useRuntimeWorkers = lazy(async () => {
|
|
|
721
765
|
return { warmed: 0 };
|
|
722
766
|
}
|
|
723
767
|
const { functionName } = targetFunction;
|
|
724
|
-
logPool("WARMUP_START", { count, functionName });
|
|
768
|
+
logPool("WARMUP_START", { count, workersToWarm, functionName });
|
|
725
769
|
bus.publish("warmup.start", { count });
|
|
726
770
|
const startTime = Date.now();
|
|
727
771
|
let success = 0;
|
package/stacks/build.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import esbuild from "esbuild";
|
|
2
2
|
import fs from "fs/promises";
|
|
3
3
|
import path from "path";
|
|
4
|
+
import crypto from "crypto";
|
|
4
5
|
import { dynamicImport } from "../util/module.js";
|
|
5
6
|
import { findAbove } from "../util/fs.js";
|
|
6
7
|
import { VisibleError } from "../error.js";
|
|
@@ -9,6 +10,14 @@ const _ = await import("@babel/generator");
|
|
|
9
10
|
const generate = _.default?.default ?? _.default;
|
|
10
11
|
// @ts-expect-error
|
|
11
12
|
import ts from "@babel/plugin-syntax-typescript";
|
|
13
|
+
/**
|
|
14
|
+
* The last config module we imported, keyed by the hash of its bundled
|
|
15
|
+
* source. An ES module can never be unloaded, so every `import()` of a fresh
|
|
16
|
+
* `.sst.config.<time>.mjs` stays in memory for the life of `sst dev`. Dev
|
|
17
|
+
* rebuilds the config on every change to a file inside its bundle (all the
|
|
18
|
+
* stack code), so reuse the module when the bundle did not actually change.
|
|
19
|
+
*/
|
|
20
|
+
let cached;
|
|
12
21
|
export async function load(input, shallow) {
|
|
13
22
|
const parsed = path.parse(input);
|
|
14
23
|
const root = await findAbove(input, "package.json");
|
|
@@ -81,6 +90,7 @@ export async function load(input, shallow) {
|
|
|
81
90
|
],
|
|
82
91
|
absWorkingDir: root,
|
|
83
92
|
outfile,
|
|
93
|
+
write: false,
|
|
84
94
|
banner: {
|
|
85
95
|
js: [
|
|
86
96
|
`import { createRequire as topLevelCreateRequire } from 'module';`,
|
|
@@ -97,12 +107,19 @@ export async function load(input, shallow) {
|
|
|
97
107
|
// },
|
|
98
108
|
entryPoints: [input],
|
|
99
109
|
});
|
|
100
|
-
|
|
110
|
+
const output = result.outputFiles?.find((f) => f.path === outfile) ?? result.outputFiles?.[0];
|
|
111
|
+
if (!output)
|
|
112
|
+
throw new VisibleError("Config build produced no output");
|
|
113
|
+
const hash = crypto.createHash("sha256").update(output.contents).digest("hex");
|
|
114
|
+
if (cached && cached.hash === hash && cached.shallow === Boolean(shallow)) {
|
|
115
|
+
return [cached.metafile, cached.mod];
|
|
116
|
+
}
|
|
117
|
+
await fs.writeFile(outfile, output.contents);
|
|
101
118
|
const mod = await dynamicImport(outfile);
|
|
102
|
-
// Logger.debug("imported", input);
|
|
103
119
|
await fs.rm(outfile, {
|
|
104
120
|
force: true,
|
|
105
121
|
});
|
|
122
|
+
cached = { hash, metafile: result.metafile, mod: mod.default, shallow: Boolean(shallow) };
|
|
106
123
|
if (!mod.default?.config)
|
|
107
124
|
throw new VisibleError(`The config file is improperly formatted.`, `Example:`, `export default {`, ` config() {`, ` return {`, ` name: "my-app",`, ` region: "us-east-1"`, ` }`, ` },`, ` stacks(app) {`, ` }`, `}`);
|
|
108
125
|
return [result.metafile, mod.default];
|