blogwright 0.4.0-beta.4 → 0.4.0-beta.5
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/README.md +13 -6
- package/agent/agent-manifest.json +1 -1
- package/agent/server.js +944 -54
- package/dist/cli.js +11 -1
- package/dist/commands.d.ts +10 -1
- package/dist/commands.js +198 -8
- package/dist/deploy.d.ts +47 -1
- package/dist/deploy.js +60 -3
- package/dist/init.js +136 -9
- package/dist/nodes.d.ts +8 -0
- package/dist/nodes.js +690 -58
- package/dist/render.d.ts +13 -1
- package/dist/render.js +16 -3
- package/dist/web-functions.d.ts +123 -0
- package/dist/web-functions.js +548 -0
- package/package.json +4 -4
package/dist/init.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* `blogwright init` - the first-run wizard. Asks the four core questions a
|
|
3
|
-
* new site needs,
|
|
3
|
+
* new site needs, whether it is server-rendered (and with which framework,
|
|
4
|
+
* which picks its `ssr` block and entry shim), then each already-discovered
|
|
5
|
+
* plugin's own `init(io)`
|
|
4
6
|
* questions (in deterministic, name-sorted order), and writes ONE commented
|
|
5
7
|
* config/production.jsonc carrying every answered block. Runs before any
|
|
6
8
|
* context exists (there is no config to load yet), so it takes its ports
|
|
@@ -9,7 +11,7 @@
|
|
|
9
11
|
* plugins as a plain array, never a `ModuleLoader`, so this stays a domain
|
|
10
12
|
* module.
|
|
11
13
|
*/
|
|
12
|
-
import { colors, findRepoRoot, parseConfig, } from 'blogwright-core';
|
|
14
|
+
import { colors, findRepoRoot, parseConfig, WEB_FUNCTION_REGIONS, } from 'blogwright-core';
|
|
13
15
|
import { renderConfigBlock } from './config-block.js';
|
|
14
16
|
const SITE_NAME_PATTERN = /^[a-z0-9-]+$/;
|
|
15
17
|
const GITHUB_REPO_PATTERN = /^[\w.-]+\/[\w.-]+$/;
|
|
@@ -113,6 +115,114 @@ async function collectPluginBlocks(plugins, terminal, logger) {
|
|
|
113
115
|
}
|
|
114
116
|
return blocks;
|
|
115
117
|
}
|
|
118
|
+
const SSR_GUIDE_URL = 'https://blogwright.iamstan.dev/guides/server-rendered-sites/';
|
|
119
|
+
/**
|
|
120
|
+
* The `ssr` block per framework, matching the guide's recipes: where the build
|
|
121
|
+
* puts the server, the variables that server reads its listen address from
|
|
122
|
+
* (so Blogwright generates the entry shim), and the browser assets kept on S3.
|
|
123
|
+
* `other` writes the defaults for the operator to fill in.
|
|
124
|
+
*/
|
|
125
|
+
const SSR_FRAMEWORKS = {
|
|
126
|
+
astro: {
|
|
127
|
+
label: 'Astro (@astrojs/node standalone)',
|
|
128
|
+
anchor: 'astro-node-standalone',
|
|
129
|
+
entries: [
|
|
130
|
+
{ prop: '"packageDir": "dist"', comment: 'all of dist/: the adapter looks for server/' },
|
|
131
|
+
{ prop: '"entryPoint": "server/entry.mjs"' },
|
|
132
|
+
{
|
|
133
|
+
prop: '"shim": { "host": "HOST", "port": "PORT" }',
|
|
134
|
+
comment: 'generated entry shim: the server reads HOST and PORT',
|
|
135
|
+
},
|
|
136
|
+
{ prop: '"staticDir": "dist/client"' },
|
|
137
|
+
{ prop: '"staticPaths": ["/_astro/*", "/favicon.svg", "/robots.txt"]' },
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
sveltekit: {
|
|
141
|
+
label: 'SvelteKit (adapter-node)',
|
|
142
|
+
anchor: 'sveltekit-adapter-node',
|
|
143
|
+
entries: [
|
|
144
|
+
{ prop: '"packageDir": "build"' },
|
|
145
|
+
{ prop: '"entryPoint": "index.js"' },
|
|
146
|
+
{
|
|
147
|
+
prop: '"shim": { "host": "HOST", "port": "PORT" }',
|
|
148
|
+
comment: 'generated entry shim: the server reads HOST and PORT',
|
|
149
|
+
},
|
|
150
|
+
{ prop: '"staticDir": "build/client"' },
|
|
151
|
+
{ prop: '"staticPaths": ["/_app/*", "/favicon.png"]' },
|
|
152
|
+
],
|
|
153
|
+
},
|
|
154
|
+
nuxt: {
|
|
155
|
+
label: 'Nuxt (Nitro node-server)',
|
|
156
|
+
anchor: 'nuxt-nitro-node-server-preset',
|
|
157
|
+
entries: [
|
|
158
|
+
{ prop: '"packageDir": ".output/server"' },
|
|
159
|
+
{ prop: '"entryPoint": "index.mjs"' },
|
|
160
|
+
{
|
|
161
|
+
prop: '"shim": { "host": "NITRO_HOST", "port": "NITRO_PORT" }',
|
|
162
|
+
comment: 'generated entry shim: Nitro reads NITRO_HOST and NITRO_PORT',
|
|
163
|
+
},
|
|
164
|
+
{ prop: '"staticDir": ".output/public"' },
|
|
165
|
+
{ prop: '"staticPaths": ["/_nuxt/*", "/favicon.ico"]' },
|
|
166
|
+
],
|
|
167
|
+
},
|
|
168
|
+
'tanstack-start': {
|
|
169
|
+
label: 'TanStack Start (Nitro node-server)',
|
|
170
|
+
anchor: 'tanstack-start-nitro',
|
|
171
|
+
entries: [
|
|
172
|
+
{ prop: '"packageDir": ".output/server"' },
|
|
173
|
+
{ prop: '"entryPoint": "index.mjs"' },
|
|
174
|
+
{
|
|
175
|
+
prop: '"shim": { "host": "NITRO_HOST", "port": "NITRO_PORT" }',
|
|
176
|
+
comment: 'generated entry shim: Nitro reads NITRO_HOST and NITRO_PORT',
|
|
177
|
+
},
|
|
178
|
+
{ prop: '"staticDir": ".output/public"' },
|
|
179
|
+
{ prop: '"staticPaths": ["/assets/*", "/favicon.svg"]' },
|
|
180
|
+
],
|
|
181
|
+
},
|
|
182
|
+
nextjs: {
|
|
183
|
+
label: 'Next.js (standalone output)',
|
|
184
|
+
anchor: 'nextjs-standalone-output',
|
|
185
|
+
entries: [
|
|
186
|
+
{ prop: '"packageDir": ".next/standalone"', comment: 'Next serves its own static assets' },
|
|
187
|
+
{ prop: '"entryPoint": "server.js"' },
|
|
188
|
+
{
|
|
189
|
+
prop: '"shim": { "host": "HOSTNAME", "port": "PORT" }',
|
|
190
|
+
comment: 'generated entry shim: the server reads HOSTNAME and PORT',
|
|
191
|
+
},
|
|
192
|
+
],
|
|
193
|
+
},
|
|
194
|
+
other: {
|
|
195
|
+
label: 'your framework',
|
|
196
|
+
anchor: 'the-entry-shim',
|
|
197
|
+
entries: [
|
|
198
|
+
{ prop: '"packageDir": "dist"', comment: 'the built server output, repo-relative' },
|
|
199
|
+
{
|
|
200
|
+
prop: '"entryPoint": "index.js"',
|
|
201
|
+
comment: 'starts the server; add "shim" unless it listens on AWS_LAMBDA_HTTP_ENDPOINT',
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
/**
|
|
207
|
+
* Ask whether the site is server-rendered and, if so, which framework, and
|
|
208
|
+
* return that framework's recipe; `undefined` for a static site.
|
|
209
|
+
*/
|
|
210
|
+
async function askSsrFramework(terminal, logger) {
|
|
211
|
+
const wanted = await ask(terminal, logger, {
|
|
212
|
+
prompt: 'server-rendered (SSR) app on a Lambda Web Function? (y/n)',
|
|
213
|
+
defaultValue: 'n',
|
|
214
|
+
validate: (v) => (/^(y|yes|n|no)$/i.test(v) ? undefined : 'answer y or n'),
|
|
215
|
+
});
|
|
216
|
+
if (!/^y/i.test(wanted ?? ''))
|
|
217
|
+
return undefined;
|
|
218
|
+
const names = Object.keys(SSR_FRAMEWORKS);
|
|
219
|
+
const choice = await ask(terminal, logger, {
|
|
220
|
+
prompt: `framework (${names.join(', ')})`,
|
|
221
|
+
required: true,
|
|
222
|
+
validate: (v) => Object.hasOwn(SSR_FRAMEWORKS, v.toLowerCase()) ? undefined : `expected ${names.join(', ')}`,
|
|
223
|
+
});
|
|
224
|
+
return SSR_FRAMEWORKS[choice.toLowerCase()];
|
|
225
|
+
}
|
|
116
226
|
/** Render one top-level item with its own trailing comma, or none when it is last. */
|
|
117
227
|
function renderTopLevelItem(item, last) {
|
|
118
228
|
const comma = last ? '' : ',';
|
|
@@ -121,13 +231,14 @@ function renderTopLevelItem(item, last) {
|
|
|
121
231
|
return ` ${item.prop}${comma}${item.comment ? ` // ${item.comment}` : ''}`;
|
|
122
232
|
}
|
|
123
233
|
/**
|
|
124
|
-
* Render the whole `config/production.jsonc` body: the
|
|
125
|
-
* their fixed, commented style,
|
|
234
|
+
* Render the whole `config/production.jsonc` body: the core entries in
|
|
235
|
+
* their fixed, commented style, then the `ssr` block when the site is
|
|
236
|
+
* server-rendered, followed by every plugin block already
|
|
126
237
|
* rendered by `collectPluginBlocks` - each its own top-level property, comma
|
|
127
238
|
* discipline shared with the core entries via `renderTopLevelItem` so no
|
|
128
|
-
* block ever leaves (or follows) a stray comma. `
|
|
129
|
-
* reproduces exactly the entries-only output this
|
|
130
|
-
* written - byte for byte, pinned by `init.test.ts`.
|
|
239
|
+
* block ever leaves (or follows) a stray comma. With no `ssr` and
|
|
240
|
+
* `pluginBlocks` empty this reproduces exactly the entries-only output this
|
|
241
|
+
* wizard has always written - byte for byte, pinned by `init.test.ts`.
|
|
131
242
|
*/
|
|
132
243
|
function renderConfig(opts) {
|
|
133
244
|
const items = [
|
|
@@ -145,6 +256,8 @@ function renderConfig(opts) {
|
|
|
145
256
|
comment: 'enables the GitHub OIDC deploy role',
|
|
146
257
|
});
|
|
147
258
|
}
|
|
259
|
+
if (opts.ssr)
|
|
260
|
+
items.push({ block: renderConfigBlock('ssr', opts.ssr.entries) });
|
|
148
261
|
for (const block of opts.pluginBlocks)
|
|
149
262
|
items.push({ block });
|
|
150
263
|
const body = items.map((item, i) => renderTopLevelItem(item, i === items.length - 1));
|
|
@@ -172,13 +285,13 @@ export async function initSite(fs, terminal, logger, plugins, root) {
|
|
|
172
285
|
logger.error(`${configPath} already exists - edit it directly, or pass --config elsewhere`);
|
|
173
286
|
return 1;
|
|
174
287
|
}
|
|
175
|
-
logger.info(colors.bold('Welcome to blogwright -
|
|
288
|
+
logger.info(colors.bold('Welcome to blogwright - a few questions and you are live.'));
|
|
176
289
|
const siteName = await ask(terminal, logger, {
|
|
177
290
|
prompt: 'site name (lowercase slug, names every AWS resource)',
|
|
178
291
|
required: true,
|
|
179
292
|
validate: (v) => SITE_NAME_PATTERN.test(v) ? undefined : 'must be lowercase letters, digits, or dashes',
|
|
180
293
|
});
|
|
181
|
-
|
|
294
|
+
let region = await ask(terminal, logger, {
|
|
182
295
|
prompt: 'AWS region',
|
|
183
296
|
defaultValue: 'us-east-1',
|
|
184
297
|
required: true,
|
|
@@ -190,12 +303,24 @@ export async function initSite(fs, terminal, logger, plugins, root) {
|
|
|
190
303
|
prompt: 'GitHub repo for CI deploys, owner/repo (blank to skip)',
|
|
191
304
|
validate: (v) => (GITHUB_REPO_PATTERN.test(v) ? undefined : 'expected owner/repo'),
|
|
192
305
|
});
|
|
306
|
+
const ssr = await askSsrFramework(terminal, logger);
|
|
307
|
+
if (ssr && !WEB_FUNCTION_REGIONS.includes(region)) {
|
|
308
|
+
const regions = WEB_FUNCTION_REGIONS.join(', ');
|
|
309
|
+
logger.warn(`server-rendered sites run on Lambda Web Functions, available in ${regions}`);
|
|
310
|
+
region = await ask(terminal, logger, {
|
|
311
|
+
prompt: 'AWS region',
|
|
312
|
+
defaultValue: 'us-east-1',
|
|
313
|
+
required: true,
|
|
314
|
+
validate: (v) => (WEB_FUNCTION_REGIONS.includes(v) ? undefined : `expected ${regions}`),
|
|
315
|
+
});
|
|
316
|
+
}
|
|
193
317
|
const pluginBlocks = await collectPluginBlocks(plugins, terminal, logger);
|
|
194
318
|
const rendered = renderConfig({
|
|
195
319
|
region: region,
|
|
196
320
|
siteName: siteName,
|
|
197
321
|
domain,
|
|
198
322
|
githubRepo,
|
|
323
|
+
ssr,
|
|
199
324
|
pluginBlocks,
|
|
200
325
|
});
|
|
201
326
|
// Re-parsed before it is trusted onto disk, mirroring `plugin-commands.ts`'s
|
|
@@ -208,6 +333,8 @@ export async function initSite(fs, terminal, logger, plugins, root) {
|
|
|
208
333
|
logger.ok(`wrote ${configPath}`);
|
|
209
334
|
logger.info('');
|
|
210
335
|
logger.info(colors.bold('Next steps:'));
|
|
336
|
+
if (ssr)
|
|
337
|
+
logger.step(`set up the build for ${ssr.label}: ${SSR_GUIDE_URL}#${ssr.anchor}`);
|
|
211
338
|
logger.step(`blogwright bootstrap${domain ? '' : ' # add --domain later if you get one'}`);
|
|
212
339
|
logger.step('blogwright deploy');
|
|
213
340
|
logger.info(colors.dim(' (bootstrap prints ACM validation CNAMEs when a domain is set)'));
|
package/dist/nodes.d.ts
CHANGED
|
@@ -22,6 +22,14 @@ export declare function builderImageAction(image: {
|
|
|
22
22
|
* is how build-agent changes propagate through CI without a separate `bootstrap`.
|
|
23
23
|
*/
|
|
24
24
|
export declare function reconcileBuilderImage(ctx: OpsContext): Promise<void>;
|
|
25
|
+
/** Where the CLI-owned placeholder package lives, inside the Lambda-read prefix. */
|
|
26
|
+
export declare const PLACEHOLDER_ARTIFACT_KEY = "web/artifacts/placeholder.zip";
|
|
27
|
+
/**
|
|
28
|
+
* The placeholder the Web Function serves until the first deploy: a tiny
|
|
29
|
+
* CLI-owned server that listens on `AWS_LAMBDA_HTTP_ENDPOINT` and answers 503.
|
|
30
|
+
* Zipped with a fixed timestamp so identical bytes never churn.
|
|
31
|
+
*/
|
|
32
|
+
export declare function placeholderPackageZip(): Uint8Array;
|
|
25
33
|
/**
|
|
26
34
|
* The workflow's OIDC subject claim, scoped per environment to match how each one
|
|
27
35
|
* deploys: previews from any PR ref; staging from pushes to main; production from the
|