@sun-asterisk/sungen 2.5.0 → 2.5.2
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/README.md +88 -7
- package/dist/cli/commands/add.d.ts.map +1 -1
- package/dist/cli/commands/add.js +109 -9
- package/dist/cli/commands/add.js.map +1 -1
- package/dist/cli/commands/figma.d.ts +11 -0
- package/dist/cli/commands/figma.d.ts.map +1 -0
- package/dist/cli/commands/figma.js +178 -0
- package/dist/cli/commands/figma.js.map +1 -0
- package/dist/cli/index.js +4 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/orchestrator/ai-rules-updater.d.ts.map +1 -1
- package/dist/orchestrator/ai-rules-updater.js +2 -0
- package/dist/orchestrator/ai-rules-updater.js.map +1 -1
- package/dist/orchestrator/figma/figma-scaffolder-helpers.d.ts +33 -0
- package/dist/orchestrator/figma/figma-scaffolder-helpers.d.ts.map +1 -0
- package/dist/orchestrator/figma/figma-scaffolder-helpers.js +135 -0
- package/dist/orchestrator/figma/figma-scaffolder-helpers.js.map +1 -0
- package/dist/orchestrator/figma/figma-scaffolder-types.d.ts +25 -0
- package/dist/orchestrator/figma/figma-scaffolder-types.d.ts.map +1 -0
- package/dist/orchestrator/figma/figma-scaffolder-types.js +7 -0
- package/dist/orchestrator/figma/figma-scaffolder-types.js.map +1 -0
- package/dist/orchestrator/figma/figma-scaffolder.d.ts +23 -0
- package/dist/orchestrator/figma/figma-scaffolder.d.ts.map +1 -0
- package/dist/orchestrator/figma/figma-scaffolder.js +212 -0
- package/dist/orchestrator/figma/figma-scaffolder.js.map +1 -0
- package/dist/orchestrator/figma/node-path-collapser.d.ts +16 -0
- package/dist/orchestrator/figma/node-path-collapser.d.ts.map +1 -0
- package/dist/orchestrator/figma/node-path-collapser.js +37 -0
- package/dist/orchestrator/figma/node-path-collapser.js.map +1 -0
- package/dist/orchestrator/figma/spec-figma-renderer.d.ts +44 -0
- package/dist/orchestrator/figma/spec-figma-renderer.d.ts.map +1 -0
- package/dist/orchestrator/figma/spec-figma-renderer.js +45 -0
- package/dist/orchestrator/figma/spec-figma-renderer.js.map +1 -0
- package/dist/orchestrator/figma/spec-figma-section-renderers.d.ts +23 -0
- package/dist/orchestrator/figma/spec-figma-section-renderers.d.ts.map +1 -0
- package/dist/orchestrator/figma/spec-figma-section-renderers.js +47 -0
- package/dist/orchestrator/figma/spec-figma-section-renderers.js.map +1 -0
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +56 -11
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +30 -17
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-review.md +4 -3
- package/dist/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +33 -1
- package/dist/orchestrator/templates/ai-instructions/claude-config.md +1 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-figma-source.md +151 -0
- package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +39 -20
- package/dist/orchestrator/templates/ai-instructions/claude-skill-tc-review.md +2 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +53 -9
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +21 -16
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +2 -2
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-review.md +4 -3
- package/dist/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +36 -4
- package/dist/orchestrator/templates/ai-instructions/copilot-config.md +1 -0
- package/dist/orchestrator/templates/ai-instructions/copilot-skill-figma-source.md +151 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-figma-source.md +151 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +61 -0
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +51 -25
- package/dist/orchestrator/templates/ai-instructions/github-skill-sungen-tc-review.md +20 -0
- package/dist/tools/figma/figma-auth.d.ts +36 -0
- package/dist/tools/figma/figma-auth.d.ts.map +1 -0
- package/dist/tools/figma/figma-auth.js +182 -0
- package/dist/tools/figma/figma-auth.js.map +1 -0
- package/dist/tools/figma/figma-cache.d.ts +45 -0
- package/dist/tools/figma/figma-cache.d.ts.map +1 -0
- package/dist/tools/figma/figma-cache.js +191 -0
- package/dist/tools/figma/figma-cache.js.map +1 -0
- package/dist/tools/figma/figma-client-types.d.ts +112 -0
- package/dist/tools/figma/figma-client-types.d.ts.map +1 -0
- package/dist/tools/figma/figma-client-types.js +7 -0
- package/dist/tools/figma/figma-client-types.js.map +1 -0
- package/dist/tools/figma/figma-errors.d.ts +49 -0
- package/dist/tools/figma/figma-errors.d.ts.map +1 -0
- package/dist/tools/figma/figma-errors.js +105 -0
- package/dist/tools/figma/figma-errors.js.map +1 -0
- package/dist/tools/figma/figma-image-downloader.d.ts +25 -0
- package/dist/tools/figma/figma-image-downloader.d.ts.map +1 -0
- package/dist/tools/figma/figma-image-downloader.js +128 -0
- package/dist/tools/figma/figma-image-downloader.js.map +1 -0
- package/dist/tools/figma/figma-node-filter.d.ts +26 -0
- package/dist/tools/figma/figma-node-filter.d.ts.map +1 -0
- package/dist/tools/figma/figma-node-filter.js +164 -0
- package/dist/tools/figma/figma-node-filter.js.map +1 -0
- package/dist/tools/figma/figma-rest-client.d.ts +24 -0
- package/dist/tools/figma/figma-rest-client.d.ts.map +1 -0
- package/dist/tools/figma/figma-rest-client.js +154 -0
- package/dist/tools/figma/figma-rest-client.js.map +1 -0
- package/dist/tools/figma/figma-url-parser.d.ts +18 -0
- package/dist/tools/figma/figma-url-parser.d.ts.map +1 -0
- package/dist/tools/figma/figma-url-parser.js +51 -0
- package/dist/tools/figma/figma-url-parser.js.map +1 -0
- package/dist/utils/exec-file-no-throw.d.ts +20 -0
- package/dist/utils/exec-file-no-throw.d.ts.map +1 -0
- package/dist/utils/exec-file-no-throw.js +36 -0
- package/dist/utils/exec-file-no-throw.js.map +1 -0
- package/package.json +1 -1
- package/src/cli/commands/add.ts +80 -9
- package/src/cli/commands/figma.ts +162 -0
- package/src/cli/index.ts +4 -2
- package/src/orchestrator/ai-rules-updater.ts +2 -0
- package/src/orchestrator/figma/figma-scaffolder-helpers.ts +126 -0
- package/src/orchestrator/figma/figma-scaffolder-types.ts +26 -0
- package/src/orchestrator/figma/figma-scaffolder.ts +209 -0
- package/src/orchestrator/figma/node-path-collapser.ts +38 -0
- package/src/orchestrator/figma/spec-figma-renderer.ts +80 -0
- package/src/orchestrator/figma/spec-figma-section-renderers.ts +46 -0
- package/src/orchestrator/templates/ai-instructions/claude-cmd-add-screen.md +56 -11
- package/src/orchestrator/templates/ai-instructions/claude-cmd-create-test.md +30 -17
- package/src/orchestrator/templates/ai-instructions/claude-cmd-review.md +4 -3
- package/src/orchestrator/templates/ai-instructions/claude-cmd-run-test.md +33 -1
- package/src/orchestrator/templates/ai-instructions/claude-config.md +1 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-figma-source.md +151 -0
- package/src/orchestrator/templates/ai-instructions/claude-skill-tc-generation.md +39 -20
- package/src/orchestrator/templates/ai-instructions/claude-skill-tc-review.md +2 -0
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-add-screen.md +53 -9
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-create-test.md +21 -16
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-delivery.md +2 -2
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-review.md +4 -3
- package/src/orchestrator/templates/ai-instructions/copilot-cmd-run-test.md +36 -4
- package/src/orchestrator/templates/ai-instructions/copilot-config.md +1 -0
- package/src/orchestrator/templates/ai-instructions/copilot-skill-figma-source.md +151 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-figma-source.md +151 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-selector-fix.md +61 -0
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-generation.md +51 -25
- package/src/orchestrator/templates/ai-instructions/github-skill-sungen-tc-review.md +20 -0
- package/src/tools/figma/figma-auth.ts +161 -0
- package/src/tools/figma/figma-cache.ts +184 -0
- package/src/tools/figma/figma-client-types.ts +125 -0
- package/src/tools/figma/figma-errors.ts +127 -0
- package/src/tools/figma/figma-image-downloader.ts +112 -0
- package/src/tools/figma/figma-node-filter.ts +198 -0
- package/src/tools/figma/figma-rest-client.ts +183 -0
- package/src/tools/figma/figma-url-parser.ts +55 -0
- package/src/utils/exec-file-no-throw.ts +45 -0
|
@@ -6,14 +6,25 @@ user-invocable: false
|
|
|
6
6
|
|
|
7
7
|
## Goal
|
|
8
8
|
|
|
9
|
-
Generate **focused test cases per screen section**
|
|
9
|
+
Generate **focused test cases per screen section** using a **tier-based approach** for faster results. Output `.feature` + `test-data.yaml` only — selectors are deferred to `/sungen:run-test`.
|
|
10
|
+
|
|
11
|
+
### Tier System
|
|
12
|
+
|
|
13
|
+
| Tier | Priority | What to generate | When |
|
|
14
|
+
|---|---|---|---|
|
|
15
|
+
| **Tier 1** (default) | `@critical` + `@high` | Happy paths, required validation, core business rules, security basics | First run of `create-test` |
|
|
16
|
+
| **Tier 2** (expand) | `@normal` + `@low` | UI presence, optional validation, edge cases, cosmetic checks | User runs `create-test` again with "Add viewpoints" mode |
|
|
17
|
+
|
|
18
|
+
**Round 1 (Tier 1)** targets **~10-15 scenarios per section** — enough to cover critical flows and catch real bugs. This is the default behavior.
|
|
19
|
+
|
|
20
|
+
**Round 2 (Tier 2)** expands to full coverage when the user explicitly chooses "Add viewpoints" or "Add new sections" update mode. Only then generate `@normal` + `@low` scenarios to fill coverage gaps.
|
|
10
21
|
|
|
11
22
|
## Update Mode
|
|
12
23
|
|
|
13
24
|
When `.feature` already has scenarios, summarize and ask:
|
|
14
|
-
1. **Add new sections** — append, continue numbering
|
|
15
|
-
2. **Add viewpoints** —
|
|
16
|
-
3. **Replace all** — overwrite
|
|
25
|
+
1. **Add new sections** — append new sections with Tier 2 (`@normal` + `@low`) scenarios, continue numbering
|
|
26
|
+
2. **Add viewpoints** — expand existing sections with Tier 2 (`@normal` + `@low`) scenarios
|
|
27
|
+
3. **Replace all** — overwrite with fresh Tier 1 (`@critical` + `@high`) generation
|
|
17
28
|
|
|
18
29
|
For append: read highest `VP-<CAT>-<NNN>`, continue from next number. Never modify existing scenarios.
|
|
19
30
|
|
|
@@ -28,14 +39,28 @@ Requirements improve every viewpoint: exact error messages for VAL, business rul
|
|
|
28
39
|
|
|
29
40
|
If also exploring live page: verify spec vs actual, flag mismatches, capture exact text.
|
|
30
41
|
|
|
42
|
+
### Figma supplement (`spec_figma.md`)
|
|
43
|
+
|
|
44
|
+
When `requirements/spec_figma.md` is present alongside `spec.md`, treat it as a **secondary input** with these rules:
|
|
45
|
+
|
|
46
|
+
- **Never override `spec.md`**: `spec.md` is authoritative for all business rules, field constraints, and behavior. `spec_figma.md` only supplements with visual/text data that `spec.md` may lack.
|
|
47
|
+
- **`## Text Inventory` → literal strings**: use text label values from this section verbatim in `test-data.yaml` (button labels, input placeholders, error messages shown in Figma). Do not paraphrase or invent alternatives.
|
|
48
|
+
- **`## Interaction States` → state coverage checkpoints**: use the listed variants (e.g., empty, loading, error, success) as a checklist for state-coverage scenarios. Only generate scenarios for states that are either (a) confirmed in both `spec.md` and `spec_figma.md`, or (b) explicitly documented in one source without contradiction from the other.
|
|
49
|
+
- **Flag disagreements**: if a field name, label, or behavior in `spec_figma.md` contradicts `spec.md`, insert an HTML comment at the top of the `.feature` file:
|
|
50
|
+
```gherkin
|
|
51
|
+
<!-- FIGMA-SPEC CONFLICT: <brief description> — using spec.md value -->
|
|
52
|
+
```
|
|
53
|
+
Then proceed using the `spec.md` value.
|
|
54
|
+
|
|
31
55
|
## Screen Input Sources
|
|
32
56
|
|
|
33
|
-
**
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
57
|
+
**Auto-detect** — the parent command (`create-test`) resolves visual sources before invoking this skill. By the time generation starts, the available sources are already determined:
|
|
58
|
+
- `spec.md` — primary, always read if present
|
|
59
|
+
- `spec_figma.md` — Figma supplement, read if present (PAT flow already completed)
|
|
60
|
+
- `ui/*.png` — visual context, read if present
|
|
61
|
+
- `test-viewpoint.md` — edge cases and known issues, read if present
|
|
37
62
|
|
|
38
|
-
**
|
|
63
|
+
**IMPORTANT:** If `spec_figma.md` exists, do NOT call any `mcp__figma__*` tool. The PAT flow is complete — just read the file.
|
|
39
64
|
|
|
40
65
|
**Single screen focus**: one URL = one screen. Don't explore sibling paths. Modals on same page = part of this screen.
|
|
41
66
|
|
|
@@ -76,7 +101,9 @@ Apply `sungen-test-design-techniques` to spec-extracted conditions:
|
|
|
76
101
|
|
|
77
102
|
Use `sungen-viewpoint` skill for per-pattern checklists across 4 viewpoints: UI/UX, Data & Validate, Logic, Security.
|
|
78
103
|
|
|
79
|
-
|
|
104
|
+
**Tier-aware gap filling:**
|
|
105
|
+
- **Tier 1 (first run)**: only add `@critical` and `@high` items from the checklists — core security checks (VP-SEC), required field validation (VP-VAL), key state transitions (VP-LOGIC). Skip `@normal`/`@low` items like hover states, empty states, tooltips.
|
|
106
|
+
- **Tier 2 (expand run)**: add `@normal` + `@low` scenarios — UI presence, optional validation, edge cases, cosmetic checks, keyboard nav, hover effects.
|
|
80
107
|
|
|
81
108
|
**Validation rule**: capture actual error messages from live page or spec.md. Use `User see {{error_var}}` — never assert just "is visible".
|
|
82
109
|
|
|
@@ -168,13 +195,14 @@ Feature: <Screen> Screen
|
|
|
168
195
|
When User click [Create] button
|
|
169
196
|
Then User see [Form] dialog
|
|
170
197
|
|
|
171
|
-
# --- Section: Create User Form ---
|
|
198
|
+
# --- Section: Create User Form (Tier 1: @critical + @high) ---
|
|
172
199
|
|
|
173
|
-
@
|
|
174
|
-
Scenario: VP-
|
|
200
|
+
@critical @extend:open_form
|
|
201
|
+
Scenario: VP-LOGIC-001 Submit form with valid data creates record
|
|
175
202
|
Given User is on [Form] dialog
|
|
176
|
-
|
|
177
|
-
And User
|
|
203
|
+
When User fill [Name] field with {{valid_name}}
|
|
204
|
+
And User click [Submit] button
|
|
205
|
+
Then User see {{success_message}} message
|
|
178
206
|
|
|
179
207
|
@high @extend:open_form
|
|
180
208
|
Scenario: VP-VAL-001 Submit with all empty fields shows errors
|
|
@@ -182,11 +210,7 @@ Feature: <Screen> Screen
|
|
|
182
210
|
When User click [Submit] button
|
|
183
211
|
Then User see [Name error] message with {{name_required_error}}
|
|
184
212
|
|
|
185
|
-
# --- Section: User Table ---
|
|
186
|
-
|
|
187
|
-
@normal
|
|
188
|
-
Scenario: VP-UI-010 Table displays all columns
|
|
189
|
-
Then User see [Name] column in [Users] table
|
|
213
|
+
# --- Section: User Table (Tier 1: @critical + @high) ---
|
|
190
214
|
|
|
191
215
|
@high
|
|
192
216
|
Scenario: VP-VAL-010 Table displays correct data
|
|
@@ -194,13 +218,15 @@ Feature: <Screen> Screen
|
|
|
194
218
|
| Name | Email |
|
|
195
219
|
| {{name_1}} | {{email_1}} |
|
|
196
220
|
|
|
197
|
-
@
|
|
198
|
-
Scenario: VP-
|
|
199
|
-
Given User
|
|
200
|
-
When User
|
|
201
|
-
Then User
|
|
221
|
+
@critical
|
|
222
|
+
Scenario: VP-SEC-010 Unauthorized user cannot access page
|
|
223
|
+
Given User is not logged in
|
|
224
|
+
When User navigate to [Screen] page
|
|
225
|
+
Then User is on [Login] page
|
|
202
226
|
```
|
|
203
227
|
|
|
228
|
+
**Tier 2 (expand run)** adds `@normal` + `@low` scenarios like UI field presence, hover states, tooltips, empty states.
|
|
229
|
+
|
|
204
230
|
### When to use DataTable vs Row Scope
|
|
205
231
|
|
|
206
232
|
| Pattern | Use when |
|
|
@@ -43,6 +43,8 @@ Score: `(dimensions_covered / 6) * 40`. Validate technique application with `sun
|
|
|
43
43
|
|
|
44
44
|
**Classification**: UI = static/always-same appearance. VAL = input validation/errors. LOGIC = behavior/state changes (includes persisted state without When). SEC = auth/permissions.
|
|
45
45
|
|
|
46
|
+
**Tier-aware scoring**: If the feature file only contains `@critical` + `@high` scenarios (Tier 1), do NOT penalize for missing VP-UI viewpoint — UI scenarios are intentionally deferred to Tier 2. Score "All applicable VP present" based on Tier 1-relevant viewpoints only (VAL, LOGIC, SEC). Note in the review output: *"VP-UI deferred to Tier 2 — run create-test with 'Add viewpoints' to expand."*
|
|
47
|
+
|
|
46
48
|
---
|
|
47
49
|
|
|
48
50
|
## Quality Rules
|
|
@@ -87,6 +89,21 @@ Do NOT mark `@manual` when data is visible in snapshot or documented in spec —
|
|
|
87
89
|
|
|
88
90
|
---
|
|
89
91
|
|
|
92
|
+
## Unverified Selectors metric
|
|
93
|
+
|
|
94
|
+
**When to check**: if `qa/screens/<screen>/selectors/<screen>.yaml` exists, count lines matching the pattern `@needs-live-verify`.
|
|
95
|
+
|
|
96
|
+
| Metric | Source | Scoring impact |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| Unverified Selectors | Count of `@needs-live-verify` comment lines in `selectors.yaml` | None — non-blocking |
|
|
99
|
+
|
|
100
|
+
- If count > 0: include the following line in the review report summary (after the score table, before Issues):
|
|
101
|
+
`⚠ <N> selectors flagged @needs-live-verify — run run-test against live URL when available.`
|
|
102
|
+
- Does NOT reduce the score or block the 60% threshold. The suite can PASS with unverified selectors.
|
|
103
|
+
- If `selectors.yaml` does not exist yet (not yet generated): omit this metric entirely.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
90
107
|
## Output Format
|
|
91
108
|
|
|
92
109
|
```markdown
|
|
@@ -99,6 +116,9 @@ Do NOT mark `@manual` when data is visible in snapshot or documented in spec —
|
|
|
99
116
|
| Viewpoint | <n> | 30 |
|
|
100
117
|
| **Total** | **<n>%** | **100** |
|
|
101
118
|
|
|
119
|
+
⚠ <N> selectors flagged @needs-live-verify — run run-test against live URL when available.
|
|
120
|
+
<!-- omit this line if selectors.yaml doesn't exist or @needs-live-verify count = 0 -->
|
|
121
|
+
|
|
102
122
|
### Issues
|
|
103
123
|
1. [SYNTAX] ...
|
|
104
124
|
2. [COVERAGE] ...
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Figma PAT lifecycle: load, save, clear, and safety checks.
|
|
3
|
+
*
|
|
4
|
+
* Storage: <cwd>/.env key=FIGMA_PAT
|
|
5
|
+
* Override: process.env.FIGMA_PAT (CI path — skips file read)
|
|
6
|
+
*
|
|
7
|
+
* Safeguards enforced on every save AND every assertSafeToUse call:
|
|
8
|
+
* 1. .env must appear in .gitignore (abort if not).
|
|
9
|
+
* 2. No tracked file may contain "FIGMA_PAT=" (git grep scan).
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Load the Figma PAT.
|
|
13
|
+
* Priority: process.env.FIGMA_PAT > .env file.
|
|
14
|
+
* Returns undefined when no PAT is configured.
|
|
15
|
+
*/
|
|
16
|
+
export declare function loadPat(cwd: string): string | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Persist PAT to <cwd>/.env, upserting only the FIGMA_PAT key.
|
|
19
|
+
* Aborts with an actionable error if safeguards are violated.
|
|
20
|
+
*
|
|
21
|
+
* @throws Error with remediation instructions on safeguard failure.
|
|
22
|
+
*/
|
|
23
|
+
export declare function savePat(cwd: string, token: string): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Remove FIGMA_PAT line from <cwd>/.env.
|
|
26
|
+
* No-op when key is not present.
|
|
27
|
+
*/
|
|
28
|
+
export declare function clearPat(cwd: string): void;
|
|
29
|
+
/**
|
|
30
|
+
* Run tracked-file scan on every Figma CLI / skill invocation.
|
|
31
|
+
* Aborts with actionable message if FIGMA_PAT= is found in any tracked file.
|
|
32
|
+
*
|
|
33
|
+
* @throws Error with remediation instructions on safeguard failure.
|
|
34
|
+
*/
|
|
35
|
+
export declare function assertSafeToUse(cwd: string): Promise<void>;
|
|
36
|
+
//# sourceMappingURL=figma-auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"figma-auth.d.ts","sourceRoot":"","sources":["../../../src/tools/figma/figma-auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAiEH;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAWvD;AAED;;;;;GAKG;AACH,wBAAsB,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAiCvE;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAM1C;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAShE"}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Figma PAT lifecycle: load, save, clear, and safety checks.
|
|
4
|
+
*
|
|
5
|
+
* Storage: <cwd>/.env key=FIGMA_PAT
|
|
6
|
+
* Override: process.env.FIGMA_PAT (CI path — skips file read)
|
|
7
|
+
*
|
|
8
|
+
* Safeguards enforced on every save AND every assertSafeToUse call:
|
|
9
|
+
* 1. .env must appear in .gitignore (abort if not).
|
|
10
|
+
* 2. No tracked file may contain "FIGMA_PAT=" (git grep scan).
|
|
11
|
+
*/
|
|
12
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
15
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
16
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
17
|
+
}
|
|
18
|
+
Object.defineProperty(o, k2, desc);
|
|
19
|
+
}) : (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
o[k2] = m[k];
|
|
22
|
+
}));
|
|
23
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
24
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
25
|
+
}) : function(o, v) {
|
|
26
|
+
o["default"] = v;
|
|
27
|
+
});
|
|
28
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
29
|
+
var ownKeys = function(o) {
|
|
30
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
31
|
+
var ar = [];
|
|
32
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
33
|
+
return ar;
|
|
34
|
+
};
|
|
35
|
+
return ownKeys(o);
|
|
36
|
+
};
|
|
37
|
+
return function (mod) {
|
|
38
|
+
if (mod && mod.__esModule) return mod;
|
|
39
|
+
var result = {};
|
|
40
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
41
|
+
__setModuleDefault(result, mod);
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
})();
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.loadPat = loadPat;
|
|
47
|
+
exports.savePat = savePat;
|
|
48
|
+
exports.clearPat = clearPat;
|
|
49
|
+
exports.assertSafeToUse = assertSafeToUse;
|
|
50
|
+
const fs = __importStar(require("fs"));
|
|
51
|
+
const path = __importStar(require("path"));
|
|
52
|
+
const exec_file_no_throw_1 = require("../../utils/exec-file-no-throw");
|
|
53
|
+
const PAT_KEY = 'FIGMA_PAT';
|
|
54
|
+
const ENV_FILE = '.env';
|
|
55
|
+
const GITIGNORE_FILE = '.gitignore';
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
// Internal helpers
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
function envFilePath(cwd) {
|
|
60
|
+
return path.join(cwd, ENV_FILE);
|
|
61
|
+
}
|
|
62
|
+
function gitignorePath(cwd) {
|
|
63
|
+
return path.join(cwd, GITIGNORE_FILE);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Read .env file lines, return empty array when file does not exist.
|
|
67
|
+
*/
|
|
68
|
+
function readEnvLines(cwd) {
|
|
69
|
+
const fp = envFilePath(cwd);
|
|
70
|
+
if (!fs.existsSync(fp))
|
|
71
|
+
return [];
|
|
72
|
+
return fs.readFileSync(fp, 'utf8').split('\n');
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Write lines back to .env, preserving a trailing newline.
|
|
76
|
+
*/
|
|
77
|
+
function writeEnvLines(cwd, lines) {
|
|
78
|
+
fs.writeFileSync(envFilePath(cwd), lines.join('\n'), 'utf8');
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Check whether .env (or .env.*) is covered by .gitignore.
|
|
82
|
+
*/
|
|
83
|
+
function isEnvIgnored(cwd) {
|
|
84
|
+
const gp = gitignorePath(cwd);
|
|
85
|
+
if (!fs.existsSync(gp))
|
|
86
|
+
return false;
|
|
87
|
+
const content = fs.readFileSync(gp, 'utf8');
|
|
88
|
+
// Accept ".env", ".env.local", ".env.*", or "*.env" patterns
|
|
89
|
+
return /^\s*(\.env[\w.*]*|\*\.env)\s*$/m.test(content);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Run `git grep -l "FIGMA_PAT="` in cwd.
|
|
93
|
+
* Returns list of matching tracked file paths (empty = safe).
|
|
94
|
+
*/
|
|
95
|
+
async function findTrackedFilesWithPat(cwd) {
|
|
96
|
+
const result = await (0, exec_file_no_throw_1.execFileNoThrow)('git', ['grep', '-l', `${PAT_KEY}=`], cwd);
|
|
97
|
+
if (result.status === 0 && result.stdout.trim()) {
|
|
98
|
+
return result.stdout.trim().split('\n').filter(Boolean);
|
|
99
|
+
}
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// Public API
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
/**
|
|
106
|
+
* Load the Figma PAT.
|
|
107
|
+
* Priority: process.env.FIGMA_PAT > .env file.
|
|
108
|
+
* Returns undefined when no PAT is configured.
|
|
109
|
+
*/
|
|
110
|
+
function loadPat(cwd) {
|
|
111
|
+
if (process.env[PAT_KEY])
|
|
112
|
+
return process.env[PAT_KEY];
|
|
113
|
+
const lines = readEnvLines(cwd);
|
|
114
|
+
for (const line of lines) {
|
|
115
|
+
const trimmed = line.trim();
|
|
116
|
+
if (trimmed.startsWith(`${PAT_KEY}=`)) {
|
|
117
|
+
return trimmed.slice(PAT_KEY.length + 1).trim();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Persist PAT to <cwd>/.env, upserting only the FIGMA_PAT key.
|
|
124
|
+
* Aborts with an actionable error if safeguards are violated.
|
|
125
|
+
*
|
|
126
|
+
* @throws Error with remediation instructions on safeguard failure.
|
|
127
|
+
*/
|
|
128
|
+
async function savePat(cwd, token) {
|
|
129
|
+
// Safeguard 1: .env must be gitignored
|
|
130
|
+
if (!isEnvIgnored(cwd)) {
|
|
131
|
+
throw new Error(`Refusing to save FIGMA_PAT: ".env" is not listed in ${GITIGNORE_FILE}.\n` +
|
|
132
|
+
`Add the following line to ${path.join(cwd, GITIGNORE_FILE)} and re-run:\n\n .env\n`);
|
|
133
|
+
}
|
|
134
|
+
// Safeguard 2: no tracked file may contain FIGMA_PAT=
|
|
135
|
+
const leakedFiles = await findTrackedFilesWithPat(cwd);
|
|
136
|
+
if (leakedFiles.length > 0) {
|
|
137
|
+
throw new Error(`Refusing to save FIGMA_PAT: token key found in tracked file(s):\n` +
|
|
138
|
+
leakedFiles.map((f) => ` ${f}`).join('\n') +
|
|
139
|
+
`\n\nRemove FIGMA_PAT from those files, commit the removal, then re-run.\n`);
|
|
140
|
+
}
|
|
141
|
+
// Upsert: replace existing line or append
|
|
142
|
+
const lines = readEnvLines(cwd);
|
|
143
|
+
const idx = lines.findIndex((l) => l.trim().startsWith(`${PAT_KEY}=`));
|
|
144
|
+
const newLine = `${PAT_KEY}=${token}`;
|
|
145
|
+
if (idx >= 0) {
|
|
146
|
+
lines[idx] = newLine;
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
// Ensure file ends with newline before appending
|
|
150
|
+
if (lines.length > 0 && lines[lines.length - 1] !== '') {
|
|
151
|
+
lines.push('');
|
|
152
|
+
}
|
|
153
|
+
lines.push(newLine);
|
|
154
|
+
}
|
|
155
|
+
writeEnvLines(cwd, lines);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Remove FIGMA_PAT line from <cwd>/.env.
|
|
159
|
+
* No-op when key is not present.
|
|
160
|
+
*/
|
|
161
|
+
function clearPat(cwd) {
|
|
162
|
+
const lines = readEnvLines(cwd);
|
|
163
|
+
const filtered = lines.filter((l) => !l.trim().startsWith(`${PAT_KEY}=`));
|
|
164
|
+
if (filtered.length !== lines.length) {
|
|
165
|
+
writeEnvLines(cwd, filtered);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Run tracked-file scan on every Figma CLI / skill invocation.
|
|
170
|
+
* Aborts with actionable message if FIGMA_PAT= is found in any tracked file.
|
|
171
|
+
*
|
|
172
|
+
* @throws Error with remediation instructions on safeguard failure.
|
|
173
|
+
*/
|
|
174
|
+
async function assertSafeToUse(cwd) {
|
|
175
|
+
const leakedFiles = await findTrackedFilesWithPat(cwd);
|
|
176
|
+
if (leakedFiles.length > 0) {
|
|
177
|
+
throw new Error(`Security check failed: FIGMA_PAT found in tracked file(s):\n` +
|
|
178
|
+
leakedFiles.map((f) => ` ${f}`).join('\n') +
|
|
179
|
+
`\n\nRemove FIGMA_PAT from those files and commit the removal before using Figma commands.\n`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=figma-auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"figma-auth.js","sourceRoot":"","sources":["../../../src/tools/figma/figma-auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version-keyed disk cache for Figma node JSON and rendered images.
|
|
3
|
+
*
|
|
4
|
+
* Layout:
|
|
5
|
+
* <cwd>/.sungen/figma-cache/<fileKey>/<versionId>/<safeNodeId>.json (filtered node)
|
|
6
|
+
* <cwd>/.sungen/figma-cache/<fileKey>/<versionId>/<safeNodeId>-raw.json (unfiltered API response)
|
|
7
|
+
* <cwd>/.sungen/figma-cache/<fileKey>/<versionId>/<safeNodeId>-<scale>.png
|
|
8
|
+
*
|
|
9
|
+
* Cache directory is created on first write (permissions 0o700 on Unix).
|
|
10
|
+
* `bustOldVersions` retains the N most recently modified version dirs.
|
|
11
|
+
*/
|
|
12
|
+
import type { FigmaCacheKind } from './figma-client-types';
|
|
13
|
+
/**
|
|
14
|
+
* Read a cached entry. Returns `undefined` on cache miss.
|
|
15
|
+
*
|
|
16
|
+
* @param kind 'json' for filtered node data, '<n>x' for PNG images.
|
|
17
|
+
*/
|
|
18
|
+
export declare function get(cwd: string, fileKey: string, versionId: string, nodeId: string, kind: FigmaCacheKind): Buffer | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Write data to the cache.
|
|
21
|
+
* Creates the version directory (mode 0o700) if it does not exist.
|
|
22
|
+
*
|
|
23
|
+
* @param kind 'json' for filtered node data, '<n>x' for PNG images.
|
|
24
|
+
*/
|
|
25
|
+
export declare function put(cwd: string, fileKey: string, versionId: string, nodeId: string, kind: FigmaCacheKind, data: Buffer | string): void;
|
|
26
|
+
/**
|
|
27
|
+
* Find the newest on-disk version that has a cached raw node JSON for `nodeId`.
|
|
28
|
+
* Returns `undefined` when no usable cache exists.
|
|
29
|
+
*
|
|
30
|
+
* Lets the scaffolder skip an expensive `/v1/files/:key/nodes` call on Starter
|
|
31
|
+
* plans where REST quota is severely restricted.
|
|
32
|
+
*/
|
|
33
|
+
export declare function findLatestCachedVersion(cwd: string, fileKey: string, nodeId: string): string | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* Remove all version directories for `fileKey` except the `keep` most recent.
|
|
36
|
+
*
|
|
37
|
+
* Directories are ordered by mtime descending; oldest are deleted first.
|
|
38
|
+
* No-op when fewer than `keep` versions exist.
|
|
39
|
+
*
|
|
40
|
+
* @param keep Number of versions to retain (default 3).
|
|
41
|
+
*/
|
|
42
|
+
export declare function bustOldVersions(cwd: string, fileKey: string, currentVersion: string, options?: {
|
|
43
|
+
keep?: number;
|
|
44
|
+
}): void;
|
|
45
|
+
//# sourceMappingURL=figma-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"figma-cache.d.ts","sourceRoot":"","sources":["../../../src/tools/figma/figma-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAiC3D;;;;GAIG;AACH,wBAAgB,GAAG,CACjB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,cAAc,GACnB,MAAM,GAAG,SAAS,CAWpB;AAED;;;;;GAKG;AACH,wBAAgB,GAAG,CACjB,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,cAAc,EACpB,IAAI,EAAE,MAAM,GAAG,MAAM,GACpB,IAAI,CASN;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,GACb,MAAM,GAAG,SAAS,CAyBpB;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,cAAc,EAAE,MAAM,EACtB,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GAC9B,IAAI,CAqCN"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Version-keyed disk cache for Figma node JSON and rendered images.
|
|
4
|
+
*
|
|
5
|
+
* Layout:
|
|
6
|
+
* <cwd>/.sungen/figma-cache/<fileKey>/<versionId>/<safeNodeId>.json (filtered node)
|
|
7
|
+
* <cwd>/.sungen/figma-cache/<fileKey>/<versionId>/<safeNodeId>-raw.json (unfiltered API response)
|
|
8
|
+
* <cwd>/.sungen/figma-cache/<fileKey>/<versionId>/<safeNodeId>-<scale>.png
|
|
9
|
+
*
|
|
10
|
+
* Cache directory is created on first write (permissions 0o700 on Unix).
|
|
11
|
+
* `bustOldVersions` retains the N most recently modified version dirs.
|
|
12
|
+
*/
|
|
13
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
16
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
17
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
18
|
+
}
|
|
19
|
+
Object.defineProperty(o, k2, desc);
|
|
20
|
+
}) : (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
o[k2] = m[k];
|
|
23
|
+
}));
|
|
24
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
25
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
26
|
+
}) : function(o, v) {
|
|
27
|
+
o["default"] = v;
|
|
28
|
+
});
|
|
29
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
30
|
+
var ownKeys = function(o) {
|
|
31
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
32
|
+
var ar = [];
|
|
33
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
34
|
+
return ar;
|
|
35
|
+
};
|
|
36
|
+
return ownKeys(o);
|
|
37
|
+
};
|
|
38
|
+
return function (mod) {
|
|
39
|
+
if (mod && mod.__esModule) return mod;
|
|
40
|
+
var result = {};
|
|
41
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
42
|
+
__setModuleDefault(result, mod);
|
|
43
|
+
return result;
|
|
44
|
+
};
|
|
45
|
+
})();
|
|
46
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
+
exports.get = get;
|
|
48
|
+
exports.put = put;
|
|
49
|
+
exports.findLatestCachedVersion = findLatestCachedVersion;
|
|
50
|
+
exports.bustOldVersions = bustOldVersions;
|
|
51
|
+
const fs = __importStar(require("node:fs"));
|
|
52
|
+
const path = __importStar(require("node:path"));
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
// Path helpers
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
const CACHE_ROOT = path.join('.sungen', 'figma-cache');
|
|
57
|
+
/** Replace characters unsafe for filenames (colons, slashes, spaces). */
|
|
58
|
+
function safeId(id) {
|
|
59
|
+
return id.replace(/[:/\\s]/g, '_');
|
|
60
|
+
}
|
|
61
|
+
function cacheDir(cwd, fileKey, versionId) {
|
|
62
|
+
return path.join(cwd, CACHE_ROOT, safeId(fileKey), safeId(versionId));
|
|
63
|
+
}
|
|
64
|
+
function cacheFilename(nodeId, kind) {
|
|
65
|
+
const safe = safeId(nodeId);
|
|
66
|
+
if (kind === 'json')
|
|
67
|
+
return `${safe}.json`;
|
|
68
|
+
if (kind === 'raw')
|
|
69
|
+
return `${safe}-raw.json`;
|
|
70
|
+
// kind is like "2x", "1x", etc.
|
|
71
|
+
return `${safe}-${kind}.png`;
|
|
72
|
+
}
|
|
73
|
+
function ensureDir(dir) {
|
|
74
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
75
|
+
}
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
// Public API
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
/**
|
|
80
|
+
* Read a cached entry. Returns `undefined` on cache miss.
|
|
81
|
+
*
|
|
82
|
+
* @param kind 'json' for filtered node data, '<n>x' for PNG images.
|
|
83
|
+
*/
|
|
84
|
+
function get(cwd, fileKey, versionId, nodeId, kind) {
|
|
85
|
+
const filePath = path.join(cacheDir(cwd, fileKey, versionId), cacheFilename(nodeId, kind));
|
|
86
|
+
if (!fs.existsSync(filePath))
|
|
87
|
+
return undefined;
|
|
88
|
+
try {
|
|
89
|
+
return fs.readFileSync(filePath);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Write data to the cache.
|
|
97
|
+
* Creates the version directory (mode 0o700) if it does not exist.
|
|
98
|
+
*
|
|
99
|
+
* @param kind 'json' for filtered node data, '<n>x' for PNG images.
|
|
100
|
+
*/
|
|
101
|
+
function put(cwd, fileKey, versionId, nodeId, kind, data) {
|
|
102
|
+
const dir = cacheDir(cwd, fileKey, versionId);
|
|
103
|
+
ensureDir(dir);
|
|
104
|
+
const filePath = path.join(dir, cacheFilename(nodeId, kind));
|
|
105
|
+
const payload = typeof data === 'string' ? Buffer.from(data, 'utf8') : data;
|
|
106
|
+
// Atomic write: tmp → rename
|
|
107
|
+
const tmpPath = `${filePath}.tmp`;
|
|
108
|
+
fs.writeFileSync(tmpPath, payload, { mode: 0o600 });
|
|
109
|
+
fs.renameSync(tmpPath, filePath);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Find the newest on-disk version that has a cached raw node JSON for `nodeId`.
|
|
113
|
+
* Returns `undefined` when no usable cache exists.
|
|
114
|
+
*
|
|
115
|
+
* Lets the scaffolder skip an expensive `/v1/files/:key/nodes` call on Starter
|
|
116
|
+
* plans where REST quota is severely restricted.
|
|
117
|
+
*/
|
|
118
|
+
function findLatestCachedVersion(cwd, fileKey, nodeId) {
|
|
119
|
+
const keyDir = path.join(cwd, CACHE_ROOT, safeId(fileKey));
|
|
120
|
+
if (!fs.existsSync(keyDir))
|
|
121
|
+
return undefined;
|
|
122
|
+
let entries;
|
|
123
|
+
try {
|
|
124
|
+
entries = fs.readdirSync(keyDir, { withFileTypes: true });
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
const rawFilename = cacheFilename(nodeId, 'raw');
|
|
130
|
+
const candidates = entries
|
|
131
|
+
.filter((e) => e.isDirectory())
|
|
132
|
+
.map((e) => {
|
|
133
|
+
const rawPath = path.join(keyDir, e.name, rawFilename);
|
|
134
|
+
if (!fs.existsSync(rawPath))
|
|
135
|
+
return undefined;
|
|
136
|
+
let mtime = 0;
|
|
137
|
+
try {
|
|
138
|
+
mtime = fs.statSync(rawPath).mtimeMs;
|
|
139
|
+
}
|
|
140
|
+
catch { /* ignore */ }
|
|
141
|
+
return { versionDir: e.name, mtime };
|
|
142
|
+
})
|
|
143
|
+
.filter((x) => x !== undefined)
|
|
144
|
+
.sort((a, b) => b.mtime - a.mtime);
|
|
145
|
+
return candidates[0]?.versionDir;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Remove all version directories for `fileKey` except the `keep` most recent.
|
|
149
|
+
*
|
|
150
|
+
* Directories are ordered by mtime descending; oldest are deleted first.
|
|
151
|
+
* No-op when fewer than `keep` versions exist.
|
|
152
|
+
*
|
|
153
|
+
* @param keep Number of versions to retain (default 3).
|
|
154
|
+
*/
|
|
155
|
+
function bustOldVersions(cwd, fileKey, currentVersion, options = {}) {
|
|
156
|
+
const keep = options.keep ?? 3;
|
|
157
|
+
const keyDir = path.join(cwd, CACHE_ROOT, safeId(fileKey));
|
|
158
|
+
if (!fs.existsSync(keyDir))
|
|
159
|
+
return;
|
|
160
|
+
let entries;
|
|
161
|
+
try {
|
|
162
|
+
entries = fs.readdirSync(keyDir, { withFileTypes: true });
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
const versionDirs = entries
|
|
168
|
+
.filter((e) => e.isDirectory())
|
|
169
|
+
.map((e) => {
|
|
170
|
+
const fullPath = path.join(keyDir, e.name);
|
|
171
|
+
let mtime = 0;
|
|
172
|
+
try {
|
|
173
|
+
mtime = fs.statSync(fullPath).mtimeMs;
|
|
174
|
+
}
|
|
175
|
+
catch { /* ignore */ }
|
|
176
|
+
return { name: e.name, fullPath, mtime };
|
|
177
|
+
})
|
|
178
|
+
// Ensure current version is always kept (bump its mtime artificially)
|
|
179
|
+
.map((d) => d.name === safeId(currentVersion)
|
|
180
|
+
? { ...d, mtime: Number.MAX_SAFE_INTEGER }
|
|
181
|
+
: d)
|
|
182
|
+
.sort((a, b) => b.mtime - a.mtime); // newest first
|
|
183
|
+
const toDelete = versionDirs.slice(keep);
|
|
184
|
+
for (const dir of toDelete) {
|
|
185
|
+
try {
|
|
186
|
+
fs.rmSync(dir.fullPath, { recursive: true, force: true });
|
|
187
|
+
}
|
|
188
|
+
catch { /* best-effort */ }
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=figma-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"figma-cache.js","sourceRoot":"","sources":["../../../src/tools/figma/figma-cache.ts"],"names":[],"mappings":"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"}
|