@abide/abide 0.43.1 → 0.44.1
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/AGENTS.md +293 -219
- package/CHANGELOG.md +63 -0
- package/README.md +191 -67
- package/package.json +2 -2
- package/src/abideLsp.ts +8 -4
- package/src/abideResolverPlugin.ts +17 -2
- package/src/build.ts +9 -0
- package/src/buildDisconnected.ts +2 -0
- package/src/devEntry.ts +44 -9
- package/src/lib/bundle/probeAbideServer.ts +1 -1
- package/src/lib/cli/parseArgvForRpc.ts +25 -26
- package/src/lib/cli/runCli.ts +9 -24
- package/src/lib/cli/tokenizeArgvFlags.ts +84 -0
- package/src/lib/cli/types/CliManifest.ts +6 -4
- package/src/lib/mcp/annotationsForMethod.ts +1 -1
- package/src/lib/mcp/toolResultFromResponse.ts +1 -1
- package/src/lib/mcp/types/JsonRpcRequest.ts +3 -3
- package/src/lib/server/AppModule.ts +1 -1
- package/src/lib/server/cli/handleCliDownload.ts +7 -5
- package/src/lib/server/cli/installScript.ts +3 -3
- package/src/lib/server/prompts/promptRegistry.ts +1 -1
- package/src/lib/server/prompts/types/Prompt.ts +2 -1
- package/src/lib/server/rpc/defineRpc.ts +9 -1
- package/src/lib/server/rpc/dispatchRpcInProcess.ts +2 -2
- package/src/lib/server/rpc/types/RpcHelper.ts +1 -1
- package/src/lib/server/runtime/createPublicAssetServer.ts +2 -2
- package/src/lib/server/runtime/createRouteDispatcher.ts +1 -1
- package/src/lib/server/runtime/createServer.ts +50 -12
- package/src/lib/server/runtime/createUiPageRenderer.ts +7 -11
- package/src/lib/server/runtime/crossOriginForbidden.ts +2 -2
- package/src/lib/server/runtime/crossOriginGate.ts +1 -1
- package/src/lib/server/runtime/devHotModuleResponse.ts +3 -3
- package/src/lib/server/runtime/disableIdleTimeoutForStream.ts +1 -1
- package/src/lib/server/runtime/gzipResponse.ts +14 -16
- package/src/lib/server/runtime/logExposedSurfaces.ts +3 -4
- package/src/lib/server/runtime/runWithRequestScope.ts +4 -1
- package/src/lib/server/runtime/serializeCacheSnapshot.ts +3 -8
- package/src/lib/server/runtime/snapshotEntryFromCache.ts +10 -7
- package/src/lib/server/runtime/streamFromIterator.ts +2 -2
- package/src/lib/server/runtime/types/InspectorRpc.ts +2 -2
- package/src/lib/server/runtime/types/RequestStore.ts +16 -0
- package/src/lib/server/runtime/withResponseDefaults.ts +8 -3
- package/src/lib/server/socket.ts +1 -1
- package/src/lib/server/sockets/createSocketDispatcher.ts +2 -2
- package/src/lib/server/sockets/defineSocket.ts +5 -1
- package/src/lib/server/sockets/socketRegistry.ts +2 -2
- package/src/lib/server/sockets/types/SocketClientFrame.ts +3 -3
- package/src/lib/shared/CACHE_WRAPPED.ts +3 -2
- package/src/lib/shared/REMOTE_FUNCTION.ts +1 -1
- package/src/lib/shared/RPC_ARGS_TYPE.ts +7 -0
- package/src/lib/shared/STREAMING_CONTENT_TYPES.ts +3 -2
- package/src/lib/shared/activeCacheStore.ts +5 -14
- package/src/lib/shared/activePage.ts +5 -19
- package/src/lib/shared/augmentModule.ts +18 -0
- package/src/lib/shared/basePath.ts +6 -5
- package/src/lib/shared/baseResolver.ts +10 -0
- package/src/lib/shared/baseSlot.ts +6 -12
- package/src/lib/shared/bodyValueForKind.ts +35 -0
- package/src/lib/shared/cache.ts +22 -2
- package/src/lib/shared/cacheEntryFromSnapshot.ts +23 -18
- package/src/lib/shared/cacheStoreResolver.ts +12 -0
- package/src/lib/shared/cacheStoreSlot.ts +5 -13
- package/src/lib/shared/changeAffectsClient.ts +35 -0
- package/src/lib/shared/createPushIterator.ts +1 -1
- package/src/lib/shared/createResolverSlot.ts +37 -0
- package/src/lib/shared/debugGate.ts +90 -0
- package/src/lib/shared/decodeResponse.ts +11 -8
- package/src/lib/shared/emitLogRecord.ts +18 -4
- package/src/lib/shared/globalCacheStoreResolver.ts +12 -0
- package/src/lib/shared/globalCacheStoreSlot.ts +6 -11
- package/src/lib/shared/hasReplayableRequest.ts +17 -0
- package/src/lib/shared/html.ts +7 -26
- package/src/lib/shared/isDebugEnabled.ts +3 -10
- package/src/lib/shared/isDebugNegated.ts +4 -6
- package/src/lib/shared/manifestModule.ts +2 -2
- package/src/lib/shared/online.ts +1 -1
- package/src/lib/shared/outboxProbeSlot.ts +1 -1
- package/src/lib/shared/pageResolver.ts +16 -0
- package/src/lib/shared/pageSlot.ts +5 -14
- package/src/lib/shared/pageUrlForFile.ts +1 -1
- package/src/lib/shared/requestScopeResolver.ts +12 -0
- package/src/lib/shared/requestScopeSlot.ts +7 -12
- package/src/lib/shared/responseBodyKind.ts +35 -0
- package/src/lib/shared/routeParamsShape.ts +20 -0
- package/src/lib/shared/setBaseResolver.ts +2 -4
- package/src/lib/shared/setCacheStoreResolver.ts +3 -5
- package/src/lib/shared/setGlobalCacheStoreResolver.ts +3 -5
- package/src/lib/shared/setPageResolver.ts +2 -5
- package/src/lib/shared/setRequestScopeResolver.ts +3 -5
- package/src/lib/shared/snapshotShippable.ts +22 -0
- package/src/lib/shared/types/CacheEntry.ts +1 -1
- package/src/lib/shared/types/CacheInvalidation.ts +3 -3
- package/src/lib/shared/types/ClientFlags.ts +5 -3
- package/src/lib/shared/types/DebugGate.ts +10 -0
- package/src/lib/shared/types/LastConnection.ts +1 -1
- package/src/lib/shared/types/ResolverSlot.ts +10 -0
- package/src/lib/shared/url.ts +4 -1
- package/src/lib/shared/withBase.ts +1 -2
- package/src/lib/shared/writeDts.ts +10 -3
- package/src/lib/shared/writePublicAssetsDts.ts +5 -9
- package/src/lib/shared/writeRoutesDts.ts +14 -46
- package/src/lib/shared/writeRpcDts.ts +13 -12
- package/src/lib/shared/writeTestRpcDts.ts +11 -11
- package/src/lib/shared/writeTestSocketsDts.ts +9 -9
- package/src/lib/test/createTestSocketChannel.ts +3 -3
- package/src/lib/ui/COMPONENT_WRAPPER_PREFIX.ts +1 -1
- package/src/lib/ui/compile/ABIDE_SEMANTIC_TOKENS_LEGEND.ts +40 -6
- package/src/lib/ui/compile/REACTIVE_CALLEES.ts +1 -1
- package/src/lib/ui/compile/UI_RUNTIME_IMPORTS.ts +49 -37
- package/src/lib/ui/compile/abideUiPlugin.ts +10 -8
- package/src/lib/ui/compile/assertRuntimeHelpersBound.ts +27 -13
- package/src/lib/ui/compile/awaitPlan.ts +14 -2
- package/src/lib/ui/compile/catchBinding.ts +8 -0
- package/src/lib/ui/compile/classStyleMergePlan.ts +108 -0
- package/src/lib/ui/compile/collectAbideDiagnostics.ts +11 -0
- package/src/lib/ui/compile/compileComponent.ts +4 -4
- package/src/lib/ui/compile/compileModule.ts +56 -21
- package/src/lib/ui/compile/compileSSR.ts +12 -10
- package/src/lib/ui/compile/compileShadow.ts +163 -12
- package/src/lib/ui/compile/composeProps.ts +3 -3
- package/src/lib/ui/compile/createShadowLanguageService.ts +58 -4
- package/src/lib/ui/compile/createShadowProgram.ts +2 -1
- package/src/lib/ui/compile/createShadowScope.ts +42 -0
- package/src/lib/ui/compile/decodeHtmlEntities.ts +4 -0
- package/src/lib/ui/compile/desugarSignals.ts +12 -7
- package/src/lib/ui/compile/eachPlan.ts +61 -0
- package/src/lib/ui/compile/elementPlan.ts +55 -0
- package/src/lib/ui/compile/encodeSemanticTokens.ts +24 -1
- package/src/lib/ui/compile/generateBuild.ts +156 -127
- package/src/lib/ui/compile/generateSSR.ts +112 -73
- package/src/lib/ui/compile/ifPlan.ts +7 -1
- package/src/lib/ui/compile/interpolatedTemplateLiteral.ts +25 -0
- package/src/lib/ui/compile/isPlainIdentifier.ts +7 -0
- package/src/lib/ui/compile/isTextLeaf.ts +8 -7
- package/src/lib/ui/compile/lowerContext.ts +36 -46
- package/src/lib/ui/compile/lowerDocAccess.ts +2 -2
- package/src/lib/ui/compile/lowerScript.ts +1 -1
- package/src/lib/ui/compile/markupTokens.ts +313 -0
- package/src/lib/ui/compile/pagePropsType.ts +23 -0
- package/src/lib/ui/compile/parseTemplate.ts +135 -62
- package/src/lib/ui/compile/reactiveBinding.ts +47 -0
- package/src/lib/ui/compile/renameSignalRefs.ts +44 -10
- package/src/lib/ui/compile/skeletonable.ts +5 -5
- package/src/lib/ui/compile/snippetPlan.ts +28 -0
- package/src/lib/ui/compile/spreadExcludedNames.ts +4 -4
- package/src/lib/ui/compile/structuralBlockTokens.ts +3 -4
- package/src/lib/ui/compile/switchPlan.ts +7 -1
- package/src/lib/ui/compile/tryPlan.ts +8 -1
- package/src/lib/ui/compile/types/AnalyzedComponent.ts +2 -2
- package/src/lib/ui/compile/types/Binding.ts +24 -0
- package/src/lib/ui/compile/types/CompiledShadow.ts +13 -1
- package/src/lib/ui/compile/types/ElementPlan.ts +43 -0
- package/src/lib/ui/compile/types/ShadowKind.ts +9 -0
- package/src/lib/ui/compile/types/ShadowScope.ts +19 -0
- package/src/lib/ui/compile/types/TemplateAttr.ts +7 -0
- package/src/lib/ui/compile/types/TemplateNode.ts +9 -9
- package/src/lib/ui/compile/withBindings.ts +45 -0
- package/src/lib/ui/createScope.ts +2 -2
- package/src/lib/ui/dom/MATHML_NAMESPACE.ts +2 -2
- package/src/lib/ui/dom/SVG_NAMESPACE.ts +3 -2
- package/src/lib/ui/dom/appendText.ts +1 -1
- package/src/lib/ui/dom/applyResolved.ts +1 -1
- package/src/lib/ui/dom/buildDetachedRange.ts +4 -3
- package/src/lib/ui/dom/cloneStatic.ts +3 -2
- package/src/lib/ui/dom/each.ts +26 -7
- package/src/lib/ui/dom/foreignWrapperTag.ts +2 -2
- package/src/lib/ui/dom/mountChild.ts +8 -1
- package/src/lib/ui/dom/mountSwappableRange.ts +79 -0
- package/src/lib/ui/dom/readCall.ts +6 -4
- package/src/lib/ui/dom/scopeLabel.ts +3 -3
- package/src/lib/ui/dom/skeleton.ts +10 -1
- package/src/lib/ui/dom/switchBlock.ts +17 -63
- package/src/lib/ui/dom/types/EachRow.ts +4 -0
- package/src/lib/ui/dom/types/SkeletonHoles.ts +4 -2
- package/src/lib/ui/dom/when.ts +11 -64
- package/src/lib/ui/history.ts +1 -2
- package/src/lib/ui/html.ts +28 -0
- package/src/lib/ui/installHotBridge.ts +0 -2
- package/src/lib/ui/renderChain.ts +16 -3
- package/src/lib/ui/router.ts +24 -3
- package/src/lib/ui/rpcOutbox/createOutboxQueue.ts +1 -1
- package/src/lib/ui/runtime/CHILD_PRESENT.ts +8 -0
- package/src/lib/ui/runtime/OUTLET_MARKER.ts +2 -2
- package/src/lib/ui/runtime/RANGE_MARKER.ts +1 -1
- package/src/lib/ui/runtime/REACTIVE_CONTEXT.ts +9 -3
- package/src/lib/ui/runtime/captureModelDoc.ts +1 -1
- package/src/lib/ui/runtime/createDoc.ts +107 -13
- package/src/lib/ui/runtime/createEffectNode.ts +5 -5
- package/src/lib/ui/runtime/flushEffects.ts +22 -9
- package/src/lib/ui/runtime/historyEntries.ts +1 -1
- package/src/lib/ui/runtime/track.ts +4 -2
- package/src/lib/ui/runtime/trigger.ts +34 -20
- package/src/lib/ui/runtime/types/Route.ts +2 -2
- package/src/lib/ui/runtime/types/UiComponent.ts +3 -2
- package/src/lib/ui/runtime/types/UiProps.ts +9 -12
- package/src/lib/ui/runtime/walkPath.ts +15 -3
- package/src/lib/ui/startClient.ts +41 -14
- package/src/lib/ui/state.ts +3 -3
- package/src/lib/ui/types/Scope.ts +3 -3
- package/src/serverBuildPlugins.ts +11 -5
- package/src/zodCjsPlugin.ts +51 -0
- package/template/.zed/settings.json +7 -1
- package/template/package.json +3 -0
- package/template/src/ui/pages/layout.abide +3 -5
- package/src/lib/shared/matchesDebugPattern.ts +0 -16
- package/src/lib/shared/parseDebugPatterns.ts +0 -21
|
@@ -10,7 +10,7 @@ Confirms a URL points at a abide server before the launcher navigates the app
|
|
|
10
10
|
window there, by fetching its unauthenticated health endpoint. Returns the
|
|
11
11
|
server's identity on success, or undefined when nothing abide answers — a network
|
|
12
12
|
error, the wrong port, or a non-abide page (a bare 403/404, a different app, a
|
|
13
|
-
captive portal's 200 — hence the `abide: true` body check, never response.ok
|
|
13
|
+
captive portal's 200 — hence the `abide: true` (or version string) body check, never response.ok
|
|
14
14
|
alone). The endpoint bypasses the app's own middleware, so an auth-guarded abide
|
|
15
15
|
app still verifies here even though its pages would later redirect to a login.
|
|
16
16
|
Probes HEALTH_PATH first, falling back to the IDENTITY_PATH alias so a newer
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { tokenizeArgvFlags } from './tokenizeArgvFlags.ts'
|
|
2
|
+
|
|
1
3
|
/*
|
|
2
4
|
Parses an argv tail into the JSON args bag for an RPC. The JSON Schema
|
|
3
5
|
on the manifest entry (when present) drives flag typing:
|
|
@@ -9,8 +11,10 @@ on the manifest entry (when present) drives flag typing:
|
|
|
9
11
|
|
|
10
12
|
For complex shapes (nested objects, unions, anyOf) the CLI exposes
|
|
11
13
|
`--json <stringified-args>` as an escape hatch that supplies the whole
|
|
12
|
-
args bag verbatim. Stdin: if a JSON object arrives piped in, it
|
|
13
|
-
as the full args bag
|
|
14
|
+
args bag verbatim. Stdin: if a JSON object arrives piped in with no argv flags, it is used
|
|
15
|
+
as the full args bag. Stdin and flags are mutually exclusive — stdin is
|
|
16
|
+
skipped when argv is non-empty to avoid hanging on a pipe that never
|
|
17
|
+
sends EOF.
|
|
14
18
|
|
|
15
19
|
Unrecognised flags throw — early loud feedback is more useful than
|
|
16
20
|
silent drops.
|
|
@@ -45,44 +49,39 @@ export async function parseArgvForRpc(
|
|
|
45
49
|
}
|
|
46
50
|
}
|
|
47
51
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
/* The shared tokenizer owns the flag-consumption grammar (boolean / inline /
|
|
53
|
+
`--json` / `--no-` negation). This loop layers RPC value semantics on each
|
|
54
|
+
yielded token: JSON-blob merge, Number coercion, array accumulation. */
|
|
55
|
+
for (const token of tokenizeArgvFlags(argv, jsonSchema)) {
|
|
56
|
+
if (token.positional !== undefined) {
|
|
57
|
+
throw new Error(`unexpected positional argument: ${token.positional}`)
|
|
58
|
+
}
|
|
59
|
+
if (token.isJson) {
|
|
60
|
+
if (token.missingValue || token.value === undefined) {
|
|
53
61
|
throw new Error('--json requires a value')
|
|
54
62
|
}
|
|
55
|
-
const parsed = JSON.parse(
|
|
63
|
+
const parsed = JSON.parse(token.value)
|
|
56
64
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
57
65
|
throw new Error('--json value must be a JSON object')
|
|
58
66
|
}
|
|
59
67
|
Object.assign(args, parsed)
|
|
60
68
|
continue
|
|
61
69
|
}
|
|
62
|
-
|
|
63
|
-
|
|
70
|
+
// The tokenizer treats `--help` / `-h` as a help token; in RPC parsing
|
|
71
|
+
// they are an unrecognised flag, surfaced loudly like any other.
|
|
72
|
+
if (token.isHelp || token.name === undefined) {
|
|
73
|
+
throw new Error('unexpected --help flag')
|
|
64
74
|
}
|
|
65
|
-
const
|
|
66
|
-
const
|
|
67
|
-
? [stripped.slice(0, stripped.indexOf('=')), stripped.slice(stripped.indexOf('=') + 1)]
|
|
68
|
-
: [stripped, undefined]
|
|
69
|
-
/* `--no-x` negates only a known boolean property x; otherwise the literal
|
|
70
|
-
name wins, so a property legitimately named `no-…` stays reachable. */
|
|
71
|
-
const negatedName = literalName.startsWith('no-')
|
|
72
|
-
? literalName.slice('no-'.length)
|
|
73
|
-
: undefined
|
|
74
|
-
const isNegated = negatedName !== undefined && properties[negatedName]?.type === 'boolean'
|
|
75
|
-
const name = isNegated ? (negatedName as string) : literalName
|
|
76
|
-
const prop = properties[name]
|
|
77
|
-
const propType = prop?.type
|
|
75
|
+
const name = token.name
|
|
76
|
+
const propType = properties[name]?.type
|
|
78
77
|
if (propType === 'boolean') {
|
|
79
|
-
args[name] = !
|
|
78
|
+
args[name] = !token.negated
|
|
80
79
|
continue
|
|
81
80
|
}
|
|
82
|
-
|
|
83
|
-
if (value === undefined) {
|
|
81
|
+
if (token.missingValue || token.value === undefined) {
|
|
84
82
|
throw new Error(`--${name} requires a value`)
|
|
85
83
|
}
|
|
84
|
+
const value = token.value
|
|
86
85
|
if (propType === 'number' || propType === 'integer') {
|
|
87
86
|
// Reject a blank value explicitly — `Number('')` / `Number(' ')` is 0,
|
|
88
87
|
// not NaN, so the NaN guard alone would silently coerce it to zero.
|
package/src/lib/cli/runCli.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { printTrimmed } from './printTrimmed.ts'
|
|
|
9
9
|
import { resolveCliTarget } from './resolveCliTarget.ts'
|
|
10
10
|
import { runSession } from './runSession.ts'
|
|
11
11
|
import { startLocalInstance } from './startLocalInstance.ts'
|
|
12
|
+
import { tokenizeArgvFlags } from './tokenizeArgvFlags.ts'
|
|
12
13
|
import type { CliManifest } from './types/CliManifest.ts'
|
|
13
14
|
import type { CliTarget } from './types/CliTarget.ts'
|
|
14
15
|
|
|
@@ -16,37 +17,21 @@ const isHelpFlag = (arg: string): boolean => arg === '--help' || arg === '-h'
|
|
|
16
17
|
|
|
17
18
|
/*
|
|
18
19
|
Whether a command's argv tail genuinely requests help, i.e. a help flag appears
|
|
19
|
-
at a flag position rather than as the value of a value-expecting flag.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
at a flag position rather than as the value of a value-expecting flag. Shares
|
|
21
|
+
tokenizeArgvFlags' grammar with parseArgvForRpc so the two can't drift: the
|
|
22
|
+
tokenizer applies the flag-consumption rule (boolean / `--name=…` / `--json`
|
|
23
|
+
consume their value), so a `--help` that was a flag's value is already swallowed
|
|
24
|
+
and never surfaces as a help token — `--title --help` passes `--help` as the
|
|
25
|
+
title value, not a help request.
|
|
23
26
|
*/
|
|
24
27
|
function commandArgvRequestsHelp(
|
|
25
28
|
argvTail: string[],
|
|
26
29
|
jsonSchema: Record<string, unknown> | undefined,
|
|
27
30
|
): boolean {
|
|
28
|
-
const
|
|
29
|
-
(
|
|
30
|
-
for (let index = 0; index < argvTail.length; index += 1) {
|
|
31
|
-
const token = argvTail[index] as string
|
|
32
|
-
if (isHelpFlag(token)) {
|
|
31
|
+
for (const token of tokenizeArgvFlags(argvTail, jsonSchema)) {
|
|
32
|
+
if (token.isHelp) {
|
|
33
33
|
return true
|
|
34
34
|
}
|
|
35
|
-
if (!token.startsWith('--')) {
|
|
36
|
-
continue
|
|
37
|
-
}
|
|
38
|
-
if (token === '--json') {
|
|
39
|
-
index += 1 // consumes its value
|
|
40
|
-
continue
|
|
41
|
-
}
|
|
42
|
-
const rawName = token.slice('--'.length)
|
|
43
|
-
if (rawName.includes('=')) {
|
|
44
|
-
continue // inline value, consumes no following token
|
|
45
|
-
}
|
|
46
|
-
const name = rawName.startsWith('no-') ? rawName.slice('no-'.length) : rawName
|
|
47
|
-
if (properties[name]?.type !== 'boolean') {
|
|
48
|
-
index += 1 // a value-expecting flag consumes the next token
|
|
49
|
-
}
|
|
50
35
|
}
|
|
51
36
|
return false
|
|
52
37
|
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/*
|
|
2
|
+
The single argv-tail flag grammar shared by help-detection (runCli) and RPC arg
|
|
3
|
+
parsing (parseArgvForRpc) so the two can never drift. Walks the tail and yields
|
|
4
|
+
one token per flag occurrence, honouring the schema-driven consumption rule:
|
|
5
|
+
|
|
6
|
+
- `--help` / `-h` → { isHelp: true }, consumes no following token
|
|
7
|
+
- `--json <blob>` → { isJson: true, value }, consumes the next token
|
|
8
|
+
(the value is the verbatim JSON args bag)
|
|
9
|
+
- boolean props (and `--no-x`) → { name, value: undefined }, consumes no token
|
|
10
|
+
- `--name=value` → { name, value }, inline, consumes no token
|
|
11
|
+
- everything else → { name, value }, consumes the next token
|
|
12
|
+
|
|
13
|
+
`--no-x` negates only a known boolean property x; otherwise the literal `no-…`
|
|
14
|
+
name is kept so a property legitimately named `no-…` stays reachable. Reports
|
|
15
|
+
`negated` and `missingValue` so the parser can apply boolean/coercion semantics
|
|
16
|
+
and throw precise errors. A bare positional (no leading `--`) yields
|
|
17
|
+
{ positional: token } so each consumer rejects or ignores it as it sees fit.
|
|
18
|
+
|
|
19
|
+
Pure; reads the schema's `properties` only to classify boolean props.
|
|
20
|
+
*/
|
|
21
|
+
export function* tokenizeArgvFlags(
|
|
22
|
+
argvTail: string[],
|
|
23
|
+
jsonSchema: Record<string, unknown> | undefined,
|
|
24
|
+
): Generator<{
|
|
25
|
+
isHelp?: boolean
|
|
26
|
+
isJson?: boolean
|
|
27
|
+
positional?: string
|
|
28
|
+
name?: string
|
|
29
|
+
value?: string
|
|
30
|
+
negated?: boolean
|
|
31
|
+
missingValue?: boolean
|
|
32
|
+
}> {
|
|
33
|
+
const properties =
|
|
34
|
+
(jsonSchema?.properties as Record<string, { type?: string }> | undefined) ?? {}
|
|
35
|
+
for (let index = 0; index < argvTail.length; index += 1) {
|
|
36
|
+
const token = argvTail[index] as string
|
|
37
|
+
if (token === '--help' || token === '-h') {
|
|
38
|
+
yield { isHelp: true }
|
|
39
|
+
continue
|
|
40
|
+
}
|
|
41
|
+
if (!token.startsWith('--')) {
|
|
42
|
+
yield { positional: token }
|
|
43
|
+
continue
|
|
44
|
+
}
|
|
45
|
+
// `--json <blob>`: the next token is the verbatim JSON args bag.
|
|
46
|
+
if (token === '--json') {
|
|
47
|
+
const value = argvTail[index + 1]
|
|
48
|
+
if (value === undefined) {
|
|
49
|
+
yield { isJson: true, missingValue: true }
|
|
50
|
+
} else {
|
|
51
|
+
index += 1
|
|
52
|
+
yield { isJson: true, value }
|
|
53
|
+
}
|
|
54
|
+
continue
|
|
55
|
+
}
|
|
56
|
+
const stripped = token.slice('--'.length)
|
|
57
|
+
const [literalName, inlineValue] = stripped.includes('=')
|
|
58
|
+
? [stripped.slice(0, stripped.indexOf('=')), stripped.slice(stripped.indexOf('=') + 1)]
|
|
59
|
+
: [stripped, undefined]
|
|
60
|
+
/* `--no-x` negates only a known boolean property x; otherwise the literal
|
|
61
|
+
name wins, so a property legitimately named `no-…` stays reachable. */
|
|
62
|
+
const negatedName = literalName.startsWith('no-')
|
|
63
|
+
? literalName.slice('no-'.length)
|
|
64
|
+
: undefined
|
|
65
|
+
const negated = negatedName !== undefined && properties[negatedName]?.type === 'boolean'
|
|
66
|
+
const name = negated ? (negatedName as string) : literalName
|
|
67
|
+
// Boolean props and inline `--name=value` consume no following token.
|
|
68
|
+
if (properties[name]?.type === 'boolean') {
|
|
69
|
+
yield { name, negated }
|
|
70
|
+
continue
|
|
71
|
+
}
|
|
72
|
+
if (inlineValue !== undefined) {
|
|
73
|
+
yield { name, value: inlineValue }
|
|
74
|
+
continue
|
|
75
|
+
}
|
|
76
|
+
const value = argvTail[index + 1]
|
|
77
|
+
if (value === undefined) {
|
|
78
|
+
yield { name, missingValue: true }
|
|
79
|
+
} else {
|
|
80
|
+
index += 1
|
|
81
|
+
yield { name, value }
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { CliManifestEntry } from './CliManifestEntry.ts'
|
|
2
2
|
|
|
3
3
|
/*
|
|
4
|
-
Map from
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
Map from command name (URL-derived via commandNameForUrl, e.g. "getReport"
|
|
5
|
+
or "users-list") to its manifest entry. Built by the bundler from
|
|
6
|
+
rpcRegistry and socketRegistry; entries are emitted for rpcs and sockets
|
|
7
|
+
with `clients.cli: true`. Sockets produce `<base>-tail` (and `-publish`
|
|
8
|
+
when `allowClientPublish` is set). The CLI binary and any programmatic
|
|
9
|
+
createClient caller read this to dispatch calls.
|
|
8
10
|
*/
|
|
9
11
|
export type CliManifest = Record<string, CliManifestEntry>
|
|
@@ -2,7 +2,7 @@ import type { HttpMethod } from '../shared/types/HttpMethod.ts'
|
|
|
2
2
|
|
|
3
3
|
/*
|
|
4
4
|
Maps an HTTP method to MCP tool annotations so a model can tell a read from
|
|
5
|
-
a write before calling. Abide derives these from the
|
|
5
|
+
a write before calling. Abide derives these from the HTTP method the RPC was
|
|
6
6
|
declared with rather than asking the author to repeat the intent:
|
|
7
7
|
- GET / HEAD → read-only, non-destructive
|
|
8
8
|
- POST → creates; not idempotent, not (necessarily) destructive
|
|
@@ -15,7 +15,7 @@ function asText(value: unknown): string {
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
/*
|
|
18
|
-
Turns an
|
|
18
|
+
Turns an RPC Response into an MCP `tools/call` result. Always
|
|
19
19
|
carries a `text` content block for backward compatibility; adds
|
|
20
20
|
`structuredContent` (an object, per the MCP spec) so models that
|
|
21
21
|
understand structured output get the typed value instead of a stringified
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/*
|
|
2
2
|
JSON-RPC 2.0 request frame as MCP delivers it over Streamable HTTP. The
|
|
3
|
-
`id` is absent for notifications
|
|
4
|
-
|
|
5
|
-
"resources/read".
|
|
3
|
+
`id` is absent for notifications; they are dispatched for their side effect
|
|
4
|
+
and acknowledged with a 202 (JSON-RPC forbids replying with an envelope).
|
|
5
|
+
`method` is a string like "tools/list" or "resources/read".
|
|
6
6
|
*/
|
|
7
7
|
export type JsonRpcRequest = {
|
|
8
8
|
jsonrpc: '2.0'
|
|
@@ -10,7 +10,7 @@ WebSockets are not exposed here — abide's only native WebSocket
|
|
|
10
10
|
surface is the sockets hub (see `abide/server/socket`), multiplexed onto a
|
|
11
11
|
single framework-owned connection per client at `/__abide/sockets`.
|
|
12
12
|
Inside request scopes, the live Bun.Server is reachable via the
|
|
13
|
-
exported `server()` function from `abide/server`; `init` receives it
|
|
13
|
+
exported `server()` function from `abide/server/server`; `init` receives it
|
|
14
14
|
explicitly because it runs outside a request.
|
|
15
15
|
*/
|
|
16
16
|
// @documentation plumbing
|
|
@@ -92,12 +92,14 @@ async function computeBinary(
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
/*
|
|
95
|
-
Handles GET /__abide/cli/<platform> — streams a gzipped tarball
|
|
96
|
-
|
|
97
|
-
ABIDE_APP_URL (and ABIDE_APP_TOKEN if the inbound request was
|
|
95
|
+
Handles GET /__abide/cli/<platform> — streams a gzipped tarball containing
|
|
96
|
+
the platform-specific thin binary, the sibling server binary, and a `.env`
|
|
97
|
+
carrying ABIDE_APP_URL (and ABIDE_APP_TOKEN if the inbound request was
|
|
98
|
+
authenticated).
|
|
98
99
|
|
|
99
|
-
Thin binaries live at `dist/cli-thin/<platform>/<programName>`
|
|
100
|
-
|
|
100
|
+
Thin binaries live at `dist/cli-thin/<platform>/<programName>` (produced
|
|
101
|
+
lazily by `buildCli` on first request). The `.env` baking `ABIDE_APP_URL`
|
|
102
|
+
is generated at download time, not build time. Missing platforms produce
|
|
101
103
|
404 — the install script reports it, doesn't try to fall back.
|
|
102
104
|
*/
|
|
103
105
|
export async function handleCliDownload(
|
|
@@ -7,9 +7,9 @@ platform-specific tarball and extracts it into `$ABIDE_INSTALL_DIR`
|
|
|
7
7
|
(default `~/.local/bin`). The tarball already contains the `.env` next
|
|
8
8
|
to the binary — no separate config write step in the script.
|
|
9
9
|
|
|
10
|
-
The script is rendered server-side so
|
|
11
|
-
|
|
12
|
-
basic shell hygiene.
|
|
10
|
+
The script is rendered server-side so the request origin is baked into the
|
|
11
|
+
`URL` variable at generation time and the embedded curl URL needs no escaping
|
|
12
|
+
or quoting beyond basic shell hygiene.
|
|
13
13
|
*/
|
|
14
14
|
export function installScript(appUrl: string, programName: string): string {
|
|
15
15
|
return `#!/usr/bin/env sh
|
|
@@ -3,7 +3,7 @@ import type { PromptRegistryEntry } from './types/PromptRegistryEntry.ts'
|
|
|
3
3
|
/*
|
|
4
4
|
Process-wide registry of every prompt declared in the app. definePrompt
|
|
5
5
|
inserts on first construction (eagerly when the registry loader walks the
|
|
6
|
-
prompts manifest
|
|
6
|
+
prompts manifest on first enumeration request). The MCP server reads this to build its
|
|
7
7
|
`prompts/list` + `prompts/get` responses.
|
|
8
8
|
*/
|
|
9
9
|
export const promptRegistry = new Map<string, PromptRegistryEntry>()
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
An MCP prompt declared by a markdown file under `src/mcp/prompts/`. The
|
|
3
3
|
resolver plugin parses the file's frontmatter + body and generates a call
|
|
4
4
|
to definePrompt, stamping in the `name` from the file path; `render(args)`
|
|
5
|
-
interpolates the body's `{{name}}` placeholders
|
|
5
|
+
interpolates the body's `{{name}}` placeholders and returns the rendered
|
|
6
|
+
string; `renderPrompt` in mcpSurface.ts then wraps it as the single user
|
|
6
7
|
message returned by `prompts/get`. Prompts are MCP-only — there is no
|
|
7
8
|
client-side counterpart, so the shape carries no ClientFlags.
|
|
8
9
|
*/
|
|
@@ -100,7 +100,15 @@ export function defineRpc<Args, Return>(
|
|
|
100
100
|
function buildRequest(args: Args | undefined): Request {
|
|
101
101
|
const store = requestContext.getStore()
|
|
102
102
|
const baseUrl = store ? store.url.href : 'http://localhost/'
|
|
103
|
-
|
|
103
|
+
/* The forwarded allowlist is request-invariant — derive it once per scope
|
|
104
|
+
and clone per call (buildRpcRequest mutates the Headers). */
|
|
105
|
+
let headers: Headers
|
|
106
|
+
if (store) {
|
|
107
|
+
store.forwardedHeaders ??= forwardHeaders(store.req.headers)
|
|
108
|
+
headers = new Headers(store.forwardedHeaders)
|
|
109
|
+
} else {
|
|
110
|
+
headers = new Headers()
|
|
111
|
+
}
|
|
104
112
|
return buildRpcRequest({ method, url, args, baseUrl, headers })
|
|
105
113
|
}
|
|
106
114
|
|
|
@@ -7,8 +7,8 @@ import { runWithRequestScope } from '../runtime/runWithRequestScope.ts'
|
|
|
7
7
|
Runs a rpc in-process: synthesizes the rpc Request from the remote's own
|
|
8
8
|
method + url and pipes it through remote.fetch — the same handler/validation/
|
|
9
9
|
error path the HTTP router uses, no network hop. The single in-process
|
|
10
|
-
dispatch every consumer surface (the CLI client
|
|
11
|
-
|
|
10
|
+
dispatch every consumer surface (the CLI client and the MCP tool dispatcher)
|
|
11
|
+
routes through, so they can't drift on how a rpc is invoked.
|
|
12
12
|
Takes the RemoteFunction directly — invocation never reads the registry
|
|
13
13
|
entry's schemas/clients (validation is closed over inside the remote), so the
|
|
14
14
|
entry is not a dependency here. `baseUrl` gives the synthetic Request its
|
|
@@ -34,7 +34,7 @@ type MutatingRpcOpts = RpcBaseOpts & {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
/*
|
|
37
|
-
Shared signature for every rpc helper (GET / POST / …).
|
|
37
|
+
Shared signature for every rpc helper (GET / POST / …). Four overloads:
|
|
38
38
|
|
|
39
39
|
- `Rpc(fn, { inputSchema, outputSchema?, clients? })` — `Args` infers
|
|
40
40
|
from `InferInput<InputSchema>`, the handler receives
|
|
@@ -18,8 +18,8 @@ sources, picked at construction:
|
|
|
18
18
|
|
|
19
19
|
Returns a server fn that resolves to `undefined` when no public file
|
|
20
20
|
matches the request path, so the caller falls through to its own 404 /
|
|
21
|
-
middleware path. The path-traversal guard
|
|
22
|
-
|
|
21
|
+
middleware path. The path-traversal guard uses the same `containsTraversal`
|
|
22
|
+
check against encoded `..` segments in the raw URL.
|
|
23
23
|
|
|
24
24
|
Async because disk mode globs `publicDir` once at construction to build a
|
|
25
25
|
Set of the available paths: every page nav and RPC falls through here, so
|
|
@@ -36,7 +36,7 @@ function methodNotAllowed(allow: string): Response {
|
|
|
36
36
|
|
|
37
37
|
/*
|
|
38
38
|
Owns route dispatch: deciding, per registered URL, whether a request hits an
|
|
39
|
-
|
|
39
|
+
an rpc, a page render, or nothing — and the method-matching that picks the
|
|
40
40
|
status. Page URLs (under src/ui/pages/) serve GET/HEAD by rendering; rpc
|
|
41
41
|
URLs (under src/server/rpc/, `/rpc/...`) dispatch to the single declared rpc,
|
|
42
42
|
405 on method mismatch; an unregistered URL is 404. Page and rpc URLs are
|
|
@@ -17,6 +17,7 @@ import { isDebugNegated } from '../../shared/isDebugNegated.ts'
|
|
|
17
17
|
import { logClosingRecord } from '../../shared/logClosingRecord.ts'
|
|
18
18
|
import { OFFLINE_HEADER } from '../../shared/OFFLINE_HEADER.ts'
|
|
19
19
|
import { parseBoundedEnvInt } from '../../shared/parseBoundedEnvInt.ts'
|
|
20
|
+
import { responseBodyKind } from '../../shared/responseBodyKind.ts'
|
|
20
21
|
import { SOCKETS_PATH } from '../../shared/SOCKETS_PATH.ts'
|
|
21
22
|
import { setAppName } from '../../shared/setAppName.ts'
|
|
22
23
|
import { setBaseResolver } from '../../shared/setBaseResolver.ts'
|
|
@@ -242,8 +243,11 @@ export async function createServer({
|
|
|
242
243
|
isn't installed. Resolved at boot so the fetch route below can branch on it.
|
|
243
244
|
*/
|
|
244
245
|
const inspectorHandler = await maybeMountInspector({ name: appName, version: appVersion })
|
|
245
|
-
/* Built on first request, then reused — the rpc registry is frozen after load.
|
|
246
|
-
|
|
246
|
+
/* Built on first request, then reused — the rpc registry is frozen after load.
|
|
247
|
+
Memoised as a promise so two concurrent cold requests share one build instead
|
|
248
|
+
of both building (the second otherwise clobbering the first). A rejected build
|
|
249
|
+
clears the memo so the next request retries rather than caching the failure. */
|
|
250
|
+
let openApiSpec: Promise<ReturnType<typeof buildOpenApiSpec>> | undefined
|
|
247
251
|
const cliCwd = process.cwd()
|
|
248
252
|
|
|
249
253
|
/* Request closing records are on by default — DEBUG=-abide is the off switch (negation, like the abide channel itself). */
|
|
@@ -362,12 +366,19 @@ export async function createServer({
|
|
|
362
366
|
const response = app?.handle
|
|
363
367
|
? await app.handle(req, (next) => handler(next, pathParams, store))
|
|
364
368
|
: await handler(req, pathParams, store)
|
|
369
|
+
/* Classify the body once (S2) and thread it into both downstream
|
|
370
|
+
steps — gzip + the closing-record stream monitor — instead of
|
|
371
|
+
each re-deriving from the Content-Type. */
|
|
372
|
+
const kind = responseBodyKind(response)
|
|
373
|
+
store.responseStreaming = kind === 'streaming'
|
|
374
|
+
// Streaming bodies (sse/jsonl, socket tail) opt out of the idle timeout.
|
|
375
|
+
if (kind === 'streaming') {
|
|
376
|
+
server.timeout(req, 0)
|
|
377
|
+
}
|
|
365
378
|
/* Gzip compressible dynamic bodies (SSR HTML, rpc/json, 404) when the
|
|
366
379
|
client accepts it; streaming frame protocols and static assets are
|
|
367
380
|
passed through untouched (see gzipResponse). */
|
|
368
|
-
|
|
369
|
-
// Streaming bodies (sse/jsonl, socket tail) opt out of the idle timeout.
|
|
370
|
-
return disableIdleTimeoutForStream(server, req, encoded)
|
|
381
|
+
return gzipResponse(req, response, kind)
|
|
371
382
|
})
|
|
372
383
|
}
|
|
373
384
|
|
|
@@ -481,6 +492,27 @@ export async function createServer({
|
|
|
481
492
|
/* Component hot module — the browser imports one edited `.abide`'s
|
|
482
493
|
hot build here instead of reloading (dev component HMR). */
|
|
483
494
|
if (clientFingerprint !== undefined && url.pathname.startsWith(DEV_HOT_PREFIX)) {
|
|
495
|
+
/* This endpoint serves `application/javascript` for the browser's
|
|
496
|
+
`import()`. A TOP-LEVEL NAVIGATION to it — clicking the module link in a
|
|
497
|
+
stack trace, or opening the URL — would DOWNLOAD the file, since browsers
|
|
498
|
+
can't render JS as a document. A navigation sends `Accept: text/html`;
|
|
499
|
+
`import()` sends a wildcard Accept. Redirect a navigation back to a real
|
|
500
|
+
page (the referring page when same-origin, else the mount root) so the
|
|
501
|
+
error surfaces in context as a normal render instead of saving a file. */
|
|
502
|
+
if ((req.headers.get('accept') ?? '').includes('text/html')) {
|
|
503
|
+
const referer = req.headers.get('referer')
|
|
504
|
+
const page =
|
|
505
|
+
referer !== null &&
|
|
506
|
+
URL.canParse(referer) &&
|
|
507
|
+
new URL(referer).origin === url.origin &&
|
|
508
|
+
!new URL(referer).pathname.startsWith(DEV_HOT_PREFIX)
|
|
509
|
+
? referer
|
|
510
|
+
: base || '/'
|
|
511
|
+
return new Response(null, {
|
|
512
|
+
status: 302,
|
|
513
|
+
headers: { Location: page, 'Cache-Control': NO_STORE },
|
|
514
|
+
})
|
|
515
|
+
}
|
|
484
516
|
return devHotModuleResponse(
|
|
485
517
|
decodeURIComponent(url.pathname.slice(DEV_HOT_PREFIX.length)),
|
|
486
518
|
)
|
|
@@ -562,14 +594,20 @@ export async function createServer({
|
|
|
562
594
|
req,
|
|
563
595
|
{},
|
|
564
596
|
async () => {
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
597
|
+
openApiSpec ??= ensureRegistriesLoaded()
|
|
598
|
+
.then(() =>
|
|
599
|
+
buildOpenApiSpec({
|
|
600
|
+
title: appName,
|
|
601
|
+
version: appVersion,
|
|
602
|
+
}),
|
|
603
|
+
)
|
|
604
|
+
.catch((error) => {
|
|
605
|
+
// Don't cache a failed build — clear the memo so a
|
|
606
|
+
// later request retries instead of 500-ing forever.
|
|
607
|
+
openApiSpec = undefined
|
|
608
|
+
throw error
|
|
570
609
|
})
|
|
571
|
-
|
|
572
|
-
return Response.json(openApiSpec, {
|
|
610
|
+
return Response.json(await openApiSpec, {
|
|
573
611
|
headers: { 'Cache-Control': NO_STORE },
|
|
574
612
|
})
|
|
575
613
|
},
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { appNameSlot } from '../../shared/appNameSlot.ts'
|
|
2
2
|
import { SSR_CACHE_CONTROL } from '../../shared/CACHE_CONTROL_VALUES.ts'
|
|
3
3
|
import { formatTraceparent } from '../../shared/formatTraceparent.ts'
|
|
4
|
-
import { isReplayableMethod } from '../../shared/isReplayableMethod.ts'
|
|
5
4
|
import { layoutChainForRoute } from '../../shared/layoutChainForRoute.ts'
|
|
6
5
|
import { safeJsonForScript } from '../../shared/safeJsonForScript.ts'
|
|
7
|
-
import
|
|
6
|
+
import { snapshotShippable } from '../../shared/snapshotShippable.ts'
|
|
8
7
|
import type { CacheSnapshotEntry } from '../../shared/types/CacheSnapshotEntry.ts'
|
|
9
8
|
import type { StreamedResolution } from '../../shared/types/StreamedResolution.ts'
|
|
10
9
|
import { renderChain } from '../../ui/renderChain.ts'
|
|
@@ -23,6 +22,9 @@ type LoadPage = () => Promise<{ default: UiComponent }>
|
|
|
23
22
|
|
|
24
23
|
const SSR_MARKER = /<!--ssr:(head|body|state)-->/g
|
|
25
24
|
const BODY_MARKER = '<!--ssr:body-->'
|
|
25
|
+
/* Compiled once — both fill the shell per page render. */
|
|
26
|
+
const HEAD_STATE_MARKER = /<!--ssr:(head|state)-->/g
|
|
27
|
+
const HEAD_CLOSE_TAG = /<\/head>/i
|
|
26
28
|
|
|
27
29
|
function wantsJson(request: Request): boolean {
|
|
28
30
|
return (request.headers.get('accept') ?? '').includes('application/json')
|
|
@@ -140,7 +142,7 @@ export function createUiPageRenderer({
|
|
|
140
142
|
build-time injector). A no-op when there are none or the shell carries no </head>. */
|
|
141
143
|
function injectRoutePreloads(html: string, routeUrl: string): string {
|
|
142
144
|
const links = routePreloadLinks(routeUrl)
|
|
143
|
-
return links === '' ? html : html.replace(
|
|
145
|
+
return links === '' ? html : html.replace(HEAD_CLOSE_TAG, `${links}</head>`)
|
|
144
146
|
}
|
|
145
147
|
|
|
146
148
|
async function renderPage(
|
|
@@ -212,9 +214,7 @@ export function createUiPageRenderer({
|
|
|
212
214
|
`<script>${SSR_SWAP_SCRIPT}${CACHE_RESOLVE_SCRIPT}</script>` +
|
|
213
215
|
`${await stateTag(routeUrl, params, store, inline)}`
|
|
214
216
|
const filled = injectRoutePreloads(
|
|
215
|
-
shell.replace(
|
|
216
|
-
key === 'head' ? head : '',
|
|
217
|
-
),
|
|
217
|
+
shell.replace(HEAD_STATE_MARKER, (_match, key: string) => (key === 'head' ? head : '')),
|
|
218
218
|
routeUrl,
|
|
219
219
|
)
|
|
220
220
|
const [before, after] = filled.split(BODY_MARKER)
|
|
@@ -245,11 +245,7 @@ export function createUiPageRenderer({
|
|
|
245
245
|
bundle, so startClient seeds the store and the block's subscription read
|
|
246
246
|
is warm. Skip keys already shipped inline in __SSR__. */
|
|
247
247
|
const streamedEntries = Array.from(store.cache.entries.values()).filter(
|
|
248
|
-
(entry
|
|
249
|
-
entry.settled === true &&
|
|
250
|
-
entry.request !== undefined &&
|
|
251
|
-
isReplayableMethod(entry.request.method.toUpperCase()) &&
|
|
252
|
-
!inlinedKeys.has(entry.key),
|
|
248
|
+
(entry) => snapshotShippable(entry) && !inlinedKeys.has(entry.key),
|
|
253
249
|
)
|
|
254
250
|
for await (const resolution of streamCacheResolutions(
|
|
255
251
|
store.cache,
|
|
@@ -3,8 +3,8 @@ import { TEXT_PLAIN } from '../../shared/TEXT_PLAIN.ts'
|
|
|
3
3
|
|
|
4
4
|
/*
|
|
5
5
|
The framework's CSRF refusal, shared by every same-origin gate — the socket
|
|
6
|
-
upgrade, the socket REST publish face, the MCP endpoint, and mutating
|
|
7
|
-
|
|
6
|
+
upgrade, the socket REST publish face, the MCP endpoint, and mutating rpcs
|
|
7
|
+
— so the 403 body can't drift between surfaces. `hint` appends a
|
|
8
8
|
surface-specific remedy; rpc names its `crossOrigin: true` opt-out so the
|
|
9
9
|
first developer this 403s can self-serve.
|
|
10
10
|
*/
|
|
@@ -6,7 +6,7 @@ import { isCrossOriginRequest } from './isCrossOriginRequest.ts'
|
|
|
6
6
|
/*
|
|
7
7
|
The framework's CSRF/CSWSH posture in one place: a cross-origin browser
|
|
8
8
|
request to a mutating framework endpoint is refused. Every endpoint that
|
|
9
|
-
parses bodies ignoring Content-Type (
|
|
9
|
+
parses bodies ignoring Content-Type (rpcs, socket publish, MCP JSON-RPC)
|
|
10
10
|
must gate here — a hostile page's text/plain form trick could otherwise
|
|
11
11
|
smuggle a payload in with the visitor's ambient cookies; non-browser clients
|
|
12
12
|
send no Origin and pass. Returns the 403 to send, or undefined to proceed.
|
|
@@ -11,8 +11,8 @@ mode — runtime sourced from `window.__abide`, self-invoking `hotReplace` — a
|
|
|
11
11
|
transpiles the embedded author TypeScript to plain JS the browser can import
|
|
12
12
|
directly (the normal pipeline relies on the bundler's `ts` loader for this). The
|
|
13
13
|
browser imports this in place of a reload; on load it swaps the component's live
|
|
14
|
-
instances. The id is guarded as a project-relative `.abide` under cwd; a bad
|
|
15
|
-
missing file 404s so the client falls back to a reload.
|
|
14
|
+
instances. The id is guarded as a project-relative `.abide` under cwd; a bad
|
|
15
|
+
module ID returns 400; a missing file 404s so the client falls back to a reload.
|
|
16
16
|
*/
|
|
17
17
|
export async function devHotModuleResponse(moduleId: string): Promise<Response> {
|
|
18
18
|
const root = process.cwd()
|
|
@@ -26,7 +26,7 @@ export async function devHotModuleResponse(moduleId: string): Promise<Response>
|
|
|
26
26
|
if (source === undefined) {
|
|
27
27
|
return new Response('not found', { status: 404 })
|
|
28
28
|
}
|
|
29
|
-
const code = compileModule(source, {
|
|
29
|
+
const { code } = compileModule(source, {
|
|
30
30
|
isLayout: moduleId.endsWith('layout.abide'),
|
|
31
31
|
moduleId,
|
|
32
32
|
hot: true,
|
|
@@ -13,7 +13,7 @@ than `body instanceof ReadableStream`, since every bodied Response exposes one.
|
|
|
13
13
|
A streamed SSR page (`text/html`) is deliberately not opted out: it inherits the
|
|
14
14
|
configured `idleTimeout` as a bounded cap on how long it can hold the connection,
|
|
15
15
|
and the client re-fetches any placeholder left unresolved by a cut stream (see
|
|
16
|
-
|
|
16
|
+
streamCacheResolutions, which yields { key, miss } markers the client re-fetches). Non-stream responses pass through.
|
|
17
17
|
*/
|
|
18
18
|
export function disableIdleTimeoutForStream(
|
|
19
19
|
server: Server<unknown>,
|