@abide/abide 0.101.0 → 0.103.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/CHANGELOG.md +26 -0
- package/bin/abide.ts +226 -164
- package/docs/REGISTRY.md +730 -0
- package/docs/RULEBOOK.md +2768 -0
- package/docs/abide.md +13127 -0
- package/package.json +6 -3
- package/scaffold/AGENTS.md +38 -0
- package/scaffold/CLAUDE.md +1 -0
- package/scaffold/src/layout.abide +18 -0
- package/src/compiler/Lowering.ts +78 -9
- package/src/compiler/ShadowMap.ts +2 -2
- package/src/compiler/TypeSession.ts +341 -71
- package/src/compiler/appMap.ts +96 -0
- package/src/compiler/argsSchemaOf.ts +1 -1
- package/src/compiler/checkApp.ts +53 -8
- package/src/compiler/checkedShadow.ts +73 -6
- package/src/compiler/classifyRead.ts +6 -4
- package/src/compiler/classifyTemplate.ts +1 -1
- package/src/compiler/compileApp.ts +598 -200
- package/src/compiler/compiledEntry.ts +48 -40
- package/src/compiler/componentGraph.ts +57 -71
- package/src/compiler/declaredExports.ts +26 -9
- package/src/compiler/emitClient.ts +142 -72
- package/src/compiler/emitComponent.ts +41 -12
- package/src/compiler/emitSource.ts +102 -13
- package/src/compiler/handlersUnder.ts +164 -76
- package/src/compiler/hooksCalled.ts +35 -0
- package/src/compiler/index.ts +7 -0
- package/src/compiler/mountsOf.ts +150 -0
- package/src/compiler/nearest.ts +14 -0
- package/src/compiler/parseAbide.ts +157 -20
- package/src/compiler/projectShadow.ts +250 -19
- package/src/compiler/reactiveTypes.ts +20 -7
- package/src/compiler/readSources.ts +15 -2
- package/src/compiler/readsIn.ts +62 -8
- package/src/compiler/renderMarkup.ts +3 -3
- package/src/compiler/scaffold.ts +37 -15
- package/src/compiler/serveLanguage.ts +101 -34
- package/src/compiler/sharedModuleWrites.ts +2 -2
- package/src/compiler/sourceMapOf.ts +193 -0
- package/src/compiler/splitImports.ts +205 -83
- package/src/compiler/tokenize.ts +14 -0
- package/src/compiler/writeDefaultProject.ts +29 -2
- package/src/server/CLIENT_BUILD.ts +11 -13
- package/src/server/DEFAULT_PORT.ts +1 -1
- package/src/server/MODULE_QUERIES.ts +27 -0
- package/src/server/TABLES.ts +7 -0
- package/src/server/VIRTUAL_DIRECTORY.ts +4 -5
- package/src/server/abideModules.ts +457 -0
- package/src/server/answerFailure.ts +16 -6
- package/src/server/answerRequest.ts +241 -107
- package/src/server/answerRpc.ts +107 -33
- package/src/server/answerSocket.ts +65 -20
- package/src/server/answerValue.ts +22 -9
- package/src/server/appDirectories.ts +1 -1
- package/src/server/appPlugins.ts +1 -1
- package/src/server/appTree.ts +238 -0
- package/src/server/argsFor.ts +2 -2
- package/src/server/assistant.ts +2 -2
- package/src/server/bridgeRelay.ts +6 -8
- package/src/server/claudeCode.ts +0 -6
- package/src/server/clientBuildOptions.ts +8 -3
- package/src/server/coerceArgs.ts +1 -1
- package/src/server/commands.ts +38 -37
- package/src/server/componentModules.d.ts +2 -1
- package/src/server/compressAnswer.ts +4 -4
- package/src/server/config.ts +25 -29
- package/src/server/configFields.ts +31 -25
- package/src/server/createApp.ts +77 -79
- package/src/server/devWorker.ts +118 -181
- package/src/server/executableName.ts +4 -0
- package/src/server/fenceServer.ts +57 -32
- package/src/server/generatedSurfaces.ts +99 -66
- package/src/server/hooks.ts +137 -52
- package/src/server/iconAccent.ts +191 -0
- package/src/server/index.ts +12 -7
- package/src/server/installSourceMaps.ts +93 -0
- package/src/server/launcherOptions.ts +33 -31
- package/src/server/launcherPage.ts +107 -64
- package/src/server/manifestOf.ts +16 -13
- package/src/server/namesUnder.ts +2 -2
- package/src/server/nativeMenu.ts +6 -6
- package/src/server/negotiateEncoding.ts +1 -1
- package/src/server/preload.ts +13 -0
- package/src/server/principal.ts +10 -5
- package/src/server/realOrAsGiven.ts +10 -0
- package/src/server/refuseAddressCollision.ts +21 -14
- package/src/server/remote.ts +49 -52
- package/src/server/render.ts +65 -94
- package/src/server/renderComponent.ts +5 -0
- package/src/server/reportHealth.ts +2 -2
- package/src/server/requestScope.ts +12 -1
- package/src/server/runBuild.ts +24 -0
- package/src/server/runCompiled.ts +5 -5
- package/src/server/runDesktop.ts +48 -28
- package/src/server/serveBuild.ts +43 -52
- package/src/server/serveDev.ts +79 -37
- package/src/server/shell.ts +7 -22
- package/src/server/sourceMappings.ts +139 -0
- package/src/server/surfaceOf.ts +20 -3
- package/src/server/testBuild.ts +1 -19
- package/src/server/withAbideSources.ts +117 -0
- package/src/shared/ADDRESSES.ts +10 -18
- package/src/shared/APP_META.ts +7 -0
- package/src/shared/CopyOptions.ts +1 -1
- package/src/shared/DEBUG_META.ts +1 -1
- package/src/shared/LAUNCHER_FILES.ts +9 -0
- package/src/shared/RESERVED_DIRECTORIES.ts +24 -0
- package/src/shared/cookies.ts +2 -2
- package/src/shared/csp.ts +1 -1
- package/src/shared/escapeText.ts +1 -1
- package/src/shared/filledPath.ts +85 -0
- package/src/shared/framing.ts +79 -6
- package/src/shared/headKeyOf.ts +2 -0
- package/src/shared/index.ts +2 -1
- package/src/shared/log.ts +27 -13
- package/src/shared/matchRoute.ts +23 -4
- package/src/{ui → shared}/menuBridge.ts +2 -2
- package/src/shared/menuItem.ts +4 -4
- package/src/shared/onMenu.ts +58 -0
- package/src/shared/reactive/Keyed.ts +2 -2
- package/src/shared/reactive/NotLanded.ts +1 -1
- package/src/shared/reactive/Reactive.ts +43 -43
- package/src/shared/reactive/argsContract.ts +4 -4
- package/src/shared/reactive/canonicalKey.ts +4 -4
- package/src/shared/reactive/chunksOfStream.ts +6 -0
- package/src/shared/reactive/index.ts +1 -1
- package/src/shared/reactive/recordWrites.ts +103 -5
- package/src/shared/reactive/structural.ts +1 -1
- package/src/shared/reactive/tracking.ts +1 -1
- package/src/shared/reactive/watch.ts +3 -3
- package/src/shared/refuse.ts +1 -1
- package/src/{ui → shared}/reportMenu.ts +8 -10
- package/src/shared/routePattern.ts +122 -48
- package/src/shared/rpc.ts +37 -8
- package/src/shared/rpcRequest.ts +27 -3
- package/src/shared/url.ts +4 -44
- package/src/shared/writePath.ts +1 -1
- package/src/ui/LAYER_NAMES.ts +1 -1
- package/src/ui/List.ts +3 -3
- package/src/ui/MOUNTING_LEVEL.ts +18 -0
- package/src/ui/Mismatch.ts +1 -1
- package/src/ui/RawRun.ts +1 -1
- package/src/ui/Router.ts +37 -36
- package/src/ui/Scope.ts +1 -1
- package/src/ui/TryRegion.ts +2 -2
- package/src/ui/applyHead.ts +33 -0
- package/src/ui/index.ts +1 -1
- package/src/ui/moveNode.ts +1 -1
- package/src/ui/reconcile.ts +2 -2
- package/src/ui/routing.ts +2 -2
- package/src/ui/setAttribute.ts +1 -1
- package/src/ui/setText.ts +1 -1
- package/src/ui/socketWire.ts +9 -7
- package/src/ui/wire.ts +1 -1
- package/scaffold/src/server/app.ts +0 -6
- package/scaffold/src/shared/.gitkeep +0 -0
- package/scaffold/src/ui/app.html +0 -8
- package/src/server/componentImports.ts +0 -56
- /package/scaffold/src/{ui/pages/page.abide → index.abide} +0 -0
- /package/src/{compiler → shared}/SPECIFIERS.ts +0 -0
package/src/server/config.ts
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
// SECTION 27 — THE RESOLVED CONFIGURATION. One process-wide value (27.9) over three layers, the last
|
|
2
2
|
// winning (27.2): what abide derives, what the app's `onConfig` defaulted, and the environment.
|
|
3
3
|
//
|
|
4
|
-
// A `reactive` BODY, so `config.invalidate` is the core's own trigger and
|
|
4
|
+
// A `reactive` BODY, so `config.invalidate` is the core's own trigger and reruns all of it (27.5),
|
|
5
5
|
// and a throw out of `onConfig` is the value's standing error, which a read throws (27.1).
|
|
6
6
|
|
|
7
7
|
import { homedir } from 'node:os' // bun exposes no home directory of its own
|
|
8
|
+
import { isRecord } from '#shared/isRecord.ts'
|
|
8
9
|
import { numberFromText } from '#shared/numberFromText.ts'
|
|
9
10
|
import { reactive } from '#shared/reactive/index.ts'
|
|
10
11
|
import { validationError } from '#shared/refuse.ts'
|
|
11
12
|
import { runSchema } from '#shared/Schema.ts'
|
|
12
13
|
import { appDirectories } from './appDirectories.ts'
|
|
13
|
-
import { type Env, HOOKS } from './hooks.ts'
|
|
14
|
+
import { type Env, HOOKS, inOrder } from './hooks.ts'
|
|
14
15
|
import { withoutTrailingSlash } from './withoutTrailingSlash.ts'
|
|
15
16
|
|
|
16
17
|
/** The fields of an app's `package.json` 27.3 derives from. */
|
|
@@ -46,10 +47,6 @@ export interface Config {
|
|
|
46
47
|
APP_NAME: string
|
|
47
48
|
APP_VERSION: string
|
|
48
49
|
APP_DATA_DIR: string
|
|
49
|
-
ABIDE_LOGS: boolean
|
|
50
|
-
ABIDE_OPENAPI: boolean
|
|
51
|
-
ABIDE_MCP: boolean
|
|
52
|
-
ABIDE_BRIDGE: boolean
|
|
53
50
|
}
|
|
54
51
|
|
|
55
52
|
/** The resolved configuration: abide's derived values, then `onConfig`'s defaults, then the env. */
|
|
@@ -57,24 +54,20 @@ export const config = reactive(resolve, { global: true })
|
|
|
57
54
|
|
|
58
55
|
function resolve(): Config {
|
|
59
56
|
const env: Env = { ...process.env }
|
|
60
|
-
const hooked = HOOKS.config
|
|
61
|
-
// 27.4 — SYNCHRONOUS BY CONTRACT; a promise handed back is a contract broken, not a value.
|
|
57
|
+
const hooked = inOrder(HOOKS.config)
|
|
62
58
|
// 27.11 — handed what 27.3 derives under the environment, so a default can be built on
|
|
63
59
|
// `APP_DATA_DIR` without deriving it a second time.
|
|
64
60
|
const derivedValues = derived()
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
ABIDE_BRIDGE: false,
|
|
76
|
-
...derivedValues,
|
|
77
|
-
...(defaults as Record<string, unknown>),
|
|
61
|
+
const document: Record<string, unknown> = { ...derivedValues }
|
|
62
|
+
// 27.6, 37.7 — EVERY `onConfig`'S DEFAULTS, merged over the ones before it.
|
|
63
|
+
for (const { defaults } of hooked) {
|
|
64
|
+
const given = defaults?.({ ...derivedValues, ...env }) ?? {}
|
|
65
|
+
// 27.4 — SYNCHRONOUS BY CONTRACT; a promise handed back is a contract broken, not a value.
|
|
66
|
+
if (given instanceof Promise)
|
|
67
|
+
throw new Error(
|
|
68
|
+
'abide: onConfig returned a promise. ConfigDefaults is synchronous; read what the defaults need before registering them. RULEBOOK 27.4.',
|
|
69
|
+
)
|
|
70
|
+
Object.assign(document, given as Record<string, unknown>)
|
|
78
71
|
}
|
|
79
72
|
// 27.8 — THE ENVIRONMENT, COERCED TO THE DEFAULT IT OVERRIDES; a value that will not coerce leaves
|
|
80
73
|
// the default standing.
|
|
@@ -83,13 +76,16 @@ function resolve(): Config {
|
|
|
83
76
|
const coerced = coerce(text, document[name])
|
|
84
77
|
if (coerced !== undefined) document[name] = coerced
|
|
85
78
|
}
|
|
86
|
-
// 27.7 — LAST, over the whole document.
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
79
|
+
// 27.7 — LAST, each over the whole document, and what each accepts merged back over it.
|
|
80
|
+
for (const { options } of hooked) {
|
|
81
|
+
const schema = options?.schema
|
|
82
|
+
if (schema === undefined) continue
|
|
83
|
+
const checked = runSchema(schema, document)
|
|
84
|
+
if (checked.issues !== null)
|
|
85
|
+
return validationError(checked.issues as never) as never
|
|
86
|
+
if (isRecord(checked.value)) Object.assign(document, checked.value)
|
|
87
|
+
}
|
|
88
|
+
return document as Config
|
|
93
89
|
}
|
|
94
90
|
|
|
95
91
|
function coerce(text: string, standing: unknown): unknown {
|
|
@@ -129,7 +125,7 @@ export function dataDirectory(name: string): string {
|
|
|
129
125
|
return `${process.env.XDG_DATA_HOME ?? `${home}/.local/share`}/${name}`
|
|
130
126
|
}
|
|
131
127
|
|
|
132
|
-
// 28.
|
|
128
|
+
// 28.4 — THE APP'S NAME IS `log`'s ROOT NAMESPACE, and the config is where the name is decided.
|
|
133
129
|
// PEEKED, so a log line written inside an effect does not subscribe it to the configuration.
|
|
134
130
|
export function derivedAppName(): string | undefined {
|
|
135
131
|
const resolved = config.peek()
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
// 38.
|
|
1
|
+
// 38.72 — THE FIELDS THE DESKTOP LAUNCHER'S START FORM ASKS FOR: the members of the `onConfig`
|
|
2
2
|
// schema's JSON Schema, probed as 20.11 probes a handler's, and the fields `onConfig`'s defaults
|
|
3
3
|
// return where the schema has no document to read.
|
|
4
4
|
|
|
5
|
-
import { HOOKS } from './hooks.ts'
|
|
5
|
+
import { HOOKS, inOrder } from './hooks.ts'
|
|
6
6
|
|
|
7
7
|
type Property = {
|
|
8
8
|
type?: string | string[]
|
|
@@ -30,32 +30,38 @@ export type ConfigField = {
|
|
|
30
30
|
export function configFields(
|
|
31
31
|
env: Record<string, string | undefined>,
|
|
32
32
|
): ConfigField[] {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
33
|
+
// 27.6, 37.7 — EVERY `onConfig`, its defaults and its schema's fields merged in route order.
|
|
34
|
+
const defaults: Record<string, unknown> = {}
|
|
35
|
+
let declared: Record<string, Property> | undefined
|
|
36
|
+
const required = new Set<string>()
|
|
37
|
+
for (const { defaults: given, options } of inOrder(HOOKS.config)) {
|
|
38
|
+
try {
|
|
39
|
+
const answered = given?.({ ...env })
|
|
40
|
+
if (answered !== null && typeof answered === 'object')
|
|
41
|
+
Object.assign(defaults, answered)
|
|
42
|
+
} catch {}
|
|
43
|
+
const schema = options?.schema as
|
|
44
|
+
| {
|
|
45
|
+
'~standard'?: {
|
|
46
|
+
jsonSchema?: { input?: (options: unknown) => unknown }
|
|
47
|
+
}
|
|
44
48
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
|
|
49
|
+
| undefined
|
|
50
|
+
let document:
|
|
51
|
+
| { properties?: Record<string, Property>; required?: string[] }
|
|
52
|
+
| undefined
|
|
53
|
+
try {
|
|
54
|
+
document = schema?.['~standard']?.jsonSchema?.input?.({
|
|
55
|
+
target: 'draft-2020-12',
|
|
56
|
+
}) as typeof document
|
|
57
|
+
} catch {}
|
|
58
|
+
if (document?.properties !== undefined)
|
|
59
|
+
declared = { ...declared, ...document.properties }
|
|
60
|
+
for (const name of document?.required ?? []) required.add(name)
|
|
61
|
+
}
|
|
55
62
|
const properties: Record<string, Property> =
|
|
56
|
-
|
|
63
|
+
declared ??
|
|
57
64
|
Object.fromEntries(Object.keys(defaults).map((name) => [name, {}]))
|
|
58
|
-
const required = new Set(document?.required ?? [])
|
|
59
65
|
const fields: ConfigField[] = []
|
|
60
66
|
for (const [name, property] of Object.entries(properties)) {
|
|
61
67
|
const fallback = defaults[name] ?? property.default
|
package/src/server/createApp.ts
CHANGED
|
@@ -10,19 +10,19 @@ import { namedLogger } from '#shared/log.ts'
|
|
|
10
10
|
import { compose } from '#shared/Middleware.ts'
|
|
11
11
|
import { MOUNT, unmounted } from '#shared/mount.ts'
|
|
12
12
|
import { RPC_SERVER } from '#shared/RPC_SERVER.ts'
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
answerRequest,
|
|
16
|
-
type LoadedSources,
|
|
17
|
-
} from './answerRequest.ts'
|
|
13
|
+
import { servedTable } from './abideModules.ts'
|
|
14
|
+
import { type AppSources, answerRequest } from './answerRequest.ts'
|
|
18
15
|
import { Mux, type MuxConnection } from './answerSocket.ts'
|
|
16
|
+
import { appDirectories } from './appDirectories.ts'
|
|
17
|
+
import { holdsApp } from './appTree.ts'
|
|
19
18
|
import { buildDirectory } from './buildDirectory.ts'
|
|
20
19
|
import { APP_PACKAGE, type AppPackage, config, packageUnder } from './config.ts'
|
|
21
20
|
import { McpSurface } from './generatedSurfaces.ts'
|
|
22
|
-
import { HOOKS, reportUnexpected } from './hooks.ts'
|
|
21
|
+
import { HOOKS, inOrder, reportUnexpected, rootRungs } from './hooks.ts'
|
|
23
22
|
import { installServer } from './installServer.ts'
|
|
24
23
|
import { admitsMux, upgraded } from './muxUpgrade.ts'
|
|
25
24
|
import { plainAnswer } from './plainAnswer.ts'
|
|
25
|
+
import { realOrAsGiven } from './realOrAsGiven.ts'
|
|
26
26
|
import {
|
|
27
27
|
type AnsweringApp,
|
|
28
28
|
applyWritten,
|
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
import { testBuild } from './testBuild.ts'
|
|
36
36
|
import { traceResponse } from './trace.ts'
|
|
37
37
|
|
|
38
|
-
// 28.
|
|
38
|
+
// 28.8 — one line per request.
|
|
39
39
|
const REQUEST_LOG = namedLogger('abide:request')
|
|
40
40
|
|
|
41
41
|
/** The app object: what a host, a test or a script holds the app as. */
|
|
@@ -54,52 +54,64 @@ export interface App {
|
|
|
54
54
|
[Symbol.asyncDispose](): Promise<void>
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
// 37.2 — the two shapes `default` may take.
|
|
58
|
-
type OwnRoute =
|
|
59
|
-
| ((
|
|
60
|
-
request: Request,
|
|
61
|
-
server: Bun.Server<unknown> | undefined,
|
|
62
|
-
) => Response | undefined | Promise<Response | undefined>)
|
|
63
|
-
| {
|
|
64
|
-
fetch: (
|
|
65
|
-
request: Request,
|
|
66
|
-
server: Bun.Server<unknown> | undefined,
|
|
67
|
-
) => unknown
|
|
68
|
-
}
|
|
69
|
-
|
|
70
57
|
/**
|
|
71
58
|
* The app this process was started in, booted off what `abide build` left beside it.
|
|
72
59
|
*
|
|
73
|
-
* 42.1 — found where the CLI finds it (38.
|
|
60
|
+
* 42.1 — found where the CLI finds it (38.25), and booted off the build (38.27).
|
|
74
61
|
*/
|
|
75
62
|
export async function createApp(): Promise<App> {
|
|
76
63
|
const root = appRoot(undefined)
|
|
77
64
|
if (root === undefined)
|
|
78
65
|
throw new Error(
|
|
79
|
-
`abide: createApp found no app. Looked for
|
|
66
|
+
`abide: createApp found no app. Looked for a .abide under ${resolve('src')}, or under ${process.cwd()} where it has no src.`,
|
|
67
|
+
)
|
|
68
|
+
const out = await servedBuild(root)
|
|
69
|
+
if (out === undefined)
|
|
70
|
+
throw new Error(
|
|
71
|
+
`abide: createApp found nothing under ${root} to build.`,
|
|
80
72
|
)
|
|
81
|
-
// 42.16 — under `bun test` the app is built for its sources as they stand rather than read off
|
|
82
|
-
// whatever `abide build` last left, which a test run would otherwise need first and could find
|
|
83
|
-
// stale.
|
|
84
|
-
const out =
|
|
85
|
-
process.env.NODE_ENV === 'test'
|
|
86
|
-
? await testBuild(root)
|
|
87
|
-
: buildDirectory(root)
|
|
88
73
|
const { loadBuild } = await import('./serveBuild.ts')
|
|
89
74
|
return bootApp(originUnder(root), () => loadBuild({ root, out }))
|
|
90
75
|
}
|
|
91
76
|
|
|
92
77
|
/**
|
|
93
|
-
* The
|
|
78
|
+
* The build of the app under `root`, served to this process's imports — or `undefined` where
|
|
79
|
+
* nothing under it builds. `fresh` builds it for its sources as they stand.
|
|
80
|
+
*
|
|
81
|
+
* 42.16 — under `bun test` the app is built for its sources as they stand rather than read off
|
|
82
|
+
* whatever `abide build` last left, which a test run would otherwise need first and could find
|
|
83
|
+
* stale. One answer for `createApp` and the test preload (38.109), so a test that boots the app
|
|
84
|
+
* reuses the build its imports were served from.
|
|
85
|
+
*/
|
|
86
|
+
export async function servedBuild(
|
|
87
|
+
root: string,
|
|
88
|
+
fresh = process.env.NODE_ENV === 'test',
|
|
89
|
+
): Promise<string | undefined> {
|
|
90
|
+
const out = fresh ? await testBuild(root) : buildDirectory(root)
|
|
91
|
+
const { serveBuild } = await import('./serveBuild.ts')
|
|
92
|
+
return (await serveBuild(root, out)) ? out : undefined
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The app's root — `--root`, else `src` where the working directory holds one, else the working
|
|
97
|
+
* directory — or `undefined` where it is no app's.
|
|
98
|
+
*
|
|
99
|
+
* 38.25 and 38.26 — `undefined` is a refusal and not a guess. An app's root is in a package — it or
|
|
100
|
+
* the directory above it holds the `package.json` (`appDirectories`) — and holds a `.abide`
|
|
101
|
+
* file; the package is asked first, so a command run from `/` answers at once rather than
|
|
102
|
+
* walking the disk.
|
|
94
103
|
*
|
|
95
|
-
*
|
|
104
|
+
* ITS REAL PATH, as the bundler and the resolver name every file under it: an app under `/tmp` is
|
|
105
|
+
* `/private/tmp` to both, and one spelling here is one fewer at every reader downstream.
|
|
96
106
|
*/
|
|
97
107
|
export function appRoot(given: string | undefined): string | undefined {
|
|
98
|
-
if (given !== undefined) return resolve(given)
|
|
99
|
-
const here = process.cwd()
|
|
100
|
-
if (existsSync(`${here}/ui`)) return here
|
|
108
|
+
if (given !== undefined) return realOrAsGiven(resolve(given))
|
|
101
109
|
const under = resolve('src')
|
|
102
|
-
|
|
110
|
+
const root = realOrAsGiven(existsSync(under) ? under : process.cwd())
|
|
111
|
+
let packaged = false
|
|
112
|
+
for (const directory of appDirectories(root))
|
|
113
|
+
packaged ||= existsSync(`${directory}/package.json`)
|
|
114
|
+
return packaged && holdsApp(root) ? root : undefined
|
|
103
115
|
}
|
|
104
116
|
|
|
105
117
|
/**
|
|
@@ -110,26 +122,26 @@ export function appRoot(given: string | undefined): string | undefined {
|
|
|
110
122
|
* under.
|
|
111
123
|
*/
|
|
112
124
|
export type AppOrigin = {
|
|
113
|
-
/**
|
|
114
|
-
|
|
125
|
+
/**
|
|
126
|
+
* Imports every module holding a `<script server>`, where the app has one. 37.1.
|
|
127
|
+
*
|
|
128
|
+
* Imported for what each CALLS: every hook it runs is filed by the time the import settles.
|
|
129
|
+
*/
|
|
130
|
+
hooks: (() => Promise<unknown>) | undefined
|
|
115
131
|
/** 27.3's name and version. */
|
|
116
132
|
package: AppPackage
|
|
117
133
|
}
|
|
118
134
|
|
|
119
|
-
/**
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/** The `AppOrigin` of an app on disk, found under its source root. */
|
|
135
|
+
/**
|
|
136
|
+
* The `AppOrigin` of an app on disk: its package under `root`, and its server blocks, out of the
|
|
137
|
+
* compilation this process serves (`serveCompilation`) — the build's, or `abide dev`'s.
|
|
138
|
+
*/
|
|
126
139
|
export function originUnder(root: string): AppOrigin {
|
|
127
|
-
const hooksModule = hooksModuleUnder(root)
|
|
128
140
|
return {
|
|
129
|
-
hooks:
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
141
|
+
hooks: async () => {
|
|
142
|
+
const table = await servedTable(root)
|
|
143
|
+
await Promise.all(table.servers.map((load) => load()))
|
|
144
|
+
},
|
|
133
145
|
package: packageUnder(root),
|
|
134
146
|
}
|
|
135
147
|
}
|
|
@@ -143,15 +155,12 @@ export type BootedApp = App & { openMux(): Mux }
|
|
|
143
155
|
/** An `App` booted over the sources `load` supplies, inside the app's `onStart` hooks. */
|
|
144
156
|
export async function bootApp(
|
|
145
157
|
origin: AppOrigin,
|
|
146
|
-
load: () => Promise<
|
|
158
|
+
load: () => Promise<AppSources>,
|
|
147
159
|
): Promise<BootedApp> {
|
|
148
160
|
installServer()
|
|
149
|
-
// 37.1 —
|
|
150
|
-
// import settles.
|
|
151
|
-
|
|
152
|
-
origin.hooks === undefined
|
|
153
|
-
? undefined
|
|
154
|
-
: ((await origin.hooks()).default ?? undefined)
|
|
161
|
+
// 37.1 — EVERY SERVER BLOCK, imported for what it CALLS: every hook it runs is filed by the
|
|
162
|
+
// time the import settles, which is before `onStart` is composed around the boot.
|
|
163
|
+
if (origin.hooks !== undefined) await origin.hooks()
|
|
155
164
|
|
|
156
165
|
// 37.4 — THE BOOT, INSIDE `onStart`. 37.6 — a throw out of a hook or out of the boot rejects
|
|
157
166
|
// `createApp`, and nothing is served.
|
|
@@ -159,8 +168,8 @@ export async function bootApp(
|
|
|
159
168
|
// 42.2 — THE CONFIGURATION IS READ AS PART OF THE BOOT, so a configuration that does not resolve
|
|
160
169
|
// is a boot that fails rather than a first request that does.
|
|
161
170
|
APP_PACKAGE.current = origin.package
|
|
162
|
-
let sources:
|
|
163
|
-
await compose(HOOKS.starts, async () => {
|
|
171
|
+
let sources: AppSources | undefined
|
|
172
|
+
await compose(inOrder(HOOKS.starts), async () => {
|
|
164
173
|
config.invalidate()
|
|
165
174
|
await config
|
|
166
175
|
sources = await load()
|
|
@@ -169,17 +178,16 @@ export async function bootApp(
|
|
|
169
178
|
throw new Error(
|
|
170
179
|
'abide: an onStart hook returned without calling start(), so the app never booted. RULEBOOK 37.4.',
|
|
171
180
|
)
|
|
172
|
-
const booted
|
|
173
|
-
...sources,
|
|
174
|
-
own: own === undefined ? undefined : ownRoute(own),
|
|
175
|
-
}
|
|
181
|
+
const booted = sources
|
|
176
182
|
|
|
177
|
-
// 16.14, 37.3 — THE APP LANE'S ONION, composed once over the whole request path
|
|
178
|
-
//
|
|
179
|
-
|
|
183
|
+
// 16.14, 37.3 — THE APP LANE'S ONION, composed once over the whole request path: the root
|
|
184
|
+
// route's rungs, the ones below it running after routing (37.2, `answerRequest`). 21.20 — the
|
|
185
|
+
// center takes its own time back out of what `fetch` counts for the whole onion.
|
|
186
|
+
const rungs = rootRungs()
|
|
187
|
+
const answer = compose(rungs, async ({ request }) => {
|
|
180
188
|
const began = performance.now()
|
|
181
189
|
const answered = await answerRequest(booted, request)
|
|
182
|
-
if (
|
|
190
|
+
if (rungs.length > 0) rungsTook(began - performance.now())
|
|
183
191
|
return answered
|
|
184
192
|
})
|
|
185
193
|
|
|
@@ -213,14 +221,14 @@ export async function bootApp(
|
|
|
213
221
|
let answered: Response
|
|
214
222
|
try {
|
|
215
223
|
answered = await answer({ request })
|
|
216
|
-
if (
|
|
224
|
+
if (rungs.length > 0)
|
|
217
225
|
rungsTook(performance.now() - started)
|
|
218
226
|
} catch (thrown) {
|
|
219
227
|
reportUnexpected(thrown)
|
|
220
228
|
answered = plainAnswer('internal server error', 500)
|
|
221
229
|
}
|
|
222
230
|
const stamped = stamp(answered)
|
|
223
|
-
// 28.
|
|
231
|
+
// 28.7 — a closed namespace does not pay for the line it would have written.
|
|
224
232
|
if (REQUEST_LOG.enabled())
|
|
225
233
|
REQUEST_LOG.debug(
|
|
226
234
|
request.method,
|
|
@@ -289,7 +297,7 @@ export async function bootApp(
|
|
|
289
297
|
// 16.42 — a stopped app answers no `raw` call made outside every request.
|
|
290
298
|
if (RPC_SERVER.raw === answerRaw) RPC_SERVER.raw = null
|
|
291
299
|
bound = undefined
|
|
292
|
-
await compose(HOOKS.stops, async () => {})(undefined)
|
|
300
|
+
await compose(inOrder(HOOKS.stops), async () => {})(undefined)
|
|
293
301
|
}
|
|
294
302
|
|
|
295
303
|
return {
|
|
@@ -416,13 +424,3 @@ function belowMount(request: Request): Request | Response {
|
|
|
416
424
|
url.pathname = below
|
|
417
425
|
return new Request(url, request)
|
|
418
426
|
}
|
|
419
|
-
|
|
420
|
-
function ownRoute(
|
|
421
|
-
own: OwnRoute,
|
|
422
|
-
): (request: Request) => Promise<Response | undefined> {
|
|
423
|
-
const answer = typeof own === 'function' ? own : own.fetch.bind(own)
|
|
424
|
-
return async (request) => {
|
|
425
|
-
const answered = await answer(request, LISTENING.server)
|
|
426
|
-
return answered instanceof Response ? answered : undefined
|
|
427
|
-
}
|
|
428
|
-
}
|