@payloadcms/figma 0.0.1-alpha.62 → 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/crypto-utils.d.ts +11 -0
- package/dist/auth/crypto-utils.js +51 -0
- package/dist/auth/oauth-flow.js +4 -3
- package/dist/auth/project-token.d.ts +32 -16
- package/dist/auth/project-token.js +165 -64
- package/dist/auth/token-store-migration.d.ts +58 -0
- package/dist/auth/token-store-migration.js +156 -0
- package/dist/auth/token-store.d.ts +51 -128
- package/dist/auth/token-store.js +326 -187
- package/dist/auth/types.d.ts +12 -1
- package/dist/cli.js +15 -0
- package/dist/commands/bootstrap.d.ts +18 -0
- package/dist/commands/bootstrap.js +90 -0
- package/dist/commands/debug.js +16 -12
- package/dist/commands/dump-tokens.d.ts +12 -0
- package/dist/commands/dump-tokens.js +69 -0
- package/dist/commands/init.js +39 -5
- package/dist/commands/list-tokens.js +2 -2
- package/dist/commands/login.js +1 -1
- package/dist/commands/logout.js +23 -4
- package/dist/db-content-api/index.js +27 -73
- package/dist/db-content-api/utilities/auth.js +4 -1
- package/dist/oauth/defaults.d.ts +3 -1
- package/dist/oauth/defaults.js +6 -5
- package/dist/oauth/endpoints/getLoginEndpoint.js +1 -1
- package/dist/oauth/index.js +1 -0
- package/dist/oauth/types.d.ts +10 -0
- package/dist/plugin/auth-preflight.d.ts +8 -0
- package/dist/plugin/auth-preflight.js +20 -0
- package/dist/plugin/bootstrap-preflight.d.ts +23 -0
- package/dist/plugin/bootstrap-preflight.js +45 -0
- package/dist/plugin/build-config.js +135 -58
- package/dist/plugin/dev-cookie-names.d.ts +14 -0
- package/dist/plugin/dev-cookie-names.js +19 -0
- package/dist/storage-content-api/client.js +4 -1
- 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/dist/utils/token-display.d.ts +5 -1
- package/dist/utils/token-display.js +47 -26
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import * as p from '@clack/prompts';
|
|
2
2
|
import arg from 'arg';
|
|
3
3
|
import pc from 'picocolors';
|
|
4
|
+
import { bootstrapCommand } from './commands/bootstrap.js';
|
|
4
5
|
import { buildLambdaZipCommand } from './commands/build-lambda-zip.js';
|
|
5
6
|
import { debugCommand } from './commands/debug.js';
|
|
6
7
|
import { deployCommand } from './commands/deploy.js';
|
|
8
|
+
import { dumpTokensCommand } from './commands/dump-tokens.js';
|
|
7
9
|
import { envCommand } from './commands/env.js';
|
|
8
10
|
import { initCommand } from './commands/init.js';
|
|
9
11
|
import { listTokensCommand } from './commands/list-tokens.js';
|
|
@@ -41,6 +43,7 @@ class Main {
|
|
|
41
43
|
'--help': Boolean,
|
|
42
44
|
'--id': String,
|
|
43
45
|
'--infra-env': String,
|
|
46
|
+
'--json': Boolean,
|
|
44
47
|
'--name': String,
|
|
45
48
|
'--skip-auth': Boolean,
|
|
46
49
|
'--skip-build': Boolean,
|
|
@@ -101,11 +104,23 @@ class Main {
|
|
|
101
104
|
await debugCommand();
|
|
102
105
|
process.exit(0);
|
|
103
106
|
}
|
|
107
|
+
// dump-tokens outputs plain text
|
|
108
|
+
if (subcommand === 'dump-tokens') {
|
|
109
|
+
await dumpTokensCommand();
|
|
110
|
+
process.exit(0);
|
|
111
|
+
}
|
|
104
112
|
// eslint-disable-next-line no-console
|
|
105
113
|
console.log('\n');
|
|
106
114
|
p.intro(pc.bgCyan(pc.black(' @payloadcms/figma ')));
|
|
107
115
|
// Route to appropriate command handler
|
|
108
116
|
switch(subcommand){
|
|
117
|
+
case 'bootstrap':
|
|
118
|
+
await bootstrapCommand({
|
|
119
|
+
id: this.args['--id'],
|
|
120
|
+
env: this.args['--env'],
|
|
121
|
+
json: this.args['--json']
|
|
122
|
+
});
|
|
123
|
+
break;
|
|
109
124
|
case 'build-lambda-zip':
|
|
110
125
|
await buildLambdaZipCommand();
|
|
111
126
|
break;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type BootstrapCommandOptions = {
|
|
2
|
+
/** Filter to a single environment (e.g. "production", "staging"). */
|
|
3
|
+
env?: string;
|
|
4
|
+
/** CMS Resource ID (FIGMA_PROJECT_ID, e.g. cms_…). */
|
|
5
|
+
id?: string;
|
|
6
|
+
/** Emit plain JSON instead of a styled note (handy for piping into jq). */
|
|
7
|
+
json?: boolean;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Fetch and display bootstrap info for a CMS resource.
|
|
11
|
+
*
|
|
12
|
+
* Calls the Control Plane bootstrap endpoint with the current OAuth credential
|
|
13
|
+
* and prints the resulting environments (`contentSystemId`, `tenantInstanceId`)
|
|
14
|
+
* plus OAuth client credentials. Useful for grabbing a tenant ID without having
|
|
15
|
+
* to `init` a project.
|
|
16
|
+
*/
|
|
17
|
+
export declare function bootstrapCommand(options?: BootstrapCommandOptions): Promise<void>;
|
|
18
|
+
//# sourceMappingURL=bootstrap.d.ts.map
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import pc from 'picocolors';
|
|
3
|
+
import { ControlPlaneError, getBootstrapInfo } from '../api/control-plane.js';
|
|
4
|
+
import { getValidCredential } from '../auth/oauth-flow.js';
|
|
5
|
+
import { getTokenStore } from '../auth/token-store.js';
|
|
6
|
+
import { getInfraEnvironment } from '../constants.js';
|
|
7
|
+
import { isDebug } from '../utils/is-debug.js';
|
|
8
|
+
import { maskToken } from '../utils/token-display.js';
|
|
9
|
+
/**
|
|
10
|
+
* Fetch and display bootstrap info for a CMS resource.
|
|
11
|
+
*
|
|
12
|
+
* Calls the Control Plane bootstrap endpoint with the current OAuth credential
|
|
13
|
+
* and prints the resulting environments (`contentSystemId`, `tenantInstanceId`)
|
|
14
|
+
* plus OAuth client credentials. Useful for grabbing a tenant ID without having
|
|
15
|
+
* to `init` a project.
|
|
16
|
+
*/ export async function bootstrapCommand(options = {}) {
|
|
17
|
+
if (!options.id) {
|
|
18
|
+
p.log.error(pc.red('--id <project-id> is required'));
|
|
19
|
+
p.note('The CMS Resource ID (FIGMA_PROJECT_ID), e.g. cms_abc123', 'Tip');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
const tokenStore = getTokenStore(getInfraEnvironment());
|
|
23
|
+
let credential;
|
|
24
|
+
try {
|
|
25
|
+
credential = await getValidCredential(tokenStore);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
p.log.error(pc.red(`Failed to read stored credentials: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
28
|
+
p.note(`Run ${pc.cyan(`@payloadcms/figma logout --infra-env ${getInfraEnvironment()} -y`)} ` + `then ${pc.cyan(`@payloadcms/figma login --infra-env ${getInfraEnvironment()}`)} and retry.`, 'Re-authenticate');
|
|
29
|
+
if (isDebug() && error instanceof Error) {
|
|
30
|
+
// eslint-disable-next-line no-console
|
|
31
|
+
console.error(error);
|
|
32
|
+
}
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
if (!credential) {
|
|
36
|
+
p.log.error(pc.red('Not authenticated.'));
|
|
37
|
+
p.note(`Run ${pc.cyan('@payloadcms/figma login')} first`, 'Tip');
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
let info;
|
|
41
|
+
try {
|
|
42
|
+
info = await getBootstrapInfo(credential, options.id, options.env);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
if (error instanceof ControlPlaneError) {
|
|
45
|
+
p.log.error(pc.red(`Bootstrap fetch failed: ${error.statusCode} ${error.message}`));
|
|
46
|
+
if (error.statusCode === 401) {
|
|
47
|
+
p.note(`Stored credentials for infra env ${pc.bold(getInfraEnvironment())} were rejected.\n` + `Run ${pc.cyan(`@payloadcms/figma logout --infra-env ${getInfraEnvironment()} -y`)} ` + `then ${pc.cyan(`@payloadcms/figma login --infra-env ${getInfraEnvironment()}`)} and retry.`, 'Re-authenticate');
|
|
48
|
+
} else if (error.statusCode === 404) {
|
|
49
|
+
p.note(`Resource ${pc.bold(options.id)} was not found in infra env ${pc.bold(getInfraEnvironment())}.\n` + `Try ${pc.cyan('--infra-env staging')} (or production) to target a different infra.`, 'Wrong infra environment?');
|
|
50
|
+
}
|
|
51
|
+
} else if (error instanceof Error) {
|
|
52
|
+
const causeMessage = error.cause instanceof Error ? `: ${error.cause.message}` : '';
|
|
53
|
+
p.log.error(pc.red(`${error.name}: ${error.message}${causeMessage}`));
|
|
54
|
+
if (isDebug()) {
|
|
55
|
+
// eslint-disable-next-line no-console
|
|
56
|
+
console.error(error);
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
p.log.error(pc.red('Unknown error'));
|
|
60
|
+
}
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
if (options.json) {
|
|
64
|
+
const showSecretsInJson = isDebug();
|
|
65
|
+
const payload = showSecretsInJson ? info : {
|
|
66
|
+
...info,
|
|
67
|
+
oauthCredentials: {
|
|
68
|
+
...info.oauthCredentials,
|
|
69
|
+
oauthClientSecret: maskToken(info.oauthCredentials.oauthClientSecret)
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
// eslint-disable-next-line no-console
|
|
73
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const showSecret = isDebug();
|
|
77
|
+
const lines = [
|
|
78
|
+
`Project ID: ${options.id}`,
|
|
79
|
+
`OAuth Client ID: ${info.oauthCredentials.oauthClientId}`,
|
|
80
|
+
`OAuth Client Secret: ${showSecret ? info.oauthCredentials.oauthClientSecret : maskToken(info.oauthCredentials.oauthClientSecret)}`,
|
|
81
|
+
'',
|
|
82
|
+
'Environments:'
|
|
83
|
+
];
|
|
84
|
+
for (const env of info.environments){
|
|
85
|
+
lines.push(` ${pc.bold(env.name)}`, ` Tenant ID (contentSystemId): ${env.contentSystemId}`, ` Tenant Instance ID: ${env.tenantInstanceId}`);
|
|
86
|
+
}
|
|
87
|
+
p.note(lines.join('\n'), 'Bootstrap Info');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
//# sourceMappingURL=bootstrap.js.map
|
package/dist/commands/debug.js
CHANGED
|
@@ -36,14 +36,14 @@ function appendSystemSection(lines, env) {
|
|
|
36
36
|
lines.push('');
|
|
37
37
|
}
|
|
38
38
|
function appendAuthSection(lines, tokenStore, cwd) {
|
|
39
|
-
const tokens = tokenStore.
|
|
39
|
+
const tokens = tokenStore.getOauthInfo();
|
|
40
40
|
lines.push('Auth:');
|
|
41
41
|
if (!tokens) {
|
|
42
42
|
lines.push(' Status: Not authenticated');
|
|
43
43
|
lines.push('');
|
|
44
44
|
return;
|
|
45
45
|
}
|
|
46
|
-
const isValid = tokenStore.
|
|
46
|
+
const isValid = tokenStore.hasValidOauthToken();
|
|
47
47
|
lines.push(` Status: ${isValid ? 'Authenticated' : 'Token expired'}`);
|
|
48
48
|
const email = getEmailFromClaims(tokenStore, cwd);
|
|
49
49
|
if (email) {
|
|
@@ -62,7 +62,7 @@ function appendTokenStoreDiagnostics(lines, tokenStore) {
|
|
|
62
62
|
lines.push(` File: ${filePath}`);
|
|
63
63
|
lines.push(` File Exists: ${fileExists}`);
|
|
64
64
|
lines.push(` File Size: ${fileSize}B`);
|
|
65
|
-
const canRead = tokenStore.
|
|
65
|
+
const canRead = tokenStore.getOauthInfo() !== null || tokenStore.getCurrentRefreshToken() !== null;
|
|
66
66
|
lines.push(` Decryption: ${fileExists ? canRead || fileSize === 0 ? 'OK' : 'FAILED' : 'N/A'}`);
|
|
67
67
|
lines.push(` Key Hash: ${getEncryptionKeyHash()}`);
|
|
68
68
|
lines.push('');
|
|
@@ -85,7 +85,12 @@ function appendProjectSection(lines, tokenStore, cwd) {
|
|
|
85
85
|
lines.push(` Content System ID: ${bootstrapData.contentSystemId}`);
|
|
86
86
|
lines.push(` Tenant Instance ID: ${bootstrapData.tenantInstanceId}`);
|
|
87
87
|
lines.push(` OAuth Client ID: ${maskToken(bootstrapData.oauthClientId)}`);
|
|
88
|
-
const projectToken = tokenStore.getProjectToken(
|
|
88
|
+
const projectToken = tokenStore.getProjectToken({
|
|
89
|
+
projectInfo: {
|
|
90
|
+
environmentName,
|
|
91
|
+
projectId
|
|
92
|
+
}
|
|
93
|
+
});
|
|
89
94
|
if (projectToken) {
|
|
90
95
|
lines.push(` Project Token: ${formatExpiryTime(projectToken.expiresAt)}`);
|
|
91
96
|
}
|
|
@@ -103,25 +108,24 @@ async function appendPackagesSection(lines) {
|
|
|
103
108
|
lines.push(` payload: ${payloadVersion}`);
|
|
104
109
|
}
|
|
105
110
|
/**
|
|
106
|
-
*
|
|
111
|
+
* Resolve email from the current project + environment scoped token when available.
|
|
107
112
|
*/ function getEmailFromClaims(tokenStore, cwd) {
|
|
108
113
|
const projectId = getEnvVarSync(cwd, 'FIGMA_PROJECT_ID');
|
|
109
114
|
const environmentName = getEnvVarSync(cwd, 'FIGMA_ENVIRONMENT_NAME');
|
|
110
115
|
if (projectId && environmentName) {
|
|
111
116
|
const bootstrapData = tokenStore.getBootstrapData(projectId, environmentName);
|
|
112
117
|
if (bootstrapData) {
|
|
113
|
-
const email = tokenStore.
|
|
118
|
+
const email = tokenStore.getProjectToken({
|
|
119
|
+
projectInfo: {
|
|
120
|
+
environmentName,
|
|
121
|
+
projectId
|
|
122
|
+
}
|
|
123
|
+
})?.claims?.email;
|
|
114
124
|
if (email) {
|
|
115
125
|
return email;
|
|
116
126
|
}
|
|
117
127
|
}
|
|
118
128
|
}
|
|
119
|
-
for (const id of tokenStore.getAllProjectTokenTenantIds()){
|
|
120
|
-
const email = tokenStore.getProjectTokenClaims(id)?.email;
|
|
121
|
-
if (email) {
|
|
122
|
-
return email;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
129
|
return null;
|
|
126
130
|
}
|
|
127
131
|
function getPayloadVersion() {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handle the `@payloadcms/figma dump-tokens` command
|
|
3
|
+
*
|
|
4
|
+
* Exports decrypted versions of encrypted token store files in
|
|
5
|
+
* `oauth/` and `projects/` subdirectories (new structure) plus any legacy
|
|
6
|
+
* per-env directories from older package versions. Creates
|
|
7
|
+
* `.decrypted.json` files next to each encrypted `.json` file.
|
|
8
|
+
*
|
|
9
|
+
* Skips the `payloadcms-figma-crypto.json` salt file at the root.
|
|
10
|
+
*/
|
|
11
|
+
export declare function dumpTokensCommand(): Promise<void>;
|
|
12
|
+
//# sourceMappingURL=dump-tokens.d.ts.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import * as p from '@clack/prompts';
|
|
2
|
+
import envPaths from 'env-paths';
|
|
3
|
+
import fsSync from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import pc from 'picocolors';
|
|
6
|
+
import { deriveEncryptionKey, exportDecryptedStoreFiles } from '../auth/crypto-utils.js';
|
|
7
|
+
/**
|
|
8
|
+
* Handle the `@payloadcms/figma dump-tokens` command
|
|
9
|
+
*
|
|
10
|
+
* Exports decrypted versions of encrypted token store files in
|
|
11
|
+
* `oauth/` and `projects/` subdirectories (new structure) plus any legacy
|
|
12
|
+
* per-env directories from older package versions. Creates
|
|
13
|
+
* `.decrypted.json` files next to each encrypted `.json` file.
|
|
14
|
+
*
|
|
15
|
+
* Skips the `payloadcms-figma-crypto.json` salt file at the root.
|
|
16
|
+
*/ export async function dumpTokensCommand() {
|
|
17
|
+
const encryptionKey = deriveEncryptionKey();
|
|
18
|
+
const newRoot = envPaths('payloadcms-figma').config;
|
|
19
|
+
const stagingLegacyRoot = envPaths('payloadcms-figma-staging').config;
|
|
20
|
+
const candidateDirs = [
|
|
21
|
+
path.join(newRoot, 'oauth'),
|
|
22
|
+
path.join(newRoot, 'projects'),
|
|
23
|
+
stagingLegacyRoot
|
|
24
|
+
];
|
|
25
|
+
const targetDirs = Array.from(new Set(candidateDirs)).filter((dir)=>fsSync.existsSync(dir));
|
|
26
|
+
if (targetDirs.length === 0) {
|
|
27
|
+
// eslint-disable-next-line no-console
|
|
28
|
+
console.log('No token store directories found.');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
// eslint-disable-next-line no-console
|
|
32
|
+
console.log(pc.yellow('⚠ dump-tokens writes DECRYPTED token files to disk.'));
|
|
33
|
+
// eslint-disable-next-line no-console
|
|
34
|
+
console.log('Target directories:');
|
|
35
|
+
for (const dir of targetDirs){
|
|
36
|
+
// eslint-disable-next-line no-console
|
|
37
|
+
console.log(` ${dir}`);
|
|
38
|
+
}
|
|
39
|
+
// eslint-disable-next-line no-console
|
|
40
|
+
console.log('');
|
|
41
|
+
const confirmed = await p.confirm({
|
|
42
|
+
initialValue: false,
|
|
43
|
+
message: 'Continue? Remember to delete the decrypted files after use.'
|
|
44
|
+
});
|
|
45
|
+
if (p.isCancel(confirmed) || !confirmed) {
|
|
46
|
+
// eslint-disable-next-line no-console
|
|
47
|
+
console.log('Cancelled.');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const decryptedFiles = [];
|
|
51
|
+
for (const dir of targetDirs){
|
|
52
|
+
decryptedFiles.push(...exportDecryptedStoreFiles(dir, encryptionKey));
|
|
53
|
+
}
|
|
54
|
+
if (decryptedFiles.length === 0) {
|
|
55
|
+
// eslint-disable-next-line no-console
|
|
56
|
+
console.log('No encrypted files could be decrypted with the current machine key.');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
// eslint-disable-next-line no-console
|
|
60
|
+
console.log(`\nCreated ${decryptedFiles.length} decrypted file(s):`);
|
|
61
|
+
for (const file of decryptedFiles){
|
|
62
|
+
// eslint-disable-next-line no-console
|
|
63
|
+
console.log(` ${file}`);
|
|
64
|
+
}
|
|
65
|
+
// eslint-disable-next-line no-console
|
|
66
|
+
console.log('\nNote: These files contain sensitive tokens. Delete after use.');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
//# sourceMappingURL=dump-tokens.js.map
|
package/dist/commands/init.js
CHANGED
|
@@ -29,12 +29,16 @@ import { cacheAllEnvironments } from '../utils/cache-bootstrap.js';
|
|
|
29
29
|
* @param tokenStore - Token store instance
|
|
30
30
|
* @param contentSystemId - Content System ID to generate token for
|
|
31
31
|
* @param spinner - Clack spinner instance for status updates (only used in debug mode)
|
|
32
|
-
*/ async function generateProjectTokenWithFeedback(tokenStore, contentSystemId, spinner) {
|
|
32
|
+
*/ async function generateProjectTokenWithFeedback(tokenStore, contentSystemId, spinner, projectInfo) {
|
|
33
33
|
if (isDebug()) {
|
|
34
34
|
spinner.start('Generating project token...');
|
|
35
35
|
}
|
|
36
36
|
try {
|
|
37
|
-
const projectToken = await getValidProjectToken(
|
|
37
|
+
const projectToken = await getValidProjectToken({
|
|
38
|
+
projectInfo,
|
|
39
|
+
tenantId: contentSystemId,
|
|
40
|
+
tokenStore
|
|
41
|
+
});
|
|
38
42
|
if (isDebug()) {
|
|
39
43
|
if (projectToken) {
|
|
40
44
|
spinner.stop(pc.green('✓ Project token generated'));
|
|
@@ -78,7 +82,7 @@ import { cacheAllEnvironments } from '../utils/cache-bootstrap.js';
|
|
|
78
82
|
p.log.warn(pc.yellow('Skipping authentication (--skip-auth mode)'));
|
|
79
83
|
} else {
|
|
80
84
|
// Check for valid cached tokens first (no API call needed)
|
|
81
|
-
if (!tokenStore.
|
|
85
|
+
if (!tokenStore.hasValidOauthToken()) {
|
|
82
86
|
// Need to refresh or authenticate - show spinner for API call
|
|
83
87
|
s.start('Checking authentication...');
|
|
84
88
|
const credential = await tryGetCredential(tokenStore);
|
|
@@ -221,6 +225,18 @@ import { cacheAllEnvironments } from '../utils/cache-bootstrap.js';
|
|
|
221
225
|
p.note('Run this command in a directory without an existing project or in an existing Payload project.', 'Action Required');
|
|
222
226
|
process.exit(1);
|
|
223
227
|
}
|
|
228
|
+
// npm leaves node_modules in a transient state after a sequence of
|
|
229
|
+
// add/remove calls; without a final `install` the subprocess that runs
|
|
230
|
+
// generate:importmap silently misses @payloadcms/figma subpath components.
|
|
231
|
+
s.start('Reconciling dependencies...');
|
|
232
|
+
try {
|
|
233
|
+
await installDependencies(process.cwd(), packageManager);
|
|
234
|
+
s.stop(pc.green('✓ Dependencies reconciled'));
|
|
235
|
+
} catch (error) {
|
|
236
|
+
s.stop(pc.red('✗ Failed to reconcile dependencies before generate:importmap'));
|
|
237
|
+
log.error(error instanceof Error ? error.message : 'Unknown error');
|
|
238
|
+
process.exit(1);
|
|
239
|
+
}
|
|
224
240
|
// Generate import map to prevent errors on first dev run
|
|
225
241
|
const importMapResult = await runScript(process.cwd(), 'generate:importmap', packageManager);
|
|
226
242
|
if (!importMapResult) {
|
|
@@ -236,7 +252,10 @@ import { cacheAllEnvironments } from '../utils/cache-bootstrap.js';
|
|
|
236
252
|
]);
|
|
237
253
|
// Generate project token (unless skipping auth)
|
|
238
254
|
if (!options.skipAuth && resolvedEnv) {
|
|
239
|
-
await generateProjectTokenWithFeedback(tokenStore, resolvedEnv.contentSystemId, s
|
|
255
|
+
await generateProjectTokenWithFeedback(tokenStore, resolvedEnv.contentSystemId, s, {
|
|
256
|
+
environmentName: resolvedEnv.name,
|
|
257
|
+
projectId: cmsResourceId
|
|
258
|
+
});
|
|
240
259
|
}
|
|
241
260
|
// Success message for existing project
|
|
242
261
|
p.outro(pc.green('✓ Project initialized successfully!'));
|
|
@@ -335,6 +354,18 @@ import { cacheAllEnvironments } from '../utils/cache-bootstrap.js';
|
|
|
335
354
|
} catch (error) {
|
|
336
355
|
p.log.warn(pc.yellow(`⚠ Failed to update .env file: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
337
356
|
}
|
|
357
|
+
// npm leaves node_modules in a transient state after a sequence of
|
|
358
|
+
// add/remove calls; without a final `install` the subprocess that runs
|
|
359
|
+
// generate:importmap silently misses @payloadcms/figma subpath components.
|
|
360
|
+
s.start('Reconciling dependencies...');
|
|
361
|
+
try {
|
|
362
|
+
await installDependencies(fullPath, packageManager);
|
|
363
|
+
s.stop(pc.green('✓ Dependencies reconciled'));
|
|
364
|
+
} catch (error) {
|
|
365
|
+
s.stop(pc.red('✗ Failed to reconcile dependencies before generate:importmap'));
|
|
366
|
+
log.error(error instanceof Error ? error.message : 'Unknown error');
|
|
367
|
+
process.exit(1);
|
|
368
|
+
}
|
|
338
369
|
// Generate import map to prevent errors on first dev run
|
|
339
370
|
const importMapResult = await runScript(fullPath, 'generate:importmap', packageManager);
|
|
340
371
|
if (!importMapResult) {
|
|
@@ -342,7 +373,10 @@ import { cacheAllEnvironments } from '../utils/cache-bootstrap.js';
|
|
|
342
373
|
}
|
|
343
374
|
// Generate project token (unless skipping auth)
|
|
344
375
|
if (!options.skipAuth && resolvedEnv) {
|
|
345
|
-
await generateProjectTokenWithFeedback(tokenStore, resolvedEnv.contentSystemId, s
|
|
376
|
+
await generateProjectTokenWithFeedback(tokenStore, resolvedEnv.contentSystemId, s, {
|
|
377
|
+
environmentName: resolvedEnv.name,
|
|
378
|
+
projectId: cmsResourceId
|
|
379
|
+
});
|
|
346
380
|
}
|
|
347
381
|
// Initialize git repository (after all files including lock file are ready)
|
|
348
382
|
initializeGitRepo(fullPath);
|
|
@@ -26,13 +26,13 @@ const ALL_ENVIRONMENTS = [
|
|
|
26
26
|
}
|
|
27
27
|
function showTokenDetails(env) {
|
|
28
28
|
const tokenStore = getTokenStore(env);
|
|
29
|
-
const tokens = tokenStore.
|
|
29
|
+
const tokens = tokenStore.getOauthInfo();
|
|
30
30
|
if (!tokens) {
|
|
31
31
|
p.log.warn(pc.yellow(`No tokens found${formatEnvSuffix(env, ' for ')}. Not authenticated.`));
|
|
32
32
|
p.note(`Run ${pc.cyan('@payloadcms/figma login')} to authenticate`, 'Tip');
|
|
33
33
|
return;
|
|
34
34
|
}
|
|
35
|
-
const isValid = tokenStore.
|
|
35
|
+
const isValid = tokenStore.hasValidOauthToken();
|
|
36
36
|
const status = isValid ? pc.green('✓ Valid') : pc.red('✗ Expired');
|
|
37
37
|
p.log.step(pc.bold(`Token Status${formatEnvSuffix(env, ' (', ')')}`));
|
|
38
38
|
// Build OAuth token info display
|
package/dist/commands/login.js
CHANGED
|
@@ -22,7 +22,7 @@ import * as log from '../utils/log.js';
|
|
|
22
22
|
try {
|
|
23
23
|
const existingCredential = await getValidCredential(tokenStore);
|
|
24
24
|
if (existingCredential) {
|
|
25
|
-
const tokens = tokenStore.
|
|
25
|
+
const tokens = tokenStore.getOauthInfo();
|
|
26
26
|
p.log.warn(pc.yellow(`Already logged in${formatEnvSuffix(env)}`));
|
|
27
27
|
if (tokens?.userId) {
|
|
28
28
|
log.info(`User ID: ${tokens.userId}`);
|
package/dist/commands/logout.js
CHANGED
|
@@ -2,6 +2,7 @@ import * as p from '@clack/prompts';
|
|
|
2
2
|
import pc from 'picocolors';
|
|
3
3
|
import { getTokenStore } from '../auth/token-store.js';
|
|
4
4
|
import { getInfraEnvironment } from '../constants.js';
|
|
5
|
+
import { getEnvVarSync } from '../utils/env-management.js';
|
|
5
6
|
import { formatEnvSuffix } from '../utils/format-env-suffix.js';
|
|
6
7
|
import * as log from '../utils/log.js';
|
|
7
8
|
const ALL_ENVIRONMENTS = [
|
|
@@ -23,7 +24,7 @@ const ALL_ENVIRONMENTS = [
|
|
|
23
24
|
}
|
|
24
25
|
async function logoutSingleEnvironment(env) {
|
|
25
26
|
const tokenStore = getTokenStore(env);
|
|
26
|
-
const hasTokens = tokenStore.
|
|
27
|
+
const hasTokens = tokenStore.getOauthInfo() !== null;
|
|
27
28
|
if (!hasTokens) {
|
|
28
29
|
p.log.warn(pc.yellow(`No tokens found${formatEnvSuffix(env, ' for ')}. Already logged out.`));
|
|
29
30
|
return;
|
|
@@ -40,7 +41,7 @@ async function logoutSingleEnvironment(env) {
|
|
|
40
41
|
s.start('Clearing tokens...');
|
|
41
42
|
try {
|
|
42
43
|
tokenStore.clearTokens();
|
|
43
|
-
tokenStore
|
|
44
|
+
clearCurrentProjectToken(tokenStore, env);
|
|
44
45
|
s.stop(pc.green(`✓ Logged out${formatEnvSuffix(env, ' from ')}`));
|
|
45
46
|
} catch (error) {
|
|
46
47
|
s.stop(pc.red('✗ Failed to clear tokens'));
|
|
@@ -52,7 +53,7 @@ async function logoutAllEnvironments() {
|
|
|
52
53
|
// Check which environments have tokens
|
|
53
54
|
const envsWithTokens = ALL_ENVIRONMENTS.filter((env)=>{
|
|
54
55
|
const store = getTokenStore(env);
|
|
55
|
-
return store.
|
|
56
|
+
return store.getOauthInfo() !== null;
|
|
56
57
|
});
|
|
57
58
|
if (envsWithTokens.length === 0) {
|
|
58
59
|
p.log.warn(pc.yellow('No tokens found in any environment. Already logged out.'));
|
|
@@ -73,7 +74,7 @@ async function logoutAllEnvironments() {
|
|
|
73
74
|
for (const env of envsWithTokens){
|
|
74
75
|
const store = getTokenStore(env);
|
|
75
76
|
store.clearTokens();
|
|
76
|
-
store
|
|
77
|
+
clearCurrentProjectToken(store, env);
|
|
77
78
|
}
|
|
78
79
|
s.stop(pc.green('✓ Logged out from all environments'));
|
|
79
80
|
} catch (error) {
|
|
@@ -82,5 +83,23 @@ async function logoutAllEnvironments() {
|
|
|
82
83
|
process.exit(1);
|
|
83
84
|
}
|
|
84
85
|
}
|
|
86
|
+
function clearCurrentProjectToken(store, env) {
|
|
87
|
+
const projectId = getEnvVarSync(process.cwd(), 'FIGMA_PROJECT_ID');
|
|
88
|
+
if (!projectId) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const bootstrapData = store.getBootstrapData(projectId, env);
|
|
92
|
+
if (!bootstrapData?.contentSystemId) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const projectInfo = {
|
|
96
|
+
environmentName: env,
|
|
97
|
+
projectId
|
|
98
|
+
};
|
|
99
|
+
// Clear scoped token for this tenant.
|
|
100
|
+
store.clearProjectToken({
|
|
101
|
+
projectInfo
|
|
102
|
+
});
|
|
103
|
+
}
|
|
85
104
|
|
|
86
105
|
//# sourceMappingURL=logout.js.map
|
|
@@ -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
|
|
@@ -25,7 +25,10 @@ export function createAuthMiddleware(opts) {
|
|
|
25
25
|
if (opts.auth.mode === 'apiKey') {
|
|
26
26
|
request.headers.set('X-Api-Key', opts.auth.apiKey);
|
|
27
27
|
} else if (opts.auth.mode === 'tokenStore') {
|
|
28
|
-
const token = await getValidProjectToken(
|
|
28
|
+
const token = await getValidProjectToken({
|
|
29
|
+
tenantId: opts.contentSystemId,
|
|
30
|
+
tokenStore: opts.auth.tokenStore
|
|
31
|
+
});
|
|
29
32
|
if (!token) {
|
|
30
33
|
throw new Error('Authentication required. Run `npx @payloadcms/figma login` to authenticate.');
|
|
31
34
|
}
|
package/dist/oauth/defaults.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { CollectionConfig, EmailField, TextField } from 'payload';
|
|
2
2
|
import type { VerifyFunction } from './types.js';
|
|
3
|
+
export declare const DEFAULT_USER_INFO_COOKIE_NAME = "figma-user-info";
|
|
3
4
|
export declare const defaultScope: string[];
|
|
4
5
|
export declare const defaultUsernameField: TextField;
|
|
5
|
-
export declare const defaultVerify: ({ collection, strategyName, usernameField, }: {
|
|
6
|
+
export declare const defaultVerify: ({ collection, strategyName, userInfoCookieName, usernameField, }: {
|
|
6
7
|
collection: CollectionConfig;
|
|
7
8
|
strategyName: string;
|
|
9
|
+
userInfoCookieName?: string;
|
|
8
10
|
usernameField: EmailField | TextField;
|
|
9
11
|
}) => VerifyFunction;
|
|
10
12
|
//# sourceMappingURL=defaults.d.ts.map
|