@hops-ops/distributed 0.0.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 +454 -0
- package/dist/auth-headers.d.ts +8 -0
- package/dist/auth-headers.js +35 -0
- package/dist/diagnostics.d.ts +2 -0
- package/dist/diagnostics.js +1 -0
- package/dist/document.d.ts +8 -0
- package/dist/document.js +6 -0
- package/dist/identity.d.ts +23 -0
- package/dist/identity.js +73 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +6 -0
- package/dist/internal/cache-engine/create.d.ts +5 -0
- package/dist/internal/cache-engine/create.js +7 -0
- package/dist/internal/cache-engine/engine.d.ts +32 -0
- package/dist/internal/cache-engine/engine.js +1287 -0
- package/dist/internal/cache-engine/errors.d.ts +5 -0
- package/dist/internal/cache-engine/errors.js +10 -0
- package/dist/internal/cache-engine/helpers.d.ts +55 -0
- package/dist/internal/cache-engine/helpers.js +545 -0
- package/dist/internal/cache-engine/index.d.ts +3 -0
- package/dist/internal/cache-engine/index.js +2 -0
- package/dist/internal/cache-engine/types.d.ts +300 -0
- package/dist/internal/cache-engine/types.js +1 -0
- package/dist/internal/cache-engine.d.ts +3 -0
- package/dist/internal/cache-engine.js +2 -0
- package/dist/lib/assert-name.d.ts +2 -0
- package/dist/lib/assert-name.js +6 -0
- package/dist/lib/compare-code-units.d.ts +2 -0
- package/dist/lib/compare-code-units.js +4 -0
- package/dist/lib/deep-equal.d.ts +2 -0
- package/dist/lib/deep-equal.js +24 -0
- package/dist/lib/freeze-record.d.ts +2 -0
- package/dist/lib/freeze-record.js +13 -0
- package/dist/lib/index.d.ts +6 -0
- package/dist/lib/index.js +6 -0
- package/dist/lib/is-plain-record.d.ts +2 -0
- package/dist/lib/is-plain-record.js +8 -0
- package/dist/lib/report.d.ts +7 -0
- package/dist/lib/report.js +26 -0
- package/dist/protocol.d.ts +179 -0
- package/dist/protocol.js +558 -0
- package/dist/react/context.d.ts +20 -0
- package/dist/react/context.js +24 -0
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.js +3 -0
- package/dist/react/query.d.ts +20 -0
- package/dist/react/query.js +99 -0
- package/dist/replica/command-id.d.ts +2 -0
- package/dist/replica/command-id.js +19 -0
- package/dist/replica/command-runtime/constants.d.ts +5 -0
- package/dist/replica/command-runtime/constants.js +5 -0
- package/dist/replica/command-runtime/create.d.ts +2 -0
- package/dist/replica/command-runtime/create.js +644 -0
- package/dist/replica/command-runtime/errors.d.ts +16 -0
- package/dist/replica/command-runtime/errors.js +41 -0
- package/dist/replica/command-runtime/index.d.ts +5 -0
- package/dist/replica/command-runtime/index.js +4 -0
- package/dist/replica/command-runtime/lib/binding.d.ts +4 -0
- package/dist/replica/command-runtime/lib/binding.js +64 -0
- package/dist/replica/command-runtime/lib/effects.d.ts +11 -0
- package/dist/replica/command-runtime/lib/effects.js +89 -0
- package/dist/replica/command-runtime/lib/index.d.ts +9 -0
- package/dist/replica/command-runtime/lib/index.js +9 -0
- package/dist/replica/command-runtime/lib/inventory.d.ts +10 -0
- package/dist/replica/command-runtime/lib/inventory.js +141 -0
- package/dist/replica/command-runtime/lib/output.d.ts +13 -0
- package/dist/replica/command-runtime/lib/output.js +190 -0
- package/dist/replica/command-runtime/lib/projection.d.ts +20 -0
- package/dist/replica/command-runtime/lib/projection.js +255 -0
- package/dist/replica/command-runtime/lib/status.d.ts +7 -0
- package/dist/replica/command-runtime/lib/status.js +127 -0
- package/dist/replica/command-runtime/lib/transport.d.ts +16 -0
- package/dist/replica/command-runtime/lib/transport.js +112 -0
- package/dist/replica/command-runtime/lib/util.d.ts +16 -0
- package/dist/replica/command-runtime/lib/util.js +127 -0
- package/dist/replica/command-runtime/lifecycle.d.ts +11 -0
- package/dist/replica/command-runtime/lifecycle.js +13 -0
- package/dist/replica/command-runtime/symbols.d.ts +30 -0
- package/dist/replica/command-runtime/symbols.js +30 -0
- package/dist/replica/command-runtime/types.d.ts +222 -0
- package/dist/replica/command-runtime/types.js +1 -0
- package/dist/replica/command-runtime.d.ts +3 -0
- package/dist/replica/command-runtime.js +2 -0
- package/dist/replica/commands/clone.d.ts +12 -0
- package/dist/replica/commands/clone.js +187 -0
- package/dist/replica/commands/constants.d.ts +6 -0
- package/dist/replica/commands/constants.js +6 -0
- package/dist/replica/commands/errors.d.ts +7 -0
- package/dist/replica/commands/errors.js +22 -0
- package/dist/replica/commands/implementation.d.ts +8 -0
- package/dist/replica/commands/implementation.js +8 -0
- package/dist/replica/commands/index.d.ts +5 -0
- package/dist/replica/commands/index.js +4 -0
- package/dist/replica/commands/prepare.d.ts +27 -0
- package/dist/replica/commands/prepare.js +77 -0
- package/dist/replica/commands/presets.d.ts +12 -0
- package/dist/replica/commands/presets.js +84 -0
- package/dist/replica/commands/receipt.d.ts +3 -0
- package/dist/replica/commands/receipt.js +36 -0
- package/dist/replica/commands/resolve.d.ts +11 -0
- package/dist/replica/commands/resolve.js +165 -0
- package/dist/replica/commands/types.d.ts +278 -0
- package/dist/replica/commands/types.js +2 -0
- package/dist/replica/commands/util.d.ts +21 -0
- package/dist/replica/commands/util.js +96 -0
- package/dist/replica/commands/validate.d.ts +18 -0
- package/dist/replica/commands/validate.js +477 -0
- package/dist/replica/commands.d.ts +3 -0
- package/dist/replica/commands.js +2 -0
- package/dist/replica/diagnostics/event-log.d.ts +4 -0
- package/dist/replica/diagnostics/event-log.js +463 -0
- package/dist/replica/diagnostics/implementation.d.ts +4 -0
- package/dist/replica/diagnostics/implementation.js +4 -0
- package/dist/replica/diagnostics/index.d.ts +2 -0
- package/dist/replica/diagnostics/index.js +1 -0
- package/dist/replica/diagnostics/inspect.d.ts +6 -0
- package/dist/replica/diagnostics/inspect.js +170 -0
- package/dist/replica/diagnostics/types.d.ts +295 -0
- package/dist/replica/diagnostics/types.js +2 -0
- package/dist/replica/diagnostics.d.ts +3 -0
- package/dist/replica/diagnostics.js +2 -0
- package/dist/replica/distributed-replica/clocks.d.ts +23 -0
- package/dist/replica/distributed-replica/clocks.js +156 -0
- package/dist/replica/distributed-replica/constants.d.ts +12 -0
- package/dist/replica/distributed-replica/constants.js +10 -0
- package/dist/replica/distributed-replica/helpers.d.ts +48 -0
- package/dist/replica/distributed-replica/helpers.js +445 -0
- package/dist/replica/distributed-replica/hydration.d.ts +19 -0
- package/dist/replica/distributed-replica/hydration.js +380 -0
- package/dist/replica/distributed-replica/impl-diagnostics.d.ts +22 -0
- package/dist/replica/distributed-replica/impl-diagnostics.js +189 -0
- package/dist/replica/distributed-replica/impl-fetch-live.d.ts +39 -0
- package/dist/replica/distributed-replica/impl-fetch-live.js +395 -0
- package/dist/replica/distributed-replica/impl-hydration-orchestrate.d.ts +56 -0
- package/dist/replica/distributed-replica/impl-hydration-orchestrate.js +299 -0
- package/dist/replica/distributed-replica/impl-optimistic.d.ts +31 -0
- package/dist/replica/distributed-replica/impl-optimistic.js +172 -0
- package/dist/replica/distributed-replica/impl-protocol.d.ts +53 -0
- package/dist/replica/distributed-replica/impl-protocol.js +108 -0
- package/dist/replica/distributed-replica/impl.d.ts +44 -0
- package/dist/replica/distributed-replica/impl.js +1776 -0
- package/dist/replica/distributed-replica/index.d.ts +2 -0
- package/dist/replica/distributed-replica/index.js +4 -0
- package/dist/replica/distributed-replica/optimistic.d.ts +30 -0
- package/dist/replica/distributed-replica/optimistic.js +221 -0
- package/dist/replica/distributed-replica/types.d.ts +147 -0
- package/dist/replica/distributed-replica/types.js +1 -0
- package/dist/replica/distributed-replica/watch.d.ts +19 -0
- package/dist/replica/distributed-replica/watch.js +95 -0
- package/dist/replica/distributed-replica.d.ts +2 -0
- package/dist/replica/distributed-replica.js +2 -0
- package/dist/replica/graphql-transport.d.ts +24 -0
- package/dist/replica/graphql-transport.js +122 -0
- package/dist/replica/identity/clone.d.ts +6 -0
- package/dist/replica/identity/clone.js +54 -0
- package/dist/replica/identity/codec.d.ts +48 -0
- package/dist/replica/identity/codec.js +789 -0
- package/dist/replica/identity/constants.d.ts +3 -0
- package/dist/replica/identity/constants.js +18 -0
- package/dist/replica/identity/implementation.d.ts +5 -0
- package/dist/replica/identity/implementation.js +5 -0
- package/dist/replica/identity/index.d.ts +3 -0
- package/dist/replica/identity/index.js +3 -0
- package/dist/replica/identity/keys.d.ts +29 -0
- package/dist/replica/identity/keys.js +173 -0
- package/dist/replica/identity.d.ts +2 -0
- package/dist/replica/identity.js +2 -0
- package/dist/replica/index-maintenance/engine.d.ts +84 -0
- package/dist/replica/index-maintenance/engine.js +677 -0
- package/dist/replica/index-maintenance/implementation.d.ts +4 -0
- package/dist/replica/index-maintenance/implementation.js +4 -0
- package/dist/replica/index-maintenance/index.d.ts +2 -0
- package/dist/replica/index-maintenance/index.js +1 -0
- package/dist/replica/index-maintenance/registry.d.ts +2 -0
- package/dist/replica/index-maintenance/registry.js +49 -0
- package/dist/replica/index-maintenance/types.d.ts +58 -0
- package/dist/replica/index-maintenance/types.js +1 -0
- package/dist/replica/index-maintenance.d.ts +3 -0
- package/dist/replica/index-maintenance.js +2 -0
- package/dist/replica/index.d.ts +17 -0
- package/dist/replica/index.js +9 -0
- package/dist/replica/materialize.d.ts +10 -0
- package/dist/replica/materialize.js +163 -0
- package/dist/replica/normalize.d.ts +27 -0
- package/dist/replica/normalize.js +298 -0
- package/dist/replica/operation-binding.d.ts +14 -0
- package/dist/replica/operation-binding.js +76 -0
- package/dist/replica/persistence/idb.d.ts +20 -0
- package/dist/replica/persistence/idb.js +193 -0
- package/dist/replica/persistence/implementation.d.ts +4 -0
- package/dist/replica/persistence/implementation.js +4 -0
- package/dist/replica/persistence/index.d.ts +2 -0
- package/dist/replica/persistence/index.js +1 -0
- package/dist/replica/persistence/state.d.ts +104 -0
- package/dist/replica/persistence/state.js +734 -0
- package/dist/replica/persistence/types.d.ts +54 -0
- package/dist/replica/persistence/types.js +1 -0
- package/dist/replica/persistence.d.ts +3 -0
- package/dist/replica/persistence.js +2 -0
- package/dist/replica/query-plan/constants.d.ts +13 -0
- package/dist/replica/query-plan/constants.js +7 -0
- package/dist/replica/query-plan/filter.d.ts +39 -0
- package/dist/replica/query-plan/filter.js +544 -0
- package/dist/replica/query-plan/index.d.ts +4 -0
- package/dist/replica/query-plan/index.js +3 -0
- package/dist/replica/query-plan/order.d.ts +11 -0
- package/dist/replica/query-plan/order.js +158 -0
- package/dist/replica/query-plan/pagination.d.ts +6 -0
- package/dist/replica/query-plan/pagination.js +90 -0
- package/dist/replica/query-plan/resolve.d.ts +14 -0
- package/dist/replica/query-plan/resolve.js +163 -0
- package/dist/replica/query-plan/types.d.ts +89 -0
- package/dist/replica/query-plan/types.js +1 -0
- package/dist/replica/query-plan/util.d.ts +13 -0
- package/dist/replica/query-plan/util.js +62 -0
- package/dist/replica/query-plan.d.ts +3 -0
- package/dist/replica/query-plan.js +2 -0
- package/dist/replica/revalidation.d.ts +9 -0
- package/dist/replica/revalidation.js +86 -0
- package/dist/replica/selection.d.ts +20 -0
- package/dist/replica/selection.js +109 -0
- package/dist/replica/types.d.ts +657 -0
- package/dist/replica/types.js +1 -0
- package/dist/request.d.ts +17 -0
- package/dist/request.js +123 -0
- package/dist/sveltekit/auth.d.ts +18 -0
- package/dist/sveltekit/auth.js +9 -0
- package/dist/sveltekit/context.d.ts +28 -0
- package/dist/sveltekit/context.js +58 -0
- package/dist/sveltekit/index.d.ts +4 -0
- package/dist/sveltekit/index.js +4 -0
- package/dist/sveltekit/replica.d.ts +145 -0
- package/dist/sveltekit/replica.js +491 -0
- package/dist/sveltekit/server-replica.d.ts +52 -0
- package/dist/sveltekit/server-replica.js +176 -0
- package/dist/sveltekit/vite.d.ts +103 -0
- package/dist/sveltekit/vite.js +878 -0
- package/dist/types.d.ts +33 -0
- package/dist/types.js +1 -0
- package/dist/websocket.d.ts +41 -0
- package/dist/websocket.js +187 -0
- package/package.json +95 -0
|
@@ -0,0 +1,878 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { existsSync, lstatSync, realpathSync } from 'node:fs';
|
|
3
|
+
import { cp, lstat, mkdir, mkdtemp, open, readFile, realpath, rename, rm, unlink, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
5
|
+
import { isMainThread } from 'node:worker_threads';
|
|
6
|
+
const GENERATED_SVELTEKIT_MODULE = 'sveltekit.ts';
|
|
7
|
+
const MAX_COMMAND_OUTPUT_BYTES = 16 * 1024 * 1024;
|
|
8
|
+
const MODULE_NAME = /^\$distributed(?:\/[A-Za-z0-9][A-Za-z0-9._-]*)*$/;
|
|
9
|
+
const COMPILER_LOCK = join('.svelte-kit', 'distributed', 'compiler.lock');
|
|
10
|
+
const COMPILER_COORDINATORS = Symbol.for('@hops-ops/distributed/sveltekit/compiler-coordinators/v1');
|
|
11
|
+
/** Vite proxy config for GraphQL HTTP and WebSocket traffic. */
|
|
12
|
+
export function distributedGraphqlProxy(options) {
|
|
13
|
+
const target = (typeof options === 'string' ? options : options.target)
|
|
14
|
+
.trim()
|
|
15
|
+
.replace(/\/$/, '');
|
|
16
|
+
const path = typeof options === 'string' ? '/graphql' : (options.path ?? '/graphql');
|
|
17
|
+
if (!/^https?:\/\//.test(target)) {
|
|
18
|
+
throw new Error('distributedGraphqlProxy target must be an absolute http(s) URL');
|
|
19
|
+
}
|
|
20
|
+
if (!path.startsWith('/')) {
|
|
21
|
+
throw new Error('distributedGraphqlProxy path must start with /');
|
|
22
|
+
}
|
|
23
|
+
return { [path]: { target, changeOrigin: true, ws: true } };
|
|
24
|
+
}
|
|
25
|
+
/** Generate every configured surface through the same transaction used by Vite. */
|
|
26
|
+
export async function generateDistributedSvelteKit(options) {
|
|
27
|
+
await runCompilerOnce(options, 'generate');
|
|
28
|
+
}
|
|
29
|
+
/** Check every configured surface through canonical `dctl client --check`; never write. */
|
|
30
|
+
export async function checkDistributedSvelteKit(options) {
|
|
31
|
+
await runCompilerOnce(options, 'check');
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Compiler-watching Vite integration for one or more authorization surfaces.
|
|
35
|
+
*
|
|
36
|
+
* Every run stages every surface first, then swaps the complete generated
|
|
37
|
+
* trees as one rollback-capable transaction. Child processes are serialized,
|
|
38
|
+
* coalesced, abortable, and always invoked without a shell.
|
|
39
|
+
*/
|
|
40
|
+
export function distributedSvelteKit(options) {
|
|
41
|
+
let resolved;
|
|
42
|
+
let dirty = false;
|
|
43
|
+
let running;
|
|
44
|
+
let completedGeneration = 0;
|
|
45
|
+
let reloadedGeneration = 0;
|
|
46
|
+
let stopped = false;
|
|
47
|
+
const children = new Set();
|
|
48
|
+
const cancellation = new AbortController();
|
|
49
|
+
let lock;
|
|
50
|
+
const stop = async () => {
|
|
51
|
+
if (stopped)
|
|
52
|
+
return;
|
|
53
|
+
stopped = true;
|
|
54
|
+
dirty = false;
|
|
55
|
+
cancellation.abort();
|
|
56
|
+
for (const child of children)
|
|
57
|
+
killChild(child, 'SIGTERM');
|
|
58
|
+
const force = setTimeout(() => {
|
|
59
|
+
for (const child of children)
|
|
60
|
+
killChild(child, 'SIGKILL');
|
|
61
|
+
}, 1_500);
|
|
62
|
+
force.unref();
|
|
63
|
+
await Promise.race([
|
|
64
|
+
running?.catch(() => undefined) ?? Promise.resolve(),
|
|
65
|
+
new Promise((resolvePromise) => {
|
|
66
|
+
const bounded = setTimeout(resolvePromise, 3_000);
|
|
67
|
+
bounded.unref();
|
|
68
|
+
})
|
|
69
|
+
]);
|
|
70
|
+
clearTimeout(force);
|
|
71
|
+
if (lock !== undefined) {
|
|
72
|
+
await releaseCompilerLock(lock);
|
|
73
|
+
lock = undefined;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
const compile = (reason, startup = false) => {
|
|
77
|
+
if (stopped) {
|
|
78
|
+
return Promise.reject(new Error(`Distributed SvelteKit compiler is closed (${reason})`));
|
|
79
|
+
}
|
|
80
|
+
dirty = true;
|
|
81
|
+
if (running !== undefined)
|
|
82
|
+
return running;
|
|
83
|
+
running = (async () => {
|
|
84
|
+
let startupPending = startup;
|
|
85
|
+
while (dirty && !stopped) {
|
|
86
|
+
dirty = false;
|
|
87
|
+
const lease = requireCompilerLock(lock);
|
|
88
|
+
const integration = requireResolved(resolved);
|
|
89
|
+
const operation = () => compileTransaction(integration, children, cancellation.signal);
|
|
90
|
+
await (startupPending
|
|
91
|
+
? withCompilerStartup(lease, integration, operation)
|
|
92
|
+
: withCompilerLock(lease, operation));
|
|
93
|
+
startupPending = false;
|
|
94
|
+
}
|
|
95
|
+
completedGeneration += 1;
|
|
96
|
+
})().finally(() => {
|
|
97
|
+
running = undefined;
|
|
98
|
+
});
|
|
99
|
+
return running;
|
|
100
|
+
};
|
|
101
|
+
return {
|
|
102
|
+
name: '@hops-ops/distributed:sveltekit',
|
|
103
|
+
enforce: 'pre',
|
|
104
|
+
async configResolved(config) {
|
|
105
|
+
if (resolved !== undefined) {
|
|
106
|
+
throw new Error('Distributed SvelteKit Vite plugin was configured more than once');
|
|
107
|
+
}
|
|
108
|
+
resolved = resolveIntegration(options, options.cwd ?? config.root);
|
|
109
|
+
await validateResolvedPaths(resolved);
|
|
110
|
+
/*
|
|
111
|
+
* SvelteKit post-build analysis loads Vite config in an isolated
|
|
112
|
+
* worker marked with SVELTEKIT_FORK. That pass reads framework
|
|
113
|
+
* configuration only; running the compiler there would contend with
|
|
114
|
+
* the owning build's physical project lock and duplicate generation.
|
|
115
|
+
*/
|
|
116
|
+
if (!isMainThread && process.env.SVELTEKIT_FORK === 'true')
|
|
117
|
+
return;
|
|
118
|
+
lock = await acquireCompilerLock(resolved.cwd);
|
|
119
|
+
try {
|
|
120
|
+
await compile('Vite startup', true);
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
await releaseCompilerLock(lock);
|
|
124
|
+
lock = undefined;
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
configureServer(server) {
|
|
129
|
+
const integration = requireResolved(resolved);
|
|
130
|
+
const roots = integration.clients.flatMap((client) => client.watchRoots);
|
|
131
|
+
if (roots.length > 0)
|
|
132
|
+
server.watcher.add(roots);
|
|
133
|
+
server.httpServer?.once('close', () => {
|
|
134
|
+
void stop();
|
|
135
|
+
});
|
|
136
|
+
},
|
|
137
|
+
buildStart() {
|
|
138
|
+
const integration = requireResolved(resolved);
|
|
139
|
+
for (const client of integration.clients) {
|
|
140
|
+
for (const root of client.watchRoots)
|
|
141
|
+
this.addWatchFile(root);
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
resolveId(source) {
|
|
145
|
+
const client = resolved?.clients.find((candidate) => candidate.module === source);
|
|
146
|
+
return client === undefined ? undefined : virtualId(client.module);
|
|
147
|
+
},
|
|
148
|
+
load(id) {
|
|
149
|
+
const client = resolved?.clients.find((candidate) => virtualId(candidate.module) === id);
|
|
150
|
+
if (client === undefined)
|
|
151
|
+
return undefined;
|
|
152
|
+
return `export * from ${JSON.stringify(portablePath(client.entry))};\n`;
|
|
153
|
+
},
|
|
154
|
+
async handleHotUpdate(context) {
|
|
155
|
+
const integration = requireResolved(resolved);
|
|
156
|
+
if (!isGraphqlInput(context.file, integration))
|
|
157
|
+
return undefined;
|
|
158
|
+
try {
|
|
159
|
+
await compile(`GraphQL change ${context.file}`);
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
context.server.ws.send({
|
|
163
|
+
type: 'error',
|
|
164
|
+
err: viteError(error)
|
|
165
|
+
});
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
if (completedGeneration <= reloadedGeneration)
|
|
169
|
+
return [];
|
|
170
|
+
for (const client of integration.clients) {
|
|
171
|
+
const module = context.server.moduleGraph.getModuleById(virtualId(client.module));
|
|
172
|
+
if (module !== undefined) {
|
|
173
|
+
context.server.moduleGraph.invalidateModule(module);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
context.server.ws.send({ type: 'full-reload', path: '*' });
|
|
177
|
+
reloadedGeneration = completedGeneration;
|
|
178
|
+
return [];
|
|
179
|
+
},
|
|
180
|
+
async watchChange(id) {
|
|
181
|
+
const integration = requireResolved(resolved);
|
|
182
|
+
if (isGraphqlInput(id, integration)) {
|
|
183
|
+
await compile(`GraphQL watch change ${id}`);
|
|
184
|
+
}
|
|
185
|
+
},
|
|
186
|
+
closeBundle: stop
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
async function runCompilerOnce(options, mode) {
|
|
190
|
+
const integration = resolveIntegration(options, options.cwd ?? process.cwd());
|
|
191
|
+
await validateResolvedPaths(integration);
|
|
192
|
+
const lock = await acquireCompilerLock(integration.cwd);
|
|
193
|
+
const children = new Set();
|
|
194
|
+
const cancellation = new AbortController();
|
|
195
|
+
const cancel = () => {
|
|
196
|
+
cancellation.abort();
|
|
197
|
+
for (const child of children)
|
|
198
|
+
killChild(child, 'SIGTERM');
|
|
199
|
+
};
|
|
200
|
+
process.once('SIGINT', cancel);
|
|
201
|
+
process.once('SIGTERM', cancel);
|
|
202
|
+
try {
|
|
203
|
+
await withCompilerLock(lock, () => mode === 'generate'
|
|
204
|
+
? compileTransaction(integration, children, cancellation.signal)
|
|
205
|
+
: checkTransaction(integration, children, cancellation.signal));
|
|
206
|
+
}
|
|
207
|
+
finally {
|
|
208
|
+
process.removeListener('SIGINT', cancel);
|
|
209
|
+
process.removeListener('SIGTERM', cancel);
|
|
210
|
+
for (const child of children)
|
|
211
|
+
killChild(child, 'SIGKILL');
|
|
212
|
+
await releaseCompilerLock(lock);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* SvelteKit language-tool aliases for the exact files used by Vite resolution.
|
|
217
|
+
*
|
|
218
|
+
* Keep this alongside the Vite plugin in the Node-only export. It performs no
|
|
219
|
+
* generation and is safe to call from `svelte.config.js`.
|
|
220
|
+
*/
|
|
221
|
+
export function distributedSvelteKitAliases(options) {
|
|
222
|
+
const integration = resolveIntegration({ ...options, command: 'dctl', commandArgs: [] }, options.cwd ?? process.cwd());
|
|
223
|
+
validateResolvedPathsSync(integration);
|
|
224
|
+
return Object.freeze(Object.fromEntries([...integration.clients]
|
|
225
|
+
.sort((left, right) => right.module.length - left.module.length ||
|
|
226
|
+
left.module.localeCompare(right.module))
|
|
227
|
+
.map((client) => [client.module, client.entry])));
|
|
228
|
+
}
|
|
229
|
+
function resolveIntegration(options, fallbackCwd) {
|
|
230
|
+
if (options === null || typeof options !== 'object') {
|
|
231
|
+
throw new TypeError('distributedSvelteKit requires configuration');
|
|
232
|
+
}
|
|
233
|
+
const cwd = resolve(options.cwd ?? fallbackCwd);
|
|
234
|
+
const command = (options.command ?? 'dctl').trim();
|
|
235
|
+
if (command.length === 0) {
|
|
236
|
+
throw new TypeError('Distributed SvelteKit command must not be empty');
|
|
237
|
+
}
|
|
238
|
+
if (!Array.isArray(options.clients) || options.clients.length === 0) {
|
|
239
|
+
throw new TypeError('Distributed SvelteKit requires at least one client surface');
|
|
240
|
+
}
|
|
241
|
+
const modules = new Set();
|
|
242
|
+
const outputs = [];
|
|
243
|
+
const clients = options.clients.map((client, index) => {
|
|
244
|
+
if (client === null || typeof client !== 'object') {
|
|
245
|
+
throw new TypeError(`Distributed client[${index}] must be an object`);
|
|
246
|
+
}
|
|
247
|
+
if (!MODULE_NAME.test(client.module)) {
|
|
248
|
+
throw new TypeError(`Distributed client module \`${client.module}\` must be $distributed or a safe $distributed/<entrypoint>`);
|
|
249
|
+
}
|
|
250
|
+
if (modules.has(client.module)) {
|
|
251
|
+
throw new TypeError(`duplicate Distributed client module \`${client.module}\``);
|
|
252
|
+
}
|
|
253
|
+
modules.add(client.module);
|
|
254
|
+
const selector = client.role !== undefined && client.surface === undefined
|
|
255
|
+
? ['--role', nonempty(client.role, `${client.module} role`)]
|
|
256
|
+
: client.surface !== undefined && client.role === undefined
|
|
257
|
+
? [
|
|
258
|
+
'--surface',
|
|
259
|
+
nonempty(client.surface, `${client.module} surface`)
|
|
260
|
+
]
|
|
261
|
+
: undefined;
|
|
262
|
+
if (selector === undefined) {
|
|
263
|
+
throw new TypeError(`Distributed client \`${client.module}\` requires exactly one of role or surface`);
|
|
264
|
+
}
|
|
265
|
+
if (!Array.isArray(client.documents) ||
|
|
266
|
+
client.documents.length === 0) {
|
|
267
|
+
throw new TypeError(`Distributed client \`${client.module}\` requires at least one GraphQL document glob`);
|
|
268
|
+
}
|
|
269
|
+
const documents = client.documents.map((document, documentIndex) => nonempty(document, `${client.module} documents[${documentIndex}]`));
|
|
270
|
+
const routes = (client.routes ?? []).map((route, routeIndex) => nonempty(route, `${client.module} routes[${routeIndex}]`));
|
|
271
|
+
validateManifestSource(client.module, client.manifest);
|
|
272
|
+
const out = containedPath(cwd, client.out, `${client.module} generated output`);
|
|
273
|
+
if (out === cwd) {
|
|
274
|
+
throw new TypeError(`Distributed client \`${client.module}\` cannot use the project root as generated output`);
|
|
275
|
+
}
|
|
276
|
+
for (const existing of outputs) {
|
|
277
|
+
if (isWithin(existing, out) || isWithin(out, existing)) {
|
|
278
|
+
throw new TypeError(`Distributed client outputs must not overlap: \`${existing}\` and \`${out}\``);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
outputs.push(out);
|
|
282
|
+
return Object.freeze({
|
|
283
|
+
module: client.module,
|
|
284
|
+
manifest: client.manifest,
|
|
285
|
+
selector,
|
|
286
|
+
documents: Object.freeze(documents),
|
|
287
|
+
routes: Object.freeze(routes),
|
|
288
|
+
out,
|
|
289
|
+
entry: join(out, GENERATED_SVELTEKIT_MODULE),
|
|
290
|
+
watchRoots: Object.freeze(documentWatchRoots(cwd, documents, out))
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
return Object.freeze({
|
|
294
|
+
cwd,
|
|
295
|
+
command,
|
|
296
|
+
commandArgs: Object.freeze((options.commandArgs ?? []).map((argument, index) => {
|
|
297
|
+
if (typeof argument !== 'string') {
|
|
298
|
+
throw new TypeError(`Distributed commandArgs[${index}] must be a string`);
|
|
299
|
+
}
|
|
300
|
+
return argument;
|
|
301
|
+
})),
|
|
302
|
+
clients: Object.freeze(clients)
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
function validateManifestSource(module, source) {
|
|
306
|
+
if (typeof source === 'string') {
|
|
307
|
+
nonempty(source, `${module} manifest path`);
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
if (source === null ||
|
|
311
|
+
typeof source !== 'object' ||
|
|
312
|
+
!Array.isArray(source.args) ||
|
|
313
|
+
source.args.length === 0 ||
|
|
314
|
+
source.args[0] !== 'client-manifest' ||
|
|
315
|
+
source.args.some((argument) => typeof argument !== 'string')) {
|
|
316
|
+
throw new TypeError(`Distributed client \`${module}\` manifest args must begin with client-manifest`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
function nonempty(value, label) {
|
|
320
|
+
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
321
|
+
throw new TypeError(`${label} must be a non-empty string`);
|
|
322
|
+
}
|
|
323
|
+
return value;
|
|
324
|
+
}
|
|
325
|
+
function containedPath(root, value, label) {
|
|
326
|
+
const target = resolve(root, nonempty(value, label));
|
|
327
|
+
if (!isWithin(root, target)) {
|
|
328
|
+
throw new TypeError(`${label} must stay within project root ${root}`);
|
|
329
|
+
}
|
|
330
|
+
return target;
|
|
331
|
+
}
|
|
332
|
+
function isWithin(root, target) {
|
|
333
|
+
const rel = relative(root, target);
|
|
334
|
+
return (rel === '' ||
|
|
335
|
+
(!rel.startsWith(`..${sep}`) && rel !== '..' && !isAbsolute(rel)));
|
|
336
|
+
}
|
|
337
|
+
function documentWatchRoots(cwd, documents, out) {
|
|
338
|
+
const roots = new Set();
|
|
339
|
+
for (const pattern of documents) {
|
|
340
|
+
const normalized = pattern.replaceAll('\\', '/');
|
|
341
|
+
const wildcard = normalized.search(/[*?[\]{}]/);
|
|
342
|
+
const prefix = wildcard === -1 ? normalized : normalized.slice(0, wildcard);
|
|
343
|
+
const base = prefix.endsWith('/')
|
|
344
|
+
? prefix.slice(0, -1)
|
|
345
|
+
: dirname(prefix);
|
|
346
|
+
const watch = resolve(cwd, base === '.' || base.length === 0 ? '.' : base);
|
|
347
|
+
if (!isWithin(cwd, watch)) {
|
|
348
|
+
throw new TypeError(`Distributed GraphQL document glob \`${pattern}\` escapes project root ${cwd}`);
|
|
349
|
+
}
|
|
350
|
+
if (!isWithin(out, watch))
|
|
351
|
+
roots.add(watch);
|
|
352
|
+
}
|
|
353
|
+
return [...roots].sort();
|
|
354
|
+
}
|
|
355
|
+
function isGraphqlInput(file, integration) {
|
|
356
|
+
const absolute = resolve(integration.cwd, file);
|
|
357
|
+
if ((!absolute.endsWith('.graphql') && !absolute.endsWith('.gql')) ||
|
|
358
|
+
!isWithin(integration.cwd, absolute)) {
|
|
359
|
+
return false;
|
|
360
|
+
}
|
|
361
|
+
if (integration.clients.some((client) => isWithin(client.out, absolute))) {
|
|
362
|
+
return false;
|
|
363
|
+
}
|
|
364
|
+
return integration.clients.some((client) => client.watchRoots.some((root) => isWithin(root, absolute)));
|
|
365
|
+
}
|
|
366
|
+
async function compileTransaction(integration, children, signal) {
|
|
367
|
+
throwIfAborted(signal);
|
|
368
|
+
await validateResolvedPaths(integration);
|
|
369
|
+
const transaction = await mkdtemp(join(integration.cwd, '.distributed-sveltekit-'));
|
|
370
|
+
const staged = [];
|
|
371
|
+
try {
|
|
372
|
+
for (const [index, client] of integration.clients.entries()) {
|
|
373
|
+
throwIfAborted(signal);
|
|
374
|
+
const manifest = await materializeManifest(integration, client, transaction, index, children, signal);
|
|
375
|
+
const output = join(transaction, `output-${index}`);
|
|
376
|
+
let hadOutput = false;
|
|
377
|
+
try {
|
|
378
|
+
const metadata = await lstat(client.out);
|
|
379
|
+
if (metadata.isSymbolicLink() || !metadata.isDirectory()) {
|
|
380
|
+
throw new Error(`generated output ${client.out} must be a real directory`);
|
|
381
|
+
}
|
|
382
|
+
hadOutput = true;
|
|
383
|
+
await cp(client.out, output, {
|
|
384
|
+
recursive: true,
|
|
385
|
+
errorOnExist: true,
|
|
386
|
+
force: false,
|
|
387
|
+
dereference: false
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
catch (error) {
|
|
391
|
+
if (!isMissing(error))
|
|
392
|
+
throw error;
|
|
393
|
+
}
|
|
394
|
+
const args = [
|
|
395
|
+
'client',
|
|
396
|
+
'--manifest',
|
|
397
|
+
manifest,
|
|
398
|
+
client.selector[0],
|
|
399
|
+
client.selector[1],
|
|
400
|
+
...client.documents.flatMap((document) => [
|
|
401
|
+
'--documents',
|
|
402
|
+
document
|
|
403
|
+
]),
|
|
404
|
+
...client.routes.flatMap((route) => ['--route', route]),
|
|
405
|
+
'--out',
|
|
406
|
+
output
|
|
407
|
+
];
|
|
408
|
+
await runCommand(integration, args, children, signal);
|
|
409
|
+
await validateGeneratedEntrypoint(integration.cwd, output, client.module);
|
|
410
|
+
staged.push({
|
|
411
|
+
client,
|
|
412
|
+
output,
|
|
413
|
+
backup: join(transaction, `backup-${index}`),
|
|
414
|
+
hadOutput
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
throwIfAborted(signal);
|
|
418
|
+
await commitOutputs(integration, staged, signal);
|
|
419
|
+
}
|
|
420
|
+
finally {
|
|
421
|
+
await rm(transaction, { recursive: true, force: true });
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
async function checkTransaction(integration, children, signal) {
|
|
425
|
+
throwIfAborted(signal);
|
|
426
|
+
await validateResolvedPaths(integration);
|
|
427
|
+
const transaction = await mkdtemp(join(integration.cwd, '.distributed-sveltekit-check-'));
|
|
428
|
+
try {
|
|
429
|
+
for (const [index, client] of integration.clients.entries()) {
|
|
430
|
+
throwIfAborted(signal);
|
|
431
|
+
const manifest = await materializeManifest(integration, client, transaction, index, children, signal);
|
|
432
|
+
await runCommand(integration, [
|
|
433
|
+
'client',
|
|
434
|
+
'--check',
|
|
435
|
+
'--manifest',
|
|
436
|
+
manifest,
|
|
437
|
+
client.selector[0],
|
|
438
|
+
client.selector[1],
|
|
439
|
+
...client.documents.flatMap((document) => [
|
|
440
|
+
'--documents',
|
|
441
|
+
document
|
|
442
|
+
]),
|
|
443
|
+
...client.routes.flatMap((route) => ['--route', route]),
|
|
444
|
+
'--out',
|
|
445
|
+
client.out
|
|
446
|
+
], children, signal);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
finally {
|
|
450
|
+
await rm(transaction, { recursive: true, force: true });
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
async function materializeManifest(integration, client, transaction, index, children, signal) {
|
|
454
|
+
throwIfAborted(signal);
|
|
455
|
+
if (typeof client.manifest === 'string') {
|
|
456
|
+
const path = containedPath(integration.cwd, client.manifest, `${client.module} manifest`);
|
|
457
|
+
const canonicalRoot = await realpath(integration.cwd);
|
|
458
|
+
const canonical = await realpath(path);
|
|
459
|
+
if (!isWithin(canonicalRoot, canonical)) {
|
|
460
|
+
throw new Error(`Distributed manifest ${path} resolves outside project root ${integration.cwd}`);
|
|
461
|
+
}
|
|
462
|
+
JSON.parse(await readFile(canonical, 'utf8'));
|
|
463
|
+
return canonical;
|
|
464
|
+
}
|
|
465
|
+
const result = await runCommand(integration, [...client.manifest.args], children, signal);
|
|
466
|
+
try {
|
|
467
|
+
JSON.parse(result.stdout);
|
|
468
|
+
}
|
|
469
|
+
catch (error) {
|
|
470
|
+
throw new Error(`dctl client-manifest for ${client.module} did not emit valid JSON`, { cause: error });
|
|
471
|
+
}
|
|
472
|
+
const temporary = join(transaction, `manifest-${index}.json.tmp`);
|
|
473
|
+
const manifest = join(transaction, `manifest-${index}.json`);
|
|
474
|
+
await writeFile(temporary, result.stdout, {
|
|
475
|
+
encoding: 'utf8',
|
|
476
|
+
flag: 'wx'
|
|
477
|
+
});
|
|
478
|
+
await rename(temporary, manifest);
|
|
479
|
+
return manifest;
|
|
480
|
+
}
|
|
481
|
+
async function validateGeneratedEntrypoint(cwd, output, module) {
|
|
482
|
+
const root = await realpath(cwd);
|
|
483
|
+
const entry = join(output, GENERATED_SVELTEKIT_MODULE);
|
|
484
|
+
const metadata = await lstat(entry);
|
|
485
|
+
if (metadata.isSymbolicLink() || !metadata.isFile()) {
|
|
486
|
+
throw new Error(`dctl client for ${module} did not emit a regular ${GENERATED_SVELTEKIT_MODULE}`);
|
|
487
|
+
}
|
|
488
|
+
const canonical = await realpath(entry);
|
|
489
|
+
if (!isWithin(root, canonical)) {
|
|
490
|
+
throw new Error(`dctl client entrypoint ${canonical} escaped project root ${root}`);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
async function commitOutputs(integration, staged, signal) {
|
|
494
|
+
throwIfAborted(signal);
|
|
495
|
+
await validateResolvedPaths(integration);
|
|
496
|
+
const applied = [];
|
|
497
|
+
try {
|
|
498
|
+
for (const item of staged) {
|
|
499
|
+
throwIfAborted(signal);
|
|
500
|
+
await mkdir(dirname(item.client.out), { recursive: true });
|
|
501
|
+
await validateNearestExistingParent(integration.cwd, item.client.out);
|
|
502
|
+
if (item.hadOutput)
|
|
503
|
+
await rename(item.client.out, item.backup);
|
|
504
|
+
try {
|
|
505
|
+
await rename(item.output, item.client.out);
|
|
506
|
+
}
|
|
507
|
+
catch (error) {
|
|
508
|
+
if (item.hadOutput)
|
|
509
|
+
await rename(item.backup, item.client.out);
|
|
510
|
+
throw error;
|
|
511
|
+
}
|
|
512
|
+
applied.push(item);
|
|
513
|
+
}
|
|
514
|
+
throwIfAborted(signal);
|
|
515
|
+
}
|
|
516
|
+
catch (error) {
|
|
517
|
+
for (const item of [...applied].reverse()) {
|
|
518
|
+
await rm(item.client.out, { recursive: true, force: true });
|
|
519
|
+
if (item.hadOutput)
|
|
520
|
+
await rename(item.backup, item.client.out);
|
|
521
|
+
}
|
|
522
|
+
throw error;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
async function runCommand(integration, args, children, signal) {
|
|
526
|
+
throwIfAborted(signal);
|
|
527
|
+
const argv = [...integration.commandArgs, ...args];
|
|
528
|
+
return await new Promise((resolvePromise, rejectPromise) => {
|
|
529
|
+
const child = spawn(integration.command, argv, {
|
|
530
|
+
cwd: integration.cwd,
|
|
531
|
+
env: process.env,
|
|
532
|
+
shell: false,
|
|
533
|
+
detached: process.platform !== 'win32',
|
|
534
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
535
|
+
});
|
|
536
|
+
children.add(child);
|
|
537
|
+
const stdout = [];
|
|
538
|
+
const stderr = [];
|
|
539
|
+
let bytes = 0;
|
|
540
|
+
let overflow = false;
|
|
541
|
+
let forceKill;
|
|
542
|
+
const cancel = () => {
|
|
543
|
+
killChild(child, 'SIGTERM');
|
|
544
|
+
forceKill ??= setTimeout(() => killChild(child, 'SIGKILL'), 1_500);
|
|
545
|
+
forceKill.unref();
|
|
546
|
+
};
|
|
547
|
+
signal.addEventListener('abort', cancel, { once: true });
|
|
548
|
+
const collect = (target) => (chunk) => {
|
|
549
|
+
if (overflow)
|
|
550
|
+
return;
|
|
551
|
+
const value = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
552
|
+
bytes += value.length;
|
|
553
|
+
if (bytes > MAX_COMMAND_OUTPUT_BYTES) {
|
|
554
|
+
overflow = true;
|
|
555
|
+
cancel();
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
target.push(value);
|
|
559
|
+
};
|
|
560
|
+
child.stdout?.on('data', collect(stdout));
|
|
561
|
+
child.stderr?.on('data', collect(stderr));
|
|
562
|
+
child.once('error', (error) => {
|
|
563
|
+
children.delete(child);
|
|
564
|
+
signal.removeEventListener('abort', cancel);
|
|
565
|
+
if (forceKill !== undefined)
|
|
566
|
+
clearTimeout(forceKill);
|
|
567
|
+
rejectPromise(commandFailure(integration, argv, '', error.message));
|
|
568
|
+
});
|
|
569
|
+
child.once('close', (code, signal) => {
|
|
570
|
+
children.delete(child);
|
|
571
|
+
cancellationCleanup();
|
|
572
|
+
const out = Buffer.concat(stdout).toString('utf8');
|
|
573
|
+
const err = Buffer.concat(stderr).toString('utf8');
|
|
574
|
+
if (overflow) {
|
|
575
|
+
rejectPromise(commandFailure(integration, argv, err, `output exceeded ${MAX_COMMAND_OUTPUT_BYTES} bytes`));
|
|
576
|
+
}
|
|
577
|
+
else if (code !== 0) {
|
|
578
|
+
rejectPromise(commandFailure(integration, argv, err, `exit ${code ?? 'unknown'}${signal === null ? '' : ` (${signal})`}`));
|
|
579
|
+
}
|
|
580
|
+
else {
|
|
581
|
+
resolvePromise({ stdout: out, stderr: err });
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
function cancellationCleanup() {
|
|
585
|
+
signal.removeEventListener('abort', cancel);
|
|
586
|
+
if (forceKill !== undefined)
|
|
587
|
+
clearTimeout(forceKill);
|
|
588
|
+
}
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
function commandFailure(integration, argv, stderr, summary) {
|
|
592
|
+
const renderedArgv = JSON.stringify([integration.command, ...argv]);
|
|
593
|
+
const details = [
|
|
594
|
+
`Distributed compiler command failed: ${summary}`,
|
|
595
|
+
`cwd: ${integration.cwd}`,
|
|
596
|
+
`argv: ${renderedArgv}`,
|
|
597
|
+
stderr.trim().length === 0 ? undefined : `stderr:\n${stderr.trim()}`
|
|
598
|
+
].filter((value) => value !== undefined);
|
|
599
|
+
return new Error(details.join('\n'));
|
|
600
|
+
}
|
|
601
|
+
async function validateResolvedPaths(integration) {
|
|
602
|
+
validateResolvedPathsSync(integration);
|
|
603
|
+
}
|
|
604
|
+
function validateResolvedPathsSync(integration) {
|
|
605
|
+
const canonicalRoot = realpathSync(integration.cwd);
|
|
606
|
+
const physicalOutputs = [];
|
|
607
|
+
for (const client of integration.clients) {
|
|
608
|
+
const output = plannedPhysicalPath(canonicalRoot, integration.cwd, client.out);
|
|
609
|
+
for (const existing of physicalOutputs) {
|
|
610
|
+
if (physicalPathKey(output).startsWith(`${physicalPathKey(existing.path)}${sep}`) ||
|
|
611
|
+
physicalPathKey(existing.path).startsWith(`${physicalPathKey(output)}${sep}`) ||
|
|
612
|
+
physicalPathKey(existing.path) === physicalPathKey(output)) {
|
|
613
|
+
throw new Error(`Distributed client outputs physically overlap: ${existing.module} (${existing.path}) and ${client.module} (${output})`);
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
physicalOutputs.push({ module: client.module, path: output });
|
|
617
|
+
for (const watchRoot of client.watchRoots) {
|
|
618
|
+
plannedPhysicalPath(canonicalRoot, integration.cwd, watchRoot);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
function plannedPhysicalPath(canonicalRoot, lexicalRoot, target) {
|
|
623
|
+
const rel = relative(lexicalRoot, target);
|
|
624
|
+
let lexical = lexicalRoot;
|
|
625
|
+
if (rel !== '') {
|
|
626
|
+
for (const component of rel.split(sep)) {
|
|
627
|
+
lexical = join(lexical, component);
|
|
628
|
+
if (!existsSync(lexical))
|
|
629
|
+
break;
|
|
630
|
+
const metadata = lstatSync(lexical);
|
|
631
|
+
if (metadata.isSymbolicLink()) {
|
|
632
|
+
throw new Error(`Distributed path ${target} contains symlink component ${lexical}`);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
let existing = target;
|
|
637
|
+
const suffix = [];
|
|
638
|
+
while (!existsSync(existing)) {
|
|
639
|
+
const parent = dirname(existing);
|
|
640
|
+
if (parent === existing) {
|
|
641
|
+
throw new Error(`Distributed path ${target} has no existing parent within ${canonicalRoot}`);
|
|
642
|
+
}
|
|
643
|
+
suffix.unshift(basename(existing));
|
|
644
|
+
existing = parent;
|
|
645
|
+
}
|
|
646
|
+
const canonicalExisting = realpathSync(existing);
|
|
647
|
+
if (!isWithin(canonicalRoot, canonicalExisting)) {
|
|
648
|
+
throw new Error(`Distributed path ${target} resolves outside project root ${canonicalRoot}`);
|
|
649
|
+
}
|
|
650
|
+
const planned = resolve(canonicalExisting, ...suffix);
|
|
651
|
+
if (!isWithin(canonicalRoot, planned)) {
|
|
652
|
+
throw new Error(`Distributed path ${target} resolves outside project root ${canonicalRoot}`);
|
|
653
|
+
}
|
|
654
|
+
return planned;
|
|
655
|
+
}
|
|
656
|
+
function physicalPathKey(path) {
|
|
657
|
+
return process.platform === 'win32' || process.platform === 'darwin'
|
|
658
|
+
? path.toLocaleLowerCase('en-US')
|
|
659
|
+
: path;
|
|
660
|
+
}
|
|
661
|
+
async function validateNearestExistingParent(root, target) {
|
|
662
|
+
const canonicalRoot = realpathSync(root);
|
|
663
|
+
plannedPhysicalPath(canonicalRoot, root, target);
|
|
664
|
+
}
|
|
665
|
+
function compilerCoordinatorRegistry() {
|
|
666
|
+
const globalScope = globalThis;
|
|
667
|
+
const existing = globalScope[COMPILER_COORDINATORS];
|
|
668
|
+
if (existing instanceof Map) {
|
|
669
|
+
return existing;
|
|
670
|
+
}
|
|
671
|
+
const registry = new Map();
|
|
672
|
+
globalScope[COMPILER_COORDINATORS] = registry;
|
|
673
|
+
return registry;
|
|
674
|
+
}
|
|
675
|
+
async function acquireCompilerLock(cwd) {
|
|
676
|
+
const path = join(cwd, COMPILER_LOCK);
|
|
677
|
+
const registry = compilerCoordinatorRegistry();
|
|
678
|
+
for (;;) {
|
|
679
|
+
const existing = registry.get(path);
|
|
680
|
+
if (existing?.closing !== undefined) {
|
|
681
|
+
await existing.closing;
|
|
682
|
+
continue;
|
|
683
|
+
}
|
|
684
|
+
const coordinator = existing ??
|
|
685
|
+
{
|
|
686
|
+
path,
|
|
687
|
+
references: 0,
|
|
688
|
+
ready: acquirePhysicalCompilerLock(cwd, path),
|
|
689
|
+
tail: Promise.resolve(),
|
|
690
|
+
startupRuns: new Map()
|
|
691
|
+
};
|
|
692
|
+
if (existing === undefined)
|
|
693
|
+
registry.set(path, coordinator);
|
|
694
|
+
coordinator.references += 1;
|
|
695
|
+
try {
|
|
696
|
+
await coordinator.ready;
|
|
697
|
+
return { coordinator, released: false };
|
|
698
|
+
}
|
|
699
|
+
catch (error) {
|
|
700
|
+
coordinator.references -= 1;
|
|
701
|
+
if (coordinator.references === 0 &&
|
|
702
|
+
registry.get(path) === coordinator) {
|
|
703
|
+
registry.delete(path);
|
|
704
|
+
}
|
|
705
|
+
throw error;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
async function acquirePhysicalCompilerLock(cwd, path) {
|
|
710
|
+
await validateNearestExistingParent(cwd, dirname(path));
|
|
711
|
+
await mkdir(dirname(path), { recursive: true });
|
|
712
|
+
await validateNearestExistingParent(cwd, path);
|
|
713
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
714
|
+
try {
|
|
715
|
+
const handle = await open(path, 'wx', 0o600);
|
|
716
|
+
try {
|
|
717
|
+
await handle.writeFile(`${JSON.stringify({ pid: process.pid, version: 1 })}\n`, 'utf8');
|
|
718
|
+
}
|
|
719
|
+
finally {
|
|
720
|
+
await handle.close();
|
|
721
|
+
}
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
catch (error) {
|
|
725
|
+
if (!isAlreadyExists(error))
|
|
726
|
+
throw error;
|
|
727
|
+
let owner;
|
|
728
|
+
try {
|
|
729
|
+
owner = JSON.parse(await readFile(path, 'utf8'));
|
|
730
|
+
}
|
|
731
|
+
catch {
|
|
732
|
+
owner = undefined;
|
|
733
|
+
}
|
|
734
|
+
const pid = owner !== null &&
|
|
735
|
+
typeof owner === 'object' &&
|
|
736
|
+
'pid' in owner &&
|
|
737
|
+
typeof owner.pid === 'number'
|
|
738
|
+
? owner.pid
|
|
739
|
+
: undefined;
|
|
740
|
+
if (pid !== undefined && processExists(pid)) {
|
|
741
|
+
throw new Error(`Distributed SvelteKit compiler already owns ${cwd} (pid ${pid})`);
|
|
742
|
+
}
|
|
743
|
+
await unlink(path).catch((unlinkError) => {
|
|
744
|
+
if (!isMissing(unlinkError))
|
|
745
|
+
throw unlinkError;
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
throw new Error(`could not acquire Distributed SvelteKit compiler lock ${path}`);
|
|
750
|
+
}
|
|
751
|
+
function withCompilerLock(lease, operation) {
|
|
752
|
+
if (lease.released) {
|
|
753
|
+
return Promise.reject(new Error('Distributed SvelteKit compiler lock lease is already released'));
|
|
754
|
+
}
|
|
755
|
+
const coordinator = lease.coordinator;
|
|
756
|
+
const run = coordinator.tail.catch(() => undefined).then(operation);
|
|
757
|
+
coordinator.tail = run.then(() => undefined, () => undefined);
|
|
758
|
+
return run;
|
|
759
|
+
}
|
|
760
|
+
function withCompilerStartup(lease, integration, operation) {
|
|
761
|
+
if (lease.released) {
|
|
762
|
+
return Promise.reject(new Error('Distributed SvelteKit compiler lock lease is already released'));
|
|
763
|
+
}
|
|
764
|
+
const key = JSON.stringify(integration);
|
|
765
|
+
const coordinator = lease.coordinator;
|
|
766
|
+
const existing = coordinator.startupRuns.get(key);
|
|
767
|
+
if (existing !== undefined)
|
|
768
|
+
return existing;
|
|
769
|
+
const run = withCompilerLock(lease, operation);
|
|
770
|
+
const cached = run.catch((error) => {
|
|
771
|
+
if (coordinator.startupRuns.get(key) === cached) {
|
|
772
|
+
coordinator.startupRuns.delete(key);
|
|
773
|
+
}
|
|
774
|
+
throw error;
|
|
775
|
+
});
|
|
776
|
+
coordinator.startupRuns.set(key, cached);
|
|
777
|
+
return cached;
|
|
778
|
+
}
|
|
779
|
+
function requireCompilerLock(lease) {
|
|
780
|
+
if (lease === undefined) {
|
|
781
|
+
throw new Error('Distributed SvelteKit compiler lock is not initialized');
|
|
782
|
+
}
|
|
783
|
+
return lease;
|
|
784
|
+
}
|
|
785
|
+
async function releaseCompilerLock(lease) {
|
|
786
|
+
if (lease.released)
|
|
787
|
+
return;
|
|
788
|
+
lease.released = true;
|
|
789
|
+
const coordinator = lease.coordinator;
|
|
790
|
+
await coordinator.tail.catch(() => undefined);
|
|
791
|
+
coordinator.references -= 1;
|
|
792
|
+
if (coordinator.references > 0)
|
|
793
|
+
return;
|
|
794
|
+
if (coordinator.references < 0) {
|
|
795
|
+
throw new Error('Distributed SvelteKit compiler lock reference underflow');
|
|
796
|
+
}
|
|
797
|
+
const registry = compilerCoordinatorRegistry();
|
|
798
|
+
if (registry.get(coordinator.path) !== coordinator)
|
|
799
|
+
return;
|
|
800
|
+
const closing = unlink(coordinator.path)
|
|
801
|
+
.catch((error) => {
|
|
802
|
+
if (!isMissing(error))
|
|
803
|
+
throw error;
|
|
804
|
+
})
|
|
805
|
+
.finally(() => {
|
|
806
|
+
if (registry.get(coordinator.path) === coordinator) {
|
|
807
|
+
registry.delete(coordinator.path);
|
|
808
|
+
}
|
|
809
|
+
});
|
|
810
|
+
coordinator.closing = closing;
|
|
811
|
+
await closing;
|
|
812
|
+
}
|
|
813
|
+
function processExists(pid) {
|
|
814
|
+
try {
|
|
815
|
+
process.kill(pid, 0);
|
|
816
|
+
return true;
|
|
817
|
+
}
|
|
818
|
+
catch (error) {
|
|
819
|
+
return (error !== null &&
|
|
820
|
+
typeof error === 'object' &&
|
|
821
|
+
'code' in error &&
|
|
822
|
+
error.code === 'EPERM');
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
function killChild(child, signal) {
|
|
826
|
+
if (child.pid === undefined)
|
|
827
|
+
return;
|
|
828
|
+
if (process.platform !== 'win32') {
|
|
829
|
+
try {
|
|
830
|
+
process.kill(-child.pid, signal);
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
catch {
|
|
834
|
+
// The child may have exited or failed to become its own process group.
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
child.kill(signal);
|
|
838
|
+
}
|
|
839
|
+
function throwIfAborted(signal) {
|
|
840
|
+
if (!signal.aborted)
|
|
841
|
+
return;
|
|
842
|
+
const error = new Error('Distributed SvelteKit compiler was cancelled');
|
|
843
|
+
error.name = 'AbortError';
|
|
844
|
+
throw error;
|
|
845
|
+
}
|
|
846
|
+
function isMissing(error) {
|
|
847
|
+
return (error !== null &&
|
|
848
|
+
typeof error === 'object' &&
|
|
849
|
+
'code' in error &&
|
|
850
|
+
error.code === 'ENOENT');
|
|
851
|
+
}
|
|
852
|
+
function isAlreadyExists(error) {
|
|
853
|
+
return (error !== null &&
|
|
854
|
+
typeof error === 'object' &&
|
|
855
|
+
'code' in error &&
|
|
856
|
+
error.code === 'EEXIST');
|
|
857
|
+
}
|
|
858
|
+
function requireResolved(value) {
|
|
859
|
+
if (value === undefined) {
|
|
860
|
+
throw new Error('Distributed SvelteKit Vite plugin has not received resolved config');
|
|
861
|
+
}
|
|
862
|
+
return value;
|
|
863
|
+
}
|
|
864
|
+
function virtualId(module) {
|
|
865
|
+
return `\0@hops-ops/distributed:sveltekit:${module}`;
|
|
866
|
+
}
|
|
867
|
+
function portablePath(path) {
|
|
868
|
+
return path.replaceAll('\\', '/');
|
|
869
|
+
}
|
|
870
|
+
function viteError(error) {
|
|
871
|
+
if (error instanceof Error) {
|
|
872
|
+
return {
|
|
873
|
+
message: error.message,
|
|
874
|
+
...(error.stack === undefined ? {} : { stack: error.stack })
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
return { message: String(error) };
|
|
878
|
+
}
|