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