@abide/abide 0.102.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 +10 -0
- package/bin/abide.ts +31 -28
- package/docs/REGISTRY.md +30 -26
- package/docs/RULEBOOK.md +98 -83
- package/docs/abide.md +771 -522
- package/package.json +2 -1
- package/scaffold/AGENTS.md +13 -6
- package/src/compiler/TypeSession.ts +110 -35
- package/src/compiler/appMap.ts +96 -0
- package/src/compiler/argsSchemaOf.ts +1 -1
- package/src/compiler/checkApp.ts +35 -18
- package/src/compiler/checkedShadow.ts +40 -7
- package/src/compiler/compileApp.ts +228 -70
- package/src/compiler/compiledEntry.ts +10 -6
- package/src/compiler/componentGraph.ts +6 -7
- package/src/compiler/declaredExports.ts +3 -3
- package/src/compiler/emitClient.ts +3 -3
- package/src/compiler/emitComponent.ts +9 -9
- package/src/compiler/emitSource.ts +5 -9
- package/src/compiler/handlersUnder.ts +114 -73
- package/src/compiler/hooksCalled.ts +35 -0
- package/src/compiler/mountsOf.ts +30 -18
- package/src/compiler/parseAbide.ts +12 -12
- package/src/compiler/projectShadow.ts +70 -45
- package/src/compiler/readsIn.ts +2 -2
- package/src/compiler/scaffold.ts +24 -21
- package/src/compiler/serveLanguage.ts +0 -24
- package/src/compiler/sharedModuleWrites.ts +2 -2
- package/src/compiler/splitImports.ts +4 -4
- package/src/compiler/writeDefaultProject.ts +28 -1
- package/src/server/MODULE_QUERIES.ts +12 -3
- package/src/server/abideModules.ts +117 -51
- package/src/server/answerRequest.ts +9 -4
- package/src/server/answerRpc.ts +18 -16
- package/src/server/appTree.ts +83 -24
- package/src/server/commands.ts +5 -4
- package/src/server/componentModules.d.ts +1 -2
- package/src/server/createApp.ts +23 -11
- package/src/server/devWorker.ts +5 -1
- package/src/server/fenceServer.ts +36 -13
- package/src/server/hooks.ts +17 -4
- package/src/server/index.ts +1 -0
- package/src/server/installSourceMaps.ts +6 -1
- package/src/server/launcherOptions.ts +12 -13
- package/src/server/launcherPage.ts +2 -2
- package/src/server/nativeMenu.ts +3 -3
- package/src/server/preload.ts +13 -0
- package/src/server/runDesktop.ts +11 -16
- package/src/server/serveDev.ts +8 -2
- package/src/server/surfaceOf.ts +2 -0
- package/src/server/withAbideSources.ts +1 -1
- package/src/shared/ADDRESSES.ts +3 -0
- package/src/shared/LAUNCHER_FILES.ts +9 -0
- package/src/shared/RESERVED_DIRECTORIES.ts +13 -4
- package/src/shared/index.ts +2 -1
- package/src/{ui → shared}/menuBridge.ts +1 -1
- package/src/shared/menuItem.ts +2 -2
- package/src/shared/onMenu.ts +58 -0
- package/src/{ui → shared}/reportMenu.ts +7 -9
- package/src/shared/routePattern.ts +58 -20
- package/src/shared/rpc.ts +1 -1
- package/src/ui/Router.ts +0 -6
- package/src/compiler/syncTypes.ts +0 -32
- package/src/compiler/writeTypes.ts +0 -82
- package/src/server/LAUNCHER_FILES.ts +0 -9
- /package/scaffold/src/{page.abide → index.abide} +0 -0
- /package/src/{compiler → shared}/SPECIFIERS.ts +0 -0
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { SERVER_SPECIFIER } from '#shared/SPECIFIERS.ts'
|
|
1
2
|
import { type CheckerAnswer, UNCHECKED } from './CheckerAnswer.ts'
|
|
2
3
|
import { Cursor, placeTemplate } from './Cursor.ts'
|
|
3
4
|
import { type LoweredChunk, Lowering, PROPS } from './Lowering.ts'
|
|
4
5
|
import type { AbideFile } from './parseAbide.ts'
|
|
6
|
+
import { privateExports } from './projectShadow.ts'
|
|
5
7
|
import {
|
|
6
8
|
concat,
|
|
7
9
|
definitions,
|
|
@@ -11,7 +13,6 @@ import {
|
|
|
11
13
|
renderMarkup,
|
|
12
14
|
SERVER_LANE,
|
|
13
15
|
} from './renderMarkup.ts'
|
|
14
|
-
import { SERVER_SPECIFIER } from './SPECIFIERS.ts'
|
|
15
16
|
import { insideShare, SETTLED_AT, shareScope } from './shareScope.ts'
|
|
16
17
|
import { splitImports, withoutProps } from './splitImports.ts'
|
|
17
18
|
import type { TemplateNode } from './TemplateNode.ts'
|
|
@@ -43,8 +44,8 @@ export function emitComponent(
|
|
|
43
44
|
file: AbideFile,
|
|
44
45
|
answer: CheckerAnswer = UNCHECKED,
|
|
45
46
|
settles = false,
|
|
46
|
-
/** The route a `<script server>` files its hooks at (37.1)
|
|
47
|
-
route
|
|
47
|
+
/** The route a `<script server>` files its hooks at (37.1). */
|
|
48
|
+
route = '/',
|
|
48
49
|
/** 38.97 — where given, every chunk the module is written around (`sourceMapOf`). */
|
|
49
50
|
chunks?: LoweredChunk[],
|
|
50
51
|
): string {
|
|
@@ -75,21 +76,20 @@ export function emitComponent(
|
|
|
75
76
|
// 31.1 and 31.4 hold INSIDE a `.abide` file, not just in its template, so the script block is
|
|
76
77
|
// lowered with the same rewrite the slots get.
|
|
77
78
|
//
|
|
78
|
-
// 34.2 — A `<script module>` IS MODULE SCOPE, here as in the browser
|
|
79
|
-
//
|
|
80
|
-
// is declared.
|
|
79
|
+
// 34.2 — A `<script module>` IS MODULE SCOPE, here as in the browser. What must stay per request
|
|
80
|
+
// is a `reactive`, which is by 11.32 wherever it is declared.
|
|
81
81
|
const moduleSetup = splitImports(lowering.modules())
|
|
82
|
-
// 34.
|
|
82
|
+
// 34.19 — A `<script server>` IS MODULE SCOPE ON THIS SIDE ALONE, run between two calls that
|
|
83
83
|
// file every hook it makes at the file's route (37.1). Its body cannot await at its top level
|
|
84
84
|
// (34.3), so nothing else runs inside the window.
|
|
85
85
|
const serverSetup = splitImports(lowering.servers(), 'source')
|
|
86
86
|
const serverBody =
|
|
87
87
|
serverSetup.body === ''
|
|
88
88
|
? ''
|
|
89
|
-
: `${lowering.use('fileRoute')}(${JSON.stringify(route
|
|
89
|
+
: `${lowering.use('fileRoute')}(${JSON.stringify(route)})
|
|
90
90
|
${serverSetup.body}
|
|
91
91
|
${lowering.use('fileRoute')}(undefined)
|
|
92
|
-
`
|
|
92
|
+
${privateExports(file.servers.join('\n'))}`
|
|
93
93
|
const setup = splitImports(lowering.scripts())
|
|
94
94
|
const componentBody = setup.body
|
|
95
95
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { STYLE_QUERY } from '#server/MODULE_QUERIES.ts'
|
|
2
|
-
import { fileKind,
|
|
2
|
+
import { fileKind, scopeOf } from '#shared/routePattern.ts'
|
|
3
3
|
import type { CheckerAnswer } from './CheckerAnswer.ts'
|
|
4
4
|
import { emitClient } from './emitClient.ts'
|
|
5
5
|
import { emitComponent } from './emitComponent.ts'
|
|
@@ -27,8 +27,8 @@ export function emitSource(
|
|
|
27
27
|
answer: CheckerAnswer,
|
|
28
28
|
invoked: boolean,
|
|
29
29
|
/**
|
|
30
|
-
*
|
|
31
|
-
* handler it declares, under the handler's name (`standInsOf`).
|
|
30
|
+
* 16.28 — what the browser's module binds in place of the server block: the wrapper of each
|
|
31
|
+
* private handler it declares, under the handler's name (`standInsOf`).
|
|
32
32
|
*/
|
|
33
33
|
standIns = '',
|
|
34
34
|
/** 38.97 — the `.abide`'s text, where the build wants each module's source map. */
|
|
@@ -46,7 +46,6 @@ export function emitSource(
|
|
|
46
46
|
transitions: boolean
|
|
47
47
|
} {
|
|
48
48
|
const kind = fileKind(position)
|
|
49
|
-
const routed = placeOf(position) === 'address'
|
|
50
49
|
// 34.4 — A COMPONENT WITH STYLES: its elements carry its token, and the styles, scoped,
|
|
51
50
|
// are a stylesheet its client module imports — which the bundle collects into the
|
|
52
51
|
// stylesheet the document links (38.23).
|
|
@@ -108,7 +107,7 @@ export function emitSource(
|
|
|
108
107
|
file,
|
|
109
108
|
answer,
|
|
110
109
|
kind !== 'component',
|
|
111
|
-
|
|
110
|
+
scopeOf(position),
|
|
112
111
|
serverChunks,
|
|
113
112
|
)
|
|
114
113
|
const name = relative.slice(relative.lastIndexOf('/') + 1)
|
|
@@ -174,10 +173,7 @@ type Mapped = {
|
|
|
174
173
|
// only where all three texts match, so two apps in one process sharing a path cannot cross.
|
|
175
174
|
const MAPPED = new Map<string, Mapped>()
|
|
176
175
|
|
|
177
|
-
/**
|
|
178
|
-
* The browser's module with each handler's wrapper after it, or the wrappers alone where it had
|
|
179
|
-
* nothing else to carry: a file a page imports for its handlers is imported all the same.
|
|
180
|
-
*/
|
|
176
|
+
/** The browser's module with each private handler's wrapper after it. */
|
|
181
177
|
function withStandIns(client: string, standIns: string): string {
|
|
182
178
|
if (standIns === '') return client
|
|
183
179
|
return client === '' ? standIns : `${client}\n${standIns}`
|
|
@@ -1,35 +1,50 @@
|
|
|
1
|
-
// 16.2 AND
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
1
|
+
// 16.2 AND 16.70 — EVERY HANDLER AN APP DECLARES, AND WHERE IT IS ANSWERED. A handler file's default
|
|
2
|
+
// export answers at that file's route (23.14), and is the app's public surface; a handler a
|
|
3
|
+
// `<script server>` declares is its file's alone, answered below `/__abide/handlers/` at the file's
|
|
4
|
+
// path and its binding's name, and listed nowhere (16.71).
|
|
5
5
|
//
|
|
6
|
-
// READ, NOT RUN. The build finds handlers by what their
|
|
7
|
-
// so a build never runs server code: a
|
|
6
|
+
// READ, NOT RUN. The build finds handlers by what their files SAY rather than by importing them,
|
|
7
|
+
// so a build never runs server code: a handler file may open a database at import, and nothing
|
|
8
8
|
// about building a browser bundle is a reason to. The checker the build already holds lists what
|
|
9
|
-
//
|
|
10
|
-
//
|
|
9
|
+
// each module exports and which call declares each (16.73) — which is also what makes an export
|
|
10
|
+
// that is not a handler a refusal here rather than an address that silently never answers.
|
|
11
11
|
|
|
12
12
|
import type { Mount } from '#server/answerRpc.ts'
|
|
13
13
|
import { refuseFrameworkRoute } from '#server/appTree.ts'
|
|
14
14
|
import { surfaceName } from '#server/surfaceOf.ts'
|
|
15
|
-
import {
|
|
15
|
+
import { PRIVATE_HANDLERS_ADDRESS } from '#shared/ADDRESSES.ts'
|
|
16
|
+
import {
|
|
17
|
+
parseRoutePattern,
|
|
18
|
+
placeOf,
|
|
19
|
+
routeOf,
|
|
20
|
+
scopeOf,
|
|
21
|
+
} from '#shared/routePattern.ts'
|
|
16
22
|
import { declaredExports } from './declaredExports.ts'
|
|
17
23
|
import type { AbideFile } from './parseAbide.ts'
|
|
24
|
+
import { PRIVATE_EXPORT } from './projectShadow.ts'
|
|
18
25
|
import type { Diagnosis, TypeSession } from './TypeSession.ts'
|
|
19
26
|
|
|
20
|
-
/** A
|
|
21
|
-
type
|
|
27
|
+
/** A handler file, by its path and its path below the app's root. */
|
|
28
|
+
export type HandlerFile = {
|
|
22
29
|
path: string
|
|
23
30
|
relative: string
|
|
24
31
|
/** 23.19 — the path it answers by: its own, or the one its mount gives it. */
|
|
25
32
|
position: string
|
|
26
|
-
file: AbideFile
|
|
27
33
|
}
|
|
28
34
|
|
|
29
|
-
|
|
35
|
+
/** A `.abide` the build read. */
|
|
36
|
+
type Source = HandlerFile & { file: AbideFile }
|
|
30
37
|
|
|
31
|
-
|
|
32
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Told of each thing the emit refuses, at the file below the root it names. The build's throws;
|
|
40
|
+
* `abide check` collects every one at its file (38.33).
|
|
41
|
+
*/
|
|
42
|
+
export type Refuse = (relative: string, thrown: unknown) => void
|
|
43
|
+
|
|
44
|
+
/** The build's answer to a refusal: the first one stops it. */
|
|
45
|
+
export const THROW: Refuse = (_relative, thrown) => {
|
|
46
|
+
throw thrown
|
|
47
|
+
}
|
|
33
48
|
|
|
34
49
|
/**
|
|
35
50
|
* The app's handler mounts, with the warnings the build raised reading them.
|
|
@@ -40,64 +55,45 @@ const STAR_EXPORT = /^export \* from /m
|
|
|
40
55
|
export async function handlersUnder(
|
|
41
56
|
session: TypeSession,
|
|
42
57
|
sources: readonly Source[],
|
|
58
|
+
files: readonly HandlerFile[],
|
|
59
|
+
refuse: Refuse = THROW,
|
|
43
60
|
): Promise<{ mounts: Mount[]; warnings: Diagnosis[] }> {
|
|
44
|
-
// 34.31 — a dependency's server block has no address of its own: what it declares is mounted
|
|
45
|
-
// where a file of the app's re-exports it, and as that file's.
|
|
46
61
|
const serving: Source[] = []
|
|
47
|
-
for (const source of sources)
|
|
48
|
-
if (source.file.servers.length
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
)
|
|
54
|
-
throw new Error(
|
|
55
|
-
`abide: ${source.relative} re-exports with \`export *\` from its <script server>, whose names the build cannot read without running it. Name each handler it re-exports. RULEBOOK 19.22.`,
|
|
56
|
-
)
|
|
57
|
-
if (placeOf(source.position) !== 'dependency') serving.push(source)
|
|
58
|
-
}
|
|
59
|
-
const project = await session.project()
|
|
62
|
+
for (const source of sources)
|
|
63
|
+
if (source.file.servers.length > 0) serving.push(source)
|
|
64
|
+
const paths: string[] = []
|
|
65
|
+
for (const file of files) paths.push(file.path)
|
|
66
|
+
const project = await session.projectWith(paths)
|
|
67
|
+
for (const source of serving) paths.push(session.projectionOf(source.path))
|
|
60
68
|
const declared =
|
|
61
|
-
project === undefined ||
|
|
69
|
+
project === undefined || paths.length === 0
|
|
62
70
|
? []
|
|
63
|
-
: await declaredExports(
|
|
64
|
-
project,
|
|
65
|
-
session.tsconfig,
|
|
66
|
-
serving.map((source) => session.projectionOf(source.path)),
|
|
67
|
-
)
|
|
71
|
+
: await declaredExports(project, session.tsconfig, paths)
|
|
68
72
|
const mounts: Mount[] = []
|
|
69
73
|
const warnings: Diagnosis[] = []
|
|
70
74
|
const names = new Map<string, string>()
|
|
71
|
-
for (let at = 0; at <
|
|
72
|
-
const { relative, position
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
copy,
|
|
87
|
-
defaults,
|
|
88
|
-
args,
|
|
89
|
-
unread,
|
|
90
|
-
} of declared[at] ?? []) {
|
|
91
|
-
if (!exported.has(name)) continue
|
|
92
|
-
if (method === undefined)
|
|
75
|
+
for (let at = 0; at < files.length; at += 1) {
|
|
76
|
+
const { relative, position } = files[at] as HandlerFile
|
|
77
|
+
try {
|
|
78
|
+
const address = routeOf(position)
|
|
79
|
+
const exported = declared[at] ?? []
|
|
80
|
+
const handler = exported.find((each) => each.name === 'default')
|
|
81
|
+
// 16.73 — the default and nothing else, declared by one of the five methods.
|
|
82
|
+
for (const each of exported)
|
|
83
|
+
if (each.name !== 'default' || each.method === undefined)
|
|
84
|
+
throw new Error(
|
|
85
|
+
each.name === 'default'
|
|
86
|
+
? `abide: ${relative}'s default export is not declared as a handler. A handler file's default is declared by abide's \`GET(…)\` (or POST, PUT, PATCH, DELETE); anything else belongs under a lib directory. RULEBOOK 16.73.`
|
|
87
|
+
: `abide: ${relative} exports \`${each.name}\`, and a handler file exports its handler as its default and nothing else. Move \`${each.name}\` into a module under a lib or a server directory. RULEBOOK 16.73.`,
|
|
88
|
+
)
|
|
89
|
+
if (handler?.method === undefined)
|
|
93
90
|
throw new Error(
|
|
94
|
-
`abide: ${relative}
|
|
91
|
+
`abide: ${relative} is a handler file — a .ts directly in an address directory — and exports no handler as its default. Export one (\`export default GET(…)\`), or move the file into a lib directory. RULEBOOK 16.73, 23.22.`,
|
|
95
92
|
)
|
|
96
|
-
const address = `${route === '/' ? '' : route}/${name}`
|
|
97
93
|
refuseFrameworkRoute(address, relative)
|
|
98
|
-
refuseUndeclaredParameters(address, args, relative
|
|
94
|
+
refuseUndeclaredParameters(address, handler.args, relative)
|
|
99
95
|
// 20.13 — one name per handler on every generated surface.
|
|
100
|
-
const surface = surfaceName(method, address)
|
|
96
|
+
const surface = surfaceName(handler.method, address)
|
|
101
97
|
const named = names.get(surface)
|
|
102
98
|
if (named !== undefined)
|
|
103
99
|
throw new Error(
|
|
@@ -106,23 +102,69 @@ export async function handlersUnder(
|
|
|
106
102
|
names.set(surface, relative)
|
|
107
103
|
mounts.push({
|
|
108
104
|
address,
|
|
109
|
-
method,
|
|
105
|
+
method: handler.method,
|
|
106
|
+
source: relative,
|
|
107
|
+
name: address.slice(address.lastIndexOf('/') + 1),
|
|
108
|
+
export: 'default',
|
|
109
|
+
scope: scopeOf(position),
|
|
110
|
+
private: false,
|
|
111
|
+
ui: handler.ui,
|
|
112
|
+
cli: handler.cli,
|
|
113
|
+
mcp: handler.mcp,
|
|
114
|
+
description: handler.description,
|
|
115
|
+
copy: handler.copy,
|
|
116
|
+
defaults: handler.defaults,
|
|
117
|
+
args: handler.args,
|
|
118
|
+
})
|
|
119
|
+
warnings.push(...handler.unread)
|
|
120
|
+
} catch (thrown) {
|
|
121
|
+
refuse(relative, thrown)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
for (let at = 0; at < serving.length; at += 1) {
|
|
125
|
+
const { relative, position } = serving[at] as Source
|
|
126
|
+
// 34.24 — a dependency's file answers where a mount places it, and its handlers below the
|
|
127
|
+
// path it has there.
|
|
128
|
+
const below = placeOf(position) === 'dependency' ? relative : position
|
|
129
|
+
for (const each of declared[files.length + at] ?? []) {
|
|
130
|
+
if (each.method === undefined) continue
|
|
131
|
+
if (!each.name.startsWith(PRIVATE_EXPORT)) continue
|
|
132
|
+
const name = each.name.slice(PRIVATE_EXPORT.length)
|
|
133
|
+
mounts.push({
|
|
134
|
+
address: privateAddress(below, name),
|
|
135
|
+
method: each.method,
|
|
110
136
|
source: relative,
|
|
111
137
|
name,
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
138
|
+
export: each.name,
|
|
139
|
+
scope: scopeOf(position),
|
|
140
|
+
private: true,
|
|
141
|
+
ui: each.ui,
|
|
142
|
+
cli: false,
|
|
143
|
+
mcp: false,
|
|
144
|
+
description: each.description,
|
|
145
|
+
copy: each.copy,
|
|
146
|
+
defaults: each.defaults,
|
|
147
|
+
args: each.args,
|
|
119
148
|
})
|
|
120
|
-
warnings.push(...unread)
|
|
149
|
+
warnings.push(...each.unread)
|
|
121
150
|
}
|
|
122
151
|
}
|
|
123
152
|
return { mounts, warnings }
|
|
124
153
|
}
|
|
125
154
|
|
|
155
|
+
/**
|
|
156
|
+
* The address of the private handler `name` the `.abide` at `path` declares: below
|
|
157
|
+
* `PRIVATE_HANDLERS_ADDRESS`, the file's path without its extension, each segment's brackets and
|
|
158
|
+
* dots dropped so nothing in it reads as a parameter, then its name (16.70).
|
|
159
|
+
*/
|
|
160
|
+
export function privateAddress(path: string, name: string): string {
|
|
161
|
+
const segments: string[] = []
|
|
162
|
+
for (const segment of path.replace(/\.abide$/, '').split('/'))
|
|
163
|
+
if (segment !== '' && segment !== '..')
|
|
164
|
+
segments.push(segment.replace(/[[\].]/g, ''))
|
|
165
|
+
return `${PRIVATE_HANDLERS_ADDRESS}/${segments.join('/')}/${name}`
|
|
166
|
+
}
|
|
167
|
+
|
|
126
168
|
/**
|
|
127
169
|
* Throws where a handler's `Args` does not declare a parameter its route carries.
|
|
128
170
|
*
|
|
@@ -133,7 +175,6 @@ function refuseUndeclaredParameters(
|
|
|
133
175
|
address: string,
|
|
134
176
|
args: unknown,
|
|
135
177
|
source: string,
|
|
136
|
-
exported: string,
|
|
137
178
|
): void {
|
|
138
179
|
const schema = args as
|
|
139
180
|
| {
|
|
@@ -153,7 +194,7 @@ function refuseUndeclaredParameters(
|
|
|
153
194
|
)
|
|
154
195
|
continue
|
|
155
196
|
throw new Error(
|
|
156
|
-
`abide: ${source}
|
|
197
|
+
`abide: ${source} answers at ${address}, and its handler's args do not declare \`${segment.name}\`. RULEBOOK 16.67.`,
|
|
157
198
|
)
|
|
158
199
|
}
|
|
159
200
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { tokenize } from './tokenize.ts'
|
|
2
|
+
|
|
3
|
+
/** Every hook abide exports, by name. */
|
|
4
|
+
export const HOOK_NAMES: ReadonlySet<string> = new Set([
|
|
5
|
+
'middleware',
|
|
6
|
+
'onStart',
|
|
7
|
+
'onStop',
|
|
8
|
+
'onError',
|
|
9
|
+
'onConfig',
|
|
10
|
+
'onHealth',
|
|
11
|
+
'onPrincipal',
|
|
12
|
+
'onLauncher',
|
|
13
|
+
'onMenu',
|
|
14
|
+
])
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Each of abide's hook names called in `source`, in the order written: the name, then `(`, and not
|
|
18
|
+
* a declaration — a member call included, since `server.onStart(…)` is abide's hook all the same.
|
|
19
|
+
* Read, not run — what `abide routes` lists and the build's refusals of a hook called where it is
|
|
20
|
+
* never read (38.107) go by.
|
|
21
|
+
*/
|
|
22
|
+
export function hooksCalled(source: string): string[] {
|
|
23
|
+
const called: string[] = []
|
|
24
|
+
let previous = ''
|
|
25
|
+
let named: string | undefined
|
|
26
|
+
for (const token of tokenize(source)) {
|
|
27
|
+
if (named !== undefined && token.text === '(') called.push(named)
|
|
28
|
+
named =
|
|
29
|
+
HOOK_NAMES.has(token.text) && previous !== 'function'
|
|
30
|
+
? token.text
|
|
31
|
+
: undefined
|
|
32
|
+
previous = token.text
|
|
33
|
+
}
|
|
34
|
+
return called
|
|
35
|
+
}
|
package/src/compiler/mountsOf.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
// 23.19 — A PACKAGE'S DIRECTORY, MOUNTED WHERE THE APP SAYS.
|
|
2
|
-
// in a file of the app's places every `.abide`
|
|
3
|
-
// file
|
|
4
|
-
//
|
|
5
|
-
// app gave it.
|
|
1
|
+
// 23.19 — A PACKAGE'S DIRECTORY, MOUNTED WHERE THE APP SAYS.
|
|
2
|
+
// `import 'kit/admin' with { type: 'routes' }` in a file of the app's places every `.abide` and
|
|
3
|
+
// handler file under the package's `admin` directory at the importing file's directory, so its
|
|
4
|
+
// pages, layouts, error pages and handlers answer below it — and the app, not the package, decides
|
|
5
|
+
// where: the mount is a line in a file the app writes, at the position the app gave it.
|
|
6
6
|
//
|
|
7
7
|
// A FILE KEEPS ITS PATH AND GAINS A POSITION. It is compiled, served and keyed by where it lies, as
|
|
8
|
-
// any dependency's is (34.
|
|
8
|
+
// any dependency's is (34.23); its POSITION — the path it would have below the root — is what
|
|
9
9
|
// `placeOf`, `fileKind` and `routeOf` read, so a mounted page is a page by the same test as the app's.
|
|
10
10
|
|
|
11
11
|
import { readFileSync, realpathSync, statSync } from 'node:fs' // bun exposes no sync read, stat or realpath of its own
|
|
12
12
|
import { dirname, relative, resolve } from 'node:path' // bun exposes no path helpers of its own
|
|
13
|
-
import {
|
|
13
|
+
import { appFiles } from '#server/appTree.ts'
|
|
14
14
|
import { placeOf } from '#shared/routePattern.ts'
|
|
15
15
|
import { nearest } from './nearest.ts'
|
|
16
16
|
import { type AbideFile, parseAbide, scriptBodies } from './parseAbide.ts'
|
|
@@ -19,11 +19,17 @@ import { attributedImports, IMPORT_ATTRIBUTES } from './splitImports.ts'
|
|
|
19
19
|
/** A `.abide` on disk, and the position it takes in the app. */
|
|
20
20
|
type Placed = { path: string; position: string }
|
|
21
21
|
|
|
22
|
-
/**
|
|
23
|
-
|
|
22
|
+
/**
|
|
23
|
+
* One directory a file mounts, and each `.abide` and each handler file under it at the position it
|
|
24
|
+
* takes in the app.
|
|
25
|
+
*/
|
|
26
|
+
type DirectoryMount = {
|
|
24
27
|
specifier: string
|
|
25
28
|
directory: string
|
|
29
|
+
/** The position its files take below the root: the mounting file's directory, `/`-ended. */
|
|
30
|
+
at: string
|
|
26
31
|
files: Placed[]
|
|
32
|
+
handlers: Placed[]
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
/**
|
|
@@ -38,8 +44,8 @@ export function mountsOf(
|
|
|
38
44
|
path: string,
|
|
39
45
|
position: string,
|
|
40
46
|
file: AbideFile,
|
|
41
|
-
):
|
|
42
|
-
const mounts:
|
|
47
|
+
): DirectoryMount[] | string {
|
|
48
|
+
const mounts: DirectoryMount[] = []
|
|
43
49
|
const at = position.includes('/')
|
|
44
50
|
? `${position.slice(0, position.lastIndexOf('/'))}/`
|
|
45
51
|
: ''
|
|
@@ -50,13 +56,19 @@ export function mountsOf(
|
|
|
50
56
|
return `abide: ${position} mounts ${specifier}, and no directory answers to it. RULEBOOK 23.19.`
|
|
51
57
|
if (ownDirectory(root, directory))
|
|
52
58
|
return `abide: ${position} mounts ${specifier}, which is the app's own directory already. RULEBOOK 23.20.`
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
mounts.push({
|
|
59
|
+
// The package's tree is the package's to refuse; a mount places what it finds.
|
|
60
|
+
const found = appFiles(directory, () => {})
|
|
61
|
+
const placed = (below: string): Placed => ({
|
|
62
|
+
path: `${directory}/${below}`,
|
|
63
|
+
position: `${at}${below}`,
|
|
64
|
+
})
|
|
65
|
+
mounts.push({
|
|
66
|
+
specifier,
|
|
67
|
+
directory,
|
|
68
|
+
at,
|
|
69
|
+
files: found.abide.map(placed),
|
|
70
|
+
handlers: found.handlers.map(placed),
|
|
71
|
+
})
|
|
60
72
|
}
|
|
61
73
|
return mounts
|
|
62
74
|
}
|
|
@@ -40,7 +40,7 @@ export type AbideFile = {
|
|
|
40
40
|
*/
|
|
41
41
|
modules: string[]
|
|
42
42
|
/**
|
|
43
|
-
* `<script server>` bodies in source order — 34.
|
|
43
|
+
* `<script server>` bodies in source order — 34.19, module scope on a server alone. Apart from
|
|
44
44
|
* `modules` because the browser's module never carries them.
|
|
45
45
|
*/
|
|
46
46
|
servers: string[]
|
|
@@ -177,12 +177,12 @@ export function parseAbide(source: string): AbideFile {
|
|
|
177
177
|
// position the client walks to after it is a different node and the page hydrates
|
|
178
178
|
// against the wrong tree.
|
|
179
179
|
const closing = source.slice(at + 2, close).trim()
|
|
180
|
-
// 34.
|
|
180
|
+
// 34.29 — a `<script>` is raw text whose body takes its close tag with it, so one met
|
|
181
181
|
// here is what is left of a block that ended early: a `</script>` in a comment or a
|
|
182
182
|
// string in it, and the source after it would be rendered as the page.
|
|
183
183
|
if (sameTag(closing, 'script'))
|
|
184
184
|
throw new Error(
|
|
185
|
-
'abide: a </script> that closes no <script> — a block ends at the first `</script>` in it, even one in a comment or a string. Write it `<\\/script>` there. RULEBOOK 34.
|
|
185
|
+
'abide: a </script> that closes no <script> — a block ends at the first `</script>` in it, even one in a comment or a string. Write it `<\\/script>` there. RULEBOOK 34.29.',
|
|
186
186
|
)
|
|
187
187
|
const innermost = open[open.length - 1]
|
|
188
188
|
if (typeof innermost === 'string' && sameTag(innermost, closing)) {
|
|
@@ -880,10 +880,10 @@ function sameTag(left: string, right: string): boolean {
|
|
|
880
880
|
/**
|
|
881
881
|
* The list a `<script>`'s body belongs in, or `undefined` where it is markup.
|
|
882
882
|
*
|
|
883
|
-
* 34.1 is the bare tag, 34.2 the bare `module` attribute and 34.
|
|
883
|
+
* 34.1 is the bare tag, 34.2 the bare `module` attribute and 34.19 the bare `server` one; anything
|
|
884
884
|
* else on the tag — `src`, `type`, `defer`, `nonce` —
|
|
885
885
|
* is a `<script>` the author wrote for the DOCUMENT, and lifting it into component setup moves it
|
|
886
|
-
* out of the markup and throws its attributes away — save beside `server`, which 34.
|
|
886
|
+
* out of the markup and throws its attributes away — save beside `server`, which 34.28 refuses.
|
|
887
887
|
*
|
|
888
888
|
* 34.13 — BUT `generics="…"` IS THE COMPONENT'S. 34.12 — `lang` is refused on any `<script>`: every
|
|
889
889
|
* block is TypeScript, so it says nothing, and read as markup it took the author's whole setup out of
|
|
@@ -911,16 +911,16 @@ function scriptBlock(
|
|
|
911
911
|
)
|
|
912
912
|
else if (attribute.lowerName !== 'generics') markup = true
|
|
913
913
|
}
|
|
914
|
-
// 34.
|
|
914
|
+
// 34.28 — a `server` attribute on a tag that would be markup ships the server's source to the
|
|
915
915
|
// document and the browser's module, so it is refused rather than demoted.
|
|
916
916
|
if (server && markup)
|
|
917
917
|
throw new Error(
|
|
918
|
-
'abide: a <script> carrying `server` is a server block, written `<script server>` with nothing beside it. RULEBOOK 34.
|
|
918
|
+
'abide: a <script> carrying `server` is a server block, written `<script server>` with nothing beside it. RULEBOOK 34.28.',
|
|
919
919
|
)
|
|
920
920
|
if (markup) return undefined
|
|
921
921
|
if (module && server)
|
|
922
922
|
throw new Error(
|
|
923
|
-
'abide: a <script> is `module` or `server`, not both — a server block is module scope already. RULEBOOK 34.
|
|
923
|
+
'abide: a <script> is `module` or `server`, not both — a server block is module scope already. RULEBOOK 34.19.',
|
|
924
924
|
)
|
|
925
925
|
return server ? servers : module ? modules : scripts
|
|
926
926
|
}
|
|
@@ -948,16 +948,16 @@ function rawBody(
|
|
|
948
948
|
if (tag.selfClosing) return { body: '', end: tag.end }
|
|
949
949
|
// The body ends at the first literal close tag, as a browser's does, so a `</script>` inside
|
|
950
950
|
// a string or a comment in a script block ends it early — and the real close tag after it
|
|
951
|
-
// is refused where the template meets it (34.
|
|
951
|
+
// is refused where the template meets it (34.29).
|
|
952
952
|
const close = closeTagAt(source, lowerTag, tag.end)
|
|
953
953
|
if (close === null)
|
|
954
954
|
return { body: source.slice(tag.end), end: source.length }
|
|
955
|
-
// 34.
|
|
956
|
-
// over can open a `<script>` of its own that takes the real close tag, and then 34.
|
|
955
|
+
// 34.30 — a component block's early end is refused AT the early end, because what is left
|
|
956
|
+
// over can open a `<script>` of its own that takes the real close tag, and then 34.29 meets
|
|
957
957
|
// nothing to refuse while the block's source between them renders as the page.
|
|
958
958
|
if (code && !startsToken(source, tag.end, close.start))
|
|
959
959
|
throw new Error(
|
|
960
|
-
'abide: a </script> inside a string, a comment or a regex ends the <script> block there, as a browser would. Write it `<\\/script>`. RULEBOOK 34.
|
|
960
|
+
'abide: a </script> inside a string, a comment or a regex ends the <script> block there, as a browser would. Write it `<\\/script>`. RULEBOOK 34.30.',
|
|
961
961
|
)
|
|
962
962
|
return { body: source.slice(tag.end, close.start), end: close.end }
|
|
963
963
|
}
|