@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/bin/abide.ts
CHANGED
|
@@ -5,19 +5,32 @@
|
|
|
5
5
|
import { existsSync, realpathSync } from 'node:fs' // bun exposes no sync existence check or realpath of its own
|
|
6
6
|
import { mkdir, rename, rm } from 'node:fs/promises' // bun exposes no directory creation, rename or recursive directory removal of its own
|
|
7
7
|
import { dirname, resolve } from 'node:path' // bun exposes no path helpers of its own
|
|
8
|
-
import { brotliCompress, constants } from 'node:zlib' // bun
|
|
9
|
-
|
|
8
|
+
import { brotliCompress, constants } from 'node:zlib' // bun's brotli is `CompressionStream`, which compresses on the main thread — 1390 ms against this pool's 300 ms over the docs bundle
|
|
9
|
+
import { appMap, mapText } from '#compiler/appMap.ts'
|
|
10
10
|
import { checkApp } from '#compiler/checkApp.ts'
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
type CompiledApp,
|
|
13
|
+
compilationOf,
|
|
14
|
+
compileApp,
|
|
15
|
+
paints,
|
|
16
|
+
} from '#compiler/compileApp.ts'
|
|
12
17
|
import { compiledEntry } from '#compiler/compiledEntry.ts'
|
|
13
18
|
import { formatDiagnosis } from '#compiler/formatDiagnosis.ts'
|
|
19
|
+
import { parseAbide } from '#compiler/parseAbide.ts'
|
|
14
20
|
import { scaffold } from '#compiler/scaffold.ts'
|
|
15
21
|
import { serveLanguage } from '#compiler/serveLanguage.ts'
|
|
22
|
+
import {
|
|
23
|
+
abideModules,
|
|
24
|
+
type Compilation,
|
|
25
|
+
servedTable,
|
|
26
|
+
serverMapsOf,
|
|
27
|
+
} from '#server/abideModules.ts'
|
|
16
28
|
import { appPlugins } from '#server/appPlugins.ts'
|
|
17
29
|
import { banner } from '#server/banner.ts'
|
|
18
30
|
import { buildDirectory } from '#server/buildDirectory.ts'
|
|
19
31
|
import {
|
|
20
32
|
CLIENT_DIRECTORY,
|
|
33
|
+
COMPILATION,
|
|
21
34
|
ENCODING_SUFFIX,
|
|
22
35
|
MANIFEST,
|
|
23
36
|
PUBLIC_DIRECTORY,
|
|
@@ -27,121 +40,135 @@ import {
|
|
|
27
40
|
stylesheetUnderMount,
|
|
28
41
|
} from '#server/clientBuildOptions.ts'
|
|
29
42
|
import { type Given, type Host, runCommands } from '#server/commands.ts'
|
|
30
|
-
import { componentImports } from '#server/componentImports.ts'
|
|
31
43
|
import { type AppPackage, packageUnder } from '#server/config.ts'
|
|
32
|
-
import {
|
|
33
|
-
|
|
34
|
-
createApp,
|
|
35
|
-
hooksModuleUnder,
|
|
36
|
-
originUnder,
|
|
37
|
-
} from '#server/createApp.ts'
|
|
44
|
+
import { appRoot, createApp, originUnder } from '#server/createApp.ts'
|
|
45
|
+
import { executableName } from '#server/executableName.ts'
|
|
38
46
|
import { fenceServer } from '#server/fenceServer.ts'
|
|
39
|
-
import {
|
|
47
|
+
import { iconAccent } from '#server/iconAccent.ts'
|
|
40
48
|
import { manifestOf, NO_MANIFEST } from '#server/manifestOf.ts'
|
|
41
49
|
import { namesUnder } from '#server/namesUnder.ts'
|
|
42
50
|
import { refuseAddressCollision } from '#server/refuseAddressCollision.ts'
|
|
43
|
-
import {
|
|
51
|
+
import { DEFAULT_DOCUMENT, intoHead } from '#server/render.ts'
|
|
44
52
|
import {
|
|
45
|
-
handlersIn,
|
|
46
53
|
loadBuild,
|
|
47
54
|
pathsUnder,
|
|
48
55
|
serveApp,
|
|
56
|
+
serveBuild,
|
|
49
57
|
} from '#server/serveBuild.ts'
|
|
50
58
|
import { serveDev } from '#server/serveDev.ts'
|
|
51
59
|
import { escapeText } from '#shared/escapeText.ts'
|
|
60
|
+
import { ICON_ADDRESSES, LAUNCHER_PAGE } from '#shared/LAUNCHER_FILES.ts'
|
|
52
61
|
|
|
53
62
|
/** Where the app a command builds or serves is, and where its build goes — both absolute. */
|
|
54
63
|
type AppDirectories = { root: string; out: string }
|
|
55
64
|
|
|
56
65
|
/**
|
|
57
|
-
* `abide`'s host: the app on disk, found by `--root` (38.
|
|
66
|
+
* `abide`'s host: the app on disk, found by `--root` (38.25), and every command that needs the
|
|
58
67
|
* source and the compiler.
|
|
59
68
|
*/
|
|
60
69
|
const DISK: Host = {
|
|
61
70
|
name: 'abide',
|
|
71
|
+
// A BUILD ALREADY THERE, NEVER ONE MADE FOR THE ASKING: `call` and the shell are aimed at a
|
|
72
|
+
// running app (38.7), which this tree may not even be.
|
|
62
73
|
handlers: async ({ values }) => {
|
|
63
74
|
const app = directoriesOf(values)
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
75
|
+
if (
|
|
76
|
+
app === undefined ||
|
|
77
|
+
!(await serveBuild(app.root, app.out, async () => undefined))
|
|
78
|
+
)
|
|
79
|
+
return []
|
|
80
|
+
return (await servedTable(app.root)).handlers
|
|
67
81
|
},
|
|
68
82
|
appPackage: () => packageUnder(appRoot(undefined) ?? process.cwd()),
|
|
69
83
|
boot: createApp,
|
|
70
|
-
serve: inApp(({ root, out }, { values: { port } }) =>
|
|
71
|
-
|
|
84
|
+
serve: inApp(async ({ root, out }, { values: { port } }) => {
|
|
85
|
+
if (!(await serveBuild(root, out, buildInProcess))) {
|
|
86
|
+
console.error(`abide start: nothing under ${root} to serve.`)
|
|
87
|
+
return 1
|
|
88
|
+
}
|
|
89
|
+
return serveApp(
|
|
72
90
|
originUnder(root),
|
|
73
91
|
() => loadBuild({ root, out }),
|
|
74
92
|
port === undefined ? undefined : Number(port),
|
|
75
|
-
)
|
|
76
|
-
),
|
|
93
|
+
)
|
|
94
|
+
}),
|
|
77
95
|
source: {
|
|
78
96
|
scaffold: scaffoldCommand,
|
|
79
97
|
dev: inApp(dev),
|
|
80
98
|
build: inApp(build),
|
|
81
99
|
check: inApp(check),
|
|
100
|
+
routes: inApp(routes),
|
|
82
101
|
compile: inApp(compile),
|
|
83
102
|
bundle: inApp(bundle),
|
|
84
103
|
lsp,
|
|
85
104
|
},
|
|
86
105
|
}
|
|
87
106
|
|
|
107
|
+
// 38.100 — what `serveBuild` builds an app nobody built with, in this process rather than a child.
|
|
108
|
+
function buildInProcess(root: string, out: string): Promise<unknown> {
|
|
109
|
+
return writeBuild({ root, out })
|
|
110
|
+
}
|
|
111
|
+
|
|
88
112
|
/** `abide build`: compiles the app, bundles its client and copies its public files into `out`. */
|
|
89
113
|
async function build(directories: AppDirectories): Promise<number> {
|
|
90
114
|
await writeBuild(directories)
|
|
91
115
|
return 0
|
|
92
116
|
}
|
|
93
117
|
|
|
94
|
-
/** `build`'s work; answers
|
|
95
|
-
async function writeBuild({
|
|
118
|
+
/** `build`'s work; answers the app it built, or `undefined` where there was nothing under `root`. */
|
|
119
|
+
async function writeBuild({
|
|
120
|
+
root,
|
|
121
|
+
out,
|
|
122
|
+
}: AppDirectories): Promise<CompiledApp | undefined> {
|
|
96
123
|
process.env.NODE_ENV ??= 'production'
|
|
97
|
-
// 38.
|
|
124
|
+
// 38.66 — what this build may remove is only what was written before it began.
|
|
98
125
|
const began = Date.now()
|
|
126
|
+
// 38.100 — a build that fails leaves no build: the previous one's marker goes before anything
|
|
127
|
+
// can fail, so `abide start` builds again and says why rather than booting what is left.
|
|
128
|
+
const marker = `${out}/${COMPILATION}`
|
|
129
|
+
await rm(marker, { force: true })
|
|
99
130
|
// `--out` is a real flag rather than a constant because two test files each building into
|
|
100
131
|
// one `.abide/` is the contention CLAUDE.md's "checks" section already records once.
|
|
101
|
-
const app = await compileApp(root
|
|
132
|
+
const app = await compileApp(root)
|
|
102
133
|
for (const warning of app.warnings)
|
|
103
134
|
console.warn(formatDiagnosis(warning, process.cwd()))
|
|
104
|
-
if (
|
|
135
|
+
if (
|
|
136
|
+
Object.keys(app.files).length === 0 &&
|
|
137
|
+
Object.keys(app.handlers).length === 0
|
|
138
|
+
) {
|
|
105
139
|
console.log(`abide build: no .abide sources or handlers under ${root}`)
|
|
106
|
-
return
|
|
140
|
+
return undefined
|
|
107
141
|
}
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
await Promise.all(writes)
|
|
115
|
-
|
|
116
|
-
// A MODULE WHOSE SOURCE IS GONE IS REMOVED, not left standing, or the output is the union of
|
|
117
|
-
// every build and a grep over it answers about code no build produces — the same class as the
|
|
118
|
-
// orphan chunks `bundleClient` prunes, one level up.
|
|
119
|
-
//
|
|
120
|
-
// PRUNED AGAINST WHAT WAS JUST WRITTEN rather than by emptying `out`: the directory is named
|
|
121
|
-
// by `--out` and a flag that deletes a tree is a different kind of thing from one that writes
|
|
122
|
-
// into it. `client/` holds `.js` and is pruned where it is built.
|
|
123
|
-
await pruneStale(out, '**/*.ts', app.modules, began)
|
|
124
|
-
// 36.11 — the path below the directory IS the address. `favicon.ico` answers at
|
|
125
|
-
// `/favicon.ico`, and nothing declares that anywhere.
|
|
126
|
-
const publicNames = namesUnder(`${root}/ui/${PUBLIC_DIRECTORY}`)
|
|
127
|
-
const publicAddresses = publicNames.map((name) => `/${name}`)
|
|
142
|
+
// 38.6 — THE COMPILATION, KEPT WHOLE: `abide start` serves every `.abide` out of it at the
|
|
143
|
+
// file's own path, with no compiler, and a module's imports resolve from the source they name.
|
|
144
|
+
const compilation = compilationOf(app)
|
|
145
|
+
// 36.11 — the directories above `public` and the path below it ARE the address.
|
|
146
|
+
// `src/public/favicon.ico` answers at `/favicon.ico`, and nothing declares that anywhere.
|
|
147
|
+
const publicAddresses = [...app.published.values()]
|
|
128
148
|
// 36.13 IS CHECKED BEFORE A BYTE IS BUNDLED OR COPIED, through `refuseAddressCollision` — which
|
|
129
149
|
// `abide dev` runs too. Checking it mid-copy left a partial output behind on the throw, and it
|
|
130
150
|
// left the dev lane unchecked entirely.
|
|
131
|
-
refuseAddressCollision(app.routes,
|
|
151
|
+
refuseAddressCollision(app.routes, app.mounts, app.published, 'abide build')
|
|
132
152
|
await bundleClient(
|
|
153
|
+
root,
|
|
133
154
|
out,
|
|
134
|
-
[
|
|
155
|
+
[
|
|
156
|
+
fenceServer(root, compilation.mounted),
|
|
157
|
+
abideModules(root, compilation),
|
|
158
|
+
...(await appPlugins(root)),
|
|
159
|
+
],
|
|
135
160
|
publicAddresses,
|
|
136
161
|
app.routes.length > 0,
|
|
137
162
|
began,
|
|
138
163
|
)
|
|
139
164
|
// An app's static files are its own whether or not it has a page yet.
|
|
140
|
-
const
|
|
165
|
+
const copied = await copyPublic(root, out, app.published, began)
|
|
166
|
+
// LAST, because its presence is what `serveBuild` reads as a finished build (38.100).
|
|
167
|
+
await writeWhole(marker, JSON.stringify(compilation))
|
|
141
168
|
console.log(
|
|
142
|
-
`abide build: ${app.sources} source(s), ${
|
|
169
|
+
`abide build: ${app.sources} source(s), ${copied} public file(s) -> ${out}`,
|
|
143
170
|
)
|
|
144
|
-
return
|
|
171
|
+
return app
|
|
145
172
|
}
|
|
146
173
|
|
|
147
174
|
/**
|
|
@@ -165,35 +192,35 @@ const SERVER_STYLESHEETS: Bun.BunPlugin = {
|
|
|
165
192
|
/**
|
|
166
193
|
* `abide compile`: builds the app, then one executable per target platform.
|
|
167
194
|
*
|
|
168
|
-
* 38.
|
|
195
|
+
* 38.10 — THE BUILD, AND THEN ONE EXECUTABLE PER TARGET with all of it inside (`compiledEntry`).
|
|
169
196
|
* `--out` is the executable rather than the build, which goes where `abide build` puts it; with no
|
|
170
|
-
* `--out` the executable is the app's own name, in the directory the command was run from (38.
|
|
197
|
+
* `--out` the executable is the app's own name, in the directory the command was run from (38.46).
|
|
171
198
|
*/
|
|
172
199
|
async function compile(
|
|
173
200
|
{ root }: AppDirectories,
|
|
174
201
|
{ values }: Given,
|
|
175
202
|
): Promise<number> {
|
|
176
|
-
// 38.
|
|
203
|
+
// 38.48.
|
|
177
204
|
if (values.target !== undefined && values.platforms !== undefined) {
|
|
178
205
|
console.error(
|
|
179
206
|
'abide compile: --target is one platform and --platforms is several; give one of them.',
|
|
180
207
|
)
|
|
181
208
|
return 1
|
|
182
209
|
}
|
|
183
|
-
const
|
|
184
|
-
if (
|
|
210
|
+
const written = await writtenEntry(root, undefined)
|
|
211
|
+
if (written === undefined) {
|
|
185
212
|
console.error(`abide compile: nothing under ${root} to compile.`)
|
|
186
213
|
return 1
|
|
187
214
|
}
|
|
188
|
-
const named = resolve(values.out ?? executableName(root))
|
|
215
|
+
const named = resolve(values.out ?? executableName(packageUnder(root).name))
|
|
189
216
|
const platforms =
|
|
190
217
|
values.platforms?.split(',') ??
|
|
191
218
|
(values.target === undefined ? [undefined] : [values.target])
|
|
192
219
|
for (const platform of platforms) {
|
|
193
|
-
// 38.
|
|
220
|
+
// 38.47.
|
|
194
221
|
const outfile =
|
|
195
222
|
values.platforms === undefined ? named : `${named}-${platform}`
|
|
196
|
-
await compileExecutable(root,
|
|
223
|
+
await compileExecutable(root, written, platform, outfile)
|
|
197
224
|
console.log(`abide compile: ${outfile}`)
|
|
198
225
|
}
|
|
199
226
|
return 0
|
|
@@ -202,39 +229,44 @@ async function compile(
|
|
|
202
229
|
/**
|
|
203
230
|
* `abide bundle`: builds the app, then a desktop app for this platform around its executable.
|
|
204
231
|
*
|
|
205
|
-
* 38.
|
|
232
|
+
* 38.10, 38.70 — `compile`'s executable, started from `runDesktop`, inside a `.app` named after the
|
|
206
233
|
* app where the command was run, or at `--out`.
|
|
207
234
|
*/
|
|
208
235
|
async function bundle(
|
|
209
236
|
{ root }: AppDirectories,
|
|
210
237
|
{ values }: Given,
|
|
211
238
|
): Promise<number> {
|
|
212
|
-
// 38.
|
|
239
|
+
// 38.77.
|
|
213
240
|
if (process.platform !== 'darwin') {
|
|
214
241
|
console.error(
|
|
215
242
|
`abide bundle: a desktop app is built for macOS, and this is ${process.platform}. \`abide compile\` builds an executable for any platform.`,
|
|
216
243
|
)
|
|
217
244
|
return 1
|
|
218
245
|
}
|
|
219
|
-
const out = resolve(
|
|
246
|
+
const out = resolve(
|
|
247
|
+
values.out ?? `${executableName(packageUnder(root).name)}.app`,
|
|
248
|
+
)
|
|
220
249
|
if (!out.endsWith('.app')) {
|
|
221
250
|
console.error(
|
|
222
251
|
`abide bundle: --out names the .app to write, and ${out} is not one.`,
|
|
223
252
|
)
|
|
224
253
|
return 1
|
|
225
254
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
return 1
|
|
231
|
-
}
|
|
232
|
-
const entry = await writtenEntry(root, async (out) => {
|
|
233
|
-
const launch = `${root}/bundle/launch.abide`
|
|
255
|
+
const entry = await writtenEntry(root, async (app) => {
|
|
256
|
+
const launcherPath = `${root}/${LAUNCHER_PAGE}`
|
|
257
|
+
const hasLauncher = app.files[LAUNCHER_PAGE] !== undefined
|
|
258
|
+
const art = await launcherIcon(root)
|
|
234
259
|
return {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
260
|
+
// 38.82 — the page, where it paints: a `launcher.abide` of its script blocks alone keeps
|
|
261
|
+
// the built-in page, and what its `onLauncher` answers is filed by the boot (37.1).
|
|
262
|
+
launch:
|
|
263
|
+
hasLauncher &&
|
|
264
|
+
paints(parseAbide(await Bun.file(launcherPath).text()))
|
|
265
|
+
? launcherPath
|
|
266
|
+
: undefined,
|
|
267
|
+
document: launchDocument(app),
|
|
268
|
+
icon: art?.icon,
|
|
269
|
+
accent: art?.accent,
|
|
238
270
|
}
|
|
239
271
|
})
|
|
240
272
|
if (entry === undefined) {
|
|
@@ -242,7 +274,7 @@ async function bundle(
|
|
|
242
274
|
return 1
|
|
243
275
|
}
|
|
244
276
|
const identity = packageUnder(root)
|
|
245
|
-
const name = executableName(root)
|
|
277
|
+
const name = executableName(packageUnder(root).name)
|
|
246
278
|
await compileExecutable(
|
|
247
279
|
root,
|
|
248
280
|
entry,
|
|
@@ -252,7 +284,7 @@ async function bundle(
|
|
|
252
284
|
const icon = await writeIcon(root, `${out}/Contents/Resources`)
|
|
253
285
|
await Bun.write(
|
|
254
286
|
`${out}/Contents/Info.plist`,
|
|
255
|
-
infoPlist(name,
|
|
287
|
+
infoPlist(name, identity, icon),
|
|
256
288
|
)
|
|
257
289
|
// AD-HOC SIGNED, WHOLE: the executable bun wrote is signed on its own, and a bundle around a
|
|
258
290
|
// signed executable that is not itself signed is one Gatekeeper reads as damaged.
|
|
@@ -262,11 +294,6 @@ async function bundle(
|
|
|
262
294
|
return 0
|
|
263
295
|
}
|
|
264
296
|
|
|
265
|
-
// 38.47 — A SCOPED NAME IS NAMED BY ITS LAST PART: `@acme/ledger` is `ledger`, as `bunx` calls it.
|
|
266
|
-
function executableName(root: string): string {
|
|
267
|
-
return (packageUnder(root).name ?? 'app').replace(/^@[^/]+\//, '')
|
|
268
|
-
}
|
|
269
|
-
|
|
270
297
|
/**
|
|
271
298
|
* Builds the app and writes the entry an executable starts from; `undefined` where there is no app.
|
|
272
299
|
*
|
|
@@ -276,53 +303,46 @@ async function writtenEntry(
|
|
|
276
303
|
root: string,
|
|
277
304
|
desktop:
|
|
278
305
|
| ((
|
|
279
|
-
|
|
306
|
+
app: CompiledApp,
|
|
280
307
|
) => Promise<Parameters<typeof compiledEntry>[0]['desktop']>)
|
|
281
308
|
| undefined,
|
|
282
|
-
): Promise<string | undefined> {
|
|
309
|
+
): Promise<{ entry: string; compilation: Compilation } | undefined> {
|
|
283
310
|
const out = buildDirectory(root)
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
for (const { source } of await handlersIn(out))
|
|
287
|
-
handlerModules.set(source, `${root}/${source}`)
|
|
311
|
+
const app = await writeBuild({ root, out })
|
|
312
|
+
if (app === undefined) return undefined
|
|
288
313
|
const entry = `${out}/compiled.ts`
|
|
289
314
|
await Bun.write(
|
|
290
315
|
entry,
|
|
291
316
|
compiledEntry({
|
|
292
|
-
desktop: await desktop?.(
|
|
293
|
-
|
|
294
|
-
|
|
317
|
+
desktop: await desktop?.(app),
|
|
318
|
+
root,
|
|
319
|
+
sourceMaps: serverMapsOf(app, root),
|
|
295
320
|
package: packageUnder(root),
|
|
296
|
-
shell: await shellUnder(root),
|
|
297
321
|
manifest: await Bun.file(`${out}/${MANIFEST}`).json(),
|
|
298
|
-
handlerModules,
|
|
299
322
|
client: pathsUnder(`${out}/${CLIENT_DIRECTORY}`),
|
|
300
323
|
published: pathsUnder(`${out}/${PUBLIC_DIRECTORY}`),
|
|
301
324
|
}),
|
|
302
325
|
)
|
|
303
|
-
return entry
|
|
326
|
+
return { entry, compilation: app }
|
|
304
327
|
}
|
|
305
328
|
|
|
306
|
-
/** One executable from `entry
|
|
329
|
+
/** One executable from `written`'s entry, for `platform` or this one, written to `outfile`. */
|
|
307
330
|
async function compileExecutable(
|
|
308
331
|
root: string,
|
|
309
|
-
entry: string,
|
|
332
|
+
written: { entry: string; compilation: Compilation },
|
|
310
333
|
platform: string | undefined,
|
|
311
334
|
outfile: string,
|
|
312
335
|
): Promise<void> {
|
|
313
336
|
const compiled = await Bun.build({
|
|
314
|
-
entrypoints: [entry],
|
|
337
|
+
entrypoints: [written.entry],
|
|
315
338
|
target: 'bun',
|
|
316
|
-
//
|
|
317
|
-
plugins: [
|
|
318
|
-
SERVER_STYLESHEETS,
|
|
319
|
-
componentImports(root, buildDirectory(root)),
|
|
320
|
-
],
|
|
339
|
+
// 34.16 — every `.abide` the app imports, as `abide start` serves it.
|
|
340
|
+
plugins: [SERVER_STYLESHEETS, abideModules(root, written.compilation)],
|
|
321
341
|
// UNMINIFIED, AND MAPPED: the executable is a server, and an error it logs is read by
|
|
322
342
|
// whoever operates it — a minified line prints as the whole page module it sits in. The
|
|
323
343
|
// map is embedded in the executable, which reads it from there.
|
|
324
344
|
sourcemap: 'inline',
|
|
325
|
-
// 38.
|
|
345
|
+
// 38.93 — parsed at build time rather than on every boot. ESM because the entry awaits at
|
|
326
346
|
// its top level, and bun's bytecode default is CommonJS, which refuses that. Nothing in the
|
|
327
347
|
// executable or the build's outputs says bytecode is inside, so no test can see this.
|
|
328
348
|
bytecode: true,
|
|
@@ -332,7 +352,7 @@ async function compileExecutable(
|
|
|
332
352
|
? {}
|
|
333
353
|
: { target: `bun-${platform}` as Bun.Build.CompileTarget }),
|
|
334
354
|
outfile,
|
|
335
|
-
// 38.
|
|
355
|
+
// 38.45 — THE EXECUTABLE IS THE BUILD'S, not the machine's: a `bunfig.toml`
|
|
336
356
|
// wherever it is started must not change what it runs. `.env` is still read, as
|
|
337
357
|
// `abide start` reads it — configuration is the environment's (27.8).
|
|
338
358
|
autoloadBunfig: false,
|
|
@@ -350,39 +370,67 @@ async function compileExecutable(
|
|
|
350
370
|
}
|
|
351
371
|
|
|
352
372
|
/**
|
|
353
|
-
* The document `src/
|
|
373
|
+
* The document `src/launcher.abide` renders into: abide's own, which the page writes its
|
|
374
|
+
* `<html>` onto (35.15).
|
|
354
375
|
*
|
|
355
|
-
* 38.
|
|
376
|
+
* 38.84 — THE PAGE'S OWN `<style>` IS IN ITS HEAD. A component's stylesheet reaches a browser with
|
|
356
377
|
* its client module, and a launch page has none: it is rendered before the app it would load from
|
|
357
378
|
* is served.
|
|
358
379
|
*/
|
|
359
|
-
|
|
360
|
-
const
|
|
361
|
-
|
|
362
|
-
?
|
|
363
|
-
:
|
|
364
|
-
const sheet = Bun.file(`${out}/bundle/launch.style.css`)
|
|
365
|
-
if (!(await sheet.exists())) return document
|
|
366
|
-
const style = `<style>\n${await sheet.text()}</style>\n`
|
|
367
|
-
const headEnd = document.search(/<\/head>/i)
|
|
368
|
-
return headEnd === -1
|
|
369
|
-
? `${style}${document}`
|
|
370
|
-
: `${document.slice(0, headEnd)}${style}${document.slice(headEnd)}`
|
|
380
|
+
function launchDocument(app: CompiledApp): string {
|
|
381
|
+
const sheet = app.files[LAUNCHER_PAGE]?.sheet
|
|
382
|
+
return sheet === undefined
|
|
383
|
+
? DEFAULT_DOCUMENT
|
|
384
|
+
: intoHead(DEFAULT_DOCUMENT, `<style>\n${sheet}</style>`)
|
|
371
385
|
}
|
|
372
386
|
|
|
373
387
|
/**
|
|
374
|
-
*
|
|
388
|
+
* The icon the launcher page shows, as a data URL, and the accent it takes from it — or nothing
|
|
389
|
+
* where the app has no icon.
|
|
375
390
|
*
|
|
376
|
-
* 38.
|
|
391
|
+
* 38.101 — drawn small by `sips` first: the accent is a fact about the picture, not its pixels.
|
|
392
|
+
*/
|
|
393
|
+
async function launcherIcon(
|
|
394
|
+
root: string,
|
|
395
|
+
): Promise<{ icon: string; accent: string | undefined } | undefined> {
|
|
396
|
+
const source = await publishedIcon(root)
|
|
397
|
+
if (source === undefined) return undefined
|
|
398
|
+
const drawn = async (size: number): Promise<Uint8Array | undefined> => {
|
|
399
|
+
const path = `${buildDirectory(root)}/icon-${size}.png`
|
|
400
|
+
const code = await spawnedTool('sips', [
|
|
401
|
+
'-s',
|
|
402
|
+
'format',
|
|
403
|
+
'png',
|
|
404
|
+
'-z',
|
|
405
|
+
String(size),
|
|
406
|
+
String(size),
|
|
407
|
+
source,
|
|
408
|
+
'--out',
|
|
409
|
+
path,
|
|
410
|
+
])
|
|
411
|
+
return code === 0 ? await Bun.file(path).bytes() : undefined
|
|
412
|
+
}
|
|
413
|
+
const [shown, sampled] = await Promise.all([drawn(256), drawn(64)])
|
|
414
|
+
if (shown === undefined) return undefined
|
|
415
|
+
return {
|
|
416
|
+
icon: `data:image/png;base64,${shown.toBase64()}`,
|
|
417
|
+
accent: sampled === undefined ? undefined : iconAccent(sampled),
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Writes the bundle's icon from the public files the build copied, and answers whether there was one.
|
|
423
|
+
*
|
|
424
|
+
* 38.79 — the file at `/icon.icns` as it is, or the one at `/icon.png` drawn at every size an
|
|
425
|
+
* `.icns` holds.
|
|
377
426
|
*/
|
|
378
427
|
async function writeIcon(root: string, resources: string): Promise<boolean> {
|
|
379
|
-
const
|
|
380
|
-
if (
|
|
381
|
-
|
|
428
|
+
const source = await publishedIcon(root)
|
|
429
|
+
if (source === undefined) return false
|
|
430
|
+
if (source.endsWith(ICON_ADDRESSES.icns)) {
|
|
431
|
+
await Bun.write(`${resources}/icon.icns`, Bun.file(source))
|
|
382
432
|
return true
|
|
383
433
|
}
|
|
384
|
-
const png = `${root}/bundle/icon.png`
|
|
385
|
-
if (!(await Bun.file(png).exists())) return false
|
|
386
434
|
const iconset = `${buildDirectory(root)}/icon.iconset`
|
|
387
435
|
await rm(iconset, { recursive: true, force: true })
|
|
388
436
|
await mkdir(iconset, { recursive: true })
|
|
@@ -395,7 +443,7 @@ async function writeIcon(root: string, resources: string): Promise<boolean> {
|
|
|
395
443
|
'-z',
|
|
396
444
|
String(size * scale),
|
|
397
445
|
String(size * scale),
|
|
398
|
-
|
|
446
|
+
source,
|
|
399
447
|
'--out',
|
|
400
448
|
`${iconset}/icon_${size}x${size}${scale === 2 ? '@2x' : ''}.png`,
|
|
401
449
|
]),
|
|
@@ -412,6 +460,15 @@ async function writeIcon(root: string, resources: string): Promise<boolean> {
|
|
|
412
460
|
)
|
|
413
461
|
}
|
|
414
462
|
|
|
463
|
+
/** 38.79 — the public file the app's icon is drawn from: `/icon.icns`, else `/icon.png`. */
|
|
464
|
+
async function publishedIcon(root: string): Promise<string | undefined> {
|
|
465
|
+
const published = `${buildDirectory(root)}/${PUBLIC_DIRECTORY}`
|
|
466
|
+
for (const address of [ICON_ADDRESSES.icns, ICON_ADDRESSES.png])
|
|
467
|
+
if (await Bun.file(`${published}${address}`).exists())
|
|
468
|
+
return `${published}${address}`
|
|
469
|
+
return undefined
|
|
470
|
+
}
|
|
471
|
+
|
|
415
472
|
/** Runs one of macOS's own tools, warning with what it wrote where it fails; answers its exit code. */
|
|
416
473
|
async function spawnedTool(tool: string, args: string[]): Promise<number> {
|
|
417
474
|
const child = Bun.spawn([tool, ...args], {
|
|
@@ -427,12 +484,7 @@ async function spawnedTool(tool: string, args: string[]): Promise<number> {
|
|
|
427
484
|
}
|
|
428
485
|
|
|
429
486
|
/** The `Info.plist` a bundle names its executable, its identity and its icon in. */
|
|
430
|
-
function infoPlist(
|
|
431
|
-
name: string,
|
|
432
|
-
displayName: string,
|
|
433
|
-
identity: AppPackage,
|
|
434
|
-
icon: boolean,
|
|
435
|
-
): string {
|
|
487
|
+
function infoPlist(name: string, identity: AppPackage, icon: boolean): string {
|
|
436
488
|
const entries: [string, string][] = [
|
|
437
489
|
...(icon
|
|
438
490
|
? ([['CFBundleIconFile', '<string>icon</string>']] as [
|
|
@@ -445,8 +497,8 @@ function infoPlist(
|
|
|
445
497
|
'CFBundleIdentifier',
|
|
446
498
|
`<string>abide.app.${name.replace(/[^A-Za-z0-9.-]/g, '-')}</string>`,
|
|
447
499
|
],
|
|
448
|
-
['CFBundleName', `<string>${escapeText(
|
|
449
|
-
['CFBundleDisplayName', `<string>${escapeText(
|
|
500
|
+
['CFBundleName', `<string>${escapeText(name)}</string>`],
|
|
501
|
+
['CFBundleDisplayName', `<string>${escapeText(name)}</string>`],
|
|
450
502
|
['CFBundlePackageType', '<string>APPL</string>'],
|
|
451
503
|
[
|
|
452
504
|
'CFBundleShortVersionString',
|
|
@@ -456,7 +508,7 @@ function infoPlist(
|
|
|
456
508
|
['LSMinimumSystemVersion', '<string>13.0</string>'],
|
|
457
509
|
['NSHighResolutionCapable', '<true/>'],
|
|
458
510
|
['NSPrincipalClass', '<string>NSApplication</string>'],
|
|
459
|
-
// 38.
|
|
511
|
+
// 38.71 — the app served on loopback over http, and one connected to wherever it runs.
|
|
460
512
|
[
|
|
461
513
|
'NSAppTransportSecurity',
|
|
462
514
|
'<dict><key>NSAllowsArbitraryLoads</key><true/></dict>',
|
|
@@ -475,30 +527,35 @@ ${body}</dict>
|
|
|
475
527
|
}
|
|
476
528
|
|
|
477
529
|
/**
|
|
478
|
-
* Copies the app's public files into the build output, and returns how many.
|
|
530
|
+
* Copies the app's public files into the build output, each at its address, and returns how many.
|
|
479
531
|
*
|
|
480
|
-
* 38.
|
|
481
|
-
* `abide start`
|
|
482
|
-
*
|
|
532
|
+
* 38.18 — every `public` directory, copied into the built output at the address each file is
|
|
533
|
+
* answered at, so `abide start` reads the address off the output's own layout. Copied rather than
|
|
534
|
+
* left where it is, so `abide start` answers out of what the build produced and an app moving a
|
|
535
|
+
* file after a build cannot change what is served.
|
|
483
536
|
*/
|
|
484
537
|
async function copyPublic(
|
|
485
538
|
root: string,
|
|
486
539
|
out: string,
|
|
487
|
-
|
|
540
|
+
published: Map<string, string>,
|
|
541
|
+
began: number,
|
|
488
542
|
): Promise<number> {
|
|
489
|
-
const from = `${root}/ui/${PUBLIC_DIRECTORY}`
|
|
490
|
-
|
|
491
543
|
const copies: Promise<void>[] = []
|
|
544
|
+
const produced = new Set<string>()
|
|
492
545
|
// 36.12 — the bytes as authored. `Bun.write` of a `BunFile` is a copy and not a re-encode.
|
|
493
|
-
for (const
|
|
546
|
+
for (const [source, address] of published) {
|
|
547
|
+
produced.add(address.slice(1))
|
|
494
548
|
copies.push(
|
|
495
549
|
writeWhole(
|
|
496
|
-
`${out}/${PUBLIC_DIRECTORY}
|
|
497
|
-
Bun.file(`${
|
|
550
|
+
`${out}/${PUBLIC_DIRECTORY}${address}`,
|
|
551
|
+
Bun.file(`${root}/${source}`),
|
|
498
552
|
),
|
|
499
553
|
)
|
|
554
|
+
}
|
|
500
555
|
await Promise.all(copies)
|
|
501
|
-
|
|
556
|
+
// 38.66 — a public file the app no longer has is no longer answered.
|
|
557
|
+
await pruneStale(`${out}/${PUBLIC_DIRECTORY}`, produced, began)
|
|
558
|
+
return published.size
|
|
502
559
|
}
|
|
503
560
|
|
|
504
561
|
/**
|
|
@@ -511,10 +568,11 @@ async function copyPublic(
|
|
|
511
568
|
* `--splitting`, so a destination's markup and bindings stay in the chunk its own route loads
|
|
512
569
|
* (30.11) rather than landing in the entry every page pays for. 38.5 IS THIS FUNCTION: hashed and
|
|
513
570
|
* minified by the bundler, the manifest off its metafile (the entry's name, and the chunks each
|
|
514
|
-
* route module needs for 38.
|
|
515
|
-
* does NOT carry yet is 38.
|
|
571
|
+
* route module needs for 38.28), and the precompressed copies (`precompressOne`). What the manifest
|
|
572
|
+
* does NOT carry yet is 38.18's list of public files.
|
|
516
573
|
*/
|
|
517
574
|
async function bundleClient(
|
|
575
|
+
root: string,
|
|
518
576
|
out: string,
|
|
519
577
|
plugins: Bun.BunPlugin[],
|
|
520
578
|
publicAddresses: string[],
|
|
@@ -525,18 +583,18 @@ async function bundleClient(
|
|
|
525
583
|
// AN APP OF HANDLERS ALONE has no page to boot, and is served with no client — the manifest
|
|
526
584
|
// written anyway, so `abide start` reads one shape whatever the app holds.
|
|
527
585
|
if (!paged) {
|
|
528
|
-
await pruneStale(client,
|
|
586
|
+
await pruneStale(client, new Set(), began)
|
|
529
587
|
await writeWhole(`${out}/${MANIFEST}`, JSON.stringify(NO_MANIFEST))
|
|
530
588
|
return
|
|
531
589
|
}
|
|
532
|
-
// 38.
|
|
590
|
+
// 38.66 — STAGED, THEN MOVED IN FILE BY FILE. `Bun.build` writes in place, so a second build
|
|
533
591
|
// into the same `out` — or `abide start` serving it — read a chunk half written, and emptying
|
|
534
592
|
// `client/` first took the one it was serving away. Every name but the manifest's is a hash of
|
|
535
593
|
// what it holds, so two builds of one tree move in the same bytes under the same names.
|
|
536
594
|
const staging = `${out}/.staging-${crypto.randomUUID()}`
|
|
537
595
|
try {
|
|
538
596
|
const built = await Bun.build({
|
|
539
|
-
...clientBuildOptions(
|
|
597
|
+
...clientBuildOptions(root, plugins, publicAddresses),
|
|
540
598
|
outdir: staging,
|
|
541
599
|
// 38.5 — MINIFIED, and EVERY NAME HASHED, the entry's included: chunks and assets are
|
|
542
600
|
// already `[name]-[hash]` by default and the entry is not, which left the one file every
|
|
@@ -575,16 +633,11 @@ async function bundleClient(
|
|
|
575
633
|
)
|
|
576
634
|
}
|
|
577
635
|
await Promise.all(moves)
|
|
578
|
-
await pruneStale(client,
|
|
579
|
-
// THE REAL PATH, because the metafile names a file on disk with its symlinks resolved — and an
|
|
580
|
-
// `--out` under `/tmp` is `/private/tmp` to the bundler, which would key nothing at all.
|
|
636
|
+
await pruneStale(client, produced, began)
|
|
581
637
|
await writeWhole(
|
|
582
638
|
`${out}/${MANIFEST}`,
|
|
583
639
|
JSON.stringify(
|
|
584
|
-
manifestOf(
|
|
585
|
-
built.metafile as Bun.BuildMetafile,
|
|
586
|
-
realpathSync(out),
|
|
587
|
-
),
|
|
640
|
+
manifestOf(built.metafile as Bun.BuildMetafile, root),
|
|
588
641
|
),
|
|
589
642
|
)
|
|
590
643
|
} finally {
|
|
@@ -597,18 +650,17 @@ async function bundleClient(
|
|
|
597
650
|
*
|
|
598
651
|
* A CHUNK WHOSE HASH CHANGED leaves the old one behind otherwise, and the directory is the union of
|
|
599
652
|
* every build ever run into it — and the cost is not disk: a grep over the built output answers about
|
|
600
|
-
* code no page loads. 38.
|
|
653
|
+
* code no page loads. 38.66 — only what was written before this build began: anything newer is a
|
|
601
654
|
* concurrent build's, and its manifest may name it.
|
|
602
655
|
*/
|
|
603
656
|
async function pruneStale(
|
|
604
657
|
directory: string,
|
|
605
|
-
|
|
606
|
-
produced: { has(name: string): boolean },
|
|
658
|
+
produced: ReadonlySet<string>,
|
|
607
659
|
began: number,
|
|
608
660
|
): Promise<void> {
|
|
609
661
|
const stale: Promise<void>[] = []
|
|
610
662
|
// A file gone since the scan reads as last written in the far future, and is left alone.
|
|
611
|
-
for (const name of namesUnder(directory
|
|
663
|
+
for (const name of namesUnder(directory))
|
|
612
664
|
if (
|
|
613
665
|
!produced.has(name) &&
|
|
614
666
|
Bun.file(`${directory}/${name}`).lastModified < began
|
|
@@ -620,7 +672,7 @@ async function pruneStale(
|
|
|
620
672
|
/**
|
|
621
673
|
* Writes `path` whole: to a name of its own beside it, then renamed over it.
|
|
622
674
|
*
|
|
623
|
-
* 38.
|
|
675
|
+
* 38.66 — a reader, or a second build, finds the last complete file there and never a partial one.
|
|
624
676
|
*/
|
|
625
677
|
async function writeWhole(path: string, content: string | Blob): Promise<void> {
|
|
626
678
|
const partial = `${path}.${crypto.randomUUID()}.partial`
|
|
@@ -687,7 +739,7 @@ function inApp<Answer>(
|
|
|
687
739
|
const app = directoriesOf(given.values)
|
|
688
740
|
if (app === undefined) {
|
|
689
741
|
console.error(
|
|
690
|
-
`abide ${given.positionals[0]}: no app here. Looked for
|
|
742
|
+
`abide ${given.positionals[0]}: no app here. Looked for a .abide under ${resolve('src')}, or under ${process.cwd()} where it has no src. Point --root at the directory your pages sit under.`,
|
|
691
743
|
)
|
|
692
744
|
return 1
|
|
693
745
|
}
|
|
@@ -734,6 +786,16 @@ async function scaffoldCommand({
|
|
|
734
786
|
return 0
|
|
735
787
|
}
|
|
736
788
|
|
|
789
|
+
/** `abide routes`: the app's map, as text or with `--json` as one document. 38.108. */
|
|
790
|
+
async function routes(
|
|
791
|
+
{ root }: AppDirectories,
|
|
792
|
+
{ values }: Given,
|
|
793
|
+
): Promise<number> {
|
|
794
|
+
const map = await appMap(root)
|
|
795
|
+
console.log(values.json ? JSON.stringify(map, null, 4) : mapText(map))
|
|
796
|
+
return 0
|
|
797
|
+
}
|
|
798
|
+
|
|
737
799
|
/**
|
|
738
800
|
* `abide check`: prints every finding, and fails on any error.
|
|
739
801
|
*
|
|
@@ -778,7 +840,7 @@ async function check(
|
|
|
778
840
|
/**
|
|
779
841
|
* `abide lsp`: the language server over stdio.
|
|
780
842
|
*
|
|
781
|
-
* 38.
|
|
843
|
+
* 38.11 — the language server, for as long as the editor holds stdin open. Nothing is printed: stdout
|
|
782
844
|
* is the protocol.
|
|
783
845
|
*
|
|
784
846
|
* Not `inApp`: an editor starts it wherever its workspace is, and each document finds its own app.
|