@abide/abide 0.101.0 → 0.102.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 +16 -0
- package/bin/abide.ts +223 -164
- package/docs/REGISTRY.md +726 -0
- package/docs/RULEBOOK.md +2753 -0
- package/docs/abide.md +12878 -0
- package/package.json +5 -3
- package/scaffold/AGENTS.md +31 -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 +262 -67
- package/src/compiler/argsSchemaOf.ts +1 -1
- package/src/compiler/checkApp.ts +43 -15
- package/src/compiler/checkedShadow.ts +39 -5
- package/src/compiler/classifyRead.ts +6 -4
- package/src/compiler/classifyTemplate.ts +1 -1
- package/src/compiler/compileApp.ts +433 -193
- package/src/compiler/compiledEntry.ts +43 -39
- package/src/compiler/componentGraph.ts +58 -71
- package/src/compiler/declaredExports.ts +24 -7
- package/src/compiler/emitClient.ts +141 -71
- package/src/compiler/emitComponent.ts +37 -8
- package/src/compiler/emitSource.ts +106 -13
- package/src/compiler/handlersUnder.ts +106 -59
- package/src/compiler/index.ts +7 -0
- package/src/compiler/mountsOf.ts +138 -0
- package/src/compiler/nearest.ts +14 -0
- package/src/compiler/parseAbide.ts +157 -20
- package/src/compiler/projectShadow.ts +213 -7
- package/src/compiler/reactiveTypes.ts +20 -7
- package/src/compiler/readSources.ts +15 -2
- package/src/compiler/readsIn.ts +61 -7
- package/src/compiler/renderMarkup.ts +3 -3
- package/src/compiler/scaffold.ts +30 -11
- package/src/compiler/serveLanguage.ts +125 -34
- package/src/compiler/sourceMapOf.ts +193 -0
- package/src/compiler/splitImports.ts +205 -83
- package/src/compiler/syncTypes.ts +32 -0
- package/src/compiler/tokenize.ts +14 -0
- package/src/compiler/writeDefaultProject.ts +1 -1
- package/src/compiler/writeTypes.ts +82 -0
- package/src/server/CLIENT_BUILD.ts +11 -13
- package/src/server/DEFAULT_PORT.ts +1 -1
- package/src/server/LAUNCHER_FILES.ts +9 -0
- package/src/server/MODULE_QUERIES.ts +18 -0
- package/src/server/TABLES.ts +7 -0
- package/src/server/VIRTUAL_DIRECTORY.ts +4 -5
- package/src/server/abideModules.ts +391 -0
- package/src/server/answerFailure.ts +16 -6
- package/src/server/answerRequest.ts +235 -106
- package/src/server/answerRpc.ts +102 -30
- 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 +179 -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 +37 -37
- package/src/server/componentModules.d.ts +3 -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 +59 -73
- package/src/server/devWorker.ts +114 -181
- package/src/server/executableName.ts +4 -0
- package/src/server/fenceServer.ts +37 -35
- package/src/server/generatedSurfaces.ts +99 -66
- package/src/server/hooks.ts +124 -52
- package/src/server/iconAccent.ts +191 -0
- package/src/server/index.ts +11 -7
- package/src/server/installSourceMaps.ts +88 -0
- package/src/server/launcherOptions.ts +34 -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 +5 -5
- package/src/server/negotiateEncoding.ts +1 -1
- 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 +52 -27
- package/src/server/serveBuild.ts +43 -52
- package/src/server/serveDev.ts +73 -37
- package/src/server/shell.ts +7 -22
- package/src/server/sourceMappings.ts +139 -0
- package/src/server/surfaceOf.ts +18 -3
- package/src/server/testBuild.ts +1 -19
- package/src/server/withAbideSources.ts +117 -0
- package/src/shared/ADDRESSES.ts +7 -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/RESERVED_DIRECTORIES.ts +15 -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 +1 -1
- package/src/shared/log.ts +27 -13
- package/src/shared/matchRoute.ts +23 -4
- package/src/shared/menuItem.ts +3 -3
- 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/shared/routePattern.ts +82 -46
- 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 +38 -31
- 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/menuBridge.ts +1 -1
- package/src/ui/moveNode.ts +1 -1
- package/src/ui/reconcile.ts +2 -2
- package/src/ui/reportMenu.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 → page.abide} +0 -0
|
@@ -1,73 +1,83 @@
|
|
|
1
|
-
// 16.2 AND 19.
|
|
2
|
-
// a
|
|
3
|
-
//
|
|
1
|
+
// 16.2 AND 19.15 — EVERY HANDLER AN APP DECLARES, AND WHERE IT IS ANSWERED. A handler is an export
|
|
2
|
+
// of a `<script server>` in a `.abide` of the app's own, and its address is that file's
|
|
3
|
+
// route — its directory's (23.14) — and then the export's name. A directory's handlers may sit in as
|
|
4
|
+
// many of its files as an author likes; two of them exporting one name is 19.1's refusal.
|
|
4
5
|
//
|
|
5
|
-
// READ, NOT RUN. The build finds handlers by what their
|
|
6
|
-
// so a build never runs server code:
|
|
7
|
-
// about building a browser bundle is a reason to. The checker the build already holds lists what
|
|
8
|
-
//
|
|
9
|
-
// not a handler a refusal here rather than an address that silently never answers.
|
|
6
|
+
// READ, NOT RUN. The build finds handlers by what their blocks SAY rather than by importing them,
|
|
7
|
+
// so a build never runs server code: a server block may open a database at import, and nothing
|
|
8
|
+
// about building a browser bundle is a reason to. The checker the build already holds lists what
|
|
9
|
+
// the file's projection exports and which call declares each (19.15) — which is also what makes an
|
|
10
|
+
// export that is not a handler a refusal here rather than an address that silently never answers.
|
|
10
11
|
|
|
11
12
|
import type { Mount } from '#server/answerRpc.ts'
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
13
|
+
import { refuseFrameworkRoute } from '#server/appTree.ts'
|
|
14
|
+
import { surfaceName } from '#server/surfaceOf.ts'
|
|
15
|
+
import { parseRoutePattern, placeOf, routeOf } from '#shared/routePattern.ts'
|
|
14
16
|
import { declaredExports } from './declaredExports.ts'
|
|
17
|
+
import type { AbideFile } from './parseAbide.ts'
|
|
15
18
|
import type { Diagnosis, TypeSession } from './TypeSession.ts'
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const modules: HandlerModule[] = []
|
|
25
|
-
for (const name of namesUnder(
|
|
26
|
-
`${root}/${HANDLER_DIRECTORY}`,
|
|
27
|
-
'**/*.ts',
|
|
28
|
-
).sort())
|
|
29
|
-
modules.push({
|
|
30
|
-
source: `${HANDLER_DIRECTORY}/${name}`,
|
|
31
|
-
path: name.replace(/\.ts$/, ''),
|
|
32
|
-
})
|
|
33
|
-
return modules
|
|
20
|
+
/** A `.abide` the build read, by its path and its path below the app's root. */
|
|
21
|
+
type Source = {
|
|
22
|
+
path: string
|
|
23
|
+
relative: string
|
|
24
|
+
/** 23.19 — the path it answers by: its own, or the one its mount gives it. */
|
|
25
|
+
position: string
|
|
26
|
+
file: AbideFile
|
|
34
27
|
}
|
|
35
28
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
*
|
|
39
|
-
|
|
40
|
-
* is an app.
|
|
41
|
-
*/
|
|
42
|
-
export function declaresHandlers(root: string): boolean {
|
|
43
|
-
return modulesUnder(root).length > 0
|
|
44
|
-
}
|
|
29
|
+
const TRANSPILER = new Bun.Transpiler({ loader: 'ts' })
|
|
30
|
+
|
|
31
|
+
// 19.22 — as the transpiler prints it, one statement to a line; `export * as name` names its export.
|
|
32
|
+
const STAR_EXPORT = /^export \* from /m
|
|
45
33
|
|
|
46
34
|
/**
|
|
47
35
|
* The app's handler mounts, with the warnings the build raised reading them.
|
|
48
36
|
*
|
|
49
|
-
* Off one read of the checker.
|
|
50
|
-
*
|
|
37
|
+
* Off one read of the checker. 18.15 — with what the build could not read of a copy's options.
|
|
38
|
+
* 19.1's one owner per route and method is `compileApp`'s, which holds the pages as well.
|
|
51
39
|
*/
|
|
52
40
|
export async function handlersUnder(
|
|
53
|
-
root: string,
|
|
54
41
|
session: TypeSession,
|
|
42
|
+
sources: readonly Source[],
|
|
55
43
|
): Promise<{ mounts: Mount[]; warnings: Diagnosis[] }> {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
+
const serving: Source[] = []
|
|
47
|
+
for (const source of sources) {
|
|
48
|
+
if (source.file.servers.length === 0) continue
|
|
49
|
+
if (
|
|
50
|
+
STAR_EXPORT.test(
|
|
51
|
+
TRANSPILER.transformSync(source.file.servers.join('\n')),
|
|
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
59
|
const project = await session.project()
|
|
60
60
|
const declared =
|
|
61
|
-
project === undefined
|
|
61
|
+
project === undefined || serving.length === 0
|
|
62
62
|
? []
|
|
63
|
-
: await declaredExports(
|
|
63
|
+
: await declaredExports(
|
|
64
|
+
project,
|
|
65
|
+
session.tsconfig,
|
|
66
|
+
serving.map((source) => session.projectionOf(source.path)),
|
|
67
|
+
)
|
|
64
68
|
const mounts: Mount[] = []
|
|
65
69
|
const warnings: Diagnosis[] = []
|
|
66
|
-
const
|
|
67
|
-
for (let at = 0; at <
|
|
68
|
-
const {
|
|
70
|
+
const names = new Map<string, string>()
|
|
71
|
+
for (let at = 0; at < serving.length; at += 1) {
|
|
72
|
+
const { relative, position, file } = serving[at] as Source
|
|
73
|
+
const route = routeOf(position)
|
|
74
|
+
// 19.15 — what THE SERVER BLOCK exports, out of everything the file's module does: its
|
|
75
|
+
// component and its `<script module>`'s names are the module's own.
|
|
76
|
+
const exported = new Set(
|
|
77
|
+
TRANSPILER.scan(file.servers.join('\n')).exports,
|
|
78
|
+
)
|
|
69
79
|
for (const {
|
|
70
|
-
name
|
|
80
|
+
name,
|
|
71
81
|
method,
|
|
72
82
|
ui,
|
|
73
83
|
cli,
|
|
@@ -78,25 +88,27 @@ export async function handlersUnder(
|
|
|
78
88
|
args,
|
|
79
89
|
unread,
|
|
80
90
|
} of declared[at] ?? []) {
|
|
91
|
+
if (!exported.has(name)) continue
|
|
81
92
|
if (method === undefined)
|
|
82
93
|
throw new Error(
|
|
83
|
-
`abide: ${
|
|
94
|
+
`abide: ${relative} exports \`${name}\` from its <script server>, which is not declared as a handler. A server block exports handlers alone, each declared by abide's \`GET(…)\` (or POST, PUT, PATCH, DELETE); anything else belongs in a module of its own. RULEBOOK 19.15.`,
|
|
84
95
|
)
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
|
|
96
|
+
const address = `${route === '/' ? '' : route}/${name}`
|
|
97
|
+
refuseFrameworkRoute(address, relative)
|
|
98
|
+
refuseUndeclaredParameters(address, args, relative, name)
|
|
99
|
+
// 20.13 — one name per handler on every generated surface.
|
|
100
|
+
const surface = surfaceName(method, address)
|
|
101
|
+
const named = names.get(surface)
|
|
102
|
+
if (named !== undefined)
|
|
91
103
|
throw new Error(
|
|
92
|
-
`abide: ${
|
|
104
|
+
`abide: ${named} and ${relative} are both named ${surface} on the generated surfaces. RULEBOOK 20.13.`,
|
|
93
105
|
)
|
|
94
|
-
|
|
106
|
+
names.set(surface, relative)
|
|
95
107
|
mounts.push({
|
|
96
108
|
address,
|
|
97
109
|
method,
|
|
98
|
-
source,
|
|
99
|
-
name
|
|
110
|
+
source: relative,
|
|
111
|
+
name,
|
|
100
112
|
ui,
|
|
101
113
|
cli,
|
|
102
114
|
mcp,
|
|
@@ -110,3 +122,38 @@ export async function handlersUnder(
|
|
|
110
122
|
}
|
|
111
123
|
return { mounts, warnings }
|
|
112
124
|
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Throws where a handler's `Args` does not declare a parameter its route carries.
|
|
128
|
+
*
|
|
129
|
+
* 16.67 — read off the JSON Schema its annotation derives, which is what the wire holds the args
|
|
130
|
+
* to: a parameter the schema does not name would reach the handler as a key it never typed.
|
|
131
|
+
*/
|
|
132
|
+
function refuseUndeclaredParameters(
|
|
133
|
+
address: string,
|
|
134
|
+
args: unknown,
|
|
135
|
+
source: string,
|
|
136
|
+
exported: string,
|
|
137
|
+
): void {
|
|
138
|
+
const schema = args as
|
|
139
|
+
| {
|
|
140
|
+
properties?: Record<string, unknown>
|
|
141
|
+
additionalProperties?: unknown
|
|
142
|
+
}
|
|
143
|
+
| undefined
|
|
144
|
+
// A record type — an index signature — declares every key.
|
|
145
|
+
const open =
|
|
146
|
+
schema?.additionalProperties !== undefined &&
|
|
147
|
+
schema.additionalProperties !== false
|
|
148
|
+
for (const segment of parseRoutePattern(address)) {
|
|
149
|
+
if (segment.kind === 'static' || open) continue
|
|
150
|
+
if (
|
|
151
|
+
schema?.properties !== undefined &&
|
|
152
|
+
segment.name in schema.properties
|
|
153
|
+
)
|
|
154
|
+
continue
|
|
155
|
+
throw new Error(
|
|
156
|
+
`abide: ${source}'s \`${exported}\` answers at ${address}, and its args do not declare \`${segment.name}\`. RULEBOOK 16.67.`,
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
}
|
package/src/compiler/index.ts
CHANGED
|
@@ -7,6 +7,13 @@
|
|
|
7
7
|
// reached only by something already doing a build. Nothing here is in any client bundle, and
|
|
8
8
|
// `compiler.test.ts` holds the two apart by asserting what `@abide/abide/server` exports.
|
|
9
9
|
|
|
10
|
+
// 34.14 — what serves what this compiles, to a bundle or to the runtime (`abideModules`).
|
|
11
|
+
export {
|
|
12
|
+
abideModules,
|
|
13
|
+
type Compilation,
|
|
14
|
+
type CompiledFile,
|
|
15
|
+
serveCompilation,
|
|
16
|
+
} from '#server/abideModules.ts'
|
|
10
17
|
export { componentGraph } from './componentGraph.ts'
|
|
11
18
|
export { emitClient, type SelfFill } from './emitClient.ts'
|
|
12
19
|
export { emitComponent } from './emitComponent.ts'
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// 23.19 — A PACKAGE'S DIRECTORY, MOUNTED WHERE THE APP SAYS. `import 'kit/admin' with { type: 'routes' }`
|
|
2
|
+
// in a file of the app's places every `.abide` under the package's `admin` directory at the importing
|
|
3
|
+
// file's directory, so its pages, layouts, error pages and handlers answer below it — and the app,
|
|
4
|
+
// not the package, decides where: the mount is a line in a file the app writes, at the position the
|
|
5
|
+
// app gave it.
|
|
6
|
+
//
|
|
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.27); its POSITION — the path it would have below the root — is what
|
|
9
|
+
// `placeOf`, `fileKind` and `routeOf` read, so a mounted page is a page by the same test as the app's.
|
|
10
|
+
|
|
11
|
+
import { readFileSync, realpathSync, statSync } from 'node:fs' // bun exposes no sync read, stat or realpath of its own
|
|
12
|
+
import { dirname, relative, resolve } from 'node:path' // bun exposes no path helpers of its own
|
|
13
|
+
import { abideUnder } from '#server/appTree.ts'
|
|
14
|
+
import { placeOf } from '#shared/routePattern.ts'
|
|
15
|
+
import { nearest } from './nearest.ts'
|
|
16
|
+
import { type AbideFile, parseAbide, scriptBodies } from './parseAbide.ts'
|
|
17
|
+
import { attributedImports, IMPORT_ATTRIBUTES } from './splitImports.ts'
|
|
18
|
+
|
|
19
|
+
/** A `.abide` on disk, and the position it takes in the app. */
|
|
20
|
+
type Placed = { path: string; position: string }
|
|
21
|
+
|
|
22
|
+
/** One directory a file mounts, and each `.abide` under it at the position it takes in the app. */
|
|
23
|
+
export type Mounted = {
|
|
24
|
+
specifier: string
|
|
25
|
+
directory: string
|
|
26
|
+
files: Placed[]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Every directory the file at `path` mounts, its position in the app being `position` — or the
|
|
31
|
+
* refusal naming what one of them cannot be.
|
|
32
|
+
*
|
|
33
|
+
* `root` is the app's, real: a directory under it is the app's own already, with a position of its
|
|
34
|
+
* own, and mounting it would give its files a second (23.20).
|
|
35
|
+
*/
|
|
36
|
+
export function mountsOf(
|
|
37
|
+
root: string,
|
|
38
|
+
path: string,
|
|
39
|
+
position: string,
|
|
40
|
+
file: AbideFile,
|
|
41
|
+
): Mounted[] | string {
|
|
42
|
+
const mounts: Mounted[] = []
|
|
43
|
+
const at = position.includes('/')
|
|
44
|
+
? `${position.slice(0, position.lastIndexOf('/'))}/`
|
|
45
|
+
: ''
|
|
46
|
+
for (const script of scriptBodies(file))
|
|
47
|
+
for (const { specifier } of attributedImports(script, 'routes')) {
|
|
48
|
+
const directory = mountedDirectory(specifier, dirname(path))
|
|
49
|
+
if (directory === undefined)
|
|
50
|
+
return `abide: ${position} mounts ${specifier}, and no directory answers to it. RULEBOOK 23.19.`
|
|
51
|
+
if (ownDirectory(root, directory))
|
|
52
|
+
return `abide: ${position} mounts ${specifier}, which is the app's own directory already. RULEBOOK 23.20.`
|
|
53
|
+
const files: Placed[] = []
|
|
54
|
+
for (const below of abideUnder(directory))
|
|
55
|
+
files.push({
|
|
56
|
+
path: `${directory}/${below}`,
|
|
57
|
+
position: `${at}${below}`,
|
|
58
|
+
})
|
|
59
|
+
mounts.push({ specifier, directory, files })
|
|
60
|
+
}
|
|
61
|
+
return mounts
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Every file mounted under the app at `root` whose own `.abide` are `relatives`, and every one those
|
|
66
|
+
* mount in turn, at its position — read for nothing but its mounts, and refusing nothing: the
|
|
67
|
+
* build is what refuses a mount (`withDependencies`).
|
|
68
|
+
*
|
|
69
|
+
* THE CHECKER'S `Routes` (30.26) asks this on every check, so a file is parsed only where its text
|
|
70
|
+
* names a mount at all.
|
|
71
|
+
*/
|
|
72
|
+
export function mountedUnder(
|
|
73
|
+
root: string,
|
|
74
|
+
relatives: readonly string[],
|
|
75
|
+
): Placed[] {
|
|
76
|
+
const queue: Placed[] = []
|
|
77
|
+
for (const relative of relatives)
|
|
78
|
+
queue.push({ path: `${root}/${relative}`, position: relative })
|
|
79
|
+
const found: Placed[] = []
|
|
80
|
+
const seen = new Set<string>()
|
|
81
|
+
for (let at = 0; at < queue.length; at += 1) {
|
|
82
|
+
const { path, position } = queue[at] as Placed
|
|
83
|
+
const text = readFileSync(path, 'utf8')
|
|
84
|
+
if (!IMPORT_ATTRIBUTES.routes.test(text)) continue
|
|
85
|
+
let file: AbideFile
|
|
86
|
+
try {
|
|
87
|
+
file = parseAbide(text)
|
|
88
|
+
} catch {
|
|
89
|
+
continue
|
|
90
|
+
}
|
|
91
|
+
const mounts = mountsOf(root, path, position, file)
|
|
92
|
+
if (typeof mounts === 'string') continue
|
|
93
|
+
for (const mount of mounts)
|
|
94
|
+
for (const placed of mount.files) {
|
|
95
|
+
if (seen.has(placed.path)) continue
|
|
96
|
+
seen.add(placed.path)
|
|
97
|
+
found.push(placed)
|
|
98
|
+
queue.push(placed)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return found
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Under the root and not a dependency's there: one of the app's own, with positions of its own.
|
|
105
|
+
function ownDirectory(root: string, directory: string): boolean {
|
|
106
|
+
if (directory === root) return true
|
|
107
|
+
const below = relative(root, directory)
|
|
108
|
+
return !below.startsWith('..') && placeOf(`${below}/-`) !== 'dependency'
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* The real directory `specifier` names from `from`: a relative path as written, and a bare one as a
|
|
113
|
+
* package and the path into it, found in the nearest `node_modules` above.
|
|
114
|
+
*
|
|
115
|
+
* A DIRECTORY, NOT A MODULE: a package's `exports` names its modules, and a mount asks for none of
|
|
116
|
+
* them, so its directory is found as bun would find the package and then walked.
|
|
117
|
+
*/
|
|
118
|
+
function mountedDirectory(specifier: string, from: string): string | undefined {
|
|
119
|
+
let candidate: string | undefined
|
|
120
|
+
if (specifier.startsWith('./') || specifier.startsWith('../'))
|
|
121
|
+
candidate = resolve(from, specifier)
|
|
122
|
+
else {
|
|
123
|
+
const parts = specifier.split('/')
|
|
124
|
+
const nameLength = specifier.startsWith('@') ? 2 : 1
|
|
125
|
+
const name = parts.slice(0, nameLength).join('/')
|
|
126
|
+
const inside = parts.slice(nameLength).join('/')
|
|
127
|
+
const found = nearest(from, `node_modules/${name}`)
|
|
128
|
+
if (found === undefined) return undefined
|
|
129
|
+
candidate = inside === '' ? found : `${found}/${inside}`
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
return statSync(candidate).isDirectory()
|
|
133
|
+
? realpathSync(candidate)
|
|
134
|
+
: undefined
|
|
135
|
+
} catch {
|
|
136
|
+
return undefined
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs' // bun exposes no sync existence check of its own
|
|
2
|
+
import { dirname } from 'node:path' // bun exposes no path helpers of its own
|
|
3
|
+
|
|
4
|
+
/** The nearest file or directory at `name` at or above `from`, or `undefined`. */
|
|
5
|
+
export function nearest(from: string, name: string): string | undefined {
|
|
6
|
+
let at = from
|
|
7
|
+
while (true) {
|
|
8
|
+
const candidate = `${at}/${name}`
|
|
9
|
+
if (existsSync(candidate)) return candidate
|
|
10
|
+
const up = dirname(at)
|
|
11
|
+
if (up === at) return undefined
|
|
12
|
+
at = up
|
|
13
|
+
}
|
|
14
|
+
}
|