@payloadcms/figma 0.1.0-alpha.7 → 0.1.0-alpha.9
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/commands/init.js +1 -1
- package/dist/next/image-loader.d.ts +6 -0
- package/dist/next/image-loader.js +14 -0
- package/dist/oauth/components/LoginButton/index.js +66 -19
- package/dist/oauth/components/LoginButton/index.scss +121 -15
- package/dist/plugin/build-config.js +5 -1
- package/dist/plugin/cache-revalidation-hooks.d.ts +6 -0
- package/dist/plugin/cache-revalidation-hooks.js +69 -0
- package/dist/utils/adapters/nextjs.js +4 -3
- package/dist/utils/adapters/nitro.js +46 -25
- package/dist/utils/adapters/vite.js +2 -1
- package/dist/utils/asset-collection.d.ts +1 -1
- package/dist/utils/asset-collection.js +26 -6
- package/dist/utils/build-lambda-zip.js +49 -16
- package/dist/utils/configureNextjsCache.d.ts +7 -0
- package/dist/utils/configureNextjsCache.js +55 -0
- package/dist/utils/fs-utils.d.ts +31 -4
- package/dist/utils/fs-utils.js +95 -15
- package/dist/utils/lambda-config.js +14 -9
- package/dist/utils/lambda-runtime/nextjs-cache.d.ts +22 -0
- package/dist/utils/lambda-runtime/nextjs-cache.js +162 -0
- package/dist/utils/next-image-loader-config.d.ts +2 -0
- package/dist/utils/next-image-loader-config.js +63 -0
- package/dist/utils/next-image-loader-file.d.ts +2 -0
- package/dist/utils/next-image-loader-file.js +11 -0
- package/dist/utils/secret-files.d.ts +25 -0
- package/dist/utils/secret-files.js +50 -0
- package/package.json +6 -1
package/dist/commands/init.js
CHANGED
|
@@ -391,7 +391,7 @@ async function maybeWriteAgentConfigFiles(args) {
|
|
|
391
391
|
log.error(error instanceof Error ? error.message : 'Unknown error');
|
|
392
392
|
process.exit(1);
|
|
393
393
|
}
|
|
394
|
-
// Apply Lambda modifications (run.sh,
|
|
394
|
+
// Apply Lambda modifications (run.sh, Next.js config, lambda:buildzip script)
|
|
395
395
|
await applyLambdaModifications(process.cwd(), packageManager);
|
|
396
396
|
// Ensure .gitignore has required entries
|
|
397
397
|
await ensureGitignore(process.cwd(), [
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
const PAYLOAD_FILE_PATH_PATTERN = /^\/(?!\/)(?:.+\/)?api\/[^/]+\/file\/.+$/;
|
|
3
|
+
export function figmaNextImageLoader({ quality, src, width }) {
|
|
4
|
+
const [pathname, search = ''] = src.split('?');
|
|
5
|
+
if (!PAYLOAD_FILE_PATH_PATTERN.test(pathname)) {
|
|
6
|
+
return src;
|
|
7
|
+
}
|
|
8
|
+
const searchParams = new URLSearchParams(search);
|
|
9
|
+
searchParams.set('w', String(width));
|
|
10
|
+
if (quality !== undefined) {
|
|
11
|
+
searchParams.set('q', String(quality));
|
|
12
|
+
}
|
|
13
|
+
return `${pathname}?${searchParams}`;
|
|
14
|
+
}
|
|
@@ -6,6 +6,17 @@ import { getSearchParam } from '../../utils/getSearchParam.js';
|
|
|
6
6
|
import { resolveParentTargetOrigin, setupIframeAutoLogin } from './iframeAutoLogin.js';
|
|
7
7
|
import './index.scss';
|
|
8
8
|
const baseClass = 'oauth-login';
|
|
9
|
+
const COLLECTION_SKELETON_KEYS = [
|
|
10
|
+
'collection-1',
|
|
11
|
+
'collection-2',
|
|
12
|
+
'collection-3',
|
|
13
|
+
'collection-4'
|
|
14
|
+
];
|
|
15
|
+
const GLOBAL_SKELETON_KEYS = [
|
|
16
|
+
'global-1',
|
|
17
|
+
'global-2',
|
|
18
|
+
'global-3'
|
|
19
|
+
];
|
|
9
20
|
// Max time we wait for the parent (figma.com) to respond with a code after
|
|
10
21
|
// posting `mint-cms-oauth-code`. If we hit this without a reply, we assume
|
|
11
22
|
// the parent isn't going to respond and fall back to the manual login button.
|
|
@@ -49,6 +60,57 @@ const FigmaIcon = ()=>/*#__PURE__*/ _jsx("svg", {
|
|
|
49
60
|
fillRule: "evenodd"
|
|
50
61
|
})
|
|
51
62
|
});
|
|
63
|
+
const PayloadAdminLoadingShimmer = ()=>/*#__PURE__*/ _jsx("div", {
|
|
64
|
+
"aria-label": "Signing in",
|
|
65
|
+
"aria-live": "polite",
|
|
66
|
+
className: `${baseClass}__admin-shimmer`,
|
|
67
|
+
role: "status",
|
|
68
|
+
children: /*#__PURE__*/ _jsxs("div", {
|
|
69
|
+
"aria-hidden": "true",
|
|
70
|
+
className: `${baseClass}__admin-shimmer-main`,
|
|
71
|
+
children: [
|
|
72
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
73
|
+
className: `${baseClass}__shimmer-heading-row`,
|
|
74
|
+
children: [
|
|
75
|
+
/*#__PURE__*/ _jsx("div", {
|
|
76
|
+
className: `${baseClass}__shimmer-block ${baseClass}__shimmer-heading-icon`
|
|
77
|
+
}),
|
|
78
|
+
/*#__PURE__*/ _jsx("div", {
|
|
79
|
+
className: `${baseClass}__shimmer-block ${baseClass}__shimmer-heading-label`
|
|
80
|
+
})
|
|
81
|
+
]
|
|
82
|
+
}),
|
|
83
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
84
|
+
className: `${baseClass}__shimmer-section`,
|
|
85
|
+
children: [
|
|
86
|
+
/*#__PURE__*/ _jsx("div", {
|
|
87
|
+
className: `${baseClass}__shimmer-block ${baseClass}__shimmer-section-label`
|
|
88
|
+
}),
|
|
89
|
+
/*#__PURE__*/ _jsx("div", {
|
|
90
|
+
className: `${baseClass}__shimmer-card-grid`,
|
|
91
|
+
children: COLLECTION_SKELETON_KEYS.map((key)=>/*#__PURE__*/ _jsx("div", {
|
|
92
|
+
className: `${baseClass}__shimmer-block ${baseClass}__shimmer-card`
|
|
93
|
+
}, key))
|
|
94
|
+
})
|
|
95
|
+
]
|
|
96
|
+
}),
|
|
97
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
98
|
+
className: `${baseClass}__shimmer-section`,
|
|
99
|
+
children: [
|
|
100
|
+
/*#__PURE__*/ _jsx("div", {
|
|
101
|
+
className: `${baseClass}__shimmer-block ${baseClass}__shimmer-section-label`
|
|
102
|
+
}),
|
|
103
|
+
/*#__PURE__*/ _jsx("div", {
|
|
104
|
+
className: `${baseClass}__shimmer-card-grid`,
|
|
105
|
+
children: GLOBAL_SKELETON_KEYS.map((key)=>/*#__PURE__*/ _jsx("div", {
|
|
106
|
+
className: `${baseClass}__shimmer-block ${baseClass}__shimmer-card`
|
|
107
|
+
}, key))
|
|
108
|
+
})
|
|
109
|
+
]
|
|
110
|
+
})
|
|
111
|
+
]
|
|
112
|
+
})
|
|
113
|
+
});
|
|
52
114
|
export const DefaultLoginButton = ({ disabled, endpointSlug })=>{
|
|
53
115
|
const { config: { admin: { user: userSlug }, routes: { api }, serverURL } } = useConfig();
|
|
54
116
|
const [authorizeURL, setAuthorizeURL] = useState('');
|
|
@@ -150,26 +212,11 @@ export const DefaultLoginButton = ({ disabled, endpointSlug })=>{
|
|
|
150
212
|
endpointSlug
|
|
151
213
|
]);
|
|
152
214
|
if (!shouldRenderButton) {
|
|
153
|
-
// In the iframe auto-login window, show a
|
|
154
|
-
//
|
|
155
|
-
//
|
|
215
|
+
// In the iframe auto-login window, show a Payload admin-shaped shimmer
|
|
216
|
+
// until the flow either navigates them away or times out into the manual
|
|
217
|
+
// button.
|
|
156
218
|
if (isInIframe === true && !disabled) {
|
|
157
|
-
return /*#__PURE__*/ _jsx(
|
|
158
|
-
className: baseClass,
|
|
159
|
-
children: /*#__PURE__*/ _jsxs("div", {
|
|
160
|
-
className: `${baseClass}__signing-in`,
|
|
161
|
-
role: "status",
|
|
162
|
-
children: [
|
|
163
|
-
/*#__PURE__*/ _jsx("span", {
|
|
164
|
-
children: "Signing in"
|
|
165
|
-
}),
|
|
166
|
-
/*#__PURE__*/ _jsx("div", {
|
|
167
|
-
"aria-hidden": "true",
|
|
168
|
-
className: `${baseClass}__spinner`
|
|
169
|
-
})
|
|
170
|
-
]
|
|
171
|
-
})
|
|
172
|
-
});
|
|
219
|
+
return /*#__PURE__*/ _jsx(PayloadAdminLoadingShimmer, {});
|
|
173
220
|
}
|
|
174
221
|
return null;
|
|
175
222
|
}
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
.oauth-login,
|
|
2
|
+
.oauth-login__admin-shimmer {
|
|
3
|
+
--oauth-login-shimmer-bg: var(--theme-bg, var(--theme-elevation-0, #ffffff));
|
|
4
|
+
--oauth-login-shimmer-block: var(--theme-elevation-100, #efefef);
|
|
5
|
+
--oauth-login-shimmer-highlight: var(--theme-elevation-200, #f7f7f7);
|
|
6
|
+
}
|
|
7
|
+
|
|
1
8
|
.oauth-login {
|
|
2
9
|
display: flex;
|
|
3
10
|
justify-content: center;
|
|
@@ -55,28 +62,127 @@
|
|
|
55
62
|
Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
56
63
|
}
|
|
57
64
|
|
|
58
|
-
&
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
&__admin-shimmer {
|
|
66
|
+
position: fixed;
|
|
67
|
+
inset: 0;
|
|
68
|
+
z-index: 1000;
|
|
69
|
+
overflow: hidden;
|
|
70
|
+
background: var(--oauth-login-shimmer-bg);
|
|
64
71
|
font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
|
|
65
72
|
Oxygen, Ubuntu, Cantarell, sans-serif;
|
|
66
73
|
}
|
|
67
74
|
|
|
68
|
-
&
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
+
&__admin-shimmer-main {
|
|
76
|
+
padding: 2rem 2.5rem;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
&__shimmer-block {
|
|
80
|
+
position: relative;
|
|
81
|
+
overflow: hidden;
|
|
82
|
+
border-radius: 0.25rem;
|
|
83
|
+
background-color: var(--oauth-login-shimmer-block);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
&__shimmer-block::after {
|
|
87
|
+
content: '';
|
|
88
|
+
position: absolute;
|
|
89
|
+
inset: 0;
|
|
90
|
+
border-radius: inherit;
|
|
91
|
+
background-image: linear-gradient(
|
|
92
|
+
100deg,
|
|
93
|
+
transparent 0%,
|
|
94
|
+
transparent 35%,
|
|
95
|
+
var(--oauth-login-shimmer-highlight) 50%,
|
|
96
|
+
transparent 65%,
|
|
97
|
+
transparent 100%
|
|
98
|
+
);
|
|
99
|
+
pointer-events: none;
|
|
100
|
+
transform: translateX(-100%);
|
|
101
|
+
animation: oauth-login-shimmer 1.45s linear infinite;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
&__shimmer-heading-row {
|
|
105
|
+
display: flex;
|
|
106
|
+
align-items: center;
|
|
107
|
+
gap: 0.5rem;
|
|
108
|
+
height: 1.75rem;
|
|
109
|
+
margin-bottom: 1rem;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
&__shimmer-heading-icon {
|
|
113
|
+
width: 1.5rem;
|
|
114
|
+
height: 1.5rem;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
&__shimmer-heading-label {
|
|
118
|
+
width: 7.5rem;
|
|
119
|
+
height: 1.25rem;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
&__shimmer-section {
|
|
123
|
+
margin-bottom: 1.5rem;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
&__shimmer-section-label {
|
|
127
|
+
width: 7.5rem;
|
|
128
|
+
height: 1.25rem;
|
|
129
|
+
margin-bottom: 0.5rem;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
&__shimmer-card-grid {
|
|
133
|
+
display: grid;
|
|
134
|
+
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
135
|
+
gap: 1rem;
|
|
136
|
+
max-width: 51.625rem;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
&__shimmer-card {
|
|
140
|
+
height: 4rem;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
@media (prefers-color-scheme: dark) {
|
|
145
|
+
.oauth-login,
|
|
146
|
+
.oauth-login__admin-shimmer {
|
|
147
|
+
--oauth-login-shimmer-bg: var(--theme-bg, var(--theme-elevation-0, #2b2b2b));
|
|
148
|
+
--oauth-login-shimmer-block: var(--theme-elevation-100, #383838);
|
|
149
|
+
--oauth-login-shimmer-highlight: var(--theme-elevation-200, #444444);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
@media (max-width: 48rem) {
|
|
154
|
+
.oauth-login {
|
|
155
|
+
&__shimmer-card-grid {
|
|
156
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
157
|
+
}
|
|
75
158
|
}
|
|
76
159
|
}
|
|
77
160
|
|
|
78
|
-
@
|
|
161
|
+
@media (max-width: 30rem) {
|
|
162
|
+
.oauth-login {
|
|
163
|
+
&__admin-shimmer-main {
|
|
164
|
+
padding-right: 0.75rem;
|
|
165
|
+
padding-left: 0.75rem;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
&__shimmer-card-grid {
|
|
169
|
+
grid-template-columns: 1fr;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
@media (prefers-reduced-motion: reduce) {
|
|
175
|
+
.oauth-login__shimmer-block::after {
|
|
176
|
+
animation: none;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
@keyframes oauth-login-shimmer {
|
|
181
|
+
from {
|
|
182
|
+
transform: translateX(-100%);
|
|
183
|
+
}
|
|
184
|
+
|
|
79
185
|
to {
|
|
80
|
-
transform:
|
|
186
|
+
transform: translateX(100%);
|
|
81
187
|
}
|
|
82
188
|
}
|
|
@@ -20,6 +20,7 @@ import { getEnvVarSync } from '../utils/env-management.js';
|
|
|
20
20
|
import * as log from '../utils/log.js';
|
|
21
21
|
import { logMissingCliAuth } from './auth-preflight.js';
|
|
22
22
|
import { logMissingContentSystemId } from './bootstrap-preflight.js';
|
|
23
|
+
import { createCacheRevalidationHooksPlugin } from './cache-revalidation-hooks.js';
|
|
23
24
|
import { LIMIT_REACHED_VIEW_KEY, LIMIT_REACHED_VIEW_PATH } from './cloud-limits/routes.js';
|
|
24
25
|
import { getDevCookieNames } from './dev-cookie-names.js';
|
|
25
26
|
import { passesMakeViewerWriteGate, withMakeViewerWriteBaseAccessDefaults } from './make-permission-access/make-permission-access.js';
|
|
@@ -427,7 +428,10 @@ export async function buildFigmaConfig(config) {
|
|
|
427
428
|
// figma.storage is false and would break remote uploads for those apps.
|
|
428
429
|
createSandboxUploadFetchPlugin({
|
|
429
430
|
userCollections: config.collections
|
|
430
|
-
})
|
|
431
|
+
}),
|
|
432
|
+
// Keep last so hooks added by app plugins also get the missing Next cache
|
|
433
|
+
// context guard used by Payload CLI operations in the Make sandbox.
|
|
434
|
+
createCacheRevalidationHooksPlugin()
|
|
431
435
|
]
|
|
432
436
|
};
|
|
433
437
|
// Inject Figma-managed identity fields only into the Payload Admin collection.
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Plugin } from 'payload';
|
|
2
|
+
/**
|
|
3
|
+
* Prevent Next cache invalidation from turning a successful Payload mutation
|
|
4
|
+
* into an apparent failure when the mutation runs outside a Next request.
|
|
5
|
+
*/
|
|
6
|
+
export declare function createCacheRevalidationHooksPlugin(): Plugin;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const missingNextCacheContext = /^Invariant: static generation store missing in (?:revalidateTag|revalidatePath|updateTag)\b/;
|
|
2
|
+
const unsupportedUpdateTagContext = /^updateTag can only be called from within a Server Action\./;
|
|
3
|
+
/**
|
|
4
|
+
* Next cache invalidation requires request-scoped state. Payload's Local API has
|
|
5
|
+
* no Next request when it runs from the CLI in a Make sandbox, so Next throws
|
|
6
|
+
* after the database write has already committed. Treat only that missing-
|
|
7
|
+
* context error as a no-op; cache invalidation still runs normally in a Next
|
|
8
|
+
* request, and all other hook failures retain their existing behavior.
|
|
9
|
+
*/ function isUnsupportedNextCacheContextError(error) {
|
|
10
|
+
if (!(error instanceof Error)) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
const errorCode = error.__NEXT_ERROR_CODE;
|
|
14
|
+
if (missingNextCacheContext.test(error.message)) {
|
|
15
|
+
return errorCode === undefined || errorCode === 'E263';
|
|
16
|
+
}
|
|
17
|
+
if (unsupportedUpdateTagContext.test(error.message)) {
|
|
18
|
+
return errorCode === undefined || errorCode === 'E872';
|
|
19
|
+
}
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
function reportIgnoredRevalidation(args, hookName) {
|
|
23
|
+
const hookArgs = args[0];
|
|
24
|
+
hookArgs?.req?.payload?.logger?.warn({
|
|
25
|
+
hook: hookName,
|
|
26
|
+
msg: 'Ignored Next cache invalidation outside a supported request context'
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function wrapHook(hook, hookName) {
|
|
30
|
+
return async (...args)=>{
|
|
31
|
+
try {
|
|
32
|
+
return await hook(...args);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
if (isUnsupportedNextCacheContextError(error)) {
|
|
35
|
+
reportIgnoredRevalidation(args, hookName);
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function wrapHooks(hooks) {
|
|
43
|
+
if (!hooks) {
|
|
44
|
+
return hooks;
|
|
45
|
+
}
|
|
46
|
+
return Object.fromEntries(Object.entries(hooks).map(([name, value])=>[
|
|
47
|
+
name,
|
|
48
|
+
Array.isArray(value) ? value.map((hook)=>typeof hook === 'function' ? wrapHook(hook, name) : hook) : value
|
|
49
|
+
]));
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Prevent Next cache invalidation from turning a successful Payload mutation
|
|
53
|
+
* into an apparent failure when the mutation runs outside a Next request.
|
|
54
|
+
*/ export function createCacheRevalidationHooksPlugin() {
|
|
55
|
+
const plugin = (config)=>({
|
|
56
|
+
...config,
|
|
57
|
+
collections: config.collections?.map((collection)=>({
|
|
58
|
+
...collection,
|
|
59
|
+
hooks: wrapHooks(collection.hooks)
|
|
60
|
+
})),
|
|
61
|
+
globals: config.globals?.map((global)=>({
|
|
62
|
+
...global,
|
|
63
|
+
hooks: wrapHooks(global.hooks)
|
|
64
|
+
}))
|
|
65
|
+
});
|
|
66
|
+
// Run after tenant plugins so hooks they add receive the same guard.
|
|
67
|
+
plugin.order = Number.POSITIVE_INFINITY;
|
|
68
|
+
return plugin;
|
|
69
|
+
}
|
|
@@ -4,18 +4,19 @@ import { collectSSGAssets, collectStaticAssets, collectStaticMetadataAssets } fr
|
|
|
4
4
|
import { buildLambdaZip } from '../build-lambda-zip.js';
|
|
5
5
|
import { resolveOutputPath } from '../deploy-output/resolveOutputPath.js';
|
|
6
6
|
import { collectFilesRecursive } from '../fs-utils.js';
|
|
7
|
+
import { isPublishableAsset } from '../secret-files.js';
|
|
7
8
|
export class NextjsAdapter {
|
|
8
9
|
name = 'nextjs';
|
|
9
10
|
async collectAssets(projectPath, output) {
|
|
10
11
|
// Bundle assets: .next/static/ → _next/static/
|
|
11
|
-
const staticAssets = await collectStaticAssets(projectPath, output);
|
|
12
|
+
const staticAssets = (await collectStaticAssets(projectPath, output)).filter(isPublishableAsset);
|
|
12
13
|
const outputPath = resolveOutputPath(projectPath, 'nextjs', output);
|
|
13
14
|
// Public assets: public/ directory
|
|
14
15
|
const publicDir = path.join(projectPath, 'public');
|
|
15
16
|
let publicAssets = [];
|
|
16
17
|
try {
|
|
17
18
|
await fs.stat(publicDir);
|
|
18
|
-
publicAssets = await collectFilesRecursive(publicDir, publicDir);
|
|
19
|
+
publicAssets = (await collectFilesRecursive(publicDir, publicDir)).filter(isPublishableAsset);
|
|
19
20
|
} catch {
|
|
20
21
|
// No public directory
|
|
21
22
|
}
|
|
@@ -27,7 +28,7 @@ export class NextjsAdapter {
|
|
|
27
28
|
for (const key of publicAssets){
|
|
28
29
|
pathMap[key] = path.join('public', key);
|
|
29
30
|
}
|
|
30
|
-
for (const key of metadataAssets.keys){
|
|
31
|
+
for (const key of metadataAssets.keys.filter(isPublishableAsset)){
|
|
31
32
|
pathMap[key] = metadataAssets.pathMap[key];
|
|
32
33
|
}
|
|
33
34
|
const uploadKeys = Object.keys(pathMap);
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import archiver from 'archiver';
|
|
2
|
-
import { createWriteStream
|
|
2
|
+
import { createWriteStream } from 'fs';
|
|
3
3
|
import fs from 'fs/promises';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { resolveNitroOutputLayout } from '../deploy-output/resolveNitroOutputLayout.js';
|
|
6
6
|
import { resolveOutputPath } from '../deploy-output/resolveOutputPath.js';
|
|
7
|
-
import { collectFilesRecursive,
|
|
7
|
+
import { collectFilesRecursive, EscapingSymlinkError, walkBundleFiles } from '../fs-utils.js';
|
|
8
|
+
import { isBundlableFile, isPublishableAsset } from '../secret-files.js';
|
|
8
9
|
/** Framework asset directories that are not pre-rendered pages. */ const NON_PAGE_DIRS = new Set([
|
|
9
10
|
'_build',
|
|
10
11
|
'_nuxt',
|
|
@@ -15,7 +16,7 @@ export class NitroAdapter {
|
|
|
15
16
|
async collectAssets(projectPath, output) {
|
|
16
17
|
const { publicPath } = await getLayout(projectPath, output);
|
|
17
18
|
const pageKeys = new Set((await scanForPages(publicPath, publicPath)).map(toPageUploadKey));
|
|
18
|
-
const assets = (await collectFilesRecursive(publicPath, publicPath)).filter((file)=>!pageKeys.has(file));
|
|
19
|
+
const assets = (await collectFilesRecursive(publicPath, publicPath)).filter((file)=>!pageKeys.has(file) && isPublishableAsset(file));
|
|
19
20
|
const pathMap = Object.fromEntries(assets.map((file)=>[
|
|
20
21
|
file,
|
|
21
22
|
path.relative(projectPath, path.join(publicPath, file))
|
|
@@ -28,7 +29,7 @@ export class NitroAdapter {
|
|
|
28
29
|
}
|
|
29
30
|
async collectPages(projectPath, output) {
|
|
30
31
|
const { publicPath } = await getLayout(projectPath, output);
|
|
31
|
-
const pages = await scanForPages(publicPath, publicPath);
|
|
32
|
+
const pages = (await scanForPages(publicPath, publicPath)).filter((page)=>isPublishableAsset(toPageUploadKey(page)));
|
|
32
33
|
const routes = pages.map((p)=>p === 'index' ? '/' : `/${p}`);
|
|
33
34
|
const uploadKeys = pages.map(toPageUploadKey);
|
|
34
35
|
const pathMap = {};
|
|
@@ -57,8 +58,8 @@ export class NitroAdapter {
|
|
|
57
58
|
throw new Error(`Nitro server build not found at ${layout.outputPath}`);
|
|
58
59
|
}
|
|
59
60
|
const serverDir = path.dirname(layout.serverEntryPath);
|
|
60
|
-
const serverPrefix = path.relative(layout.outputPath, serverDir).split(path.sep).join('/');
|
|
61
|
-
const publicPrefix = path.relative(layout.outputPath, layout.publicPath).split(path.sep).join('/');
|
|
61
|
+
const serverPrefix = zipPrefix(path.relative(layout.outputPath, serverDir).split(path.sep).join('/'));
|
|
62
|
+
const publicPrefix = zipPrefix(path.relative(layout.outputPath, layout.publicPath).split(path.sep).join('/'));
|
|
62
63
|
const serverEntry = path.relative(layout.outputPath, layout.serverEntryPath).split(path.sep).join('/');
|
|
63
64
|
const runShPath = path.join(layout.outputPath, 'run.sh');
|
|
64
65
|
await fs.writeFile(runShPath, createRunScript(serverEntry), {
|
|
@@ -66,22 +67,33 @@ export class NitroAdapter {
|
|
|
66
67
|
});
|
|
67
68
|
const zipPath = path.join(projectPath, 'lambda.zip');
|
|
68
69
|
try {
|
|
70
|
+
const runShStat = await fs.stat(runShPath);
|
|
71
|
+
const [allServerFiles, publicFiles] = await Promise.all([
|
|
72
|
+
walkBundleFiles(serverDir),
|
|
73
|
+
walkPublicBundleFiles(layout.publicPath)
|
|
74
|
+
]);
|
|
75
|
+
const bundlableServerFiles = allServerFiles.filter((file)=>isBundlableFile(file.relativePath));
|
|
76
|
+
const unzippedBytes = bundlableServerFiles.reduce((total, file)=>total + file.size, 0) + publicFiles.reduce((total, file)=>total + file.size, 0) + runShStat.size;
|
|
69
77
|
await createZip(zipPath, [
|
|
70
78
|
{
|
|
71
|
-
|
|
72
|
-
|
|
79
|
+
files: bundlableServerFiles,
|
|
80
|
+
prefix: serverPrefix
|
|
73
81
|
},
|
|
74
82
|
{
|
|
75
|
-
|
|
76
|
-
|
|
83
|
+
files: publicFiles,
|
|
84
|
+
prefix: publicPrefix
|
|
77
85
|
},
|
|
78
86
|
{
|
|
79
|
-
|
|
80
|
-
|
|
87
|
+
files: [
|
|
88
|
+
{
|
|
89
|
+
relativePath: 'run.sh',
|
|
90
|
+
size: runShStat.size,
|
|
91
|
+
sourcePath: runShPath
|
|
92
|
+
}
|
|
93
|
+
],
|
|
94
|
+
prefix: ''
|
|
81
95
|
}
|
|
82
96
|
]);
|
|
83
|
-
const runShStat = await fs.stat(runShPath);
|
|
84
|
-
const unzippedBytes = await getDirectorySize(serverDir) + await getDirectorySize(layout.publicPath) + runShStat.size;
|
|
85
97
|
return {
|
|
86
98
|
unzippedBytes,
|
|
87
99
|
zipPath
|
|
@@ -135,9 +147,25 @@ NODE_ENV=production exec node '${escapedEntry}'
|
|
|
135
147
|
}
|
|
136
148
|
return pages;
|
|
137
149
|
}
|
|
150
|
+
function zipPrefix(relativeDir) {
|
|
151
|
+
return relativeDir ? `${relativeDir}/` : '';
|
|
152
|
+
}
|
|
138
153
|
function toPageUploadKey(page) {
|
|
139
154
|
return page === 'index' ? 'index.html' : `${page}/index.html`;
|
|
140
155
|
}
|
|
156
|
+
async function walkPublicBundleFiles(publicPath) {
|
|
157
|
+
try {
|
|
158
|
+
// The Nitro server serves this tree statically, so secrets copied into the
|
|
159
|
+
// public directory must not reach the Lambda bundle either.
|
|
160
|
+
const files = await walkBundleFiles(publicPath);
|
|
161
|
+
return files.filter((file)=>isPublishableAsset(file.relativePath));
|
|
162
|
+
} catch (error) {
|
|
163
|
+
if (error instanceof EscapingSymlinkError) {
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
166
|
+
return [];
|
|
167
|
+
}
|
|
168
|
+
}
|
|
141
169
|
async function createZip(zipPath, entries) {
|
|
142
170
|
return new Promise((resolve, reject)=>{
|
|
143
171
|
const output = createWriteStream(zipPath);
|
|
@@ -150,17 +178,10 @@ async function createZip(zipPath, entries) {
|
|
|
150
178
|
archive.on('error', (err)=>reject(err));
|
|
151
179
|
archive.pipe(output);
|
|
152
180
|
for (const entry of entries){
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
} else {
|
|
158
|
-
archive.file(entry.source, {
|
|
159
|
-
name: entry.prefix + path.basename(entry.source)
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
} catch {
|
|
163
|
-
// Skip missing entries
|
|
181
|
+
for (const file of entry.files){
|
|
182
|
+
archive.file(file.sourcePath, {
|
|
183
|
+
name: entry.prefix + file.relativePath
|
|
184
|
+
});
|
|
164
185
|
}
|
|
165
186
|
}
|
|
166
187
|
void archive.finalize();
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import { resolveOutputPath } from '../deploy-output/resolveOutputPath.js';
|
|
3
3
|
import { collectFilesRecursive } from '../fs-utils.js';
|
|
4
|
+
import { isPublishableAsset } from '../secret-files.js';
|
|
4
5
|
export class ViteAdapter {
|
|
5
6
|
fallback = '/index.html';
|
|
6
7
|
name = 'vite';
|
|
7
8
|
async collectAssets(projectPath, output) {
|
|
8
9
|
const outputPath = resolveOutputPath(projectPath, 'vite', output);
|
|
9
|
-
const files = await collectFilesRecursive(outputPath, outputPath);
|
|
10
|
+
const files = (await collectFilesRecursive(outputPath, outputPath)).filter(isPublishableAsset);
|
|
10
11
|
const pathMap = Object.fromEntries(files.map((file)=>[
|
|
11
12
|
file,
|
|
12
13
|
path.relative(projectPath, path.join(outputPath, file))
|
|
@@ -30,7 +30,7 @@ type StaticMetadataAssets = Omit<SSGAssets, 'routeAssetMap'>;
|
|
|
30
30
|
/**
|
|
31
31
|
* Collect SSG asset paths by parsing .next/prerender-manifest.json
|
|
32
32
|
*
|
|
33
|
-
* For each page route in the manifest's `routes` object, collects:
|
|
33
|
+
* For each static page route without revalidation in the manifest's `routes` object, collects:
|
|
34
34
|
* - {route}.html (full HTML)
|
|
35
35
|
* - {route}.rsc (React Server Components payload)
|
|
36
36
|
* - {route}.meta (response headers JSON)
|
|
@@ -77,7 +77,7 @@ const MAX_ASSET_SIZE = 50 * 1024 * 1024 // 50MB
|
|
|
77
77
|
/**
|
|
78
78
|
* Collect SSG asset paths by parsing .next/prerender-manifest.json
|
|
79
79
|
*
|
|
80
|
-
* For each page route in the manifest's `routes` object, collects:
|
|
80
|
+
* For each static page route without revalidation in the manifest's `routes` object, collects:
|
|
81
81
|
* - {route}.html (full HTML)
|
|
82
82
|
* - {route}.rsc (React Server Components payload)
|
|
83
83
|
* - {route}.meta (response headers JSON)
|
|
@@ -112,11 +112,6 @@ const MAX_ASSET_SIZE = 50 * 1024 * 1024 // 50MB
|
|
|
112
112
|
const keys = [];
|
|
113
113
|
const pathMap = {};
|
|
114
114
|
const routeAssetMap = {};
|
|
115
|
-
const extensions = [
|
|
116
|
-
'.html',
|
|
117
|
-
'.rsc',
|
|
118
|
-
'.meta'
|
|
119
|
-
];
|
|
120
115
|
const appDir = path.join(outputPath, 'server', 'app');
|
|
121
116
|
for (const [routeKey, route] of Object.entries(manifest.routes)){
|
|
122
117
|
// Current manifests classify app pages explicitly. Older App Router manifests
|
|
@@ -125,9 +120,34 @@ const MAX_ASSET_SIZE = 50 * 1024 * 1024 // 50MB
|
|
|
125
120
|
if (!isPageRoute) {
|
|
126
121
|
continue;
|
|
127
122
|
}
|
|
123
|
+
// ISR prerenders are seeds for the Next.js runtime, not permanent S3 pages.
|
|
124
|
+
// Keep legacy manifests without this field; only false explicitly disables revalidation.
|
|
125
|
+
if (route.initialRevalidateSeconds !== undefined && route.initialRevalidateSeconds !== false) {
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
// SSR and PPR can also disable revalidation. Only static computation can be
|
|
129
|
+
// served from S3; an initial response is safe for a static CSR shell.
|
|
130
|
+
const hasStaticRendering = route.compute === 'static' && (route.response === 'complete' || route.response === 'initial');
|
|
131
|
+
// Older SSG entries omit these fields. Ambiguous PPR entries must use Lambda.
|
|
132
|
+
const hasLegacyStaticRendering = route.compute === undefined && route.response === undefined && (route.renderingMode === undefined || route.renderingMode === 'STATIC');
|
|
133
|
+
if (!hasStaticRendering && !hasLegacyStaticRendering) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
128
136
|
// Strip leading slash for S3 key; handle root "/" → "index"
|
|
129
137
|
const stripped = routeKey === '/' ? 'index' : routeKey.replace(/^\//, '');
|
|
130
138
|
const routeAssets = [];
|
|
139
|
+
// For partially static routes, dataRoute can name a runtime endpoint even
|
|
140
|
+
// when the build has no full RSC file. Next.js sets prefetchDataRoute only
|
|
141
|
+
// when it emits that static payload.
|
|
142
|
+
const hasFullRSC = route.renderingMode !== 'PARTIALLY_STATIC' || typeof route.prefetchDataRoute === 'string';
|
|
143
|
+
const extensions = hasFullRSC ? [
|
|
144
|
+
'.html',
|
|
145
|
+
'.rsc',
|
|
146
|
+
'.meta'
|
|
147
|
+
] : [
|
|
148
|
+
'.html',
|
|
149
|
+
'.meta'
|
|
150
|
+
];
|
|
131
151
|
for (const ext of extensions){
|
|
132
152
|
const s3Key = `${stripped}${ext}`;
|
|
133
153
|
keys.push(s3Key);
|