@payloadcms/figma 0.1.0-alpha.3 → 0.1.0-alpha.5

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.
Files changed (116) hide show
  1. package/dist/api/check-project-subdomain-availability.d.ts +12 -0
  2. package/dist/api/check-project-subdomain-availability.js +32 -0
  3. package/dist/api/control-plane-error.d.ts +6 -0
  4. package/dist/api/control-plane-error.js +8 -0
  5. package/dist/api/control-plane-fetch.d.ts +7 -0
  6. package/dist/api/control-plane-fetch.js +37 -0
  7. package/dist/api/control-plane.d.ts +0 -8
  8. package/dist/api/control-plane.js +1 -47
  9. package/dist/api/create-project.d.ts +18 -0
  10. package/dist/api/create-project.js +37 -0
  11. package/dist/api/list-project-workspaces.d.ts +8 -0
  12. package/dist/api/list-project-workspaces.js +28 -0
  13. package/dist/api/project-resource-type.d.ts +1 -0
  14. package/dist/api/project-resource-type.js +1 -0
  15. package/dist/auth/headless-oauth-flow.d.ts +23 -0
  16. package/dist/auth/headless-oauth-flow.js +69 -0
  17. package/dist/auth/oauth-flow.d.ts +2 -0
  18. package/dist/auth/oauth-flow.js +13 -8
  19. package/dist/auth/token-store.d.ts +4 -1
  20. package/dist/auth/token-store.js +14 -0
  21. package/dist/auth/types.d.ts +12 -0
  22. package/dist/cli.d.ts +10 -1
  23. package/dist/cli.js +39 -4
  24. package/dist/commands/bootstrap.js +2 -1
  25. package/dist/commands/deploy.d.ts +10 -0
  26. package/dist/commands/deploy.js +39 -11
  27. package/dist/commands/detect-project-resource-type.d.ts +3 -0
  28. package/dist/commands/detect-project-resource-type.js +28 -0
  29. package/dist/commands/init.d.ts +2 -2
  30. package/dist/commands/init.js +163 -78
  31. package/dist/commands/login.d.ts +11 -1
  32. package/dist/commands/login.js +66 -4
  33. package/dist/commands/prompt-to-create-project/format-project-subdomain.d.ts +1 -0
  34. package/dist/commands/prompt-to-create-project/format-project-subdomain.js +3 -0
  35. package/dist/commands/prompt-to-create-project/format-unavailable-subdomain-reason.d.ts +1 -0
  36. package/dist/commands/prompt-to-create-project/format-unavailable-subdomain-reason.js +7 -0
  37. package/dist/commands/prompt-to-create-project/get-default-project-name.d.ts +1 -0
  38. package/dist/commands/prompt-to-create-project/get-default-project-name.js +13 -0
  39. package/dist/commands/prompt-to-create-project/prompt-for-available-subdomain.d.ts +7 -0
  40. package/dist/commands/prompt-to-create-project/prompt-for-available-subdomain.js +32 -0
  41. package/dist/commands/prompt-to-create-project/resolve-project-region.d.ts +8 -0
  42. package/dist/commands/prompt-to-create-project/resolve-project-region.js +27 -0
  43. package/dist/commands/prompt-to-create-project/resolve-project-subdomain.d.ts +9 -0
  44. package/dist/commands/prompt-to-create-project/resolve-project-subdomain.js +54 -0
  45. package/dist/commands/prompt-to-create-project/resolve-text-input.d.ts +9 -0
  46. package/dist/commands/prompt-to-create-project/resolve-text-input.js +23 -0
  47. package/dist/commands/prompt-to-create-project/resolve-workspace-plan-key.d.ts +12 -0
  48. package/dist/commands/prompt-to-create-project/resolve-workspace-plan-key.js +36 -0
  49. package/dist/commands/prompt-to-create-project/validate-subdomain.d.ts +1 -0
  50. package/dist/commands/prompt-to-create-project/validate-subdomain.js +9 -0
  51. package/dist/commands/prompt-to-create-project.d.ts +24 -0
  52. package/dist/commands/prompt-to-create-project.js +69 -0
  53. package/dist/commands/setup-agent.d.ts +12 -0
  54. package/dist/commands/setup-agent.js +123 -0
  55. package/dist/commands/write-project-configuration.d.ts +12 -0
  56. package/dist/commands/write-project-configuration.js +16 -0
  57. package/dist/db-content-api/index.js +52 -33
  58. package/dist/db-content-api/utilities/data/injectGlobalType.d.ts +6 -0
  59. package/dist/db-content-api/utilities/data/injectGlobalType.js +11 -0
  60. package/dist/db-content-api/utilities/isRecord.d.ts +9 -0
  61. package/dist/db-content-api/utilities/isRecord.js +19 -0
  62. package/dist/db-content-api/utilities/joins.js +5 -1
  63. package/dist/db-content-api/utilities/meta/resolvesToRelationshipValue.d.ts +10 -0
  64. package/dist/db-content-api/utilities/meta/resolvesToRelationshipValue.js +95 -0
  65. package/dist/db-content-api/utilities/meta/stringifyIdsInWhere.d.ts +9 -0
  66. package/dist/db-content-api/utilities/meta/stringifyIdsInWhere.js +112 -0
  67. package/dist/db-content-api/utilities/where.d.ts +2 -6
  68. package/dist/db-content-api/utilities/where.js +20 -9
  69. package/dist/exports/views.d.ts +1 -0
  70. package/dist/exports/views.js +1 -0
  71. package/dist/imageSizeOptions.d.ts +23 -0
  72. package/dist/imageSizeOptions.js +26 -0
  73. package/dist/index.d.ts +1 -0
  74. package/dist/lib/write-agent-config.d.ts +12 -0
  75. package/dist/lib/write-agent-config.js +52 -0
  76. package/dist/oauth/defaults.d.ts +1 -0
  77. package/dist/oauth/defaults.js +1 -0
  78. package/dist/oauth/endpoints/getLoginEndpoint.js +7 -2
  79. package/dist/oauth/index.js +24 -9
  80. package/dist/oauth/utilities/getAdminCollectionSlug.d.ts +1 -1
  81. package/dist/oauth/utilities/getAuthorizeURL.d.ts +3 -0
  82. package/dist/oauth/utilities/getAuthorizeURL.js +8 -2
  83. package/dist/plugin/build-config.js +22 -9
  84. package/dist/plugin/cloud-limits/CloudLimitModal/index.d.ts +2 -3
  85. package/dist/plugin/cloud-limits/CloudLimitModal/index.js +4 -16
  86. package/dist/plugin/cloud-limits/CloudLimitModal/index.scss +5 -0
  87. package/dist/plugin/cloud-limits/CloudLimitProvider/index.d.ts +0 -3
  88. package/dist/plugin/cloud-limits/CloudLimitProvider/index.js +65 -124
  89. package/dist/plugin/cloud-limits/LimitReachedView/index.css +14 -0
  90. package/dist/plugin/cloud-limits/LimitReachedView/index.d.ts +4 -0
  91. package/dist/plugin/cloud-limits/LimitReachedView/index.js +52 -0
  92. package/dist/plugin/cloud-limits/SlugRegistryContext/index.d.ts +7 -2
  93. package/dist/plugin/cloud-limits/SlugTracker/index.d.ts +0 -8
  94. package/dist/plugin/cloud-limits/SlugTracker/index.js +14 -10
  95. package/dist/plugin/cloud-limits/routes.d.ts +2 -0
  96. package/dist/plugin/cloud-limits/routes.js +4 -0
  97. package/dist/plugin/cloud-limits/server/redirectInitialAutosaveCreate.d.ts +9 -0
  98. package/dist/plugin/cloud-limits/server/redirectInitialAutosaveCreate.js +28 -0
  99. package/dist/plugin/cloud-limits/shared.d.ts +1 -1
  100. package/dist/plugin/cloud-limits/shared.js +25 -0
  101. package/dist/plugin/cloud-limits/utilities/cloudLimitInterceptor.d.ts +2 -0
  102. package/dist/plugin/cloud-limits/utilities/cloudLimitInterceptor.js +134 -0
  103. package/dist/plugin/cloud-limits/utilities/toasts.d.ts +4 -0
  104. package/dist/plugin/cloud-limits/utilities/toasts.js +19 -0
  105. package/dist/skills/deploy-to-figma/SKILL.md +234 -0
  106. package/dist/skills/deploy-to-figma/references/cli-reference.md +146 -0
  107. package/dist/types.d.ts +9 -0
  108. package/dist/utils/lambda-config.js +4 -14
  109. package/dist/utils/messages.js +22 -3
  110. package/dist/utils/payload-generate.d.ts +10 -0
  111. package/dist/utils/payload-generate.js +59 -0
  112. package/dist/utils/pnpm-builds.d.ts +14 -0
  113. package/dist/utils/pnpm-builds.js +125 -0
  114. package/package.json +9 -1
  115. package/dist/lib/download-skill.d.ts +0 -12
  116. package/dist/lib/download-skill.js +0 -77
