@payloadcms/figma 0.1.0-alpha.8 → 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/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 +11 -6
- package/dist/utils/adapters/vite.js +1 -14
- package/dist/utils/asset-collection.d.ts +1 -1
- package/dist/utils/asset-collection.js +9 -1
- package/dist/utils/build-lambda-zip.js +2 -1
- package/dist/utils/lambda-config.js +14 -9
- 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
|
+
}
|
|
@@ -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);
|
|
@@ -5,6 +5,7 @@ import path from 'path';
|
|
|
5
5
|
import { resolveNitroOutputLayout } from '../deploy-output/resolveNitroOutputLayout.js';
|
|
6
6
|
import { resolveOutputPath } from '../deploy-output/resolveOutputPath.js';
|
|
7
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 = {};
|
|
@@ -67,14 +68,15 @@ export class NitroAdapter {
|
|
|
67
68
|
const zipPath = path.join(projectPath, 'lambda.zip');
|
|
68
69
|
try {
|
|
69
70
|
const runShStat = await fs.stat(runShPath);
|
|
70
|
-
const [
|
|
71
|
+
const [allServerFiles, publicFiles] = await Promise.all([
|
|
71
72
|
walkBundleFiles(serverDir),
|
|
72
73
|
walkPublicBundleFiles(layout.publicPath)
|
|
73
74
|
]);
|
|
74
|
-
const
|
|
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;
|
|
75
77
|
await createZip(zipPath, [
|
|
76
78
|
{
|
|
77
|
-
files:
|
|
79
|
+
files: bundlableServerFiles,
|
|
78
80
|
prefix: serverPrefix
|
|
79
81
|
},
|
|
80
82
|
{
|
|
@@ -153,7 +155,10 @@ function toPageUploadKey(page) {
|
|
|
153
155
|
}
|
|
154
156
|
async function walkPublicBundleFiles(publicPath) {
|
|
155
157
|
try {
|
|
156
|
-
|
|
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));
|
|
157
162
|
} catch (error) {
|
|
158
163
|
if (error instanceof EscapingSymlinkError) {
|
|
159
164
|
throw error;
|
|
@@ -1,20 +1,7 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import { resolveOutputPath } from '../deploy-output/resolveOutputPath.js';
|
|
3
3
|
import { collectFilesRecursive } from '../fs-utils.js';
|
|
4
|
-
|
|
5
|
-
* A buildless static deploy points `--output` at the project root, so local
|
|
6
|
-
* secrets and private history sit beside the intended web assets. Every
|
|
7
|
-
* collected file becomes a publicly reachable route, so never publish these.
|
|
8
|
-
*/ const EXCLUDED_ASSET_NAMES = new Set([
|
|
9
|
-
'.git',
|
|
10
|
-
'.netrc',
|
|
11
|
-
'.npmrc',
|
|
12
|
-
'.ssh',
|
|
13
|
-
'node_modules'
|
|
14
|
-
]);
|
|
15
|
-
function isPublishableAsset(key) {
|
|
16
|
-
return key.split('/').every((segment)=>!EXCLUDED_ASSET_NAMES.has(segment) && !segment.startsWith('.env'));
|
|
17
|
-
}
|
|
4
|
+
import { isPublishableAsset } from '../secret-files.js';
|
|
18
5
|
export class ViteAdapter {
|
|
19
6
|
fallback = '/index.html';
|
|
20
7
|
name = 'vite';
|
|
@@ -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 without revalidation 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 without revalidation 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)
|
|
@@ -125,6 +125,14 @@ const MAX_ASSET_SIZE = 50 * 1024 * 1024 // 50MB
|
|
|
125
125
|
if (route.initialRevalidateSeconds !== undefined && route.initialRevalidateSeconds !== false) {
|
|
126
126
|
continue;
|
|
127
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 = [];
|
|
@@ -5,6 +5,7 @@ import { configureNextjsCache } from './configureNextjsCache.js';
|
|
|
5
5
|
import { inspectNextjsOutput } from './deploy-output/inspectNextjsOutput.js';
|
|
6
6
|
import { resolveOutputPath } from './deploy-output/resolveOutputPath.js';
|
|
7
7
|
import { walkBundleFiles } from './fs-utils.js';
|
|
8
|
+
import { isBundlableFile } from './secret-files.js';
|
|
8
9
|
/**
|
|
9
10
|
* AWS Lambda's hard limit on unzipped deployment code size (250 MB). The unzipped
|
|
10
11
|
* total must be strictly less than this value, so a bundle exactly equal to it is
|
|
@@ -31,7 +32,7 @@ import { walkBundleFiles } from './fs-utils.js';
|
|
|
31
32
|
if (!layout) {
|
|
32
33
|
throw new Error(`Standalone build not found at ${outputPath}`);
|
|
33
34
|
}
|
|
34
|
-
const files = await walkBundleFiles(layout.standalonePath);
|
|
35
|
+
const files = (await walkBundleFiles(layout.standalonePath)).filter((file)=>isBundlableFile(file.relativePath));
|
|
35
36
|
// Override archive entries in memory without changing Next's standalone output.
|
|
36
37
|
const bundleFileOverrides = await createBundleFileOverrides(layout.standalonePath, layout.serverEntryPath);
|
|
37
38
|
const zipPath = path.join(projectPath, 'lambda.zip');
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { IndentationText, Node, Project, SyntaxKind } from 'ts-morph';
|
|
4
|
+
import { addFigmaImageLoaderConfig } from './next-image-loader-config.js';
|
|
5
|
+
import { writeFigmaImageLoaderFile } from './next-image-loader-file.js';
|
|
4
6
|
import { ensurePnpmWorkspace } from './pnpm-builds.js';
|
|
5
7
|
const RUN_SH_CONTENT = `#!/bin/bash -x
|
|
6
8
|
|
|
@@ -18,8 +20,9 @@ NODE_ENV=production exec node server.js
|
|
|
18
20
|
const runShPath = path.join(projectPath, 'run.sh');
|
|
19
21
|
await fs.writeFile(runShPath, RUN_SH_CONTENT, 'utf-8');
|
|
20
22
|
await fs.chmod(runShPath, 0o755);
|
|
21
|
-
// 2.
|
|
23
|
+
// 2. Add Next.js-specific configuration.
|
|
22
24
|
await addNextConfigProperties(projectPath);
|
|
25
|
+
await writeFigmaImageLoaderFile(projectPath);
|
|
23
26
|
// 3. Add lambda:buildzip script to package.json
|
|
24
27
|
await addLambdaBuildScript(projectPath, packageManager);
|
|
25
28
|
// 4. Write pnpm-workspace.yaml (hoisted linker + build approvals)
|
|
@@ -48,6 +51,7 @@ NODE_ENV=production exec node server.js
|
|
|
48
51
|
/**
|
|
49
52
|
* Add required Next.js config properties using ts-morph AST parsing
|
|
50
53
|
* - output: 'standalone' (for Lambda deployment)
|
|
54
|
+
* - images.loader: 'custom' and images.loaderFile for Payload media
|
|
51
55
|
*/ async function addNextConfigProperties(projectPath) {
|
|
52
56
|
const nextConfigPath = await findNextConfigPath(projectPath);
|
|
53
57
|
const project = new Project({
|
|
@@ -65,15 +69,16 @@ NODE_ENV=production exec node server.js
|
|
|
65
69
|
if (!configObject) {
|
|
66
70
|
throw new Error(`Could not find Next.js config object in ${path.basename(nextConfigPath)}. ` + `Expected either 'export default { ... }' or 'export default wrapper(configVar, ...)'`);
|
|
67
71
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
if (configObject.getProperty('output') === undefined) {
|
|
73
|
+
configObject.addPropertyAssignment({
|
|
74
|
+
name: 'output',
|
|
75
|
+
initializer: "'standalone'"
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
addFigmaImageLoaderConfig(configObject);
|
|
79
|
+
if (!sourceFile.isSaved()) {
|
|
80
|
+
await sourceFile.save();
|
|
71
81
|
}
|
|
72
|
-
configObject.addPropertyAssignment({
|
|
73
|
-
name: 'output',
|
|
74
|
-
initializer: "'standalone'"
|
|
75
|
-
});
|
|
76
|
-
await sourceFile.save();
|
|
77
82
|
}
|
|
78
83
|
/**
|
|
79
84
|
* Find the config object literal from either:
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Node, SyntaxKind } from 'ts-morph';
|
|
2
|
+
import { FIGMA_IMAGE_LOADER_PATH } from './next-image-loader-file.js';
|
|
3
|
+
export function addFigmaImageLoaderConfig(configObject) {
|
|
4
|
+
const imagesProperty = configObject.getProperty('images');
|
|
5
|
+
if (!imagesProperty) {
|
|
6
|
+
configObject.addPropertyAssignment({
|
|
7
|
+
name: 'images',
|
|
8
|
+
initializer: `{
|
|
9
|
+
loader: 'custom',
|
|
10
|
+
loaderFile: '${FIGMA_IMAGE_LOADER_PATH}',
|
|
11
|
+
}`
|
|
12
|
+
});
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
let imagesValue;
|
|
16
|
+
if (Node.isPropertyAssignment(imagesProperty)) {
|
|
17
|
+
imagesValue = imagesProperty.getInitializer();
|
|
18
|
+
} else if (Node.isShorthandPropertyAssignment(imagesProperty)) {
|
|
19
|
+
imagesValue = imagesProperty.getNameNode();
|
|
20
|
+
}
|
|
21
|
+
const imagesObject = resolveLocalObjectLiteral(imagesValue);
|
|
22
|
+
if (!imagesObject) {
|
|
23
|
+
throw new Error('Figma could not add the image loader because it could not inspect the `images` configuration. ' + 'Define `images` as an object in this Next.js config file and rerun ' + '`npx @payloadcms/figma init`.');
|
|
24
|
+
}
|
|
25
|
+
const loaderProperty = imagesObject.getProperty('loader');
|
|
26
|
+
const loaderFileProperty = imagesObject.getProperty('loaderFile');
|
|
27
|
+
if (loaderProperty || loaderFileProperty) {
|
|
28
|
+
const isFigmaLoader = getStringPropertyValue(loaderProperty) === 'custom' && getStringPropertyValue(loaderFileProperty) === FIGMA_IMAGE_LOADER_PATH;
|
|
29
|
+
if (!isFigmaLoader) {
|
|
30
|
+
throw new Error('A custom Next.js image loader is already configured, so Figma left it unchanged. ' + 'Remove the existing images.loader and images.loaderFile settings and rerun ' + '`npx @payloadcms/figma init`.');
|
|
31
|
+
}
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
imagesObject.addPropertyAssignment({
|
|
35
|
+
name: 'loader',
|
|
36
|
+
initializer: "'custom'"
|
|
37
|
+
});
|
|
38
|
+
imagesObject.addPropertyAssignment({
|
|
39
|
+
name: 'loaderFile',
|
|
40
|
+
initializer: `'${FIGMA_IMAGE_LOADER_PATH}'`
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
function resolveLocalObjectLiteral(node) {
|
|
44
|
+
if (Node.isObjectLiteralExpression(node)) {
|
|
45
|
+
return node;
|
|
46
|
+
}
|
|
47
|
+
if (!Node.isIdentifier(node)) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
const sourceFile = node.getSourceFile();
|
|
51
|
+
for (const definition of node.getDefinitionNodes()){
|
|
52
|
+
if (Node.isVariableDeclaration(definition) && definition.getSourceFile() === sourceFile) {
|
|
53
|
+
return definition.getInitializerIfKind(SyntaxKind.ObjectLiteralExpression);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
function getStringPropertyValue(property) {
|
|
59
|
+
if (!property || !Node.isPropertyAssignment(property)) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
return property.getInitializerIfKind(SyntaxKind.StringLiteral)?.getLiteralValue();
|
|
63
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
export const FIGMA_IMAGE_LOADER_PATH = './figma-image-loader.ts';
|
|
4
|
+
const FIGMA_IMAGE_LOADER_CONTENT = `'use client'
|
|
5
|
+
|
|
6
|
+
// Generated by @payloadcms/figma. Do not edit.
|
|
7
|
+
export { figmaNextImageLoader as default } from '@payloadcms/figma/next-image-loader'
|
|
8
|
+
`;
|
|
9
|
+
export async function writeFigmaImageLoaderFile(projectPath) {
|
|
10
|
+
await fs.writeFile(path.join(projectPath, FIGMA_IMAGE_LOADER_PATH), FIGMA_IMAGE_LOADER_CONTENT);
|
|
11
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* True when a forward-slash asset key holds no secret or private-history path
|
|
3
|
+
* segment.
|
|
4
|
+
*
|
|
5
|
+
* A deploy can point `--output` at a directory that also holds those files (a
|
|
6
|
+
* buildless static root, or a framework output folder that copied them in).
|
|
7
|
+
* Every collected asset becomes a publicly reachable route, so `node_modules`
|
|
8
|
+
* is excluded here too.
|
|
9
|
+
*/
|
|
10
|
+
export declare function isPublishableAsset(key: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* True when a forward-slash bundle path belongs in a Lambda zip.
|
|
13
|
+
*
|
|
14
|
+
* A server bundle needs its `node_modules`, but never the project's `.env`
|
|
15
|
+
* files or credentials: a deployed function reads its configuration from
|
|
16
|
+
* deploy environment variables, and a packed secret only widens the blast
|
|
17
|
+
* radius of any later path that serves bundle files. `.env.production` is the
|
|
18
|
+
* deliberate exception, an escape hatch for configuration too large for the
|
|
19
|
+
* Lambda environment-variable limit.
|
|
20
|
+
*
|
|
21
|
+
* Paths inside `node_modules` are kept as-is. A dependency's own dotfiles are
|
|
22
|
+
* not the developer's secrets, and removing files a package may read at
|
|
23
|
+
* runtime would break the bundle.
|
|
24
|
+
*/
|
|
25
|
+
export declare function isBundlableFile(relativePath: string): boolean;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local secrets and private history that must never leave the developer's
|
|
3
|
+
* machine: environment files, git history, and credential files.
|
|
4
|
+
*/ const SECRET_NAMES = new Set([
|
|
5
|
+
'.git',
|
|
6
|
+
'.netrc',
|
|
7
|
+
'.npmrc',
|
|
8
|
+
'.ssh'
|
|
9
|
+
]);
|
|
10
|
+
/** Installed dependencies: private for static hosting, required by a server bundle. */ const DEPENDENCY_DIR = 'node_modules';
|
|
11
|
+
/**
|
|
12
|
+
* The one environment file a Lambda zip may carry. AWS caps a function's
|
|
13
|
+
* environment variables at 4 KB total, so a project with more configuration
|
|
14
|
+
* than that has no way to pass it except inside the bundle. It is still never
|
|
15
|
+
* publishable: `isPublishableAsset` rejects it like any other `.env` file.
|
|
16
|
+
*/ const BUNDLABLE_ENV_FILE = '.env.production';
|
|
17
|
+
/**
|
|
18
|
+
* True when a forward-slash asset key holds no secret or private-history path
|
|
19
|
+
* segment.
|
|
20
|
+
*
|
|
21
|
+
* A deploy can point `--output` at a directory that also holds those files (a
|
|
22
|
+
* buildless static root, or a framework output folder that copied them in).
|
|
23
|
+
* Every collected asset becomes a publicly reachable route, so `node_modules`
|
|
24
|
+
* is excluded here too.
|
|
25
|
+
*/ export function isPublishableAsset(key) {
|
|
26
|
+
return key.split('/').every((segment)=>segment !== DEPENDENCY_DIR && !isSecretName(segment));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* True when a forward-slash bundle path belongs in a Lambda zip.
|
|
30
|
+
*
|
|
31
|
+
* A server bundle needs its `node_modules`, but never the project's `.env`
|
|
32
|
+
* files or credentials: a deployed function reads its configuration from
|
|
33
|
+
* deploy environment variables, and a packed secret only widens the blast
|
|
34
|
+
* radius of any later path that serves bundle files. `.env.production` is the
|
|
35
|
+
* deliberate exception, an escape hatch for configuration too large for the
|
|
36
|
+
* Lambda environment-variable limit.
|
|
37
|
+
*
|
|
38
|
+
* Paths inside `node_modules` are kept as-is. A dependency's own dotfiles are
|
|
39
|
+
* not the developer's secrets, and removing files a package may read at
|
|
40
|
+
* runtime would break the bundle.
|
|
41
|
+
*/ export function isBundlableFile(relativePath) {
|
|
42
|
+
const segments = relativePath.split('/');
|
|
43
|
+
if (segments.includes(DEPENDENCY_DIR)) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
return segments.every((segment)=>segment === BUNDLABLE_ENV_FILE || !isSecretName(segment));
|
|
47
|
+
}
|
|
48
|
+
function isSecretName(segment) {
|
|
49
|
+
return SECRET_NAMES.has(segment) || segment.startsWith('.env');
|
|
50
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@payloadcms/figma",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.9",
|
|
4
4
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -14,6 +14,11 @@
|
|
|
14
14
|
"types": "./dist/exports/client.d.ts",
|
|
15
15
|
"default": "./dist/exports/client.js"
|
|
16
16
|
},
|
|
17
|
+
"./next-image-loader": {
|
|
18
|
+
"import": "./dist/next/image-loader.js",
|
|
19
|
+
"types": "./dist/next/image-loader.d.ts",
|
|
20
|
+
"default": "./dist/next/image-loader.js"
|
|
21
|
+
},
|
|
17
22
|
"./views": {
|
|
18
23
|
"import": "./dist/exports/views.js",
|
|
19
24
|
"types": "./dist/exports/views.d.ts",
|