@abide/abide 0.123.1 → 0.123.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/CHANGELOG.md +33 -0
- package/bin/abide.ts +36 -65
- package/docs/REGISTRY.md +2 -2
- package/docs/RULEBOOK.md +2 -2
- package/docs/abide.md +2 -2
- package/package.json +1 -1
- package/src/compiler/Diagnostics.ts +198 -0
- package/src/compiler/KeptChecker.ts +97 -0
- package/src/compiler/Lowering.ts +1 -1
- package/src/compiler/PositionedRefusal.ts +1 -1
- package/src/compiler/TypeSession.ts +62 -85
- package/src/compiler/appLayout.ts +28 -20
- package/src/compiler/argsDefaultsIn.ts +1 -1
- package/src/compiler/checkApp.ts +64 -68
- package/src/compiler/checkedShadow.ts +1 -1
- package/src/compiler/compileApp.ts +101 -70
- package/src/compiler/declaredExports.ts +2 -4
- package/src/compiler/emitClient.ts +1 -1
- package/src/compiler/formatAbide.ts +2 -2
- package/src/compiler/handlersUnder.ts +17 -29
- package/src/compiler/lineOf.ts +0 -19
- package/src/compiler/matchersOf.ts +3 -3
- package/src/compiler/mountsOf.ts +3 -3
- package/src/compiler/readSources.ts +16 -18
- package/src/compiler/readsIn.ts +1 -1
- package/src/compiler/serveLanguage.ts +23 -57
- package/src/compiler/sharedModuleWrites.ts +1 -1
- package/src/compiler/templateWarnings.ts +1 -2
- package/src/compiler/trackingWarnings.ts +1 -1
- package/src/server/CLIENT_BUILD.ts +4 -32
- package/src/server/RESUME_MILLISECONDS.ts +2 -0
- package/src/server/answerRequest.ts +11 -300
- package/src/server/answerRpc.ts +12 -54
- package/src/server/answerSocket.ts +8 -35
- package/src/server/appBuild.ts +377 -0
- package/src/server/appPlugins.ts +15 -17
- package/src/server/appTree.ts +6 -5
- package/src/server/argsContract.ts +209 -0
- package/src/server/bridgeRelay.ts +153 -65
- package/src/server/claudeCode.ts +30 -127
- package/src/server/config.ts +6 -7
- package/src/server/createApp.ts +30 -64
- package/src/server/devWorker.ts +11 -15
- package/src/server/handlerFileMount.ts +40 -0
- package/src/server/index.ts +2 -2
- package/src/server/packageDirectory.ts +14 -0
- package/src/server/preload.ts +3 -2
- package/src/server/principal.ts +4 -3
- package/src/server/remote.ts +1 -1
- package/src/server/runCompiled.ts +1 -1
- package/src/server/runDesktop.ts +1 -1
- package/src/server/seedBuffer.ts +1 -1
- package/src/server/serveDev.ts +24 -58
- package/src/server/servedApp.ts +390 -0
- package/src/server/signInChanged.ts +20 -0
- package/src/server/surfaceOf.ts +2 -1
- package/src/server/versionOf.ts +6 -15
- package/src/shared/applyPatch.ts +1 -1
- package/src/shared/muxFrames.ts +1 -1
- package/src/shared/reactive/Keyed.ts +1 -1
- package/src/shared/reactive/Reactive.ts +21 -8
- package/src/shared/reactive/argsContract.ts +131 -4
- package/src/shared/reactive/index.ts +1 -0
- package/src/shared/reactive/patchLog.ts +79 -0
- package/src/shared/reactive/patchMarks.ts +34 -0
- package/src/shared/reactive/recordWrites.ts +11 -14
- package/src/shared/reactive/share.ts +4 -3
- package/src/shared/rpc.ts +12 -19
- package/src/shared/validateJson.ts +14 -3
- package/src/ui/ArmRegion.ts +10 -4
- package/src/ui/Level.ts +11 -20
- package/src/ui/LevelChain.ts +272 -0
- package/src/ui/Router.ts +54 -245
- package/src/ui/Scope.ts +17 -3
- package/src/ui/TryRegion.ts +22 -23
- package/src/ui/bindSpread.ts +2 -4
- package/src/ui/listenForRoutes.ts +3 -10
- package/src/ui/outboxQueue.ts +21 -3
- package/src/ui/reconcile.ts +4 -6
- package/src/ui/routing.ts +2 -1
- package/src/ui/rows.ts +2 -2
- package/src/ui/setAttribute.ts +12 -3
- package/src/ui/socketWire.ts +26 -11
- package/src/compiler/Finding.ts +0 -2
- package/src/compiler/Refusals.ts +0 -65
- package/src/compiler/formatDiagnosis.ts +0 -20
- package/src/server/appDirectories.ts +0 -9
- package/src/server/argsFor.ts +0 -39
- package/src/server/buildDirectory.ts +0 -10
- package/src/server/coerceArgs.ts +0 -109
- package/src/server/derivedArgs.ts +0 -95
- package/src/server/runBuild.ts +0 -24
- package/src/server/serveBuild.ts +0 -144
- package/src/server/testBuild.ts +0 -220
- package/src/shared/reactive/FOLLOWED.ts +0 -8
- package/src/shared/reactive/PATCHES.ts +0 -11
- package/src/shared/reactive/PATCH_SINKS.ts +0 -26
- package/src/shared/reactive/canonicalKey.ts +0 -95
package/src/server/claudeCode.ts
CHANGED
|
@@ -14,24 +14,16 @@ import { refuse } from '#shared/refuse.ts'
|
|
|
14
14
|
|
|
15
15
|
import {
|
|
16
16
|
type AssistantContent,
|
|
17
|
-
type AssistantEvent,
|
|
18
17
|
type AssistantMessage,
|
|
19
18
|
blocksOf,
|
|
20
19
|
forwardOf,
|
|
21
20
|
type Provider,
|
|
22
21
|
} from './assistant.ts'
|
|
23
22
|
import { BRIDGE_VERSION } from './BRIDGE_VERSION.ts'
|
|
24
|
-
import {
|
|
25
|
-
followSignIn,
|
|
26
|
-
mintCode,
|
|
27
|
-
openTurn,
|
|
28
|
-
pairedBridge,
|
|
29
|
-
watchPairing,
|
|
30
|
-
} from './bridgeRelay.ts'
|
|
23
|
+
import { mintCode, pairingStatus, runTurn } from './bridgeRelay.ts'
|
|
31
24
|
import { callerOf } from './callerOf.ts'
|
|
32
25
|
import { appOrigin } from './config.ts'
|
|
33
26
|
import { mcpFailure } from './generatedSurfaces.ts'
|
|
34
|
-
import { Inbox } from './Inbox.ts'
|
|
35
27
|
import { signedInAs } from './principal.ts'
|
|
36
28
|
import { request } from './requestScope.ts'
|
|
37
29
|
|
|
@@ -65,53 +57,19 @@ export type BridgeStatus = {
|
|
|
65
57
|
function claudeCodeProvider(config: ClaudeCodeConfig = {}): Provider {
|
|
66
58
|
return {
|
|
67
59
|
async *run(messages, tools, { maxTurns, signal }) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
throw refuse(
|
|
72
|
-
409,
|
|
73
|
-
'No Claude Code is paired with this browser as the one signed in. claudeCode.bridgeCommand() is the command that pairs one.',
|
|
74
|
-
)
|
|
75
|
-
const turn = openTurn(bridge)
|
|
76
|
-
bridge.frames.push({
|
|
77
|
-
type: 'turn',
|
|
78
|
-
id: turn.id,
|
|
60
|
+
yield* runTurn({
|
|
61
|
+
caller: askingCaller(),
|
|
62
|
+
signedIn: signedInAs(),
|
|
79
63
|
prompt: foldedContent(messages),
|
|
80
64
|
config,
|
|
81
65
|
maxTurns,
|
|
66
|
+
// 44.17 — answered by the app's own MCP surface, through the turn's `Toolbox`.
|
|
67
|
+
answer: (method, params) =>
|
|
68
|
+
forwardOf(tools)(method, params).catch((thrown: unknown) =>
|
|
69
|
+
mcpFailure(-32603, String(thrown)),
|
|
70
|
+
),
|
|
71
|
+
signal,
|
|
82
72
|
})
|
|
83
|
-
// 44.18 — a caller hanging up ends the run on the bridge.
|
|
84
|
-
const cancel = (): void => {
|
|
85
|
-
bridge.frames.push({ type: 'cancel', turn: turn.id })
|
|
86
|
-
}
|
|
87
|
-
signal.addEventListener('abort', cancel)
|
|
88
|
-
try {
|
|
89
|
-
for await (const item of turn.inbox) {
|
|
90
|
-
if (item.kind === 'event')
|
|
91
|
-
yield item.event as AssistantEvent
|
|
92
|
-
else if (item.kind === 'mcp')
|
|
93
|
-
// 44.17 — answered by the app's own MCP surface, through the turn's `Toolbox`.
|
|
94
|
-
void forwardOf(tools)(item.method, item.params)
|
|
95
|
-
.catch((thrown: unknown) =>
|
|
96
|
-
mcpFailure(-32603, String(thrown)),
|
|
97
|
-
)
|
|
98
|
-
.then((reply) => {
|
|
99
|
-
bridge.frames.push({
|
|
100
|
-
type: 'reply',
|
|
101
|
-
turn: turn.id,
|
|
102
|
-
id: item.id,
|
|
103
|
-
reply,
|
|
104
|
-
})
|
|
105
|
-
})
|
|
106
|
-
else if (item.kind === 'end') return
|
|
107
|
-
// 44.21.
|
|
108
|
-
else
|
|
109
|
-
throw refuse(502, `Claude Code failed: ${item.message}`)
|
|
110
|
-
}
|
|
111
|
-
} finally {
|
|
112
|
-
signal.removeEventListener('abort', cancel)
|
|
113
|
-
turn.close()
|
|
114
|
-
}
|
|
115
73
|
},
|
|
116
74
|
}
|
|
117
75
|
}
|
|
@@ -157,25 +115,25 @@ function askingCaller(): string {
|
|
|
157
115
|
return caller
|
|
158
116
|
}
|
|
159
117
|
|
|
160
|
-
/** One pairing code, as the command a visitor runs and the link they open, and when it stops standing. */
|
|
161
|
-
type Pairing = { command: string; link: string; expires: number }
|
|
162
|
-
|
|
163
118
|
/**
|
|
164
119
|
* The command and the link that pair a bridge with this browser, and when their code stops standing.
|
|
165
120
|
* 44.14. The `tools` it is given are written after the command's `--` and in the link, so the visitor
|
|
166
121
|
* running either grants them (44.29, 44.32, 44.40).
|
|
167
122
|
*/
|
|
168
|
-
function bridgeCommand(options: PairingOptions = {}):
|
|
169
|
-
|
|
123
|
+
function bridgeCommand(options: PairingOptions = {}): {
|
|
124
|
+
command: string
|
|
125
|
+
link: string
|
|
126
|
+
expires: number
|
|
127
|
+
} {
|
|
128
|
+
const { code, expires } = mintCode(askingCaller(), signedInAs())
|
|
129
|
+
return { ...commandFor(code, options), expires }
|
|
170
130
|
}
|
|
171
131
|
|
|
172
|
-
/** The command and the link
|
|
132
|
+
/** The command and the link that pair a bridge by `code`, granting `tools`. */
|
|
173
133
|
function commandFor(
|
|
174
|
-
|
|
175
|
-
signedIn: string,
|
|
134
|
+
code: string,
|
|
176
135
|
{ tools }: PairingOptions,
|
|
177
|
-
):
|
|
178
|
-
const { code, expires } = mintCode(caller, signedIn)
|
|
136
|
+
): { command: string; link: string } {
|
|
179
137
|
const site = `${appOrigin() ?? new URL(request().url).origin}${mounted('')}`
|
|
180
138
|
const granted = tools?.length ? tools.join(',') : undefined
|
|
181
139
|
const query = new URLSearchParams({ site, code, version: BRIDGE_VERSION })
|
|
@@ -184,80 +142,25 @@ function commandFor(
|
|
|
184
142
|
return {
|
|
185
143
|
command: `bunx @abide/bridge@${BRIDGE_VERSION} ${site} ${code}${grant}`,
|
|
186
144
|
link: `${BRIDGE_SCHEME}://pair?${query}`,
|
|
187
|
-
expires,
|
|
188
145
|
}
|
|
189
146
|
}
|
|
190
147
|
|
|
191
148
|
/**
|
|
192
149
|
* This browser's pairing as it changes: unpaired with the command to run and the link to open, then
|
|
193
|
-
* paired the moment a bridge connects, and unpaired again if it goes
|
|
194
|
-
*
|
|
195
|
-
* 44.34 — IT FOLLOWS WHOEVER SIGNS IN AT THE BROWSER, though its own request carries the cookies it
|
|
196
|
-
* opened with: a sign-in or a sign-out made since is the pairing it watches from then on, and the
|
|
197
|
-
* command it offers is that sign-in's.
|
|
150
|
+
* paired the moment a bridge connects, and unpaired again if it goes — following whoever signs in at
|
|
151
|
+
* the browser since. 44.20, 44.34.
|
|
198
152
|
*/
|
|
199
153
|
async function* bridge(
|
|
200
154
|
options: PairingOptions = {},
|
|
201
155
|
): AsyncGenerator<BridgeStatus> {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
let watchingAs = ''
|
|
211
|
-
let unwatch = (): void => {}
|
|
212
|
-
let unfollow = (): void => {}
|
|
213
|
-
const follow = (now: string, next: string): void => {
|
|
214
|
-
caller = now
|
|
215
|
-
signedIn = next
|
|
216
|
-
rouse()
|
|
217
|
-
}
|
|
218
|
-
signal.addEventListener('abort', rouse)
|
|
219
|
-
let pairing: Pairing | undefined
|
|
220
|
-
let said = ''
|
|
221
|
-
try {
|
|
222
|
-
while (!signal.aborted) {
|
|
223
|
-
const watching = `${caller} ${signedIn}`
|
|
224
|
-
if (watching !== watchingAs) {
|
|
225
|
-
unwatch()
|
|
226
|
-
unfollow()
|
|
227
|
-
unwatch = watchPairing(caller, signedIn, rouse)
|
|
228
|
-
unfollow = followSignIn(caller, follow)
|
|
229
|
-
watchingAs = watching
|
|
230
|
-
pairing = undefined
|
|
231
|
-
}
|
|
232
|
-
const paired = pairedBridge(caller, signedIn) !== undefined
|
|
233
|
-
if (
|
|
234
|
-
!paired &&
|
|
235
|
-
(pairing === undefined || pairing.expires <= Date.now())
|
|
236
|
-
)
|
|
237
|
-
pairing = commandFor(caller, signedIn, options)
|
|
238
|
-
const status: BridgeStatus = {
|
|
239
|
-
paired,
|
|
240
|
-
command: paired ? undefined : pairing?.command,
|
|
241
|
-
link: paired ? undefined : pairing?.link,
|
|
242
|
-
}
|
|
243
|
-
// The link is minted with the command, so the command alone tells a new pairing.
|
|
244
|
-
const saying = `${status.paired} ${status.command}`
|
|
245
|
-
if (saying !== said) {
|
|
246
|
-
said = saying
|
|
247
|
-
yield status
|
|
248
|
-
}
|
|
249
|
-
// A code that expires while nothing is paired is replaced with a fresh one.
|
|
250
|
-
const renew = paired
|
|
251
|
-
? undefined
|
|
252
|
-
: setTimeout(rouse, (pairing?.expires ?? 0) - Date.now())
|
|
253
|
-
await waiting.next()
|
|
254
|
-
clearTimeout(renew)
|
|
255
|
-
}
|
|
256
|
-
} finally {
|
|
257
|
-
signal.removeEventListener('abort', rouse)
|
|
258
|
-
unwatch()
|
|
259
|
-
unfollow()
|
|
260
|
-
}
|
|
156
|
+
for await (const code of pairingStatus(
|
|
157
|
+
askingCaller(),
|
|
158
|
+
signedInAs(),
|
|
159
|
+
request().signal,
|
|
160
|
+
))
|
|
161
|
+
yield code === undefined
|
|
162
|
+
? { paired: true, command: undefined, link: undefined }
|
|
163
|
+
: { paired: false, ...commandFor(code, options) }
|
|
261
164
|
}
|
|
262
165
|
|
|
263
166
|
/** The visitor's own Claude Code as a provider, with the pairing that reaches it. */
|
package/src/server/config.ts
CHANGED
|
@@ -12,8 +12,8 @@ import { reactive } from '#shared/reactive/index.ts'
|
|
|
12
12
|
import { validationError } from '#shared/refuse.ts'
|
|
13
13
|
import { runSchema } from '#shared/Schema.ts'
|
|
14
14
|
|
|
15
|
-
import { appDirectories } from './appDirectories.ts'
|
|
16
15
|
import { type Env, HOOKS, inOrder } from './hooks.ts'
|
|
16
|
+
import { packageDirectory } from './packageDirectory.ts'
|
|
17
17
|
import { withoutTrailingSlash } from './withoutTrailingSlash.ts'
|
|
18
18
|
|
|
19
19
|
/** The fields of an app's `package.json` 27.3 derives from. */
|
|
@@ -27,14 +27,13 @@ export type AppPackage = { name?: string; version?: string }
|
|
|
27
27
|
*/
|
|
28
28
|
export const APP_PACKAGE: { current: AppPackage } = { current: {} }
|
|
29
29
|
|
|
30
|
-
/** The `package.json`
|
|
30
|
+
/** The `package.json` of an app on disk (`packageDirectory`), or nothing where it has none. */
|
|
31
31
|
export function packageUnder(root: string): AppPackage {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
32
|
+
try {
|
|
33
|
+
return require(`${packageDirectory(root)}/package.json`) as AppPackage
|
|
34
|
+
} catch {
|
|
35
|
+
return {}
|
|
36
36
|
}
|
|
37
|
-
return {}
|
|
38
37
|
}
|
|
39
38
|
|
|
40
39
|
/**
|
package/src/server/createApp.ts
CHANGED
|
@@ -16,14 +16,17 @@ import { MOUNT, unmounted } from '#shared/mount.ts'
|
|
|
16
16
|
import { PRINCIPAL_HEADER } from '#shared/PRINCIPAL_HEADER.ts'
|
|
17
17
|
import { RPC_SERVER } from '#shared/RPC_SERVER.ts'
|
|
18
18
|
|
|
19
|
-
import { servedTable } from './abideModules.ts'
|
|
20
19
|
import { laneFailure } from './answerFailure.ts'
|
|
21
20
|
import { answerRequest, type AppSources } from './answerRequest.ts'
|
|
22
21
|
import type { Mount } from './answerRpc.ts'
|
|
23
22
|
import { Mux, type MuxConnection } from './answerSocket.ts'
|
|
24
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
type Builder,
|
|
25
|
+
processBuild,
|
|
26
|
+
servedBuild,
|
|
27
|
+
spawnedBuild,
|
|
28
|
+
} from './appBuild.ts'
|
|
25
29
|
import { holdsApp } from './appTree.ts'
|
|
26
|
-
import { buildDirectory } from './buildDirectory.ts'
|
|
27
30
|
import {
|
|
28
31
|
APP_PACKAGE,
|
|
29
32
|
type AppPackage,
|
|
@@ -36,6 +39,7 @@ import { HOOKS, inOrder, reportUnexpected, rootRungs } from './hooks.ts'
|
|
|
36
39
|
import { IDLE_TIMEOUT_SECONDS } from './IDLE_TIMEOUT_SECONDS.ts'
|
|
37
40
|
import { installServer } from './installServer.ts'
|
|
38
41
|
import { admitsMux, muxSocketOptions, upgraded } from './muxUpgrade.ts'
|
|
42
|
+
import { packageDirectory } from './packageDirectory.ts'
|
|
39
43
|
import { plainAnswer } from './plainAnswer.ts'
|
|
40
44
|
import { presumePrincipal } from './principal.ts'
|
|
41
45
|
import { realOrAsGiven } from './realOrAsGiven.ts'
|
|
@@ -50,7 +54,6 @@ import {
|
|
|
50
54
|
serverTiming,
|
|
51
55
|
} from './requestScope.ts'
|
|
52
56
|
import { heldBody } from './streamEvent.ts'
|
|
53
|
-
import { testBuild } from './testBuild.ts'
|
|
54
57
|
import { traceResponse } from './trace.ts'
|
|
55
58
|
|
|
56
59
|
// 28.8 — one line per request.
|
|
@@ -80,13 +83,16 @@ export interface App {
|
|
|
80
83
|
/**
|
|
81
84
|
* The app this process was started in, booted off what `abide build` left beside it.
|
|
82
85
|
*
|
|
83
|
-
* 42.1 — found where the CLI finds it (38.25), and booted off the build (38.27)
|
|
86
|
+
* 42.1 — found where the CLI finds it (38.25), and booted off the build (38.27) — under `bun test`,
|
|
87
|
+
* the build of its sources as they stand (42.16), which the test preload served already (38.109).
|
|
84
88
|
*/
|
|
85
89
|
export async function createApp(): Promise<App> {
|
|
86
90
|
const root = appRoot(undefined)
|
|
87
91
|
const app = await appOnDisk(
|
|
88
|
-
root === undefined
|
|
89
|
-
|
|
92
|
+
root === undefined
|
|
93
|
+
? undefined
|
|
94
|
+
: { root, out: await processBuild(root) },
|
|
95
|
+
spawnedBuild,
|
|
90
96
|
).boot()
|
|
91
97
|
if (typeof app === 'string')
|
|
92
98
|
throw new Error(`abide: createApp found ${app}`)
|
|
@@ -117,13 +123,11 @@ export function noAppHere(): string {
|
|
|
117
123
|
|
|
118
124
|
/**
|
|
119
125
|
* The app on disk at `found` — its source root and build directory, both absolute — or the
|
|
120
|
-
* refusal where nothing was found.
|
|
121
|
-
* builds one nobody built — 38.100) and answers the directory it is in, or `undefined` where
|
|
122
|
-
* nothing under the root builds.
|
|
126
|
+
* refusal where nothing was found. Its boot builds it with `build` where nobody has (38.100).
|
|
123
127
|
*/
|
|
124
128
|
export function appOnDisk(
|
|
125
129
|
found: { root: string; out: string } | undefined,
|
|
126
|
-
|
|
130
|
+
build: Builder,
|
|
127
131
|
): AppHandle {
|
|
128
132
|
if (found === undefined)
|
|
129
133
|
return {
|
|
@@ -132,49 +136,25 @@ export function appOnDisk(
|
|
|
132
136
|
boot: async () => noAppHere(),
|
|
133
137
|
}
|
|
134
138
|
const { root, out } = found
|
|
135
|
-
const origin = originUnder(root)
|
|
136
139
|
return {
|
|
137
|
-
package:
|
|
138
|
-
handlers: async () =>
|
|
139
|
-
|
|
140
|
-
if (!(await serveBuild(root, out, async () => undefined))) return []
|
|
141
|
-
return (await servedTable(root)).handlers
|
|
142
|
-
},
|
|
140
|
+
package: packageUnder(root),
|
|
141
|
+
handlers: async () =>
|
|
142
|
+
(await servedBuild(root, out, undefined))?.table.handlers ?? [],
|
|
143
143
|
boot: async () => {
|
|
144
|
-
const served = await
|
|
144
|
+
const served = await servedBuild(root, out, build)
|
|
145
145
|
if (served === undefined) return `nothing under ${root} to build.`
|
|
146
|
-
|
|
147
|
-
return bootApp(origin, () => loadBuild({ root, out: served }))
|
|
146
|
+
return bootApp(served.origin, served.load)
|
|
148
147
|
},
|
|
149
148
|
}
|
|
150
149
|
}
|
|
151
150
|
|
|
152
|
-
/**
|
|
153
|
-
* The build of the app under `root`, served to this process's imports — or `undefined` where
|
|
154
|
-
* nothing under it builds. `fresh` builds it for its sources as they stand.
|
|
155
|
-
*
|
|
156
|
-
* 42.16 — under `bun test` the app is built for its sources as they stand rather than read off
|
|
157
|
-
* whatever `abide build` last left, which a test run would otherwise need first and could find
|
|
158
|
-
* stale. One answer for `createApp` and the test preload (38.109), so a test that boots the app
|
|
159
|
-
* reuses the build its imports were served from.
|
|
160
|
-
*/
|
|
161
|
-
export async function servedBuild(
|
|
162
|
-
root: string,
|
|
163
|
-
fresh = process.env.NODE_ENV === 'test',
|
|
164
|
-
): Promise<string | undefined> {
|
|
165
|
-
const out = fresh ? await testBuild(root) : buildDirectory(root)
|
|
166
|
-
const { serveBuild } = await import('./serveBuild.ts')
|
|
167
|
-
return (await serveBuild(root, out)) ? out : undefined
|
|
168
|
-
}
|
|
169
|
-
|
|
170
151
|
/**
|
|
171
152
|
* The app's root — `--root`, else `src` where the working directory holds one, else the working
|
|
172
153
|
* directory — or `undefined` where it is no app's.
|
|
173
154
|
*
|
|
174
|
-
* 38.25 and 38.26 — `undefined` is a refusal and not a guess. An app's root is in a package
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
* walking the disk.
|
|
155
|
+
* 38.25 and 38.26 — `undefined` is a refusal and not a guess. An app's root is in a package
|
|
156
|
+
* (`packageDirectory`) and holds a `.abide` file; the package is asked first, so a command run from
|
|
157
|
+
* `/` answers at once rather than walking the disk.
|
|
178
158
|
*
|
|
179
159
|
* ITS REAL PATH, as the bundler and the resolver name every file under it: an app under `/tmp` is
|
|
180
160
|
* `/private/tmp` to both, and one spelling here is one fewer at every reader downstream.
|
|
@@ -183,18 +163,18 @@ export function appRoot(given: string | undefined): string | undefined {
|
|
|
183
163
|
if (given !== undefined) return realOrAsGiven(resolve(given))
|
|
184
164
|
const under = resolve('src')
|
|
185
165
|
const root = realOrAsGiven(existsSync(under) ? under : process.cwd())
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
166
|
+
return existsSync(`${packageDirectory(root)}/package.json`) &&
|
|
167
|
+
holdsApp(root)
|
|
168
|
+
? root
|
|
169
|
+
: undefined
|
|
190
170
|
}
|
|
191
171
|
|
|
192
172
|
/**
|
|
193
173
|
* What an app is booted as besides its sources: its hooks module and its `package.json`.
|
|
194
174
|
*
|
|
195
|
-
* The two things `bootApp` reads before the boot and not through it
|
|
196
|
-
*
|
|
197
|
-
*
|
|
175
|
+
* The two things `bootApp` reads before the boot and not through it — off the server's table of
|
|
176
|
+
* the compilation this process serves (`serveApp`), or handed over by a compiled app's entry, which
|
|
177
|
+
* holds the table itself.
|
|
198
178
|
*/
|
|
199
179
|
export type AppOrigin = {
|
|
200
180
|
/**
|
|
@@ -207,20 +187,6 @@ export type AppOrigin = {
|
|
|
207
187
|
package: AppPackage
|
|
208
188
|
}
|
|
209
189
|
|
|
210
|
-
/**
|
|
211
|
-
* The `AppOrigin` of an app on disk: its package under `root`, and its server blocks, out of the
|
|
212
|
-
* compilation this process serves (`serveCompilation`) — the build's, or `abide dev`'s.
|
|
213
|
-
*/
|
|
214
|
-
export function originUnder(root: string): AppOrigin {
|
|
215
|
-
return {
|
|
216
|
-
hooks: async () => {
|
|
217
|
-
const table = await servedTable(root)
|
|
218
|
-
await Promise.all(table.servers.map((load) => load()))
|
|
219
|
-
},
|
|
220
|
-
package: packageUnder(root),
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
190
|
/**
|
|
225
191
|
* An `App` as a command boots it: with the mux over its tables, which `listen` opens and `abide dev`'s
|
|
226
192
|
* worker opens in its stead — one `AnsweringApp`, and one MCP surface, per app either way.
|
package/src/server/devWorker.ts
CHANGED
|
@@ -21,12 +21,8 @@
|
|
|
21
21
|
import { DEV_ADDRESS } from '#shared/ADDRESSES.ts'
|
|
22
22
|
import { escapeText } from '#shared/escapeText.ts'
|
|
23
23
|
|
|
24
|
-
import {
|
|
25
|
-
|
|
26
|
-
serveCompilation,
|
|
27
|
-
servedTable,
|
|
28
|
-
} from './abideModules.ts'
|
|
29
|
-
import { type AppSources, appSources } from './answerRequest.ts'
|
|
24
|
+
import type { Compilation } from './abideModules.ts'
|
|
25
|
+
import type { AppSources } from './answerRequest.ts'
|
|
30
26
|
import type { Mux, MuxConnection } from './answerSocket.ts'
|
|
31
27
|
import { publishedUnder } from './appTree.ts'
|
|
32
28
|
import {
|
|
@@ -35,7 +31,7 @@ import {
|
|
|
35
31
|
clientBundle,
|
|
36
32
|
type ServedFile,
|
|
37
33
|
} from './clientBundle.ts'
|
|
38
|
-
import { bootApp, type BootedApp
|
|
34
|
+
import { bootApp, type BootedApp } from './createApp.ts'
|
|
39
35
|
import { keepServing } from './faultPolicy.ts'
|
|
40
36
|
import { type Manifest, NO_MANIFEST } from './manifestOf.ts'
|
|
41
37
|
import { admitsMux, muxSocketOptions } from './muxUpgrade.ts'
|
|
@@ -46,6 +42,7 @@ import {
|
|
|
46
42
|
serveOver,
|
|
47
43
|
type ToRelay,
|
|
48
44
|
} from './responseRelay.ts'
|
|
45
|
+
import { appSources, serveApp } from './servedApp.ts'
|
|
49
46
|
import { withAbideSources } from './withAbideSources.ts'
|
|
50
47
|
|
|
51
48
|
// The reload client, which the worker serves and every document it answers loads.
|
|
@@ -211,31 +208,30 @@ async function open(
|
|
|
211
208
|
// the bundler resolves the browser's graph through it, so the two lanes cannot disagree
|
|
212
209
|
// about what a specifier means. Registering it is process-wide — which is a worker rather
|
|
213
210
|
// than the command, so a reload is a new registry rather than a cache to bust.
|
|
214
|
-
|
|
211
|
+
const serving = serveApp(root, compilation)
|
|
215
212
|
|
|
216
213
|
// ASKED FOR TOGETHER. The bundle, the public directory and the server's own
|
|
217
|
-
// tables depend on none of each other — the plugin
|
|
218
|
-
// them asks it anything — and on a reload every one of them is latency the
|
|
219
|
-
// so the bundle is handed over still building (`SourceParts.client`).
|
|
214
|
+
// tables depend on none of each other — `serveApp` registered the plugin as it was called,
|
|
215
|
+
// before any of them asks it anything — and on a reload every one of them is latency the
|
|
216
|
+
// author waits on, so the bundle is handed over still building (`SourceParts.client`).
|
|
220
217
|
//
|
|
221
218
|
// ONE LISTING of the public directories — the compile's — for the bundle's externals and for
|
|
222
219
|
// what is served. 38.19 — OUT OF THE SOURCE TREE. `abide build` copies these into its output
|
|
223
220
|
// and dev has no output, so the address answers where the author's file actually is.
|
|
224
221
|
const published = publishedUnder(root, start.published)
|
|
225
222
|
const bundling = bundleInMemory(root, compilation, start.published)
|
|
226
|
-
const table = await
|
|
227
|
-
const handlers = table.handlers
|
|
223
|
+
const { origin, table } = await serving
|
|
228
224
|
|
|
229
225
|
// THE SAME `App` `abide start` boots, so an app's hooks and middleware run in dev too — and its
|
|
230
226
|
// sources are read INSIDE the boot, as `abide start` reads them: a document's head carries
|
|
231
227
|
// what the resolved configuration says (28.4's app name), which is not resolved before it.
|
|
232
|
-
app = await bootApp(
|
|
228
|
+
app = await bootApp(origin, async () => {
|
|
233
229
|
// 38.17 — THE RELOAD CLIENT IS THIS WORKER'S, not the bundle's, and it goes in whether or
|
|
234
230
|
// not there is an entry beside it: a bundle that failed is exactly when a reader needs the
|
|
235
231
|
// next save to reach them. `clientTags` writes nothing for a manifest with no entry.
|
|
236
232
|
const sources = await appSources({
|
|
237
233
|
table,
|
|
238
|
-
handlers,
|
|
234
|
+
handlers: table.handlers,
|
|
239
235
|
published,
|
|
240
236
|
client: bundling.then((bundle) => ({
|
|
241
237
|
manifest: bundle.manifest,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Mount } from './answerRpc.ts'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The row of a handler file (16.73): its default, answered at the route the file is placed at and
|
|
5
|
+
* named for the file — what its declaration says of it laid over that.
|
|
6
|
+
*/
|
|
7
|
+
export function handlerFileMount(
|
|
8
|
+
file: Pick<Mount, 'address' | 'source' | 'scope' | 'matchers'>,
|
|
9
|
+
declared: Pick<
|
|
10
|
+
Mount,
|
|
11
|
+
| 'method'
|
|
12
|
+
| 'ui'
|
|
13
|
+
| 'cli'
|
|
14
|
+
| 'mcp'
|
|
15
|
+
| 'description'
|
|
16
|
+
| 'copy'
|
|
17
|
+
| 'defaults'
|
|
18
|
+
| 'args'
|
|
19
|
+
| 'result'
|
|
20
|
+
>,
|
|
21
|
+
): Mount {
|
|
22
|
+
return {
|
|
23
|
+
address: file.address,
|
|
24
|
+
method: declared.method,
|
|
25
|
+
source: file.source,
|
|
26
|
+
name: file.address.slice(file.address.lastIndexOf('/') + 1),
|
|
27
|
+
export: 'default',
|
|
28
|
+
scope: file.scope,
|
|
29
|
+
matchers: file.matchers,
|
|
30
|
+
private: false,
|
|
31
|
+
ui: declared.ui,
|
|
32
|
+
cli: declared.cli,
|
|
33
|
+
mcp: declared.mcp,
|
|
34
|
+
description: declared.description,
|
|
35
|
+
copy: declared.copy,
|
|
36
|
+
defaults: declared.defaults,
|
|
37
|
+
args: declared.args,
|
|
38
|
+
result: declared.result,
|
|
39
|
+
}
|
|
40
|
+
}
|
package/src/server/index.ts
CHANGED
|
@@ -14,7 +14,7 @@ import { installServer } from './installServer.ts'
|
|
|
14
14
|
// markup with, 32.20's `safeUrl`, and 31.8's `writePath`.
|
|
15
15
|
export * from '#shared'
|
|
16
16
|
// 18.11 — one write a patch made, as a followed `GET`'s `patch` carries it.
|
|
17
|
-
export type { PatchOp } from '#shared/reactive/
|
|
17
|
+
export type { PatchOp } from '#shared/reactive/patchLog.ts'
|
|
18
18
|
export {
|
|
19
19
|
type Clients,
|
|
20
20
|
DELETE,
|
|
@@ -54,7 +54,7 @@ export { type App, createApp } from './createApp.ts'
|
|
|
54
54
|
export { callsInRender } from './deferredBuilds.ts'
|
|
55
55
|
// 19.9's run-time half: what the module a handler is loaded through calls to hold it to the schema
|
|
56
56
|
// its annotation derives (15.8). Not an app's name.
|
|
57
|
-
export { derivedArgs } from './
|
|
57
|
+
export { derivedArgs } from './argsContract.ts'
|
|
58
58
|
// 37.1's run-time half: what a compiled server module calls around its `<script server>`, so the
|
|
59
59
|
// hooks it makes are filed at its route.
|
|
60
60
|
export {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs' // bun exposes no sync existence check of its own
|
|
2
|
+
import { dirname } from 'node:path' // bun exposes no path helpers of its own
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The package the app under `root` belongs to: `root` where it holds a `package.json`, else the
|
|
6
|
+
* directory above it.
|
|
7
|
+
*
|
|
8
|
+
* `root` is the app's root — `src/` beside its package, or the package itself where it has no
|
|
9
|
+
* `src`. What is the package's rather than the source's — its `package.json`, its `bunfig.toml`, its
|
|
10
|
+
* build (38.27) — is found here and nowhere else.
|
|
11
|
+
*/
|
|
12
|
+
export function packageDirectory(root: string): string {
|
|
13
|
+
return existsSync(`${root}/package.json`) ? root : dirname(root)
|
|
14
|
+
}
|
package/src/server/preload.ts
CHANGED
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
// one place that runs first. The build is `createApp`'s own under `bun test` (42.16), so a test that
|
|
6
6
|
// boots the app afterwards reuses it rather than building twice.
|
|
7
7
|
|
|
8
|
+
import { processBuild, servedBuild, spawnedBuild } from './appBuild.ts'
|
|
8
9
|
import { addDerived } from './config.ts'
|
|
9
|
-
import { appRoot
|
|
10
|
+
import { appRoot } from './createApp.ts'
|
|
10
11
|
|
|
11
12
|
// 38.120 — a sign-in seals under test where the app declared no secret: derived, so the app's
|
|
12
13
|
// `onConfig` default and the env both still win (27.2), where an env value would beat the default.
|
|
@@ -17,7 +18,7 @@ const root = appRoot(undefined)
|
|
|
17
18
|
// packages' `.abide` files are each their own app's rather than one app laid out across them.
|
|
18
19
|
// Fresh whatever `NODE_ENV` says: `bun test` sets it only where nothing else has.
|
|
19
20
|
if (root !== undefined && !(await isWorkspaceRoot(root)))
|
|
20
|
-
await servedBuild(root, true)
|
|
21
|
+
await servedBuild(root, await processBuild(root, true), spawnedBuild)
|
|
21
22
|
|
|
22
23
|
async function isWorkspaceRoot(directory: string): Promise<boolean> {
|
|
23
24
|
const manifest = Bun.file(`${directory}/package.json`)
|
package/src/server/principal.ts
CHANGED
|
@@ -19,11 +19,11 @@ import { refuse } from '#shared/refuse.ts'
|
|
|
19
19
|
import { declare } from '#shared/rpc.ts'
|
|
20
20
|
|
|
21
21
|
import { mountedOwn } from './answerRpc.ts'
|
|
22
|
-
import { signedInAt } from './bridgeRelay.ts'
|
|
23
22
|
import { callerOf, callerOrMint, mintCaller } from './callerOf.ts'
|
|
24
23
|
import { config, positiveSetting } from './config.ts'
|
|
25
24
|
import { HOOKS, inOrder } from './hooks.ts'
|
|
26
25
|
import { type RequestScope, type Sealed, within } from './requestScope.ts'
|
|
26
|
+
import { signInChanged } from './signInChanged.ts'
|
|
27
27
|
|
|
28
28
|
// 26.18.
|
|
29
29
|
const PRINCIPAL_COOKIE = 'abide-principal'
|
|
@@ -268,7 +268,7 @@ async function set(claims: Record<string, unknown>): Promise<void> {
|
|
|
268
268
|
// 44.34 — a status stream opened before this sign-in follows it.
|
|
269
269
|
const caller = callerOf()
|
|
270
270
|
if (caller !== undefined)
|
|
271
|
-
|
|
271
|
+
signInChanged(caller, caller, identityOf(scope.opened))
|
|
272
272
|
principalMembers().document.set(await resolved)
|
|
273
273
|
PRINCIPAL_LOG.debug('set')
|
|
274
274
|
}
|
|
@@ -288,7 +288,8 @@ function clear(): void {
|
|
|
288
288
|
scope.principal = Promise.resolve(ANONYMOUS)
|
|
289
289
|
scope.opened = null
|
|
290
290
|
// 44.34 — and one opened before this sign-out follows it to the caller it rotated to.
|
|
291
|
-
if (signedOut !== undefined)
|
|
291
|
+
if (signedOut !== undefined)
|
|
292
|
+
signInChanged(signedOut, minted, identityOf(null))
|
|
292
293
|
principalMembers().document.set(ANONYMOUS)
|
|
293
294
|
principalMembers().caller.set(minted)
|
|
294
295
|
PRINCIPAL_LOG.debug('clear')
|
package/src/server/remote.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { applyPatch } from '#shared/applyPatch.ts'
|
|
|
7
7
|
import { eventsOf } from '#shared/framing.ts'
|
|
8
8
|
import { HandlerRoute } from '#shared/HandlerRoute.ts'
|
|
9
9
|
import { firstMatch, partsOf } from '#shared/matchRoute.ts'
|
|
10
|
-
import type { PatchOp } from '#shared/reactive/
|
|
10
|
+
import type { PatchOp } from '#shared/reactive/patchLog.ts'
|
|
11
11
|
import { rankRoutes } from '#shared/routePattern.ts'
|
|
12
12
|
import type { Method } from '#shared/rpc.ts'
|
|
13
13
|
import { argsInURL, rpcRequest } from '#shared/rpcRequest.ts'
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { type Host, runCommands } from './commands.ts'
|
|
6
6
|
import { type AppHandle, type AppOrigin, bootApp } from './createApp.ts'
|
|
7
|
-
import { type BuildParts, builtSources } from './
|
|
7
|
+
import { type BuildParts, builtSources } from './servedApp.ts'
|
|
8
8
|
|
|
9
9
|
export async function runCompiled(
|
|
10
10
|
origin: AppOrigin,
|
package/src/server/runDesktop.ts
CHANGED
|
@@ -36,7 +36,7 @@ import { randomToken } from './randomToken.ts'
|
|
|
36
36
|
import { type Component, renderDocument } from './render.ts'
|
|
37
37
|
import { LISTENING } from './requestScope.ts'
|
|
38
38
|
import { runCompiled } from './runCompiled.ts'
|
|
39
|
-
import { type BuildParts, builtSources } from './
|
|
39
|
+
import { type BuildParts, builtSources } from './servedApp.ts'
|
|
40
40
|
|
|
41
41
|
/** What the launcher did last, and what its forms held, kept across launches. 38.73. */
|
|
42
42
|
type Remembered = {
|
package/src/server/seedBuffer.ts
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
// refresh, and says nothing.
|
|
24
24
|
|
|
25
25
|
import { namedLogger } from '#shared/log.ts'
|
|
26
|
-
import { canonicalKey } from '#shared/reactive/
|
|
26
|
+
import { canonicalKey } from '#shared/reactive/argsContract.ts'
|
|
27
27
|
import {
|
|
28
28
|
chunksOfStream,
|
|
29
29
|
isReactive,
|