@payloadcms/figma 0.0.1-alpha.63 → 0.0.1-alpha.65
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/callback-server.d.ts +19 -7
- package/dist/auth/callback-server.js +72 -31
- package/dist/auth/crypto-utils.d.ts +11 -0
- package/dist/auth/crypto-utils.js +22 -1
- package/dist/auth/oauth-flow.d.ts +3 -1
- package/dist/auth/oauth-flow.js +14 -6
- 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.d.ts +2 -0
- package/dist/auth/token-store.js +43 -3
- package/dist/auth/types.d.ts +11 -0
- package/dist/cli.js +15 -1
- package/dist/commands/bootstrap.d.ts +18 -0
- package/dist/commands/bootstrap.js +90 -0
- package/dist/commands/init.d.ts +4 -0
- package/dist/commands/init.js +76 -4
- package/dist/config/oauth.d.ts +2 -1
- package/dist/config/oauth.js +7 -1
- package/dist/db-content-api/generated/content-api-types.d.ts +6 -0
- package/dist/db-content-api/index.d.ts +2 -0
- package/dist/db-content-api/index.js +36 -74
- package/dist/lib/download-skill.d.ts +13 -0
- package/dist/lib/download-skill.js +79 -0
- package/dist/oauth/endpoints/getLoginEndpoint.js +26 -107
- package/dist/oauth/endpoints/getTokenLoginEndpoint.d.ts +17 -0
- package/dist/oauth/endpoints/getTokenLoginEndpoint.js +105 -0
- package/dist/oauth/index.js +8 -0
- package/dist/oauth/utilities/establishSession.d.ts +23 -0
- package/dist/oauth/utilities/establishSession.js +82 -0
- package/dist/oauth/utilities/exchangeCodeForAccessToken.d.ts +24 -0
- package/dist/oauth/utilities/exchangeCodeForAccessToken.js +28 -0
- package/dist/oauth/utilities/isAbsoluteURL.d.ts +2 -0
- package/dist/oauth/utilities/isAbsoluteURL.js +3 -0
- package/dist/plugin/build-config.js +65 -46
- package/dist/types.d.ts +2 -0
- package/dist/utils/download-template.d.ts +9 -1
- package/dist/utils/download-template.js +24 -19
- package/dist/utils/messages.js +9 -0
- package/dist/utils/parse-template-spec.d.ts +12 -0
- package/dist/utils/parse-template-spec.js +62 -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/dist/utils/project.d.ts +2 -1
- package/dist/utils/project.js +2 -2
- package/package.json +9 -1
- package/dist/db-content-api/README.md +0 -98
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { APIError, generateCookie } from 'payload';
|
|
2
|
+
import { getProjectToken, getUserInfo } from '../../api/figma-api.js';
|
|
3
|
+
import { createCookieOptions } from './createCookieOptions.js';
|
|
4
|
+
import { getCookieExpiration } from './getCookieExpiration.js';
|
|
5
|
+
/**
|
|
6
|
+
* Given a valid OAuth access_token: fetch user info (non-fatal), mint a
|
|
7
|
+
* project token, and write the main + refresh + user-info cookies to
|
|
8
|
+
* `req.responseHeaders`. Throws `APIError` if the project token mint fails.
|
|
9
|
+
*/ export const establishSession = async ({ accessToken, collection, collectionOptions, contentSystemId, debugLogger, pluginOptions, refreshTokenExpiresIn, req, strategy })=>{
|
|
10
|
+
const oauthCredential = {
|
|
11
|
+
type: 'oauth',
|
|
12
|
+
token: accessToken
|
|
13
|
+
};
|
|
14
|
+
// Non-fatal: user just won't have handle/image on first request.
|
|
15
|
+
let figmaUserInfo = null;
|
|
16
|
+
try {
|
|
17
|
+
figmaUserInfo = await getUserInfo(oauthCredential);
|
|
18
|
+
debugLogger.info({
|
|
19
|
+
figmaUserInfo,
|
|
20
|
+
msg: 'Fetched Figma user info'
|
|
21
|
+
});
|
|
22
|
+
} catch (err) {
|
|
23
|
+
debugLogger.error({
|
|
24
|
+
err,
|
|
25
|
+
msg: 'Failed to fetch Figma user info'
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
const projectToken = await getProjectToken(oauthCredential, contentSystemId);
|
|
29
|
+
if (!projectToken.token) {
|
|
30
|
+
const errMsg = 'No project token returned from Figma API';
|
|
31
|
+
req.payload.logger.error({
|
|
32
|
+
msg: errMsg
|
|
33
|
+
});
|
|
34
|
+
throw new APIError(errMsg);
|
|
35
|
+
}
|
|
36
|
+
const cookieOptions = createCookieOptions({
|
|
37
|
+
collection,
|
|
38
|
+
headers: req.headers,
|
|
39
|
+
pluginOptions
|
|
40
|
+
});
|
|
41
|
+
if (!req.responseHeaders) {
|
|
42
|
+
req.responseHeaders = new Headers();
|
|
43
|
+
}
|
|
44
|
+
// The -refresh cookie holds the OAuth access_token so refreshTokens can
|
|
45
|
+
// re-mint project_tokens from it on JWT expiry.
|
|
46
|
+
if (!collectionOptions.usePayloadJWT) {
|
|
47
|
+
const refreshCookie = generateCookie({
|
|
48
|
+
...cookieOptions,
|
|
49
|
+
name: `${strategy.cookieName}-refresh`,
|
|
50
|
+
expires: getCookieExpiration(cookieOptions?.expires ?? refreshTokenExpiresIn ?? 31_556_952),
|
|
51
|
+
returnCookieAsObject: false,
|
|
52
|
+
value: accessToken
|
|
53
|
+
});
|
|
54
|
+
req.responseHeaders.append('Set-Cookie', refreshCookie);
|
|
55
|
+
}
|
|
56
|
+
const tokenExpires = cookieOptions?.expires ? getCookieExpiration(cookieOptions.expires) : new Date(projectToken.expiresAt);
|
|
57
|
+
const tokenCookie = generateCookie({
|
|
58
|
+
...cookieOptions,
|
|
59
|
+
name: strategy.cookieName,
|
|
60
|
+
expires: tokenExpires,
|
|
61
|
+
returnCookieAsObject: false,
|
|
62
|
+
value: projectToken.token
|
|
63
|
+
});
|
|
64
|
+
req.responseHeaders.append('Set-Cookie', tokenCookie);
|
|
65
|
+
// Short-lived handoff cookie consumed by defaultVerify on the next request.
|
|
66
|
+
if (figmaUserInfo) {
|
|
67
|
+
const userInfoValue = Buffer.from(JSON.stringify({
|
|
68
|
+
handle: figmaUserInfo.handle,
|
|
69
|
+
img_url: figmaUserInfo.img_url
|
|
70
|
+
})).toString('base64');
|
|
71
|
+
const userInfoCookie = generateCookie({
|
|
72
|
+
...cookieOptions,
|
|
73
|
+
name: pluginOptions?.userInfoCookieName ?? 'figma-user-info',
|
|
74
|
+
expires: new Date(Date.now() + 60_000),
|
|
75
|
+
returnCookieAsObject: false,
|
|
76
|
+
value: userInfoValue
|
|
77
|
+
});
|
|
78
|
+
req.responseHeaders.append('Set-Cookie', userInfoCookie);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
//# sourceMappingURL=establishSession.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Strategy } from '../strategy/index.js';
|
|
2
|
+
interface Args {
|
|
3
|
+
/** Authorization code received from the IdP redirect. */
|
|
4
|
+
code: string;
|
|
5
|
+
/** PKCE verifier; omit for non-PKCE flows. */
|
|
6
|
+
codeVerifier?: string;
|
|
7
|
+
/** Must match the redirect_uri the authorization request used. */
|
|
8
|
+
redirectUri: string;
|
|
9
|
+
strategy: Strategy;
|
|
10
|
+
}
|
|
11
|
+
export interface TokenEndpointResponse {
|
|
12
|
+
access_token?: null | string;
|
|
13
|
+
error?: string;
|
|
14
|
+
error_description?: string;
|
|
15
|
+
expires_in?: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* POSTs an authorization code to the IdP's `token_endpoint` and returns the
|
|
19
|
+
* raw response body. Callers handle error/missing-token logging in their own
|
|
20
|
+
* style — this helper only owns the request shape.
|
|
21
|
+
*/
|
|
22
|
+
export declare const exchangeCodeForAccessToken: ({ code, codeVerifier, redirectUri, strategy, }: Args) => Promise<TokenEndpointResponse>;
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=exchangeCodeForAccessToken.d.ts.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* POSTs an authorization code to the IdP's `token_endpoint` and returns the
|
|
3
|
+
* raw response body. Callers handle error/missing-token logging in their own
|
|
4
|
+
* style — this helper only owns the request shape.
|
|
5
|
+
*/ export const exchangeCodeForAccessToken = async ({ code, codeVerifier, redirectUri, strategy })=>{
|
|
6
|
+
const body = {
|
|
7
|
+
client_id: strategy.clientID,
|
|
8
|
+
code,
|
|
9
|
+
grant_type: 'authorization_code',
|
|
10
|
+
redirect_uri: redirectUri
|
|
11
|
+
};
|
|
12
|
+
if (codeVerifier) {
|
|
13
|
+
body.code_verifier = codeVerifier;
|
|
14
|
+
}
|
|
15
|
+
if (strategy.collectionOptions.clientSecret) {
|
|
16
|
+
body.client_secret = strategy.collectionOptions.clientSecret;
|
|
17
|
+
}
|
|
18
|
+
const res = await fetch(strategy.meta.token_endpoint, {
|
|
19
|
+
body: new URLSearchParams(body),
|
|
20
|
+
headers: {
|
|
21
|
+
'Content-Type': 'application/x-www-form-urlencoded'
|
|
22
|
+
},
|
|
23
|
+
method: 'POST'
|
|
24
|
+
});
|
|
25
|
+
return await res.json();
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
//# sourceMappingURL=exchangeCodeForAccessToken.js.map
|
|
@@ -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)
|
|
@@ -160,6 +220,7 @@ export async function buildFigmaConfig(config) {
|
|
|
160
220
|
mode: 'apiKey'
|
|
161
221
|
},
|
|
162
222
|
contentSystemId,
|
|
223
|
+
environmentName: process.env.FIGMA_ENVIRONMENT_NAME,
|
|
163
224
|
url
|
|
164
225
|
});
|
|
165
226
|
} else if (process.env.FIGMA_DEV_JWT === 'true') {
|
|
@@ -168,6 +229,7 @@ export async function buildFigmaConfig(config) {
|
|
|
168
229
|
mode: 'devJwt'
|
|
169
230
|
},
|
|
170
231
|
contentSystemId,
|
|
232
|
+
environmentName: process.env.FIGMA_ENVIRONMENT_NAME,
|
|
171
233
|
url
|
|
172
234
|
});
|
|
173
235
|
} else {
|
|
@@ -177,53 +239,10 @@ export async function buildFigmaConfig(config) {
|
|
|
177
239
|
tokenStore: getTokenStore()
|
|
178
240
|
},
|
|
179
241
|
contentSystemId,
|
|
242
|
+
environmentName: process.env.FIGMA_ENVIRONMENT_NAME,
|
|
180
243
|
url
|
|
181
244
|
});
|
|
182
245
|
}
|
|
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
246
|
// Build complete config with Figma platform defaults
|
|
228
247
|
const configWithFigmaDefaults = {
|
|
229
248
|
...config,
|
|
@@ -257,8 +276,8 @@ export async function buildFigmaConfig(config) {
|
|
|
257
276
|
// Add oauth to plugins if not already present
|
|
258
277
|
plugins: [
|
|
259
278
|
...config.plugins ?? [],
|
|
260
|
-
...
|
|
261
|
-
|
|
279
|
+
...config.figma.storage !== false ? [
|
|
280
|
+
createStoragePlugin(url, contentSystemId)
|
|
262
281
|
] : [],
|
|
263
282
|
oAuth2Plugin({
|
|
264
283
|
collections: [
|
package/dist/types.d.ts
CHANGED
|
@@ -11,11 +11,13 @@ export interface Args extends arg.Spec {
|
|
|
11
11
|
'--list': BooleanConstructor;
|
|
12
12
|
'--logout': BooleanConstructor;
|
|
13
13
|
'--name': StringConstructor;
|
|
14
|
+
'--template': StringConstructor;
|
|
14
15
|
'--yes': BooleanConstructor;
|
|
15
16
|
'-e': string;
|
|
16
17
|
'-f': string;
|
|
17
18
|
'-h': string;
|
|
18
19
|
'-n': string;
|
|
20
|
+
'-t': string;
|
|
19
21
|
'-y': string;
|
|
20
22
|
}
|
|
21
23
|
export type CliArgs = arg.Result<Args>;
|
|
@@ -5,11 +5,19 @@ export declare class TemplateDownloadError extends Error {
|
|
|
5
5
|
cause?: Error | undefined;
|
|
6
6
|
constructor(message: string, cause?: Error | undefined);
|
|
7
7
|
}
|
|
8
|
+
export type TemplateSource = {
|
|
9
|
+
owner: string;
|
|
10
|
+
ref: string;
|
|
11
|
+
repo: string;
|
|
12
|
+
templatePath: string;
|
|
13
|
+
};
|
|
14
|
+
export declare const DEFAULT_TEMPLATE_SOURCE: TemplateSource;
|
|
8
15
|
/**
|
|
9
16
|
* Download Payload template from GitHub with retry logic
|
|
10
17
|
*
|
|
11
18
|
* @param projectDir - Directory to extract template into
|
|
19
|
+
* @param source - Optional override of repo / ref / template path
|
|
12
20
|
* @throws TemplateDownloadError if download fails
|
|
13
21
|
*/
|
|
14
|
-
export declare function downloadTemplateFromGitHub(projectDir: string): Promise<void>;
|
|
22
|
+
export declare function downloadTemplateFromGitHub(projectDir: string, source?: Partial<TemplateSource>): Promise<void>;
|
|
15
23
|
//# sourceMappingURL=download-template.d.ts.map
|
|
@@ -11,23 +11,30 @@ import { x } from 'tar';
|
|
|
11
11
|
this.name = 'TemplateDownloadError';
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
export const DEFAULT_TEMPLATE_SOURCE = {
|
|
15
|
+
owner: 'payloadcms',
|
|
16
|
+
ref: 'v3.84.1',
|
|
17
|
+
repo: 'payload',
|
|
18
|
+
templatePath: 'blank'
|
|
19
|
+
};
|
|
17
20
|
const MAX_RETRIES = 3;
|
|
18
|
-
const INITIAL_RETRY_DELAY = 1000
|
|
19
|
-
;
|
|
21
|
+
const INITIAL_RETRY_DELAY = 1000;
|
|
20
22
|
/**
|
|
21
23
|
* Download Payload template from GitHub with retry logic
|
|
22
24
|
*
|
|
23
25
|
* @param projectDir - Directory to extract template into
|
|
26
|
+
* @param source - Optional override of repo / ref / template path
|
|
24
27
|
* @throws TemplateDownloadError if download fails
|
|
25
|
-
*/ export async function downloadTemplateFromGitHub(projectDir) {
|
|
28
|
+
*/ export async function downloadTemplateFromGitHub(projectDir, source) {
|
|
29
|
+
const resolved = {
|
|
30
|
+
...DEFAULT_TEMPLATE_SOURCE,
|
|
31
|
+
...source
|
|
32
|
+
};
|
|
26
33
|
let lastError;
|
|
27
34
|
for(let attempt = 1; attempt <= MAX_RETRIES; attempt++){
|
|
28
35
|
try {
|
|
29
|
-
await downloadTemplateAttempt(projectDir);
|
|
30
|
-
return;
|
|
36
|
+
await downloadTemplateAttempt(projectDir, resolved);
|
|
37
|
+
return;
|
|
31
38
|
} catch (error) {
|
|
32
39
|
lastError = error instanceof Error ? error : new Error('Unknown error');
|
|
33
40
|
if (attempt < MAX_RETRIES) {
|
|
@@ -37,31 +44,29 @@ const INITIAL_RETRY_DELAY = 1000 // 1 second
|
|
|
37
44
|
}
|
|
38
45
|
}
|
|
39
46
|
}
|
|
40
|
-
// All retries failed
|
|
41
47
|
throw new TemplateDownloadError(`Failed to download template after ${MAX_RETRIES} attempts`, lastError);
|
|
42
48
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const
|
|
47
|
-
const
|
|
49
|
+
async function downloadTemplateAttempt(projectDir, source) {
|
|
50
|
+
const url = `https://codeload.github.com/${source.owner}/${source.repo}/tar.gz/${source.ref}`;
|
|
51
|
+
// GitHub strips the leading 'v' and replaces '/' with '-' when naming the tarball root dir
|
|
52
|
+
const refSlug = source.ref.replace(/^v/, '').replace(/\//g, '-');
|
|
53
|
+
const tarballPrefix = `${source.repo}-${refSlug}`;
|
|
54
|
+
const filter = `${tarballPrefix}/templates/${source.templatePath}/`;
|
|
55
|
+
const strip = 2 + source.templatePath.split('/').length;
|
|
48
56
|
try {
|
|
49
|
-
// Ensure target directory exists
|
|
50
57
|
await fs.mkdir(projectDir, {
|
|
51
58
|
recursive: true
|
|
52
59
|
});
|
|
53
60
|
await pipeline(await downloadTarStream(url), x({
|
|
54
61
|
cwd: projectDir,
|
|
55
62
|
filter: (p)=>p.includes(filter),
|
|
56
|
-
strip
|
|
63
|
+
strip
|
|
57
64
|
}));
|
|
58
65
|
} catch (error) {
|
|
59
66
|
throw new TemplateDownloadError('Failed to download template from GitHub', error instanceof Error ? error : undefined);
|
|
60
67
|
}
|
|
61
68
|
}
|
|
62
|
-
|
|
63
|
-
* Download tar stream from URL
|
|
64
|
-
*/ async function downloadTarStream(url) {
|
|
69
|
+
async function downloadTarStream(url) {
|
|
65
70
|
const res = await fetch(url);
|
|
66
71
|
if (!res.ok) {
|
|
67
72
|
throw new Error(`HTTP ${res.status}: ${res.statusText}`);
|
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
|
|
@@ -38,6 +39,14 @@ export function helpMessage() {
|
|
|
38
39
|
${pc.cyan('@payloadcms/figma init --id <id> --env staging')} Initialize for specific environment
|
|
39
40
|
${pc.dim('--name, -n <name>')} Set project directory name (skips prompt)
|
|
40
41
|
${pc.dim('--force')} Force reconfiguration of existing project
|
|
42
|
+
${pc.dim('--no-skill')} Skip installing the Payload skill into .claude/skills/payload/
|
|
43
|
+
${pc.dim('--template, -t <spec>')} Override scaffold template (e.g. v3.80.0:website, owner/repo#ref:path)
|
|
44
|
+
|
|
45
|
+
${pc.bold('BOOTSTRAP COMMAND')}
|
|
46
|
+
|
|
47
|
+
${pc.cyan('@payloadcms/figma bootstrap --id <cms-resource-id>')} Print bootstrap info
|
|
48
|
+
${pc.dim('--env <environment>')} Filter to a single environment
|
|
49
|
+
${pc.dim('--json')} Output JSON instead of styled note
|
|
41
50
|
|
|
42
51
|
${pc.bold('ENV COMMAND')}
|
|
43
52
|
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { TemplateSource } from './download-template.js';
|
|
2
|
+
export declare class TemplateSpecParseError extends Error {
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Parse a --template spec string into a partial TemplateSource override.
|
|
7
|
+
*
|
|
8
|
+
* Format: [<owner>/<repo>#]<ref>:<template-path>
|
|
9
|
+
* Shorthand: a bare token with no `#` and no `:` is treated as the template path.
|
|
10
|
+
*/
|
|
11
|
+
export declare function parseTemplateSpec(spec: string): Partial<TemplateSource>;
|
|
12
|
+
//# sourceMappingURL=parse-template-spec.d.ts.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export class TemplateSpecParseError extends Error {
|
|
2
|
+
constructor(message){
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = 'TemplateSpecParseError';
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Parse a --template spec string into a partial TemplateSource override.
|
|
9
|
+
*
|
|
10
|
+
* Format: [<owner>/<repo>#]<ref>:<template-path>
|
|
11
|
+
* Shorthand: a bare token with no `#` and no `:` is treated as the template path.
|
|
12
|
+
*/ export function parseTemplateSpec(spec) {
|
|
13
|
+
const trimmed = spec.trim();
|
|
14
|
+
if (!trimmed) {
|
|
15
|
+
throw new TemplateSpecParseError('Template spec is empty');
|
|
16
|
+
}
|
|
17
|
+
let rest = trimmed;
|
|
18
|
+
const result = {};
|
|
19
|
+
const hashIndex = rest.indexOf('#');
|
|
20
|
+
if (hashIndex !== -1) {
|
|
21
|
+
const repoPart = rest.slice(0, hashIndex);
|
|
22
|
+
rest = rest.slice(hashIndex + 1);
|
|
23
|
+
const slashCount = (repoPart.match(/\//g) ?? []).length;
|
|
24
|
+
if (slashCount !== 1) {
|
|
25
|
+
throw new TemplateSpecParseError(`Expected "<owner>/<repo>" before "#", got "${repoPart}"`);
|
|
26
|
+
}
|
|
27
|
+
const [owner, repo] = repoPart.split('/');
|
|
28
|
+
if (!owner || !repo) {
|
|
29
|
+
throw new TemplateSpecParseError(`Invalid owner/repo: "${repoPart}"`);
|
|
30
|
+
}
|
|
31
|
+
result.owner = owner;
|
|
32
|
+
result.repo = repo;
|
|
33
|
+
} else {
|
|
34
|
+
// Check for slash only in the portion before the first colon (the ref part).
|
|
35
|
+
// Slashes after the colon are valid multi-segment template paths.
|
|
36
|
+
const beforeColon = rest.includes(':') ? rest.slice(0, rest.indexOf(':')) : rest;
|
|
37
|
+
if (beforeColon.includes('/')) {
|
|
38
|
+
throw new TemplateSpecParseError(`"<owner>/<repo>" must be followed by "#<ref>" (got "${trimmed}")`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const colonIndex = rest.indexOf(':');
|
|
42
|
+
if (colonIndex !== -1) {
|
|
43
|
+
const ref = rest.slice(0, colonIndex);
|
|
44
|
+
const templatePath = rest.slice(colonIndex + 1);
|
|
45
|
+
if (!ref) {
|
|
46
|
+
throw new TemplateSpecParseError('Ref before ":" is empty');
|
|
47
|
+
}
|
|
48
|
+
if (!templatePath) {
|
|
49
|
+
throw new TemplateSpecParseError('Template path after ":" is empty');
|
|
50
|
+
}
|
|
51
|
+
result.ref = ref;
|
|
52
|
+
result.templatePath = templatePath;
|
|
53
|
+
return result;
|
|
54
|
+
}
|
|
55
|
+
if (hashIndex !== -1) {
|
|
56
|
+
throw new TemplateSpecParseError('Spec with "<owner>/<repo>#<ref>" must include ":<template-path>"');
|
|
57
|
+
}
|
|
58
|
+
result.templatePath = rest;
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
//# sourceMappingURL=parse-template-spec.js.map
|