@payloadcms/figma 0.0.1-alpha.63 → 0.0.1-alpha.64
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/auth/oauth-flow.js +1 -0
- package/dist/auth/project-token.d.ts +15 -10
- package/dist/auth/project-token.js +147 -64
- package/dist/auth/token-store-migration.js +2 -2
- package/dist/auth/token-store.js +2 -2
- package/dist/auth/types.d.ts +4 -0
- package/dist/cli.js +9 -0
- package/dist/commands/bootstrap.d.ts +18 -0
- package/dist/commands/bootstrap.js +90 -0
- package/dist/commands/init.js +32 -2
- package/dist/db-content-api/index.js +27 -73
- package/dist/plugin/build-config.js +62 -46
- package/dist/utils/messages.js +7 -0
- package/dist/utils/payload-config-modifier.js +96 -107
- package/dist/utils/payload-package-check.d.ts +21 -1
- package/dist/utils/payload-package-check.js +66 -26
- package/package.json +1 -1
|
@@ -673,41 +673,6 @@ async function upsert(args) {
|
|
|
673
673
|
payload: this.payload
|
|
674
674
|
});
|
|
675
675
|
}
|
|
676
|
-
// Mirrors MAX_DOCUMENT_UPDATES in the content API's updateDocuments.ts.
|
|
677
|
-
// updateJobs may be called with larger limits (e.g. 150), so we batch sequentially.
|
|
678
|
-
const CONTENT_API_MAX_UPDATES = 20;
|
|
679
|
-
// Issues a single update request against payload-jobs and returns the unwrapped docs.
|
|
680
|
-
async function updateJobsBatch({ batchSize, docData, meta, sortClause, whereQuery }) {
|
|
681
|
-
const { data: response, error } = await this.client.POST('/api/v0/documents:update', {
|
|
682
|
-
body: {
|
|
683
|
-
collection: 'payload-jobs',
|
|
684
|
-
contentSystemId: this.contentSystemId,
|
|
685
|
-
createOnMissing: false,
|
|
686
|
-
doc: docData,
|
|
687
|
-
...batchSize != null && {
|
|
688
|
-
limit: batchSize
|
|
689
|
-
},
|
|
690
|
-
returning: {},
|
|
691
|
-
sort: sortClause,
|
|
692
|
-
where: whereQuery,
|
|
693
|
-
...meta
|
|
694
|
-
}
|
|
695
|
-
});
|
|
696
|
-
if (error) {
|
|
697
|
-
throw new Error(`Content API updateJobs error: ${JSON.stringify(error)}`);
|
|
698
|
-
}
|
|
699
|
-
if (!response) {
|
|
700
|
-
throw new Error('No response from updateJobs');
|
|
701
|
-
}
|
|
702
|
-
if (!response.result || !('data' in response.result)) {
|
|
703
|
-
return [];
|
|
704
|
-
}
|
|
705
|
-
return response.result.data.map((doc)=>unwrapDocument({
|
|
706
|
-
collectionSlug: 'payload-jobs',
|
|
707
|
-
doc,
|
|
708
|
-
payload: this.payload
|
|
709
|
-
}));
|
|
710
|
-
}
|
|
711
676
|
async function updateJobs(args) {
|
|
712
677
|
const { id, limit, returning, sort, where } = args;
|
|
713
678
|
if (id == null && where == null) {
|
|
@@ -737,49 +702,38 @@ async function updateJobs(args) {
|
|
|
737
702
|
locale: undefined,
|
|
738
703
|
where: whereClause
|
|
739
704
|
});
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
docData,
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
return docs;
|
|
754
|
-
}
|
|
755
|
-
// Batched path: issue sequential requests of ≤ CONTENT_API_MAX_UPDATES each.
|
|
756
|
-
// This relies on the update itself causing matched jobs to no longer satisfy the where
|
|
757
|
-
// clause on subsequent batches (e.g. setting processing: true removes them from a
|
|
758
|
-
// "processing: false" query). If a future updateJobs call in Payload core updates jobs
|
|
759
|
-
// in a way that does NOT change their match status, the same jobs could be updated
|
|
760
|
-
// multiple times across batches.
|
|
761
|
-
const allDocs = [];
|
|
762
|
-
let remaining = limit;
|
|
763
|
-
while(remaining > 0){
|
|
764
|
-
const batchSize = Math.min(remaining, CONTENT_API_MAX_UPDATES);
|
|
765
|
-
const batchDocs = await updateJobsBatch.call(this, {
|
|
766
|
-
batchSize,
|
|
767
|
-
docData,
|
|
768
|
-
meta,
|
|
769
|
-
sortClause,
|
|
770
|
-
whereQuery
|
|
771
|
-
});
|
|
772
|
-
allDocs.push(...batchDocs);
|
|
773
|
-
remaining -= batchSize;
|
|
774
|
-
// Stop early when this batch returned fewer docs than requested — no more matching docs.
|
|
775
|
-
if (batchDocs.length < batchSize) {
|
|
776
|
-
break;
|
|
705
|
+
const { data: response, error } = await this.client.POST('/api/v0/documents:update', {
|
|
706
|
+
body: {
|
|
707
|
+
collection: 'payload-jobs',
|
|
708
|
+
contentSystemId: this.contentSystemId,
|
|
709
|
+
createOnMissing: false,
|
|
710
|
+
doc: docData,
|
|
711
|
+
...limit != null && {
|
|
712
|
+
limit
|
|
713
|
+
},
|
|
714
|
+
returning: {},
|
|
715
|
+
sort: sortClause,
|
|
716
|
+
where: whereQuery,
|
|
717
|
+
...meta
|
|
777
718
|
}
|
|
719
|
+
});
|
|
720
|
+
if (error) {
|
|
721
|
+
throw new Error(`Content API updateJobs error: ${JSON.stringify(error)}`);
|
|
722
|
+
}
|
|
723
|
+
if (!response) {
|
|
724
|
+
throw new Error('No response from updateJobs');
|
|
778
725
|
}
|
|
779
726
|
if (returning === false) {
|
|
780
727
|
return null;
|
|
781
728
|
}
|
|
782
|
-
|
|
729
|
+
if (!response.result || !('data' in response.result)) {
|
|
730
|
+
return [];
|
|
731
|
+
}
|
|
732
|
+
return response.result.data.map((doc)=>unwrapDocument({
|
|
733
|
+
collectionSlug: 'payload-jobs',
|
|
734
|
+
doc,
|
|
735
|
+
payload: this.payload
|
|
736
|
+
}));
|
|
783
737
|
}
|
|
784
738
|
function createGlobal(args) {
|
|
785
739
|
// Globals are singletons identified by their slug. Use upsert so concurrent calls
|
|
@@ -104,6 +104,66 @@ function missingOAuthCredential(name) {
|
|
|
104
104
|
function messageOf(error) {
|
|
105
105
|
return error instanceof Error ? error.message : 'Unknown error';
|
|
106
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* Creates storage configuration for upload collections
|
|
109
|
+
*/ function createStorageConfig(url, contentSystemId) {
|
|
110
|
+
const storageConfig = process.env.FIGMA_CONTENT_API_ACCESS_KEY ? {
|
|
111
|
+
baseUrl: url,
|
|
112
|
+
contentApiKey: process.env.FIGMA_CONTENT_API_ACCESS_KEY
|
|
113
|
+
} : process.env.FIGMA_DEV_JWT === 'true' ? {
|
|
114
|
+
auth: {
|
|
115
|
+
mode: 'devJwt'
|
|
116
|
+
},
|
|
117
|
+
baseUrl: url,
|
|
118
|
+
contentSystemId
|
|
119
|
+
} : {
|
|
120
|
+
auth: {
|
|
121
|
+
mode: 'tokenStore',
|
|
122
|
+
tokenStore: getTokenStore()
|
|
123
|
+
},
|
|
124
|
+
baseUrl: url,
|
|
125
|
+
contentSystemId
|
|
126
|
+
};
|
|
127
|
+
const adapter = contentApiStorageAdapter(storageConfig);
|
|
128
|
+
const storageClient = createStorageClient(storageConfig);
|
|
129
|
+
return {
|
|
130
|
+
adapter,
|
|
131
|
+
storageClient
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Plugin that applies cloud storage configuration to all upload-enabled collections.
|
|
136
|
+
* Runs at the end of the plugin chain so it sees collections added by user plugins too.
|
|
137
|
+
*/ function createStoragePlugin(url, contentSystemId) {
|
|
138
|
+
return (incomingConfig)=>{
|
|
139
|
+
const uploadCollections = (incomingConfig.collections || []).filter((c)=>c.upload);
|
|
140
|
+
if (uploadCollections.length === 0) {
|
|
141
|
+
return incomingConfig;
|
|
142
|
+
}
|
|
143
|
+
const { adapter, storageClient } = createStorageConfig(url, contentSystemId);
|
|
144
|
+
const collectionsMap = uploadCollections.reduce((acc, c)=>{
|
|
145
|
+
acc[c.slug] = {
|
|
146
|
+
adapter,
|
|
147
|
+
disableLocalStorage: true
|
|
148
|
+
};
|
|
149
|
+
return acc;
|
|
150
|
+
}, {});
|
|
151
|
+
initClientUploads({
|
|
152
|
+
clientHandler: '@payloadcms/figma/client#ContentApiClientUploadHandler',
|
|
153
|
+
collections: collectionsMap,
|
|
154
|
+
config: incomingConfig,
|
|
155
|
+
enabled: true,
|
|
156
|
+
serverHandler: getGenerateSignedURLHandler({
|
|
157
|
+
client: storageClient
|
|
158
|
+
}),
|
|
159
|
+
serverHandlerPath: '/content-api-storage-signed-url'
|
|
160
|
+
});
|
|
161
|
+
const storagePlugin = cloudStoragePlugin({
|
|
162
|
+
collections: collectionsMap
|
|
163
|
+
});
|
|
164
|
+
return storagePlugin(incomingConfig);
|
|
165
|
+
};
|
|
166
|
+
}
|
|
107
167
|
export async function buildFigmaConfig(config) {
|
|
108
168
|
const envConfig = getEnvConfig();
|
|
109
169
|
// Resolve contentSystemId: config first, env var, then local store fallback (dev)
|
|
@@ -180,50 +240,6 @@ export async function buildFigmaConfig(config) {
|
|
|
180
240
|
url
|
|
181
241
|
});
|
|
182
242
|
}
|
|
183
|
-
// Build storage plugin if there are upload collections and storage is not disabled
|
|
184
|
-
const uploadCollections = (config.collections || []).filter((c)=>c.upload);
|
|
185
|
-
let storagePlugin;
|
|
186
|
-
if (uploadCollections.length > 0 && config.figma.storage !== false) {
|
|
187
|
-
const storageConfig = process.env.FIGMA_CONTENT_API_ACCESS_KEY ? {
|
|
188
|
-
baseUrl: url,
|
|
189
|
-
contentApiKey: process.env.FIGMA_CONTENT_API_ACCESS_KEY
|
|
190
|
-
} : process.env.FIGMA_DEV_JWT === 'true' ? {
|
|
191
|
-
auth: {
|
|
192
|
-
mode: 'devJwt'
|
|
193
|
-
},
|
|
194
|
-
baseUrl: url,
|
|
195
|
-
contentSystemId
|
|
196
|
-
} : {
|
|
197
|
-
auth: {
|
|
198
|
-
mode: 'tokenStore',
|
|
199
|
-
tokenStore: getTokenStore()
|
|
200
|
-
},
|
|
201
|
-
baseUrl: url,
|
|
202
|
-
contentSystemId
|
|
203
|
-
};
|
|
204
|
-
const adapter = contentApiStorageAdapter(storageConfig);
|
|
205
|
-
const storageClient = createStorageClient(storageConfig);
|
|
206
|
-
const collectionsMap = uploadCollections.reduce((acc, c)=>{
|
|
207
|
-
acc[c.slug] = {
|
|
208
|
-
adapter,
|
|
209
|
-
disableLocalStorage: true
|
|
210
|
-
};
|
|
211
|
-
return acc;
|
|
212
|
-
}, {});
|
|
213
|
-
initClientUploads({
|
|
214
|
-
clientHandler: '@payloadcms/figma/client#ContentApiClientUploadHandler',
|
|
215
|
-
collections: collectionsMap,
|
|
216
|
-
config: config,
|
|
217
|
-
enabled: true,
|
|
218
|
-
serverHandler: getGenerateSignedURLHandler({
|
|
219
|
-
client: storageClient
|
|
220
|
-
}),
|
|
221
|
-
serverHandlerPath: '/content-api-storage-signed-url'
|
|
222
|
-
});
|
|
223
|
-
storagePlugin = cloudStoragePlugin({
|
|
224
|
-
collections: collectionsMap
|
|
225
|
-
});
|
|
226
|
-
}
|
|
227
243
|
// Build complete config with Figma platform defaults
|
|
228
244
|
const configWithFigmaDefaults = {
|
|
229
245
|
...config,
|
|
@@ -257,8 +273,8 @@ export async function buildFigmaConfig(config) {
|
|
|
257
273
|
// Add oauth to plugins if not already present
|
|
258
274
|
plugins: [
|
|
259
275
|
...config.plugins ?? [],
|
|
260
|
-
...
|
|
261
|
-
|
|
276
|
+
...config.figma.storage !== false ? [
|
|
277
|
+
createStoragePlugin(url, contentSystemId)
|
|
262
278
|
] : [],
|
|
263
279
|
oAuth2Plugin({
|
|
264
280
|
collections: [
|
package/dist/utils/messages.js
CHANGED
|
@@ -14,6 +14,7 @@ export function helpMessage() {
|
|
|
14
14
|
${pc.cyan('logout')} Clear all stored tokens
|
|
15
15
|
${pc.cyan('list-tokens')} Show stored token information
|
|
16
16
|
${pc.cyan('init')} Initialize a Figma CMS project
|
|
17
|
+
${pc.cyan('bootstrap')} Print bootstrap info (tenant IDs, OAuth creds) for a project
|
|
17
18
|
${pc.cyan('debug')} Show debug info for troubleshooting
|
|
18
19
|
${pc.cyan('env')} Switch active environment
|
|
19
20
|
${pc.cyan('deploy')} Deploy your project to Figma
|
|
@@ -39,6 +40,12 @@ export function helpMessage() {
|
|
|
39
40
|
${pc.dim('--name, -n <name>')} Set project directory name (skips prompt)
|
|
40
41
|
${pc.dim('--force')} Force reconfiguration of existing project
|
|
41
42
|
|
|
43
|
+
${pc.bold('BOOTSTRAP COMMAND')}
|
|
44
|
+
|
|
45
|
+
${pc.cyan('@payloadcms/figma bootstrap --id <cms-resource-id>')} Print bootstrap info
|
|
46
|
+
${pc.dim('--env <environment>')} Filter to a single environment
|
|
47
|
+
${pc.dim('--json')} Output JSON instead of styled note
|
|
48
|
+
|
|
42
49
|
${pc.bold('ENV COMMAND')}
|
|
43
50
|
|
|
44
51
|
${pc.cyan('@payloadcms/figma env')} List and switch environments
|
|
@@ -5,7 +5,7 @@ import * as log from './log.js';
|
|
|
5
5
|
import { getAddCommand, getRunCommand } from './package-manager.js';
|
|
6
6
|
import { addFigmaProperty, applyModifications, detectRequiredChanges } from './payload-config-ast.js';
|
|
7
7
|
import { findPayloadConfig } from './payload-config-finder.js';
|
|
8
|
-
import { checkPackageInstalled, getPayloadVersion,
|
|
8
|
+
import { checkPackageInstalled, getPayloadVersion, installPackages, uninstallPackages } from './payload-package-check.js';
|
|
9
9
|
import { runTypeScriptCheck } from './typescript-validator.js';
|
|
10
10
|
import { getOwnVersion } from './version-check.js';
|
|
11
11
|
/**
|
|
@@ -14,10 +14,9 @@ import { getOwnVersion } from './version-check.js';
|
|
|
14
14
|
*/ export async function ensurePayloadFigmaConfig(projectPath, packageManager, figmaConfig) {
|
|
15
15
|
const changes = [];
|
|
16
16
|
const warnings = [];
|
|
17
|
-
let
|
|
17
|
+
let astModified = false;
|
|
18
18
|
try {
|
|
19
19
|
log.debug(`Starting payload config modification for: ${projectPath}`);
|
|
20
|
-
// 1. Find config file
|
|
21
20
|
const configPath = await findPayloadConfig(projectPath);
|
|
22
21
|
if (!configPath) {
|
|
23
22
|
log.debug(`Config file not found in: ${projectPath}`);
|
|
@@ -29,32 +28,10 @@ import { getOwnVersion } from './version-check.js';
|
|
|
29
28
|
};
|
|
30
29
|
}
|
|
31
30
|
log.debug(`Found config file: ${configPath}`);
|
|
32
|
-
//
|
|
31
|
+
// Pin @payloadcms/* packages to the project's payload version when possible.
|
|
33
32
|
const payloadVersion = await getPayloadVersion(projectPath);
|
|
34
33
|
log.debug(`Detected payload version: ${payloadVersion || 'unknown'}`);
|
|
35
|
-
//
|
|
36
|
-
const isInstalled = await checkPackageInstalled(projectPath, '@payloadcms/figma');
|
|
37
|
-
if (!isInstalled) {
|
|
38
|
-
log.debug('@payloadcms/figma not installed, attempting installation...');
|
|
39
|
-
try {
|
|
40
|
-
const figmaVersion = await getOwnVersion();
|
|
41
|
-
await installPackage(projectPath, '@payloadcms/figma', packageManager, figmaVersion);
|
|
42
|
-
changes.push('Installed @payloadcms/figma');
|
|
43
|
-
modified = true;
|
|
44
|
-
log.debug('@payloadcms/figma installed successfully');
|
|
45
|
-
} catch (error) {
|
|
46
|
-
log.debug(`Failed to install @payloadcms/figma: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
47
|
-
return {
|
|
48
|
-
changes: [],
|
|
49
|
-
error: 'Failed to install @payloadcms/figma',
|
|
50
|
-
modified: false,
|
|
51
|
-
success: false
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
} else {
|
|
55
|
-
log.debug('@payloadcms/figma already installed');
|
|
56
|
-
}
|
|
57
|
-
// 3. Parse config with ts-morph
|
|
34
|
+
// Parse first so a malformed config fails fast before we touch node_modules.
|
|
58
35
|
log.debug('Parsing config file with ts-morph...');
|
|
59
36
|
const project = new Project({
|
|
60
37
|
manipulationSettings: {
|
|
@@ -64,17 +41,15 @@ import { getOwnVersion } from './version-check.js';
|
|
|
64
41
|
skipAddingFilesFromTsConfig: true
|
|
65
42
|
});
|
|
66
43
|
const sourceFile = project.addSourceFileAtPath(configPath);
|
|
67
|
-
// 4. Detect required changes
|
|
68
44
|
log.debug('Detecting required changes...');
|
|
69
45
|
const detection = detectRequiredChanges(sourceFile);
|
|
70
46
|
log.debug(`Detection results: needsImportChange=${detection.needsImportChange}, hasAlias=${detection.hasAlias}, hasBuildConfig=${detection.hasBuildConfig}`);
|
|
71
|
-
// Handle failure modes
|
|
72
47
|
if (detection.hasAlias) {
|
|
73
48
|
log.debug('Config uses import alias (e.g., buildConfig as X)');
|
|
74
49
|
return {
|
|
75
50
|
changes,
|
|
76
51
|
error: 'Config uses import alias - manual modification required',
|
|
77
|
-
modified,
|
|
52
|
+
modified: false,
|
|
78
53
|
success: false
|
|
79
54
|
};
|
|
80
55
|
}
|
|
@@ -83,25 +58,95 @@ import { getOwnVersion } from './version-check.js';
|
|
|
83
58
|
return {
|
|
84
59
|
changes,
|
|
85
60
|
error: 'Config structure not recognized (no buildConfig call found)',
|
|
86
|
-
modified,
|
|
61
|
+
modified: false,
|
|
87
62
|
success: false
|
|
88
63
|
};
|
|
89
64
|
}
|
|
90
|
-
//
|
|
65
|
+
// Compute the full install/uninstall diff up front so each can run as a
|
|
66
|
+
// single package-manager call. npm leaves node_modules transient between
|
|
67
|
+
// back-to-back calls, which causes generate:importmap to silently miss
|
|
68
|
+
// subpath components — batching avoids that intermediate state.
|
|
69
|
+
const packagesToInstall = [];
|
|
70
|
+
const packagesToUninstall = [];
|
|
71
|
+
const figmaInstalled = await checkPackageInstalled(projectPath, '@payloadcms/figma');
|
|
72
|
+
if (!figmaInstalled) {
|
|
73
|
+
const figmaVersion = await getOwnVersion();
|
|
74
|
+
packagesToInstall.push({
|
|
75
|
+
name: '@payloadcms/figma',
|
|
76
|
+
changeMessage: 'Installed @payloadcms/figma',
|
|
77
|
+
version: figmaVersion
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
const editorIsDefault = !detection.editorProperty || detection.editorProperty.isDefault;
|
|
81
|
+
if (editorIsDefault) {
|
|
82
|
+
const hasLexical = await checkPackageInstalled(projectPath, '@payloadcms/richtext-lexical');
|
|
83
|
+
if (!hasLexical) {
|
|
84
|
+
packagesToInstall.push({
|
|
85
|
+
name: '@payloadcms/richtext-lexical',
|
|
86
|
+
changeMessage: 'Installed @payloadcms/richtext-lexical (default editor)',
|
|
87
|
+
version: payloadVersion
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
log.debug(`Custom editor detected (${detection.editorProperty?.importSource}) - skipping lexical installation`);
|
|
92
|
+
}
|
|
93
|
+
const hasCloudStorage = await checkPackageInstalled(projectPath, '@payloadcms/plugin-cloud-storage');
|
|
94
|
+
if (!hasCloudStorage) {
|
|
95
|
+
packagesToInstall.push({
|
|
96
|
+
name: '@payloadcms/plugin-cloud-storage',
|
|
97
|
+
changeMessage: 'Installed @payloadcms/plugin-cloud-storage',
|
|
98
|
+
version: payloadVersion
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
if (detection.dbProperty) {
|
|
102
|
+
packagesToUninstall.push(detection.dbProperty.importSource);
|
|
103
|
+
}
|
|
104
|
+
if (detection.sharpProperty) {
|
|
105
|
+
packagesToUninstall.push('sharp');
|
|
106
|
+
}
|
|
107
|
+
if (packagesToInstall.length > 0) {
|
|
108
|
+
log.debug(`Installing ${packagesToInstall.length} packages in a single operation: ${packagesToInstall.map((p)=>p.name).join(', ')}`);
|
|
109
|
+
try {
|
|
110
|
+
await installPackages(projectPath, packagesToInstall.map(({ name, version })=>({
|
|
111
|
+
name,
|
|
112
|
+
version
|
|
113
|
+
})), packageManager);
|
|
114
|
+
for (const { changeMessage } of packagesToInstall){
|
|
115
|
+
changes.push(changeMessage);
|
|
116
|
+
}
|
|
117
|
+
} catch (error) {
|
|
118
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
119
|
+
log.debug(`Batched install failed: ${errorMessage}`);
|
|
120
|
+
const figmaInBatch = packagesToInstall.some((p)=>p.name === '@payloadcms/figma');
|
|
121
|
+
if (figmaInBatch) {
|
|
122
|
+
// Without @payloadcms/figma the rewritten config would import a
|
|
123
|
+
// missing package, so abort before touching the file.
|
|
124
|
+
return {
|
|
125
|
+
changes: [],
|
|
126
|
+
error: 'Failed to install @payloadcms/figma',
|
|
127
|
+
modified: false,
|
|
128
|
+
success: false
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
// Lexical and cloud-storage are non-fatal — payload still loads without
|
|
132
|
+
// them, so warn (with package names) and continue.
|
|
133
|
+
const failedNames = packagesToInstall.map((p)=>p.name).join(', ');
|
|
134
|
+
warnings.push(`Could not install dependencies (${failedNames}) — install them manually. Details: ${errorMessage}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
91
137
|
log.debug('Applying modifications...');
|
|
92
138
|
const modResult = applyModifications(sourceFile, detection);
|
|
93
139
|
if (modResult.modified) {
|
|
94
140
|
changes.push(...modResult.changes);
|
|
95
|
-
|
|
141
|
+
astModified = true;
|
|
96
142
|
log.debug(`Applied ${modResult.changes.length} modifications`);
|
|
97
143
|
}
|
|
98
|
-
// 6. Add figma property if config provided and doesn't exist
|
|
99
144
|
if (figmaConfig && !detection.figmaObjectExists) {
|
|
100
145
|
log.debug('Adding figma property to buildConfig...');
|
|
101
146
|
const figmaResult = addFigmaProperty(sourceFile, figmaConfig);
|
|
102
147
|
if (figmaResult.modified) {
|
|
103
148
|
changes.push(...figmaResult.changes);
|
|
104
|
-
|
|
149
|
+
astModified = true;
|
|
105
150
|
log.debug(`Added figma property`);
|
|
106
151
|
} else {
|
|
107
152
|
log.debug('Figma property not added (may already exist)');
|
|
@@ -109,95 +154,39 @@ import { getOwnVersion } from './version-check.js';
|
|
|
109
154
|
} else if (detection.figmaObjectExists) {
|
|
110
155
|
log.debug('Figma property already exists, skipping');
|
|
111
156
|
}
|
|
112
|
-
|
|
113
|
-
if (modified) {
|
|
157
|
+
if (astModified) {
|
|
114
158
|
sourceFile.formatText({
|
|
115
159
|
indentSize: 2,
|
|
116
160
|
tabSize: 2
|
|
117
161
|
});
|
|
118
162
|
await sourceFile.save();
|
|
119
163
|
log.debug('Config file saved');
|
|
120
|
-
// 7. Handle package management
|
|
121
|
-
const packagesToUninstall = [];
|
|
122
|
-
// Uninstall db adapter (buildFigmaConfig provides its own)
|
|
123
|
-
if (detection.dbProperty) {
|
|
124
|
-
packagesToUninstall.push(detection.dbProperty.importSource);
|
|
125
|
-
}
|
|
126
|
-
// Uninstall sharp (Figma infrastructure handles image processing)
|
|
127
|
-
if (detection.sharpProperty) {
|
|
128
|
-
packagesToUninstall.push('sharp');
|
|
129
|
-
}
|
|
130
|
-
// Ensure lexical is installed if user doesn't have a custom editor
|
|
131
|
-
// (buildFigmaConfig uses lexicalEditor() as default)
|
|
132
|
-
if (!detection.editorProperty) {
|
|
133
|
-
log.debug('No editor property detected - will ensure lexical is installed for default editor');
|
|
134
|
-
} else if (detection.editorProperty.isDefault) {
|
|
135
|
-
log.debug('Default lexicalEditor() detected - will ensure lexical is installed');
|
|
136
|
-
} else {
|
|
137
|
-
log.debug(`Custom editor detected (${detection.editorProperty.importSource}) - skipping lexical installation`);
|
|
138
|
-
}
|
|
139
|
-
if (!detection.editorProperty || detection.editorProperty.isDefault) {
|
|
140
|
-
log.debug('Checking if @payloadcms/richtext-lexical is installed...');
|
|
141
|
-
const hasLexical = await checkPackageInstalled(projectPath, '@payloadcms/richtext-lexical');
|
|
142
|
-
if (!hasLexical) {
|
|
143
|
-
log.debug('@payloadcms/richtext-lexical not found, installing...');
|
|
144
|
-
try {
|
|
145
|
-
await installPackage(projectPath, '@payloadcms/richtext-lexical', packageManager, payloadVersion);
|
|
146
|
-
changes.push('Installed @payloadcms/richtext-lexical (default editor)');
|
|
147
|
-
log.debug('Successfully installed @payloadcms/richtext-lexical');
|
|
148
|
-
} catch (error) {
|
|
149
|
-
log.debug(`Failed to install @payloadcms/richtext-lexical: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
150
|
-
// Don't fail on install error - user can install manually if needed
|
|
151
|
-
warnings.push('Could not install @payloadcms/richtext-lexical - you may need to install it manually');
|
|
152
|
-
}
|
|
153
|
-
} else {
|
|
154
|
-
log.debug('@payloadcms/richtext-lexical already installed, skipping');
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
// Ensure cloud-storage is installed (required peer dependency for buildFigmaConfig)
|
|
158
|
-
log.debug('Checking if @payloadcms/plugin-cloud-storage is installed...');
|
|
159
|
-
const hasCloudStorage = await checkPackageInstalled(projectPath, '@payloadcms/plugin-cloud-storage');
|
|
160
|
-
if (!hasCloudStorage) {
|
|
161
|
-
log.debug('@payloadcms/plugin-cloud-storage not found, installing...');
|
|
162
|
-
try {
|
|
163
|
-
await installPackage(projectPath, '@payloadcms/plugin-cloud-storage', packageManager, payloadVersion);
|
|
164
|
-
changes.push('Installed @payloadcms/plugin-cloud-storage');
|
|
165
|
-
log.debug('Successfully installed @payloadcms/plugin-cloud-storage');
|
|
166
|
-
} catch (error) {
|
|
167
|
-
log.debug(`Failed to install @payloadcms/plugin-cloud-storage: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
168
|
-
warnings.push('Could not install @payloadcms/plugin-cloud-storage - you may need to install it manually');
|
|
169
|
-
}
|
|
170
|
-
} else {
|
|
171
|
-
log.debug('@payloadcms/plugin-cloud-storage already installed, skipping');
|
|
172
|
-
}
|
|
173
|
-
if (packagesToUninstall.length > 0) {
|
|
174
|
-
log.debug(`Uninstalling ${packagesToUninstall.length} orphaned packages...`);
|
|
175
|
-
}
|
|
176
|
-
for (const pkg of packagesToUninstall){
|
|
177
|
-
try {
|
|
178
|
-
await uninstallPackage(projectPath, pkg, packageManager);
|
|
179
|
-
changes.push(`Uninstalled ${pkg}`);
|
|
180
|
-
log.debug(`Uninstalled ${pkg}`);
|
|
181
|
-
} catch (error) {
|
|
182
|
-
log.debug(`Failed to uninstall ${pkg}: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
183
|
-
// Don't fail on uninstall errors - orphaned package is better than broken config
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
// 8. Format file
|
|
187
164
|
log.debug('Formatting config file...');
|
|
188
165
|
const formatResult = await formatFile(configPath, packageManager);
|
|
189
166
|
if (!formatResult.success && formatResult.warning) {
|
|
190
167
|
warnings.push(formatResult.warning);
|
|
191
168
|
log.debug(`Formatting warning: ${formatResult.warning}`);
|
|
192
169
|
}
|
|
193
|
-
// 9. Run TypeScript validation
|
|
194
170
|
log.debug('Running TypeScript validation...');
|
|
195
171
|
const tsResult = await runTypeScriptCheck(projectPath, packageManager);
|
|
196
172
|
if (!tsResult.success && tsResult.errors.length > 0) {
|
|
197
173
|
warnings.push(`TypeScript validation found issues - run '${getRunCommand(packageManager)} build' for details`);
|
|
198
174
|
log.debug(`TypeScript validation found ${tsResult.errors.length} issues`);
|
|
199
175
|
}
|
|
200
|
-
}
|
|
176
|
+
}
|
|
177
|
+
if (packagesToUninstall.length > 0) {
|
|
178
|
+
log.debug(`Uninstalling ${packagesToUninstall.length} orphaned packages: ${packagesToUninstall.join(', ')}`);
|
|
179
|
+
const uninstallResult = await uninstallPackages(projectPath, packagesToUninstall, packageManager);
|
|
180
|
+
if (uninstallResult.success) {
|
|
181
|
+
for (const pkg of packagesToUninstall){
|
|
182
|
+
changes.push(`Uninstalled ${pkg}`);
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
warnings.push(`Could not uninstall ${uninstallResult.failedPackages.join(', ')} — they may remain as orphaned dependencies in package.json`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
const modified = astModified || changes.length > 0;
|
|
189
|
+
if (!modified) {
|
|
201
190
|
log.debug('No modifications needed - config already correct');
|
|
202
191
|
}
|
|
203
192
|
log.debug('Payload config modification completed successfully');
|
|
@@ -215,7 +204,7 @@ import { getOwnVersion } from './version-check.js';
|
|
|
215
204
|
return {
|
|
216
205
|
changes,
|
|
217
206
|
error: error instanceof Error ? error.message : 'Unknown error occurred',
|
|
218
|
-
modified,
|
|
207
|
+
modified: astModified,
|
|
219
208
|
success: false
|
|
220
209
|
};
|
|
221
210
|
}
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import type { PackageManager } from './package-manager.js';
|
|
2
|
+
export type PackageSpec = {
|
|
3
|
+
name: string;
|
|
4
|
+
version?: string;
|
|
5
|
+
};
|
|
6
|
+
export type UninstallResult = {
|
|
7
|
+
failedPackages: string[];
|
|
8
|
+
success: boolean;
|
|
9
|
+
};
|
|
2
10
|
/**
|
|
3
11
|
* Check if a package is installed in dependencies (not devDependencies)
|
|
4
12
|
*/
|
|
@@ -13,11 +21,23 @@ export declare function getPayloadVersion(projectPath: string): Promise<string |
|
|
|
13
21
|
* @param version - Version to install (without caret). Defaults to 'latest'.
|
|
14
22
|
*/
|
|
15
23
|
export declare function installPackage(projectPath: string, packageName: string, packageManager: PackageManager, version?: string): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Install multiple packages as production dependencies in a single package-manager call.
|
|
26
|
+
* No-op when packages is empty. Throws on failure with exit code and truncated stderr.
|
|
27
|
+
*/
|
|
28
|
+
export declare function installPackages(projectPath: string, packages: PackageSpec[], packageManager: PackageManager): Promise<void>;
|
|
16
29
|
/**
|
|
17
30
|
* Uninstall a package
|
|
18
31
|
* Does not throw on failure - orphaned package is better than broken config
|
|
19
32
|
*/
|
|
20
|
-
export declare function uninstallPackage(projectPath: string, packageName: string, packageManager: PackageManager): Promise<
|
|
33
|
+
export declare function uninstallPackage(projectPath: string, packageName: string, packageManager: PackageManager): Promise<UninstallResult>;
|
|
34
|
+
/**
|
|
35
|
+
* Uninstall multiple packages in a single package-manager call.
|
|
36
|
+
* No-op when packageNames is empty. Does not throw — orphaned packages are
|
|
37
|
+
* preferable to a crash mid-config-rewrite. Returns a success flag so callers
|
|
38
|
+
* can avoid claiming "Uninstalled X" when the package manager actually failed.
|
|
39
|
+
*/
|
|
40
|
+
export declare function uninstallPackages(projectPath: string, packageNames: string[], packageManager: PackageManager): Promise<UninstallResult>;
|
|
21
41
|
/**
|
|
22
42
|
* Run an npm script from package.json
|
|
23
43
|
* Does not throw on failure - logs warning and continues
|