@byline/cli 1.6.2 → 1.7.1
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/dist/phases/wire/index.d.ts.map +1 -1
- package/dist/phases/wire/index.js +14 -1
- package/dist/phases/wire/index.js.map +1 -1
- package/dist/phases/wire/server-uploads.d.ts +3 -0
- package/dist/phases/wire/server-uploads.d.ts.map +1 -0
- package/dist/phases/wire/server-uploads.js +195 -0
- package/dist/phases/wire/server-uploads.js.map +1 -0
- package/dist/templates/byline/server.config.ts +8 -1
- package/dist/templates/byline-examples/collections/media/components/media-list-view.tsx +2 -18
- package/dist/templates/byline-examples/collections/media/components/media-thumbnail.tsx +3 -14
- package/dist/templates/byline-examples/server.config.ts +9 -4
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/phases/wire/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/phases/wire/index.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,KAAK,EAA2B,MAAM,gBAAgB,CAAA;AAiBpE,eAAO,MAAM,SAAS,EAAE,KA+CvB,CAAA"}
|
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
import { wireRootTsx } from './root-tsx.js';
|
|
2
2
|
import { wireServerTs } from './server-ts.js';
|
|
3
|
+
import { wireServerUploads } from './server-uploads.js';
|
|
3
4
|
import { wireStartTs } from './start-ts.js';
|
|
4
5
|
import { wireTsconfig } from './tsconfig.js';
|
|
5
6
|
import { wireViteConfig } from './vite-config.js';
|
|
6
|
-
|
|
7
|
+
// Order matters: `wireServerTs` injects the side-effect import for
|
|
8
|
+
// `byline/server.config` at the top of `src/server.ts`; `wireServerUploads`
|
|
9
|
+
// then runs in the same file to wrap the `fetch` handler with the runtime
|
|
10
|
+
// uploads helper. The two sub-edits are independent, but keeping them
|
|
11
|
+
// adjacent makes the resulting diff easier to read.
|
|
12
|
+
const SUB_EDITS = [
|
|
13
|
+
wireServerTs,
|
|
14
|
+
wireServerUploads,
|
|
15
|
+
wireStartTs,
|
|
16
|
+
wireRootTsx,
|
|
17
|
+
wireTsconfig,
|
|
18
|
+
wireViteConfig,
|
|
19
|
+
];
|
|
7
20
|
export const wirePhase = {
|
|
8
21
|
id: 'wire',
|
|
9
22
|
title: 'Wire — inject imports + path mappings + verify vite.config.ts',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/phases/wire/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAIjD,MAAM,SAAS,GAAc,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/phases/wire/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-uploads.d.ts","sourceRoot":"","sources":["../../../src/phases/wire/server-uploads.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,OAAO,EAAiB,MAAM,aAAa,CAAA;AAgBzD,eAAO,MAAM,iBAAiB,EAAE,OAS/B,CAAA"}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { Node, Project, SyntaxKind, } from 'ts-morph';
|
|
3
|
+
const REL = 'src/server.ts';
|
|
4
|
+
const SERVE_UPLOADS_NAME = 'serveUploads';
|
|
5
|
+
const SERVE_UPLOADS_MODULE = '@byline/host-tanstack-start/integrations/serve-uploads';
|
|
6
|
+
const SNIPPET = `import { ${SERVE_UPLOADS_NAME} } from '${SERVE_UPLOADS_MODULE}'
|
|
7
|
+
|
|
8
|
+
// Inside createServerEntry({ ... }), replace the fetch handler with:
|
|
9
|
+
async fetch(request) {
|
|
10
|
+
const upload = await ${SERVE_UPLOADS_NAME}(request)
|
|
11
|
+
if (upload) return upload
|
|
12
|
+
return handler.fetch(request)
|
|
13
|
+
},
|
|
14
|
+
`;
|
|
15
|
+
export const wireServerUploads = {
|
|
16
|
+
key: 'server-uploads',
|
|
17
|
+
title: `Wrap fetch with \`${SERVE_UPLOADS_NAME}\` runtime handler in ${REL}`,
|
|
18
|
+
async preview(ctx) {
|
|
19
|
+
return run(ctx, true);
|
|
20
|
+
},
|
|
21
|
+
async apply(ctx) {
|
|
22
|
+
return run(ctx, false);
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
async function run(ctx, dryRun) {
|
|
26
|
+
const path = ctx.resolve(REL);
|
|
27
|
+
if (!existsSync(path)) {
|
|
28
|
+
return { status: 'blocked', message: `${REL} not found — host phase should have caught this` };
|
|
29
|
+
}
|
|
30
|
+
// Cheap pre-check: if the helper is already referenced anywhere, we
|
|
31
|
+
// assume it's wired. Avoids a full AST round-trip on the common
|
|
32
|
+
// already-wired case and on any non-trivial server.ts the user has
|
|
33
|
+
// hand-customized to call serveUploads themselves.
|
|
34
|
+
const text = readFileSync(path, 'utf8');
|
|
35
|
+
if (text.includes(SERVE_UPLOADS_NAME)) {
|
|
36
|
+
return { status: 'skipped', message: `${REL}: ${SERVE_UPLOADS_NAME} already wired` };
|
|
37
|
+
}
|
|
38
|
+
const project = new Project({ useInMemoryFileSystem: false, skipAddingFilesFromTsConfig: true });
|
|
39
|
+
let source;
|
|
40
|
+
try {
|
|
41
|
+
source = project.addSourceFileAtPath(path);
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return manualBail(`${REL}: could not parse`);
|
|
45
|
+
}
|
|
46
|
+
const createServerEntryCall = findCreateServerEntryCall(source);
|
|
47
|
+
if (!createServerEntryCall) {
|
|
48
|
+
return manualBail(`${REL}: no \`createServerEntry(...)\` call found`);
|
|
49
|
+
}
|
|
50
|
+
const optionsLiteral = getFirstObjectArg(createServerEntryCall);
|
|
51
|
+
if (!optionsLiteral) {
|
|
52
|
+
return manualBail(`${REL}: \`createServerEntry\` argument is not an inline object literal — cannot safely auto-edit`);
|
|
53
|
+
}
|
|
54
|
+
const fetchProp = optionsLiteral.getProperty('fetch');
|
|
55
|
+
if (!fetchProp) {
|
|
56
|
+
return manualBail(`${REL}: no \`fetch\` property on \`createServerEntry\` options`);
|
|
57
|
+
}
|
|
58
|
+
if (!isCanonicalFetchProperty(fetchProp)) {
|
|
59
|
+
return manualBail(`${REL}: existing \`fetch\` does not match the canonical scaffold (\`fetch(request) { return handler.fetch(request) }\`) — manual wire required`);
|
|
60
|
+
}
|
|
61
|
+
if (dryRun) {
|
|
62
|
+
return {
|
|
63
|
+
status: 'done',
|
|
64
|
+
message: `${REL}: will wrap fetch with ${SERVE_UPLOADS_NAME}`,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
ensureImport(source);
|
|
68
|
+
replaceFetchMethod(optionsLiteral);
|
|
69
|
+
source.saveSync();
|
|
70
|
+
return { status: 'done', message: `${REL}: wrapped fetch with ${SERVE_UPLOADS_NAME}` };
|
|
71
|
+
}
|
|
72
|
+
function manualBail(message) {
|
|
73
|
+
return { status: 'manual', message, snippet: SNIPPET };
|
|
74
|
+
}
|
|
75
|
+
function findCreateServerEntryCall(source) {
|
|
76
|
+
// Walk top-level descendants looking for any `createServerEntry(...)`
|
|
77
|
+
// call. The TanStack Start scaffold exports it via `export default`,
|
|
78
|
+
// but we accept any position in the file.
|
|
79
|
+
for (const call of source.getDescendantsOfKind(SyntaxKind.CallExpression)) {
|
|
80
|
+
if (call.getExpression().getText() === 'createServerEntry')
|
|
81
|
+
return call;
|
|
82
|
+
}
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
function getFirstObjectArg(call) {
|
|
86
|
+
const args = call.getArguments();
|
|
87
|
+
if (args.length === 0)
|
|
88
|
+
return undefined;
|
|
89
|
+
const first = args[0];
|
|
90
|
+
if (first && Node.isObjectLiteralExpression(first))
|
|
91
|
+
return first;
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Recognise the canonical TanStack Start scaffold shape so we only edit
|
|
96
|
+
* a `fetch` we fully understand. Three accepted forms (all equivalent
|
|
97
|
+
* in behaviour):
|
|
98
|
+
*
|
|
99
|
+
* fetch(request) { return handler.fetch(request) } // method shorthand
|
|
100
|
+
* fetch: (request) => handler.fetch(request) // arrow w/ expr body
|
|
101
|
+
* fetch: (request) => { return handler.fetch(request) }
|
|
102
|
+
* fetch: function (request) { return handler.fetch(request) }
|
|
103
|
+
*
|
|
104
|
+
* Anything else — extra statements, async, type annotations on the
|
|
105
|
+
* parameter, a different parameter name, a wrapped/intercepted call —
|
|
106
|
+
* forces a manual bail.
|
|
107
|
+
*/
|
|
108
|
+
function isCanonicalFetchProperty(prop) {
|
|
109
|
+
const shape = extractFunctionShape(prop);
|
|
110
|
+
if (!shape)
|
|
111
|
+
return false;
|
|
112
|
+
const { parameters, body } = shape;
|
|
113
|
+
if (parameters.length !== 1)
|
|
114
|
+
return false;
|
|
115
|
+
if (parameters[0]?.getName() !== 'request')
|
|
116
|
+
return false;
|
|
117
|
+
if (!body)
|
|
118
|
+
return false;
|
|
119
|
+
// Body should evaluate `handler.fetch(request)` — either as the sole
|
|
120
|
+
// statement of a block (`{ return handler.fetch(request) }`) or as
|
|
121
|
+
// the bare expression of an arrow function.
|
|
122
|
+
let returnExpr;
|
|
123
|
+
if (Node.isBlock(body)) {
|
|
124
|
+
const stmts = body.getStatements();
|
|
125
|
+
if (stmts.length !== 1)
|
|
126
|
+
return false;
|
|
127
|
+
const only = stmts[0];
|
|
128
|
+
if (!only || !Node.isReturnStatement(only))
|
|
129
|
+
return false;
|
|
130
|
+
returnExpr = only.getExpression();
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
returnExpr = body;
|
|
134
|
+
}
|
|
135
|
+
if (!returnExpr || !Node.isCallExpression(returnExpr))
|
|
136
|
+
return false;
|
|
137
|
+
// Callee text, whitespace-stripped, must be exactly `handler.fetch`.
|
|
138
|
+
const callee = returnExpr.getExpression().getText().replace(/\s+/g, '');
|
|
139
|
+
if (callee !== 'handler.fetch')
|
|
140
|
+
return false;
|
|
141
|
+
const callArgs = returnExpr.getArguments();
|
|
142
|
+
if (callArgs.length !== 1)
|
|
143
|
+
return false;
|
|
144
|
+
if (callArgs[0]?.getText() !== 'request')
|
|
145
|
+
return false;
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
function extractFunctionShape(prop) {
|
|
149
|
+
if (Node.isMethodDeclaration(prop)) {
|
|
150
|
+
return { parameters: prop.getParameters(), body: prop.getBody() };
|
|
151
|
+
}
|
|
152
|
+
if (Node.isPropertyAssignment(prop)) {
|
|
153
|
+
const init = prop.getInitializer();
|
|
154
|
+
if (!init)
|
|
155
|
+
return undefined;
|
|
156
|
+
if (Node.isArrowFunction(init) || Node.isFunctionExpression(init)) {
|
|
157
|
+
return { parameters: init.getParameters(), body: init.getBody() };
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
function ensureImport(source) {
|
|
163
|
+
const existing = source
|
|
164
|
+
.getImportDeclarations()
|
|
165
|
+
.find((d) => d.getModuleSpecifierValue() === SERVE_UPLOADS_MODULE);
|
|
166
|
+
if (existing) {
|
|
167
|
+
const already = existing.getNamedImports().some((n) => n.getName() === SERVE_UPLOADS_NAME);
|
|
168
|
+
if (!already)
|
|
169
|
+
existing.addNamedImport(SERVE_UPLOADS_NAME);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
// Insert after the last existing import; placement among siblings
|
|
173
|
+
// doesn't matter for behaviour.
|
|
174
|
+
const imports = source.getImportDeclarations();
|
|
175
|
+
source.insertImportDeclaration(imports.length, {
|
|
176
|
+
moduleSpecifier: SERVE_UPLOADS_MODULE,
|
|
177
|
+
namedImports: [SERVE_UPLOADS_NAME],
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
function replaceFetchMethod(options) {
|
|
181
|
+
const existing = options.getProperty('fetch');
|
|
182
|
+
if (existing)
|
|
183
|
+
existing.remove();
|
|
184
|
+
options.addMethod({
|
|
185
|
+
name: 'fetch',
|
|
186
|
+
isAsync: true,
|
|
187
|
+
parameters: [{ name: 'request' }],
|
|
188
|
+
statements: [
|
|
189
|
+
`const upload = await ${SERVE_UPLOADS_NAME}(request)`,
|
|
190
|
+
'if (upload) return upload',
|
|
191
|
+
'return handler.fetch(request)',
|
|
192
|
+
],
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
//# sourceMappingURL=server-uploads.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-uploads.js","sourceRoot":"","sources":["../../../src/phases/wire/server-uploads.ts"],"names":[],"mappings":"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"}
|
|
@@ -77,8 +77,15 @@ async function buildBylineCore(): Promise<BylineCore<AdminStore>> {
|
|
|
77
77
|
// Local filesystem — suitable for development and self-hosted
|
|
78
78
|
// deployments. For cloud/production, swap to `@byline/storage-s3`
|
|
79
79
|
// (see the commented example below).
|
|
80
|
+
//
|
|
81
|
+
// IMPORTANT: `uploadDir` lives OUTSIDE `public/` on purpose. With
|
|
82
|
+
// TanStack Start + Nitro, anything under `public/` is snapshotted
|
|
83
|
+
// into `.output/public/` at build time, and the static handler reads
|
|
84
|
+
// from that snapshot — so newly-uploaded files 404 until the next
|
|
85
|
+
// rebuild. Pair this with a runtime `/uploads/*` handler in
|
|
86
|
+
// `src/server.ts` that streams from `uploadDir` on every request.
|
|
80
87
|
storage: localStorageProvider({
|
|
81
|
-
uploadDir: './
|
|
88
|
+
uploadDir: './uploads',
|
|
82
89
|
baseUrl: '/uploads',
|
|
83
90
|
}),
|
|
84
91
|
// S3-compatible alternative (AWS S3 / Cloudflare R2 / MinIO). Replace
|
|
@@ -47,19 +47,6 @@ function formatNumber(n: number, decimalPlaces: number): string {
|
|
|
47
47
|
})
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
// ---------------------------------------------------------------------------
|
|
51
|
-
// Helpers
|
|
52
|
-
// ---------------------------------------------------------------------------
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Derive the avif thumbnail URL from the original storageUrl using the same
|
|
56
|
-
* convention as MediaThumbnailCell / the Sharp upload processor.
|
|
57
|
-
* `/uploads/media/2026/02/img.jpg` → `/uploads/media/2026/02/img-thumbnail.avif`
|
|
58
|
-
*/
|
|
59
|
-
function deriveThumbnailUrl(storageUrl: string): string {
|
|
60
|
-
return storageUrl.replace(/\.[^.]+$/, '-thumbnail.avif')
|
|
61
|
-
}
|
|
62
|
-
|
|
63
50
|
// ---------------------------------------------------------------------------
|
|
64
51
|
// Order-by config
|
|
65
52
|
// ---------------------------------------------------------------------------
|
|
@@ -261,11 +248,8 @@ export function MediaListView({
|
|
|
261
248
|
{(data.docs as any[]).map((doc) => {
|
|
262
249
|
const fields = doc.fields ?? {}
|
|
263
250
|
const img = fields.image as StoredFileValue | null | undefined
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
? deriveThumbnailUrl(img.storageUrl)
|
|
267
|
-
: img.storageUrl
|
|
268
|
-
: null
|
|
251
|
+
const thumbVariant = img?.variants?.find((v) => v.name === 'thumbnail')
|
|
252
|
+
const thumbUrl = thumbVariant?.storageUrl ?? img?.storageUrl ?? null
|
|
269
253
|
|
|
270
254
|
const updatedAt = doc.updatedAt ?? null
|
|
271
255
|
|
|
@@ -8,18 +8,6 @@
|
|
|
8
8
|
|
|
9
9
|
import type { FormatterProps, StoredFileValue } from '@byline/core'
|
|
10
10
|
|
|
11
|
-
/**
|
|
12
|
-
* Derive the thumbnail URL from the original storageUrl.
|
|
13
|
-
*
|
|
14
|
-
* Sharp writes variants as siblings of the original file using the naming
|
|
15
|
-
* convention `<basename>-<variantName>.<outputExt>`:
|
|
16
|
-
* `/uploads/media/2026/02/abc-photo.jpg`
|
|
17
|
-
* → `/uploads/media/2026/02/abc-photo-thumbnail.avif`
|
|
18
|
-
*/
|
|
19
|
-
function deriveThumbnailUrl(storageUrl: string): string {
|
|
20
|
-
return storageUrl.replace(/\.[^.]+$/, '-thumbnail.avif')
|
|
21
|
-
}
|
|
22
|
-
|
|
23
11
|
/**
|
|
24
12
|
* FormatBadge renders a muted pill showing the image format (e.g. JPEG, PNG, SVG).
|
|
25
13
|
* Intended for use alongside the status badge in list-view card meta.
|
|
@@ -34,7 +22,7 @@ export function FormatBadge({ format }: { format: string }) {
|
|
|
34
22
|
|
|
35
23
|
/**
|
|
36
24
|
* MediaThumbnailCell renders a small preview image in the Media list view.
|
|
37
|
-
* When
|
|
25
|
+
* When a `thumbnail` variant has been generated its `storageUrl` is used;
|
|
38
26
|
* otherwise the original storage URL is shown.
|
|
39
27
|
*/
|
|
40
28
|
export function MediaThumbnail({ record }: FormatterProps) {
|
|
@@ -50,7 +38,8 @@ export function MediaThumbnail({ record }: FormatterProps) {
|
|
|
50
38
|
)
|
|
51
39
|
}
|
|
52
40
|
|
|
53
|
-
const
|
|
41
|
+
const thumbVariant = img.variants?.find((v) => v.name === 'thumbnail')
|
|
42
|
+
const thumbUrl = thumbVariant?.storageUrl ?? img.storageUrl
|
|
54
43
|
|
|
55
44
|
return (
|
|
56
45
|
<img
|
|
@@ -119,11 +119,16 @@ async function buildBylineCore(): Promise<BylineCore<AdminStore>> {
|
|
|
119
119
|
// addition to) this site-wide default.
|
|
120
120
|
//
|
|
121
121
|
// Local filesystem is suitable for development and self-hosted
|
|
122
|
-
// deployments. The `uploadDir` is served
|
|
123
|
-
//
|
|
124
|
-
//
|
|
122
|
+
// deployments. The `uploadDir` is served at `baseUrl` by a runtime
|
|
123
|
+
// handler in `src/server.ts` — NOT by the framework's static-asset
|
|
124
|
+
// pipeline. Keeping uploads outside `public/` is what lets newly-
|
|
125
|
+
// uploaded files appear without a rebuild: `vite build` snapshots
|
|
126
|
+
// `public/` into `.output/public/`, but the runtime handler reads
|
|
127
|
+
// `uploadDir` directly on every request. For cloud/production
|
|
128
|
+
// deployments, swap to `@byline/storage-s3` — see the commented
|
|
129
|
+
// example below.
|
|
125
130
|
storage: localStorageProvider({
|
|
126
|
-
uploadDir: './
|
|
131
|
+
uploadDir: './uploads',
|
|
127
132
|
baseUrl: '/uploads',
|
|
128
133
|
}),
|
|
129
134
|
// S3-compatible alternative (AWS S3 / Cloudflare R2 / MinIO). Replace
|