@crombolo/server 0.14.4 → 0.16.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/package.json +15 -13
- package/src/atomic-write.ts +13 -0
- package/src/config.ts +2 -2
- package/src/domains.ts +5 -4
- package/src/index.ts +3 -0
- package/src/member-auth.ts +5 -1
- package/src/remote-media.ts +4 -4
- package/src/server.ts +3 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crombolo/server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "The crombolo HTTP server: routing, configuration, media stores and background jobs.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Crombolo",
|
|
@@ -18,7 +18,9 @@
|
|
|
18
18
|
},
|
|
19
19
|
"type": "module",
|
|
20
20
|
"exports": {
|
|
21
|
-
".": "./src/index.ts"
|
|
21
|
+
".": "./src/index.ts",
|
|
22
|
+
"./remote-credentials": "./src/remote-credentials.ts",
|
|
23
|
+
"./atomic-write": "./src/atomic-write.ts"
|
|
22
24
|
},
|
|
23
25
|
"files": [
|
|
24
26
|
"src",
|
|
@@ -28,17 +30,17 @@
|
|
|
28
30
|
"access": "public"
|
|
29
31
|
},
|
|
30
32
|
"dependencies": {
|
|
31
|
-
"@crombolo/api-delivery": "0.
|
|
32
|
-
"@crombolo/api-management": "0.
|
|
33
|
-
"@crombolo/assistant": "0.
|
|
34
|
-
"@crombolo/auth": "0.
|
|
35
|
-
"@crombolo/backoffice-host": "0.
|
|
36
|
-
"@crombolo/contracts": "0.
|
|
37
|
-
"@crombolo/core": "0.
|
|
38
|
-
"@crombolo/data": "0.
|
|
39
|
-
"@crombolo/render": "0.
|
|
40
|
-
"@crombolo/schema": "0.
|
|
41
|
-
"@crombolo/transfer": "0.
|
|
33
|
+
"@crombolo/api-delivery": "0.16.0",
|
|
34
|
+
"@crombolo/api-management": "0.16.0",
|
|
35
|
+
"@crombolo/assistant": "0.16.0",
|
|
36
|
+
"@crombolo/auth": "0.16.0",
|
|
37
|
+
"@crombolo/backoffice-host": "0.16.0",
|
|
38
|
+
"@crombolo/contracts": "0.16.0",
|
|
39
|
+
"@crombolo/core": "0.16.0",
|
|
40
|
+
"@crombolo/data": "0.16.0",
|
|
41
|
+
"@crombolo/render": "0.16.0",
|
|
42
|
+
"@crombolo/schema": "0.16.0",
|
|
43
|
+
"@crombolo/transfer": "0.16.0",
|
|
42
44
|
"@logtape/logtape": "^2.3.8"
|
|
43
45
|
}
|
|
44
46
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Writes a file whole or not at all: to a temporary file beside it, then a
|
|
3
|
+
* rename, so a crash or a second process never sees half of it.
|
|
4
|
+
*/
|
|
5
|
+
import { mkdirSync, renameSync, writeFileSync } from 'node:fs'
|
|
6
|
+
import { dirname } from 'node:path'
|
|
7
|
+
|
|
8
|
+
export function writeFileAtomic(path: string, data: string | Uint8Array): void {
|
|
9
|
+
mkdirSync(dirname(path), { recursive: true })
|
|
10
|
+
const temporary = `${path}.${process.pid}.${Date.now().toString(36)}.tmp`
|
|
11
|
+
writeFileSync(temporary, data)
|
|
12
|
+
renameSync(temporary, path)
|
|
13
|
+
}
|
package/src/config.ts
CHANGED
|
@@ -230,7 +230,7 @@ export interface CromboloConfig {
|
|
|
230
230
|
* CROMBOLO_SCHEMA_WRITABLE says otherwise outright.
|
|
231
231
|
*/
|
|
232
232
|
schemaWritable: boolean
|
|
233
|
-
/** Sync `schema/` at boot; a pipeline that runs `crombolo
|
|
233
|
+
/** Sync `schema/` at boot; a pipeline that runs `crombolo types sync` turns this off. */
|
|
234
234
|
syncSchemaAtBoot: boolean
|
|
235
235
|
/** Which database engine; from CROMBOLO_DB. */
|
|
236
236
|
dialect: 'sqlite' | 'postgres'
|
|
@@ -739,4 +739,4 @@ export function describeDatabase(
|
|
|
739
739
|
}
|
|
740
740
|
}
|
|
741
741
|
|
|
742
|
-
export const VERSION = '0.
|
|
742
|
+
export const VERSION = '0.16.0'
|
package/src/domains.ts
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
type NodeRow,
|
|
26
26
|
resolveNodeRef,
|
|
27
27
|
} from '@crombolo/data'
|
|
28
|
+
import { writeFileAtomic } from './atomic-write.ts'
|
|
28
29
|
|
|
29
30
|
export const DOMAINS_FILE = 'domains.toml'
|
|
30
31
|
|
|
@@ -152,8 +153,8 @@ export async function writeDomains(
|
|
|
152
153
|
const file = domainsFilePath(siteDir)
|
|
153
154
|
const backup = join(siteDir, DOMAINS_BACKUP_FILE)
|
|
154
155
|
const kept = existsSync(file) ? readFileSync(file, 'utf8') : undefined
|
|
155
|
-
if (kept !== undefined)
|
|
156
|
-
|
|
156
|
+
if (kept !== undefined) writeFileAtomic(backup, kept)
|
|
157
|
+
writeFileAtomic(file, writeDomainsFile(declarations))
|
|
157
158
|
return { file, backup: kept === undefined ? undefined : backup }
|
|
158
159
|
}
|
|
159
160
|
|
|
@@ -167,10 +168,10 @@ export async function undoDomainsFile(
|
|
|
167
168
|
return { ok: false, file, message: `there is no ${DOMAINS_BACKUP_FILE} to go back to` }
|
|
168
169
|
const previous = readFileSync(backup, 'utf8')
|
|
169
170
|
const current = existsSync(file) ? readFileSync(file, 'utf8') : undefined
|
|
170
|
-
|
|
171
|
+
writeFileAtomic(file, previous)
|
|
171
172
|
// So that undo is itself undoable, which is what somebody who has just undone
|
|
172
173
|
// the wrong thing needs.
|
|
173
|
-
|
|
174
|
+
writeFileAtomic(backup, current ?? '')
|
|
174
175
|
return { ok: true, file }
|
|
175
176
|
}
|
|
176
177
|
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { createNodeLookup } from './access.ts'
|
|
1
2
|
export * from './adapters/audit.ts'
|
|
2
3
|
export * from './adapters/blueprints.ts'
|
|
3
4
|
export * from './adapters/bundles.ts'
|
|
@@ -12,7 +13,9 @@ export * from './adapters/public-access.ts'
|
|
|
12
13
|
export * from './adapters/redirects.ts'
|
|
13
14
|
export * from './adapters/references.ts'
|
|
14
15
|
export * from './adapters/template-files.ts'
|
|
16
|
+
export { createUserPorts } from './adapters/users.ts'
|
|
15
17
|
export * from './api-client.ts'
|
|
18
|
+
export * from './atomic-write.ts'
|
|
16
19
|
export * from './backup.ts'
|
|
17
20
|
export * from './boot-failure.ts'
|
|
18
21
|
export * from './bundle-install.ts'
|
package/src/member-auth.ts
CHANGED
|
@@ -359,7 +359,11 @@ export function createMemberAuth(
|
|
|
359
359
|
// Checked before the password, so the answer never depends on whether the
|
|
360
360
|
// password was right; the same throwaway verify as above keeps the time taken
|
|
361
361
|
// from saying what the response does not.
|
|
362
|
-
|
|
362
|
+
// A member with no password signs in only through a provider (story 035).
|
|
363
|
+
// Refused like a wrong password, in the same time, and not counted: a
|
|
364
|
+
// stranger's guesses must not lock out the provider sign-in, nor say that
|
|
365
|
+
// the account has no password.
|
|
366
|
+
if (member.isLockedOut || !member.isApproved || !member.passwordHash) {
|
|
363
367
|
await verifyPassword(input.password ?? '', await hashPassword(crypto.randomUUID()))
|
|
364
368
|
return failed()
|
|
365
369
|
}
|
package/src/remote-media.ts
CHANGED
|
@@ -7,8 +7,9 @@
|
|
|
7
7
|
* Only the store the site serves from is wrapped. An export reads the plain
|
|
8
8
|
* store, so pushing a lazily-pulled site never downloads what it never used.
|
|
9
9
|
*/
|
|
10
|
-
import { existsSync,
|
|
11
|
-
import {
|
|
10
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
11
|
+
import { join } from 'node:path'
|
|
12
|
+
import { writeFileAtomic } from './atomic-write.ts'
|
|
12
13
|
import { logger } from './logging.ts'
|
|
13
14
|
import type { MediaStore, StoredFile } from './media-store.ts'
|
|
14
15
|
import { freshAccessToken, type Keychain, osKeychain } from './remote-credentials.ts'
|
|
@@ -36,8 +37,7 @@ export function readRemoteMedia(siteDir: string): RemoteMediaSource | undefined
|
|
|
36
37
|
|
|
37
38
|
export function writeRemoteMedia(siteDir: string, source: RemoteMediaSource): void {
|
|
38
39
|
const file = join(siteDir, REMOTE_MEDIA_FILE)
|
|
39
|
-
|
|
40
|
-
writeFileSync(file, `${JSON.stringify(source, null, 2)}\n`)
|
|
40
|
+
writeFileAtomic(file, `${JSON.stringify(source, null, 2)}\n`)
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/** The site's store, reading through to its remote when the site was pulled lazily. */
|
package/src/server.ts
CHANGED
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
resolveStaticFile,
|
|
40
40
|
serveStaticFile,
|
|
41
41
|
} from '@crombolo/backoffice-host'
|
|
42
|
+
import { servedContract } from '@crombolo/contracts'
|
|
42
43
|
import {
|
|
43
44
|
CHOICE_SOURCE_KINDS,
|
|
44
45
|
type ChoiceSourceKind,
|
|
@@ -185,8 +186,6 @@ import { summariseUpgrades } from './upgrade-summary.ts'
|
|
|
185
186
|
import { assertViewRuntime } from './view-runtime.ts'
|
|
186
187
|
import { bundleEventProblems, createWebhookService, webhookStoreView } from './webhooks.ts'
|
|
187
188
|
|
|
188
|
-
const CONTRACT_PATH = Bun.resolveSync('@crombolo/contracts/OpenApi.json', import.meta.dir)
|
|
189
|
-
|
|
190
189
|
export interface ServerHandle {
|
|
191
190
|
config: CromboloConfig
|
|
192
191
|
fetch(request: Request): Promise<Response>
|
|
@@ -1712,7 +1711,8 @@ export async function createServer(config: CromboloConfig = loadConfig()): Promi
|
|
|
1712
1711
|
|
|
1713
1712
|
if (pathname.startsWith(apiPrefix)) {
|
|
1714
1713
|
if (pathname === `${apiPrefix}openapi.json`) {
|
|
1715
|
-
|
|
1714
|
+
// Umbraco's contract with crombolo's deliberate differences (contracts/src/overrides.ts).
|
|
1715
|
+
return new Response(servedContract(), {
|
|
1716
1716
|
headers: { 'content-type': 'application/json' },
|
|
1717
1717
|
})
|
|
1718
1718
|
}
|