@@ -0,0 +1,13 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+ export async function getDefaultProjectName(projectPath) {
4
+ try {
5
+ const packageJson = JSON.parse(await fs.readFile(path.join(projectPath, 'package.json'), 'utf-8'));
6
+ if (typeof packageJson.name === 'string' && packageJson.name.trim()) {
7
+ return packageJson.name.replace(/^@[^/]+\//, '');
8
+ }
9
+ } catch {
10
+ // Fall back to directory name.
11
+ }
12
+ return path.basename(path.resolve(projectPath)) || 'figma-project';
13
+ }
@@ -0,0 +1,7 @@
1
+ import type { AuthCredential } from '../../auth/credentials.js';
2
+ interface PromptForAvailableSubdomainArgs {
3
+ credential: AuthCredential;
4
+ initialValue: string;
5
+ }
6
+ export declare function promptForAvailableSubdomain({ credential, initialValue, }: PromptForAvailableSubdomainArgs): Promise<string>;
7
+ export {};
@@ -0,0 +1,32 @@
1
+ import * as p from '@clack/prompts';
2
+ import pc from 'picocolors';
3
+ import { checkProjectSubdomainAvailability } from '../../api/check-project-subdomain-availability.js';
4
+ import { formatUnavailableSubdomainReason } from './format-unavailable-subdomain-reason.js';
5
+ import { validateSubdomain } from './validate-subdomain.js';
6
+ export async function promptForAvailableSubdomain({ credential, initialValue }) {
7
+ let nextInitialValue = initialValue;
8
+ while(true){
9
+ const promptedSubdomain = await p.text({
10
+ initialValue: nextInitialValue,
11
+ message: 'Project subdomain:',
12
+ validate: validateSubdomain
13
+ });
14
+ const subdomain = unwrapPrompt(promptedSubdomain).trim();
15
+ const availability = await checkProjectSubdomainAvailability({
16
+ credential,
17
+ subdomain
18
+ });
19
+ if (availability.available) {
20
+ return subdomain;
21
+ }
22
+ p.log.error(pc.red(formatUnavailableSubdomainReason(availability.reason)));
23
+ nextInitialValue = subdomain;
24
+ }
25
+ }
26
+ function unwrapPrompt(value) {
27
+ if (p.isCancel(value)) {
28
+ p.cancel('Operation cancelled');
29
+ process.exit(0);
30
+ }
31
+ return value;
32
+ }
@@ -0,0 +1,8 @@
1
+ import type { ProjectResourceType } from '../../api/project-resource-type.js';
2
+ interface ResolveProjectRegionArgs {
3
+ nonInteractive?: boolean;
4
+ resourceType: ProjectResourceType;
5
+ value?: string;
6
+ }
7
+ export declare function resolveProjectRegion({ nonInteractive, resourceType, value, }: ResolveProjectRegionArgs): Promise<string>;
8
+ export {};
@@ -0,0 +1,27 @@
1
+ import * as p from '@clack/prompts';
2
+ export async function resolveProjectRegion({ nonInteractive, resourceType, value }) {
3
+ if (value !== undefined) {
4
+ const region = value.trim();
5
+ if (!region) {
6
+ throw new Error('Region is required');
7
+ }
8
+ return region;
9
+ }
10
+ if (resourceType === 'vite' || nonInteractive) {
11
+ return 'us-east-1';
12
+ }
13
+ const selectedRegion = await p.select({
14
+ message: 'Select a region:',
15
+ options: [
16
+ {
17
+ label: 'US East (us-east-1)',
18
+ value: 'us-east-1'
19
+ }
20
+ ]
21
+ });
22
+ if (p.isCancel(selectedRegion)) {
23
+ p.cancel('Operation cancelled');
24
+ process.exit(0);
25
+ }
26
+ return selectedRegion;
27
+ }
@@ -0,0 +1,9 @@
1
+ import type { AuthCredential } from '../../auth/credentials.js';
2
+ interface ResolveProjectSubdomainArgs {
3
+ credential: AuthCredential;
4
+ initialValue: string;
5
+ nonInteractive?: boolean;
6
+ value?: string;
7
+ }
8
+ export declare function resolveProjectSubdomain({ credential, initialValue, nonInteractive, value, }: ResolveProjectSubdomainArgs): Promise<string>;
9
+ export {};
@@ -0,0 +1,54 @@
1
+ import * as p from '@clack/prompts';
2
+ import pc from 'picocolors';
3
+ import { checkProjectSubdomainAvailability } from '../../api/check-project-subdomain-availability.js';
4
+ import { formatUnavailableSubdomainReason } from './format-unavailable-subdomain-reason.js';
5
+ import { promptForAvailableSubdomain } from './prompt-for-available-subdomain.js';
6
+ import { validateSubdomain } from './validate-subdomain.js';
7
+ const GENERATED_SUBDOMAIN_ATTEMPT_COUNT = 5;
8
+ export async function resolveProjectSubdomain({ credential, initialValue, nonInteractive, value }) {
9
+ const providedSubdomain = value === undefined ? undefined : validateProvidedSubdomain(value);
10
+ if (!nonInteractive) {
11
+ if (providedSubdomain) {
12
+ const availability = await checkProjectSubdomainAvailability({
13
+ credential,
14
+ subdomain: providedSubdomain
15
+ });
16
+ if (availability.available) {
17
+ return providedSubdomain;
18
+ }
19
+ p.log.error(pc.red(formatUnavailableSubdomainReason(availability.reason)));
20
+ }
21
+ return promptForAvailableSubdomain({
22
+ credential,
23
+ initialValue: providedSubdomain ?? initialValue
24
+ });
25
+ }
26
+ const baseSubdomain = providedSubdomain ?? initialValue;
27
+ for(let attempt = 0; attempt < GENERATED_SUBDOMAIN_ATTEMPT_COUNT; attempt++){
28
+ const subdomain = attempt === 0 ? baseSubdomain : addRandomSuffix(baseSubdomain);
29
+ const availability = await checkProjectSubdomainAvailability({
30
+ credential,
31
+ subdomain
32
+ });
33
+ if (availability.available) {
34
+ return subdomain;
35
+ }
36
+ if (providedSubdomain) {
37
+ throw new Error(`${formatUnavailableSubdomainReason(availability.reason)} Pass a different --subdomain.`);
38
+ }
39
+ }
40
+ throw new Error(`Could not generate an available subdomain after ${GENERATED_SUBDOMAIN_ATTEMPT_COUNT} attempts. Pass --subdomain.`);
41
+ }
42
+ function validateProvidedSubdomain(value) {
43
+ const validationError = validateSubdomain(value);
44
+ if (validationError) {
45
+ throw new Error(validationError);
46
+ }
47
+ return value.trim();
48
+ }
49
+ function addRandomSuffix(value) {
50
+ // Append a hyphen and six random lowercase alphanumeric characters, e.g. `-a1b2c3`.
51
+ const suffix = Math.random().toString(36).slice(2, 8).padEnd(6, '0');
52
+ const prefix = value.slice(0, 53).replace(/-+$/g, '');
53
+ return `${prefix}-${suffix}`;
54
+ }
@@ -0,0 +1,9 @@
1
+ interface ResolveTextInputArgs {
2
+ fieldName: string;
3
+ initialValue: string;
4
+ message: string;
5
+ nonInteractive?: boolean;
6
+ value?: string;
7
+ }
8
+ export declare function resolveTextInput({ fieldName, initialValue, message, nonInteractive, value, }: ResolveTextInputArgs): Promise<string>;
9
+ export {};
@@ -0,0 +1,23 @@
1
+ import * as p from '@clack/prompts';
2
+ export async function resolveTextInput({ fieldName, initialValue, message, nonInteractive, value }) {
3
+ if (value !== undefined) {
4
+ const trimmedValue = value.trim();
5
+ if (!trimmedValue) {
6
+ throw new Error(`${fieldName} is required`);
7
+ }
8
+ return trimmedValue;
9
+ }
10
+ if (nonInteractive) {
11
+ return initialValue;
12
+ }
13
+ const promptedValue = await p.text({
14
+ initialValue,
15
+ message,
16
+ validate: (candidate)=>candidate?.trim() ? undefined : `${fieldName} is required`
17
+ });
18
+ if (p.isCancel(promptedValue)) {
19
+ p.cancel('Operation cancelled');
20
+ process.exit(0);
21
+ }
22
+ return promptedValue.trim();
23
+ }
@@ -0,0 +1,12 @@
1
+ interface ProjectWorkspace {
2
+ isDefault: boolean;
3
+ name: string;
4
+ planKey: string;
5
+ }
6
+ interface ResolveWorkspacePlanKeyArgs {
7
+ nonInteractive?: boolean;
8
+ value?: string;
9
+ workspaces: ProjectWorkspace[];
10
+ }
11
+ export declare function resolveWorkspacePlanKey({ nonInteractive, value, workspaces, }: ResolveWorkspacePlanKeyArgs): Promise<string>;
12
+ export {};
@@ -0,0 +1,36 @@
1
+ import * as p from '@clack/prompts';
2
+ export async function resolveWorkspacePlanKey({ nonInteractive, value, workspaces }) {
3
+ if (value !== undefined) {
4
+ const planKey = value.trim();
5
+ const workspace = workspaces.find((candidate)=>candidate.planKey === planKey);
6
+ if (!workspace) {
7
+ throw new Error(`Workspace plan key "${planKey}" is not eligible. Available: ${formatWorkspaces(workspaces)}`);
8
+ }
9
+ return workspace.planKey;
10
+ }
11
+ if (workspaces.length === 1) {
12
+ return workspaces[0].planKey;
13
+ }
14
+ if (nonInteractive) {
15
+ const defaultWorkspace = workspaces.find((workspace)=>workspace.isDefault);
16
+ if (defaultWorkspace) {
17
+ return defaultWorkspace.planKey;
18
+ }
19
+ throw new Error(`Multiple eligible Figma workspaces. Pass --workspace <plan-key>. Available: ${formatWorkspaces(workspaces)}`);
20
+ }
21
+ const selectedWorkspacePlanKey = await p.select({
22
+ message: 'Select a Figma workspace:',
23
+ options: workspaces.map((workspace)=>({
24
+ label: workspace.name,
25
+ value: workspace.planKey
26
+ }))
27
+ });
28
+ if (p.isCancel(selectedWorkspacePlanKey)) {
29
+ p.cancel('Operation cancelled');
30
+ process.exit(0);
31
+ }
32
+ return selectedWorkspacePlanKey;
33
+ }
34
+ function formatWorkspaces(workspaces) {
35
+ return workspaces.map((workspace)=>`${workspace.name} (${workspace.planKey})`).join(', ');
36
+ }
@@ -0,0 +1 @@
1
+ export declare function validateSubdomain(value: string | undefined): "Subdomain is required" | "Use 1–60 lowercase letters, numbers, or hyphens" | undefined;
@@ -0,0 +1,9 @@
1
+ export function validateSubdomain(value) {
2
+ if (!value?.trim()) {
3
+ return 'Subdomain is required';
4
+ }
5
+ if (!/^[a-z0-9](?:[a-z0-9-]{0,58}[a-z0-9])?$/.test(value.trim())) {
6
+ return 'Use 1–60 lowercase letters, numbers, or hyphens';
7
+ }
8
+ return undefined;
9
+ }
@@ -0,0 +1,24 @@
1
+ import type { ProjectResourceType } from '../api/project-resource-type.js';
2
+ import type { AuthCredential } from '../auth/credentials.js';
3
+ interface ProjectCreationInputs {
4
+ description?: string;
5
+ name?: string;
6
+ region?: string;
7
+ subdomain?: string;
8
+ workspacePlanKey?: string;
9
+ }
10
+ interface PromptToCreateProjectArgs {
11
+ credential: AuthCredential;
12
+ inputs?: ProjectCreationInputs;
13
+ nonInteractive?: boolean;
14
+ projectPath: string;
15
+ resourceType: ProjectResourceType;
16
+ }
17
+ interface PromptToCreateProjectResponse {
18
+ domainName: string;
19
+ id: string;
20
+ name: string;
21
+ }
22
+ /** Prompt for required project fields and create a blank Figma CMS project. */
23
+ export declare function promptToCreateProject({ credential, inputs, nonInteractive, projectPath, resourceType, }: PromptToCreateProjectArgs): Promise<PromptToCreateProjectResponse>;
24
+ export {};
@@ -0,0 +1,69 @@
1
+ import * as p from '@clack/prompts';
2
+ import pc from 'picocolors';
3
+ import { createProject } from '../api/create-project.js';
4
+ import { listProjectWorkspaces } from '../api/list-project-workspaces.js';
5
+ import { formatProjectSubdomain } from './prompt-to-create-project/format-project-subdomain.js';
6
+ import { getDefaultProjectName } from './prompt-to-create-project/get-default-project-name.js';
7
+ import { resolveProjectRegion } from './prompt-to-create-project/resolve-project-region.js';
8
+ import { resolveProjectSubdomain } from './prompt-to-create-project/resolve-project-subdomain.js';
9
+ import { resolveTextInput } from './prompt-to-create-project/resolve-text-input.js';
10
+ import { resolveWorkspacePlanKey } from './prompt-to-create-project/resolve-workspace-plan-key.js';
11
+ /** Prompt for required project fields and create a blank Figma CMS project. */ export async function promptToCreateProject({ credential, inputs, nonInteractive, projectPath, resourceType }) {
12
+ const workspaces = await listProjectWorkspaces(credential);
13
+ if (workspaces.length === 0) {
14
+ if (nonInteractive) {
15
+ throw new Error('No eligible Figma workspaces found.');
16
+ }
17
+ p.log.error(pc.red('No eligible Figma workspaces found.'));
18
+ process.exit(1);
19
+ }
20
+ const defaultProjectName = await getDefaultProjectName(projectPath);
21
+ const projectName = await resolveTextInput({
22
+ fieldName: 'Project name',
23
+ initialValue: defaultProjectName,
24
+ message: 'Project name:',
25
+ nonInteractive,
26
+ value: inputs?.name
27
+ });
28
+ const projectDescription = await resolveTextInput({
29
+ fieldName: 'Description',
30
+ initialValue: `${projectName} project`,
31
+ message: 'Project description:',
32
+ nonInteractive,
33
+ value: inputs?.description
34
+ });
35
+ const projectSubdomain = await resolveProjectSubdomain({
36
+ credential,
37
+ initialValue: formatProjectSubdomain(projectName),
38
+ nonInteractive,
39
+ value: inputs?.subdomain
40
+ });
41
+ const parentPlanKey = await resolveWorkspacePlanKey({
42
+ nonInteractive,
43
+ value: inputs?.workspacePlanKey,
44
+ workspaces
45
+ });
46
+ const region = await resolveProjectRegion({
47
+ nonInteractive,
48
+ resourceType,
49
+ value: inputs?.region
50
+ });
51
+ const spinner = p.spinner();
52
+ spinner.start('Creating Figma project...');
53
+ try {
54
+ const project = await createProject({
55
+ name: projectName,
56
+ credential,
57
+ description: projectDescription,
58
+ parentPlanKey,
59
+ region,
60
+ resourceType,
61
+ subdomain: projectSubdomain
62
+ });
63
+ spinner.stop(pc.green(`✓ Project created: ${project.domainName}`));
64
+ return project;
65
+ } catch (error) {
66
+ spinner.stop(pc.red('✗ Failed to create project'));
67
+ throw error;
68
+ }
69
+ }
@@ -0,0 +1,12 @@
1
+ export interface SetupAgentOptions {
2
+ global?: boolean;
3
+ project?: boolean;
4
+ yes?: boolean;
5
+ }
6
+ /** Install or update the bundled deploy skill through the standard skills CLI. */
7
+ export declare function setupAgentCommand(options: SetupAgentOptions, dependencies?: {
8
+ homeDir?: string;
9
+ isTTY?: boolean;
10
+ projectDir?: string;
11
+ skillDir?: string;
12
+ }): Promise<void>;
@@ -0,0 +1,123 @@
1
+ import * as p from '@clack/prompts';
2
+ import spawn from 'cross-spawn';
3
+ import crypto from 'node:crypto';
4
+ import fs from 'node:fs/promises';
5
+ import { createRequire } from 'node:module';
6
+ import os from 'node:os';
7
+ import path from 'node:path';
8
+ const SKILL_NAME = 'deploy-to-figma';
9
+ const require = createRequire(import.meta.url);
10
+ const SKILLS_CLI_PATH = require.resolve('skills/bin/cli.mjs');
11
+ /** Install or update the bundled deploy skill through the standard skills CLI. */ export async function setupAgentCommand(options, dependencies = {}) {
12
+ if (options.global && options.project) {
13
+ throw new Error('Use either --global or --project, not both.');
14
+ }
15
+ const isTTY = dependencies.isTTY ?? Boolean(process.stdin.isTTY && process.stdout.isTTY);
16
+ const scope = await resolveScope(options, isTTY);
17
+ const projectDir = dependencies.projectDir ?? process.cwd();
18
+ const homeDir = dependencies.homeDir ?? os.homedir();
19
+ const skillDir = dependencies.skillDir ?? resolveBundledSkillDir();
20
+ const installedDir = path.join(scope === 'global' ? homeDir : projectDir, '.agents', 'skills', SKILL_NAME);
21
+ const [bundledHash, installedHash] = await Promise.all([
22
+ hashDirectory(skillDir),
23
+ hashDirectory(installedDir)
24
+ ]);
25
+ if (!bundledHash) {
26
+ throw new Error(`Bundled ${SKILL_NAME} skill was not found at ${skillDir}.`);
27
+ }
28
+ if (bundledHash === installedHash) {
29
+ p.log.success(`✓ ${SKILL_NAME} skill is already up to date (${scope})`);
30
+ return;
31
+ }
32
+ p.log.step(`${installedHash ? 'Updating' : 'Installing'} ${SKILL_NAME} skill (${scope})...`);
33
+ const args = [
34
+ 'add',
35
+ skillDir,
36
+ '--skill',
37
+ SKILL_NAME,
38
+ '--copy',
39
+ ...scope === 'global' ? [
40
+ '--global'
41
+ ] : [],
42
+ ...options.yes || !isTTY ? [
43
+ '--yes'
44
+ ] : []
45
+ ];
46
+ const exitCode = await run(process.execPath, [
47
+ SKILLS_CLI_PATH,
48
+ ...args
49
+ ], projectDir);
50
+ if (exitCode !== 0) {
51
+ throw new Error(`Skill installation failed with exit code ${exitCode}.`);
52
+ }
53
+ p.log.success(`✓ ${SKILL_NAME} skill ${installedHash ? 'updated' : 'installed'} (${scope})`);
54
+ }
55
+ async function resolveScope(options, isTTY) {
56
+ if (options.global) {
57
+ return 'global';
58
+ }
59
+ if (options.project) {
60
+ return 'project';
61
+ }
62
+ if (!isTTY) {
63
+ throw new Error('Non-interactive setup requires either --global or --project.');
64
+ }
65
+ const selected = await p.select({
66
+ initialValue: 'global',
67
+ message: 'Where should the Figma deploy skill be installed?',
68
+ options: [
69
+ {
70
+ label: 'Globally (recommended)',
71
+ value: 'global'
72
+ },
73
+ {
74
+ label: 'Current project only',
75
+ value: 'project'
76
+ }
77
+ ]
78
+ });
79
+ if (p.isCancel(selected)) {
80
+ throw new Error('Skill installation cancelled.');
81
+ }
82
+ return selected;
83
+ }
84
+ function resolveBundledSkillDir() {
85
+ return path.resolve(import.meta.dirname, '..', 'skills', SKILL_NAME);
86
+ }
87
+ async function hashDirectory(directory) {
88
+ if (!await pathExists(directory)) {
89
+ return null;
90
+ }
91
+ const hash = crypto.createHash('sha256');
92
+ async function visit(currentDir, relativeDir = '') {
93
+ const entries = await fs.readdir(currentDir, {
94
+ withFileTypes: true
95
+ });
96
+ entries.sort((left, right)=>left.name.localeCompare(right.name));
97
+ for (const entry of entries){
98
+ const relativePath = path.join(relativeDir, entry.name);
99
+ const absolutePath = path.join(currentDir, entry.name);
100
+ if (entry.isDirectory()) {
101
+ await visit(absolutePath, relativePath);
102
+ } else if (entry.isFile()) {
103
+ hash.update(relativePath);
104
+ hash.update(await fs.readFile(absolutePath));
105
+ }
106
+ }
107
+ }
108
+ await visit(directory);
109
+ return hash.digest('hex');
110
+ }
111
+ function run(command, args, cwd) {
112
+ return new Promise((resolve, reject)=>{
113
+ const child = spawn(command, args, {
114
+ cwd,
115
+ stdio: 'inherit'
116
+ });
117
+ child.once('error', reject);
118
+ child.once('close', (code)=>resolve(code ?? 1));
119
+ });
120
+ }
121
+ async function pathExists(filePath) {
122
+ return fs.access(filePath).then(()=>true).catch(()=>false);
123
+ }
@@ -0,0 +1,12 @@
1
+ import type { BootstrapInfo } from '../api/control-plane.js';
2
+ import type { TokenStore } from '../auth/token-store.js';
3
+ interface WriteProjectConfigurationArgs {
4
+ bootstrapInfo: BootstrapInfo | null;
5
+ environmentName: string;
6
+ projectDir: string;
7
+ projectId: string;
8
+ shouldWriteEnvironmentVariables?: boolean;
9
+ tokenStore: TokenStore;
10
+ }
11
+ export declare function writeProjectConfiguration({ bootstrapInfo, environmentName, projectDir, projectId, shouldWriteEnvironmentVariables, tokenStore, }: WriteProjectConfigurationArgs): Promise<void>;
12
+ export {};
@@ -0,0 +1,16 @@
1
+ import { getInfraEnvironment } from '../constants.js';
2
+ import { cacheAllEnvironments } from '../utils/cache-bootstrap.js';
3
+ import { addOrUpdateEnvVar } from '../utils/env-management.js';
4
+ export async function writeProjectConfiguration({ bootstrapInfo, environmentName, projectDir, projectId, shouldWriteEnvironmentVariables = true, tokenStore }) {
5
+ if (shouldWriteEnvironmentVariables) {
6
+ await addOrUpdateEnvVar(projectDir, 'FIGMA_PROJECT_ID', projectId);
7
+ await addOrUpdateEnvVar(projectDir, 'FIGMA_ENVIRONMENT_NAME', environmentName);
8
+ const infraEnvironment = getInfraEnvironment();
9
+ if (infraEnvironment !== 'production') {
10
+ await addOrUpdateEnvVar(projectDir, 'FIGMA_INFRA_ENV', infraEnvironment);
11
+ }
12
+ }
13
+ if (bootstrapInfo) {
14
+ cacheAllEnvironments(tokenStore, projectId, bootstrapInfo);
15
+ }
16
+ }