@crombolo/cli 0.16.0 → 0.18.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/oclif.manifest.json +5 -1
- package/package.json +8 -8
- package/src/classify.ts +2 -2
- package/src/commands/components/new.ts +21 -6
- package/src/operations/commands.ts +2 -1
- package/src/reference.ts +177 -40
- package/src/support/components.ts +11 -1
- package/src/support/prompt.ts +4 -4
- package/src/support/publish.ts +8 -1
- package/src/support/serve.ts +2 -1
- package/src/templates.ts +2 -2
- package/templates/basic/bundle/package.json +1 -1
- package/templates/demo/harbourstone/bundle/package.json +1 -1
- package/templates/starter/bundle/package.json +1 -1
package/oclif.manifest.json
CHANGED
|
@@ -1152,6 +1152,10 @@
|
|
|
1152
1152
|
{
|
|
1153
1153
|
"name": "template",
|
|
1154
1154
|
"type": "boolean"
|
|
1155
|
+
},
|
|
1156
|
+
{
|
|
1157
|
+
"name": "email",
|
|
1158
|
+
"type": "boolean"
|
|
1155
1159
|
}
|
|
1156
1160
|
],
|
|
1157
1161
|
"errors": [
|
|
@@ -8073,5 +8077,5 @@
|
|
|
8073
8077
|
]
|
|
8074
8078
|
}
|
|
8075
8079
|
},
|
|
8076
|
-
"version": "0.
|
|
8080
|
+
"version": "0.18.0"
|
|
8077
8081
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crombolo/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "The crombolo command line: scaffolding a site, schema, views, domains, content transfer and upgrades.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Crombolo",
|
|
@@ -36,13 +36,13 @@
|
|
|
36
36
|
"access": "public"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@crombolo/api-management": "0.
|
|
40
|
-
"@crombolo/auth": "0.
|
|
41
|
-
"@crombolo/core": "0.
|
|
42
|
-
"@crombolo/data": "0.
|
|
43
|
-
"@crombolo/schema": "0.
|
|
44
|
-
"@crombolo/server": "0.
|
|
45
|
-
"@crombolo/transfer": "0.
|
|
39
|
+
"@crombolo/api-management": "0.18.0",
|
|
40
|
+
"@crombolo/auth": "0.18.0",
|
|
41
|
+
"@crombolo/core": "0.18.0",
|
|
42
|
+
"@crombolo/data": "0.18.0",
|
|
43
|
+
"@crombolo/schema": "0.18.0",
|
|
44
|
+
"@crombolo/server": "0.18.0",
|
|
45
|
+
"@crombolo/transfer": "0.18.0",
|
|
46
46
|
"@oclif/core": "^5.1.8"
|
|
47
47
|
},
|
|
48
48
|
"oclif": {
|
package/src/classify.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* code and the envelope follow from one table.
|
|
4
4
|
*/
|
|
5
5
|
import { MigrationStepError, UpgradeStateError } from '@crombolo/data'
|
|
6
|
-
import { CommandError, PassphraseError } from '@crombolo/server'
|
|
6
|
+
import { CommandError, PassphraseError, setting } from '@crombolo/server'
|
|
7
7
|
import { CliError, ERROR_KINDS, type ErrorKind } from './errors.ts'
|
|
8
8
|
import { RemoteError, TimedOut } from './remote/client.ts'
|
|
9
9
|
|
|
@@ -84,7 +84,7 @@ export function classify(error: unknown): CliError {
|
|
|
84
84
|
if (typeof code === 'string' && SYSTEM_CODES[code])
|
|
85
85
|
return new CliError(SYSTEM_CODES[code], (error as Error).message)
|
|
86
86
|
return new CliError('internal', error instanceof Error ? error.message : String(error), {
|
|
87
|
-
details:
|
|
87
|
+
details: setting('CROMBOLO_DEBUG') && error instanceof Error ? error.stack : undefined,
|
|
88
88
|
})
|
|
89
89
|
}
|
|
90
90
|
|
|
@@ -2,10 +2,16 @@ import { existsSync } from 'node:fs'
|
|
|
2
2
|
import { mkdir, writeFile } from 'node:fs/promises'
|
|
3
3
|
import { join } from 'node:path'
|
|
4
4
|
import { viewScaffold } from '@crombolo/schema'
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
componentFileFor,
|
|
7
|
+
componentScaffold,
|
|
8
|
+
EMAIL_FOLDER,
|
|
9
|
+
emailScaffold,
|
|
10
|
+
isSafeAlias,
|
|
11
|
+
} from '@crombolo/server'
|
|
6
12
|
import { Args } from '@oclif/core'
|
|
7
13
|
import { BaseCommand } from '../../base.ts'
|
|
8
|
-
import { declaredTypes } from '../../support/components.ts'
|
|
14
|
+
import { declaredTypes, formsNamingEmailTemplate } from '../../support/components.ts'
|
|
9
15
|
import { here } from '../../support/describe.ts'
|
|
10
16
|
import { siteConfig } from '../../support/site.ts'
|
|
11
17
|
|
|
@@ -21,6 +27,7 @@ export default class ComponentsNew extends BaseCommand {
|
|
|
21
27
|
outputFields: [
|
|
22
28
|
{ name: 'created', type: 'string' },
|
|
23
29
|
{ name: 'template', type: 'boolean' },
|
|
30
|
+
{ name: 'email', type: 'boolean' },
|
|
24
31
|
],
|
|
25
32
|
errors: ['usage', 'conflict'],
|
|
26
33
|
} as const
|
|
@@ -33,17 +40,25 @@ export default class ComponentsNew extends BaseCommand {
|
|
|
33
40
|
const file = componentFileFor(config.componentsDir, args.alias)
|
|
34
41
|
if (existsSync(file)) this.fail('conflict', `${here(file)} already exists.`)
|
|
35
42
|
// A content type that names this alias gets the page scaffold, which takes
|
|
36
|
-
// PageProps;
|
|
43
|
+
// PageProps; one in emails/, or one a form's Send email names, gets the
|
|
44
|
+
// e-mail one, which takes EmailProps; anything else gets the plain one.
|
|
37
45
|
const type = declaredTypes(config).find((candidate) =>
|
|
38
46
|
candidate.components.includes(args.alias),
|
|
39
47
|
)
|
|
40
|
-
|
|
48
|
+
const email =
|
|
49
|
+
!type &&
|
|
50
|
+
(args.alias.startsWith(EMAIL_FOLDER) ||
|
|
51
|
+
formsNamingEmailTemplate(config, args.alias).length > 0)
|
|
52
|
+
if (!type && !email)
|
|
41
53
|
this.say(
|
|
42
54
|
` note no content type declares the template "${args.alias}" — add it to one's templates, or \`crombolo types new\``,
|
|
43
55
|
)
|
|
44
56
|
await mkdir(join(file, '..'), { recursive: true })
|
|
45
|
-
await writeFile(
|
|
57
|
+
await writeFile(
|
|
58
|
+
file,
|
|
59
|
+
type ? viewScaffold(type) : email ? emailScaffold(args.alias) : componentScaffold(args.alias),
|
|
60
|
+
)
|
|
46
61
|
this.say(` created ${here(file)}`)
|
|
47
|
-
return { created: here(file), template: Boolean(type) }
|
|
62
|
+
return { created: here(file), template: Boolean(type), email }
|
|
48
63
|
}
|
|
49
64
|
}
|
|
@@ -67,6 +67,7 @@ import {
|
|
|
67
67
|
replaceSummary,
|
|
68
68
|
type SiteExportOptions,
|
|
69
69
|
type SiteExportResult,
|
|
70
|
+
setting,
|
|
70
71
|
syncOptionsFor,
|
|
71
72
|
type TransferInput,
|
|
72
73
|
useSchemaStore,
|
|
@@ -723,7 +724,7 @@ export interface CommandAudit {
|
|
|
723
724
|
* Never fails the command: what it records has already happened.
|
|
724
725
|
*/
|
|
725
726
|
export async function recordCommand(config: CromboloConfig, entry: CommandAudit): Promise<void> {
|
|
726
|
-
let actor =
|
|
727
|
+
let actor = setting('CROMBOLO_ACTOR')
|
|
727
728
|
try {
|
|
728
729
|
actor ??= userInfo().username
|
|
729
730
|
} catch {
|
package/src/reference.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The command line reference as HTML, for the product site's
|
|
3
|
-
*
|
|
4
|
-
* `crombolo schema` prints, so the
|
|
2
|
+
* The command line reference as HTML, for the product site's docs: built from
|
|
3
|
+
* the CLI manifest's data, through the same clispec description
|
|
4
|
+
* `crombolo schema` prints, so the pages cannot say anything the CLI does not.
|
|
5
5
|
*/
|
|
6
6
|
import type { Interfaces } from '@oclif/core'
|
|
7
7
|
import { clispecDocument } from './clispec.ts'
|
|
@@ -25,6 +25,73 @@ interface Arg {
|
|
|
25
25
|
description?: string
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
/** One page of the reference: the topics, by the first word of a command, it covers. */
|
|
29
|
+
export interface CliPageSpec {
|
|
30
|
+
slug: string
|
|
31
|
+
title: string
|
|
32
|
+
summary: string
|
|
33
|
+
topics: readonly string[]
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Where each topic is documented. A new topic has to be placed here, or the reference test fails. */
|
|
37
|
+
export const CLI_PAGES: readonly CliPageSpec[] = [
|
|
38
|
+
{
|
|
39
|
+
slug: 'sites',
|
|
40
|
+
title: 'Sites',
|
|
41
|
+
summary: 'Create a site, start it, check on it, and describe the command line to a script.',
|
|
42
|
+
topics: ['init', 'start', 'status', 'health', 'generate', 'export', 'schema'],
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
slug: 'types',
|
|
46
|
+
title: 'Content types',
|
|
47
|
+
summary: 'Check, apply and change the content model kept in schema/.',
|
|
48
|
+
topics: ['types'],
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
slug: 'components',
|
|
52
|
+
title: 'Components and assets',
|
|
53
|
+
summary:
|
|
54
|
+
'Create and check the templates under components/, and the styles and scripts beside them.',
|
|
55
|
+
topics: ['components', 'assets'],
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
slug: 'content',
|
|
59
|
+
title: 'Content and media',
|
|
60
|
+
summary: 'Pages, media, the dictionary, domains and the recycle bin.',
|
|
61
|
+
topics: ['content', 'media', 'dictionary', 'domains', 'recycle-bin'],
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
slug: 'remotes',
|
|
65
|
+
title: 'Remotes, pull and push',
|
|
66
|
+
summary: 'Sign in to another environment, and move content and schema between them.',
|
|
67
|
+
topics: ['login', 'logout', 'whoami', 'remote', 'pull', 'push', 'bundle'],
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
slug: 'import',
|
|
71
|
+
title: 'Importing from Umbraco',
|
|
72
|
+
summary: 'Bring an Umbraco site’s content types, content and media across.',
|
|
73
|
+
topics: ['import'],
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
slug: 'users',
|
|
77
|
+
title: 'Users and access',
|
|
78
|
+
summary: 'Back office users, the administrator’s password and API clients.',
|
|
79
|
+
topics: ['users', 'admin', 'api-client'],
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
slug: 'operations',
|
|
83
|
+
title: 'Operations',
|
|
84
|
+
summary: 'Caches, the search index, maintenance pauses and upgrades.',
|
|
85
|
+
topics: ['cache', 'index', 'maintenance', 'upgrade'],
|
|
86
|
+
},
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
export interface CliReferencePage extends CliPageSpec {
|
|
90
|
+
html: string
|
|
91
|
+
/** The commands on the page, as typed: `content publish`. */
|
|
92
|
+
commands: string[]
|
|
93
|
+
}
|
|
94
|
+
|
|
28
95
|
function usage(name: string, args: Arg[]): string {
|
|
29
96
|
const words = args.map((arg) => {
|
|
30
97
|
if (!arg.name.startsWith('--')) return arg.required ? `<${arg.name}>` : `[<${arg.name}>]`
|
|
@@ -61,58 +128,128 @@ const EFFECTS: Record<string, string> = {
|
|
|
61
128
|
non_idempotent: 'Each run acts again.',
|
|
62
129
|
}
|
|
63
130
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const document = clispecDocument({
|
|
131
|
+
const documentOf = (manifest: Interfaces.Manifest) =>
|
|
132
|
+
clispecDocument({
|
|
67
133
|
bin: 'crombolo',
|
|
68
134
|
version: manifest.version,
|
|
69
135
|
commands: Object.values(manifest.commands),
|
|
70
136
|
})
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
137
|
+
|
|
138
|
+
type Command = ReturnType<typeof documentOf>['commands'][number]
|
|
139
|
+
|
|
140
|
+
const topicOf = (command: Command) => String(command.name).split(' ')[0] as string
|
|
141
|
+
|
|
142
|
+
/** The anchor a command has on its page: `content publish` → `content-publish`. */
|
|
143
|
+
export const commandAnchor = (name: string): string => name.replaceAll(' ', '-')
|
|
144
|
+
|
|
145
|
+
function commandHtml(command: Command): string {
|
|
146
|
+
const name = String(command.name)
|
|
147
|
+
const args = ((command.args as Arg[] | undefined) ?? []).slice()
|
|
148
|
+
const parts = [
|
|
149
|
+
`<h2 id="${escapeHtml(commandAnchor(name))}">${code(`crombolo ${name}`)}</h2>`,
|
|
150
|
+
`<p>${escapeHtml(sentence(String(command.description)))} ${EFFECTS[String(command.effects)] ?? ''}</p>`,
|
|
151
|
+
`<pre><code>${escapeHtml(usage(name, args))}</code></pre>`,
|
|
81
152
|
]
|
|
82
|
-
|
|
83
|
-
parts.push(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
153
|
+
if (args.length > 0)
|
|
154
|
+
parts.push(
|
|
155
|
+
`<table><thead><tr><th>Argument</th><th>What it is</th></tr></thead><tbody>${args.map(argRow).join('')}</tbody></table>`,
|
|
156
|
+
)
|
|
157
|
+
return parts.join('\n')
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** The topics no page in `CLI_PAGES` covers. */
|
|
161
|
+
export function unplacedTopics(manifest: Interfaces.Manifest): string[] {
|
|
162
|
+
const placed = new Set(CLI_PAGES.flatMap((page) => page.topics))
|
|
163
|
+
return [...new Set(documentOf(manifest).commands.map(topicOf))].filter((t) => !placed.has(t))
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* One page per entry in `CLI_PAGES`, in its order. A topic no page names — a
|
|
168
|
+
* plugin's, say — gets a page of its own after them, so nothing is dropped.
|
|
169
|
+
*/
|
|
170
|
+
export function cliReferencePages(manifest: Interfaces.Manifest): CliReferencePage[] {
|
|
171
|
+
const commands = documentOf(manifest).commands
|
|
172
|
+
const specs = [
|
|
173
|
+
...CLI_PAGES,
|
|
174
|
+
...unplacedTopics(manifest).map((topic) => ({
|
|
175
|
+
slug: topic,
|
|
176
|
+
title: topic,
|
|
177
|
+
summary: `The ${topic} commands.`,
|
|
178
|
+
topics: [topic],
|
|
179
|
+
})),
|
|
180
|
+
]
|
|
181
|
+
return specs
|
|
182
|
+
.map((spec) => {
|
|
183
|
+
const on = spec.topics.flatMap((topic) => commands.filter((c) => topicOf(c) === topic))
|
|
184
|
+
return {
|
|
185
|
+
...spec,
|
|
186
|
+
commands: on.map((c) => String(c.name)),
|
|
187
|
+
html: on.map(commandHtml).join('\n'),
|
|
188
|
+
}
|
|
189
|
+
})
|
|
190
|
+
.filter((page) => page.commands.length > 0)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** The front page of the reference: what every command shares, then each page and its commands. */
|
|
194
|
+
export function cliIndexHtml(pages: readonly CliReferencePage[], base: string): string {
|
|
195
|
+
const parts = [
|
|
196
|
+
`<p>Every command, generated from the CLI itself. Each takes ${code('--help')} for its entry ` +
|
|
197
|
+
`here, and ${code('-o json')} for a result a script can read. ${code('crombolo schema')} prints ` +
|
|
198
|
+
'the same description for a machine, as a <a href="https://clispec.dev">clispec</a> document. ' +
|
|
199
|
+
'A command that fails exits with a code saying why: see the <a href="/docs/reference/exit-codes/">exit codes</a>.</p>',
|
|
200
|
+
]
|
|
201
|
+
for (const page of pages) {
|
|
202
|
+
const url = `${base}${page.slug}/`
|
|
203
|
+
parts.push(
|
|
204
|
+
`<h2 id="${escapeHtml(page.slug)}"><a href="${url}">${escapeHtml(page.title)}</a></h2>`,
|
|
205
|
+
)
|
|
206
|
+
parts.push(`<p>${escapeHtml(page.summary)}</p>`)
|
|
207
|
+
parts.push(
|
|
208
|
+
`<ul>${page.commands
|
|
209
|
+
.map(
|
|
210
|
+
(name) =>
|
|
211
|
+
`<li><a href="${url}#${commandAnchor(name)}">${code(`crombolo ${name}`)}</a></li>`,
|
|
97
212
|
)
|
|
98
|
-
|
|
213
|
+
.join('')}</ul>`,
|
|
214
|
+
)
|
|
99
215
|
}
|
|
100
|
-
parts.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
216
|
+
return parts.join('\n')
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** The exit codes and the error envelope, from the CLI's own list. */
|
|
220
|
+
export function exitCodesHtml(manifest: Interfaces.Manifest): string {
|
|
221
|
+
const document = documentOf(manifest)
|
|
105
222
|
const rows = [
|
|
106
223
|
...document.errors.map((e) => [String(e.exit_code), e.kind, e.description] as const),
|
|
107
224
|
...document.outcomes.map((o) => [String(o.code), o.name, o.description] as const),
|
|
108
225
|
].sort((a, b) => Number(a[0]) - Number(b[0]))
|
|
109
|
-
|
|
226
|
+
return [
|
|
227
|
+
'<h2 id="codes">The codes</h2>',
|
|
228
|
+
`<p>A command that succeeds exits with ${code('0')}. A failure exits with its kind's code, and a ` +
|
|
229
|
+
"check that finds what it looks for exits with an outcome's code, its findings being its output.</p>",
|
|
110
230
|
`<table><thead><tr><th>Code</th><th>Kind</th><th>When</th></tr></thead><tbody>${rows
|
|
111
231
|
.map(
|
|
112
232
|
([codeNumber, kind, description]) =>
|
|
113
233
|
`<tr><td>${codeNumber}</td><td>${code(kind)}</td><td>${escapeHtml(description).replaceAll(/`([^`]+)`/g, '<code>$1</code>')}</td></tr>`,
|
|
114
234
|
)
|
|
115
235
|
.join('')}</tbody></table>`,
|
|
116
|
-
|
|
117
|
-
|
|
236
|
+
'<h2 id="envelope">The error envelope</h2>',
|
|
237
|
+
`<p>With ${code('-o json')}, or whenever stderr is not a terminal, the last line of stderr is a JSON ` +
|
|
238
|
+
'object naming the kind, so a script can tell a missing page from a refused one without reading the message.</p>',
|
|
239
|
+
`<pre><code>${escapeHtml(
|
|
240
|
+
JSON.stringify(
|
|
241
|
+
{
|
|
242
|
+
error: {
|
|
243
|
+
kind: 'not_found',
|
|
244
|
+
message: 'There is no page at /about/team.',
|
|
245
|
+
hint: "Check the page's path or key.",
|
|
246
|
+
exit_code: 5,
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
null,
|
|
250
|
+
2,
|
|
251
|
+
),
|
|
252
|
+
)}</code></pre>`,
|
|
253
|
+
`<p>${code('hint')}, ${code('details')} and ${code('retryable')} appear only when there is something to say.</p>`,
|
|
254
|
+
].join('\n')
|
|
118
255
|
}
|
|
@@ -1,7 +1,17 @@
|
|
|
1
|
-
/** The content types a site declares, which say which components are templates. */
|
|
1
|
+
/** The content types and forms a site declares, which say which components are templates. */
|
|
2
2
|
import { existsSync } from 'node:fs'
|
|
3
3
|
import type { CromboloConfig } from '@crombolo/server'
|
|
4
4
|
import { loadTypes } from './types.ts'
|
|
5
5
|
|
|
6
6
|
export const declaredTypes = (config: CromboloConfig) =>
|
|
7
7
|
existsSync(config.schemaDir) ? loadTypes(config).set.contentTypes : []
|
|
8
|
+
|
|
9
|
+
/** The forms whose Send email workflow names `alias` as its e-mail template. */
|
|
10
|
+
export const formsNamingEmailTemplate = (config: CromboloConfig, alias: string) =>
|
|
11
|
+
existsSync(config.schemaDir)
|
|
12
|
+
? (loadTypes(config).set.forms ?? []).filter((form) =>
|
|
13
|
+
form.workflows.some(
|
|
14
|
+
(workflow) => workflow.type === 'sendEmail' && workflow.settings.template === alias,
|
|
15
|
+
),
|
|
16
|
+
)
|
|
17
|
+
: []
|
package/src/support/prompt.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* confirmation refuses at once, naming the flag that answers it, and never
|
|
5
5
|
* reads stdin.
|
|
6
6
|
*/
|
|
7
|
-
import type
|
|
7
|
+
import { type CromboloConfig, setting } from '@crombolo/server'
|
|
8
8
|
import { CliError } from '../errors.ts'
|
|
9
9
|
import * as ops from '../operations/commands.ts'
|
|
10
10
|
|
|
@@ -83,7 +83,7 @@ export async function passphraseToOpen(
|
|
|
83
83
|
target: string,
|
|
84
84
|
config: CromboloConfig,
|
|
85
85
|
): Promise<string | undefined> {
|
|
86
|
-
const given =
|
|
86
|
+
const given = setting(PASSPHRASE_ENV)
|
|
87
87
|
if (given) return given
|
|
88
88
|
let sealed = false
|
|
89
89
|
try {
|
|
@@ -100,14 +100,14 @@ export async function passphraseToOpen(
|
|
|
100
100
|
/** The passphrase for the people a file carries: the environment's, asked for, or none. */
|
|
101
101
|
export async function passphraseForPeople(): Promise<string | undefined> {
|
|
102
102
|
return (
|
|
103
|
-
|
|
103
|
+
setting(PASSPHRASE_ENV) ??
|
|
104
104
|
(interactive() ? await readSecret(' Passphrase for its people: ') : undefined)
|
|
105
105
|
)
|
|
106
106
|
}
|
|
107
107
|
|
|
108
108
|
/** A new passphrase to seal an export with: the environment's, or asked for twice. */
|
|
109
109
|
export async function passphraseToSeal(): Promise<string> {
|
|
110
|
-
const given =
|
|
110
|
+
const given = setting(PASSPHRASE_ENV)
|
|
111
111
|
if (given) {
|
|
112
112
|
if (given.length < MIN_PASSPHRASE)
|
|
113
113
|
throw new CliError('usage', `${PASSPHRASE_ENV} is shorter than ${MIN_PASSPHRASE} characters.`)
|
package/src/support/publish.ts
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
describeRefFailure,
|
|
15
15
|
resolveNodeRef,
|
|
16
16
|
} from '@crombolo/data'
|
|
17
|
-
import { bootstrapDatabase } from '@crombolo/server'
|
|
17
|
+
import { bootstrapDatabase, createContentNotifier, resolveEmailPort } from '@crombolo/server'
|
|
18
18
|
import { Args, Flags } from '@oclif/core'
|
|
19
19
|
import { CliError } from '../errors.ts'
|
|
20
20
|
import { many } from './flags.ts'
|
|
@@ -124,12 +124,19 @@ export async function publishPages(
|
|
|
124
124
|
)
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
+
// The people who asked to hear of a publish here hear of this one too (story 045).
|
|
128
|
+
const notifier = createContentNotifier(db, { canSend: Boolean(resolveEmailPort(config)) })
|
|
127
129
|
for (const node of targets) {
|
|
128
130
|
const result = publishing
|
|
129
131
|
? await docs.publish(node.key, cultures.length > 0 ? cultures : null)
|
|
130
132
|
: await docs.unpublish(node.key, cultures.length > 0 ? cultures : null)
|
|
131
133
|
items.push({ key: node.key, name: node.text ?? node.key, changed: Boolean(result) })
|
|
132
134
|
if (!result) continue
|
|
135
|
+
if (publishing)
|
|
136
|
+
await notifier.raise(
|
|
137
|
+
{ actions: ['Umb.Document.Publish'], key: node.key, keyKind: 'document' },
|
|
138
|
+
{ key: null, name: 'The command line' },
|
|
139
|
+
)
|
|
133
140
|
io.say(` ${publishing ? 'published' : 'unpublished'} ${node.text ?? node.key}`)
|
|
134
141
|
}
|
|
135
142
|
io.say(
|
package/src/support/serve.ts
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
mediaStoreFor,
|
|
24
24
|
placeBlobs,
|
|
25
25
|
type ServerHandle,
|
|
26
|
+
setting,
|
|
26
27
|
} from '@crombolo/server'
|
|
27
28
|
import { importBundle } from '@crombolo/transfer'
|
|
28
29
|
import { CliError } from '../errors.ts'
|
|
@@ -199,7 +200,7 @@ export async function serveSite(io: Io, options: StartOptions): Promise<Started>
|
|
|
199
200
|
* platform's restart policy and alerts take over. docs/operations.md.
|
|
200
201
|
*/
|
|
201
202
|
async function bootWithRetries(io: Io, config: CromboloConfig, options: StartOptions) {
|
|
202
|
-
const attempts = Math.max(1, Number(
|
|
203
|
+
const attempts = Math.max(1, Number(setting('CROMBOLO_BOOT_ATTEMPTS') ?? 10) || 10)
|
|
203
204
|
const delays = bootRetryDelays(attempts)
|
|
204
205
|
let failure: BootFailure | undefined
|
|
205
206
|
let progress = { attempt: 1, attempts, retryInSeconds: undefined as number | undefined }
|
package/src/templates.ts
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'
|
|
18
18
|
import { join, relative } from 'node:path'
|
|
19
19
|
import { siteMediaTypeFiles } from '@crombolo/schema'
|
|
20
|
-
import { VERSION } from '@crombolo/server'
|
|
20
|
+
import { setting, VERSION } from '@crombolo/server'
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* `templates/`, beside `src/` and `bin/` in the published package.
|
|
@@ -25,7 +25,7 @@ import { VERSION } from '@crombolo/server'
|
|
|
25
25
|
* without writing into the package that other runs read.
|
|
26
26
|
*/
|
|
27
27
|
export const TEMPLATES_DIR =
|
|
28
|
-
|
|
28
|
+
setting('CROMBOLO_TEMPLATES_DIR') || join(import.meta.dir, '..', 'templates')
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* A bundle whose server half a template wires into the site it scaffolds: the
|