fg-devkit 1.14.4 → 1.15.0
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.
|
@@ -11,6 +11,13 @@ alwaysApply: true
|
|
|
11
11
|
- Avoid unrelated refactors in the same task.
|
|
12
12
|
- Preserve existing behavior unless a behavior change is requested.
|
|
13
13
|
|
|
14
|
+
## Code comments
|
|
15
|
+
|
|
16
|
+
- Do not add comments in code unless the user explicitly asks for them.
|
|
17
|
+
- Prefer self-explanatory naming and structure over inline comments.
|
|
18
|
+
- When updating existing code, do not add new comments or expand existing ones unless requested.
|
|
19
|
+
- Exception: update comments only when they are required to keep an existing comment accurate after a behavior change in the same task.
|
|
20
|
+
|
|
14
21
|
## Communication efficiency
|
|
15
22
|
|
|
16
23
|
- Do not waste tokens: keep responses concise, high-signal, and scoped to the request.
|
|
@@ -33,7 +40,7 @@ alwaysApply: true
|
|
|
33
40
|
- Keep TypeScript and Vue template typing strict (`strict: true`, `strictTemplates: true`).
|
|
34
41
|
- Do not introduce `any`; use `unknown` for unsafe input and narrow before use.
|
|
35
42
|
- Type API boundaries explicitly (request payloads, response DTOs, mutation variables, callback contracts).
|
|
36
|
-
- Prefer named reusable types
|
|
43
|
+
- Prefer named reusable types under `src/types/<domain>/` for shared domain shapes; re-export from `src/types/index.ts`. Do not keep shared interfaces in views, stores, or query files.
|
|
37
44
|
- Avoid non-null assertions unless guarded by `enabled` checks or equivalent runtime guarantees.
|
|
38
45
|
|
|
39
46
|
## API boundaries
|
|
@@ -44,9 +51,10 @@ alwaysApply: true
|
|
|
44
51
|
|
|
45
52
|
## State-management boundaries
|
|
46
53
|
|
|
47
|
-
- Use TanStack Query
|
|
48
|
-
- Use Pinia
|
|
49
|
-
- Do not duplicate the same state across the query cache and
|
|
54
|
+
- Use TanStack Query for server state and caching. Do not add Vuex for new work.
|
|
55
|
+
- Use Pinia only for client/app state that is not owned by the server (for example `src/pinia/*`).
|
|
56
|
+
- Do not duplicate the same state across the query cache and Pinia unless the project explicitly documents that pattern.
|
|
57
|
+
- When a project is migrating Vuex → Query/Pinia, follow that repo’s local migration rules (if present) instead of expanding Vuex.
|
|
50
58
|
|
|
51
59
|
## TanStack Query server-state conventions
|
|
52
60
|
|
|
@@ -85,6 +93,7 @@ alwaysApply: true
|
|
|
85
93
|
## FDS-first component usage
|
|
86
94
|
|
|
87
95
|
- Use `fds-vue-core` components as the default choice for UI building blocks.
|
|
96
|
+
- Multi-step wizards use `FdsWizard` from `fds-vue-core`. Follow the project’s wizard rule file when one exists (for example `.cursor/rules/fds-wizard.md`).
|
|
88
97
|
- Before adding custom UI, check whether an FDS component already solves the use case.
|
|
89
98
|
- Prefer extending composition around FDS components over re-implementing design-system behavior.
|
|
90
99
|
- Keep visual consistency by matching FDS variants, spacing patterns, and interaction styles.
|
|
@@ -113,6 +122,7 @@ alwaysApply: true
|
|
|
113
122
|
- Keep spacing and typography explicit in class names; avoid hidden styling in deep selectors.
|
|
114
123
|
- Use `!` utility overrides sparingly and only when a component default cannot be changed safely.
|
|
115
124
|
- For Tailwind v4 important modifiers, use postfix-important utility syntax (for example `grid-cols-1!`) instead of prefix syntax (for example `!grid-cols-1`).
|
|
125
|
+
- Stack repeated items with parent `flex flex-col gap-*` (or `space-y-*`). Do not use `mb-* last:mb-0` or `last-of-type:mb-*` — those variants often miss the last `v-for` item (extra Vue/FDS nodes) and trip Tailwind conflict lint.
|
|
116
126
|
|
|
117
127
|
## Figma MCP implementation workflow
|
|
118
128
|
|
|
@@ -166,9 +176,11 @@ alwaysApply: true
|
|
|
166
176
|
|
|
167
177
|
## Testing expectations
|
|
168
178
|
|
|
169
|
-
-
|
|
170
|
-
-
|
|
179
|
+
- Do not add new automated tests unless the user explicitly asks for them.
|
|
180
|
+
- Update existing tests when your change requires it (for example broken assertions, renamed APIs, or changed behavior covered by current tests).
|
|
181
|
+
- When new tests are requested, add them close to the affected feature.
|
|
171
182
|
- Keep tests behavior-focused and avoid brittle implementation-detail assertions.
|
|
183
|
+
- When no new tests are requested, rely on lint, typecheck, and manual verification of critical flows where practical.
|
|
172
184
|
|
|
173
185
|
## CI/CD
|
|
174
186
|
|
|
@@ -178,7 +190,8 @@ alwaysApply: true
|
|
|
178
190
|
|
|
179
191
|
## Quality checks before finishing
|
|
180
192
|
|
|
181
|
-
- Run relevant checks for touched code (lint
|
|
193
|
+
- Run relevant checks for touched code (lint and typecheck when practical).
|
|
194
|
+
- Run and update existing tests when your change affects them; add new tests only when the user explicitly requests them.
|
|
182
195
|
- Fix issues introduced by your change before handing off.
|
|
183
196
|
- Call out anything you could not verify.
|
|
184
197
|
|
package/package.json
CHANGED
package/scripts/fg-devkit.mjs
CHANGED
|
@@ -559,7 +559,7 @@ Commands:
|
|
|
559
559
|
fallow [fallow-args...] Run Fallow using fg-devkit bundled package
|
|
560
560
|
dead-code [fallow-args...] Run Fallow dead code analysis (shortcut for: fallow dead-code)
|
|
561
561
|
poeditor-download [args...] Download locale files via PoEditor (reads poeditor/.poeditor.json by default)
|
|
562
|
-
poeditor-upload [args...] Upload locale file via PoEditor (reads poeditor/.poeditor.json by default)
|
|
562
|
+
poeditor-upload [args...] Upload locale file via PoEditor (reads poeditor/.poeditor.json by default; --sync-terms deletes missing terms)
|
|
563
563
|
scaffold-vue <target-dir> [--name …] [--slug …] [--force] …
|
|
564
564
|
Copy the bundled Vue scaffold into target-dir (see script header)
|
|
565
565
|
sync-scaffold-vue-deps [policy-file]
|
|
@@ -589,11 +589,14 @@ function isFgDevkitMaintainerContext() {
|
|
|
589
589
|
|
|
590
590
|
async function runInteractiveMenu() {
|
|
591
591
|
let selectPrompt
|
|
592
|
+
let confirmPrompt
|
|
592
593
|
try {
|
|
593
594
|
const inquirerSelect = await import('@inquirer/select')
|
|
595
|
+
const inquirerConfirm = await import('@inquirer/confirm')
|
|
594
596
|
selectPrompt = inquirerSelect.default
|
|
597
|
+
confirmPrompt = inquirerConfirm.default
|
|
595
598
|
} catch {
|
|
596
|
-
console.error('[fg-devkit] Interactive menu requires @inquirer/select but
|
|
599
|
+
console.error('[fg-devkit] Interactive menu requires @inquirer/select and @inquirer/confirm but they are not available.')
|
|
597
600
|
console.error('[fg-devkit] Run pnpm install in your project and try again.')
|
|
598
601
|
return 1
|
|
599
602
|
}
|
|
@@ -769,7 +772,34 @@ async function runInteractiveMenu() {
|
|
|
769
772
|
loop: false,
|
|
770
773
|
})
|
|
771
774
|
if (choice === 'cancel') continue
|
|
772
|
-
|
|
775
|
+
if (choice === 'download') {
|
|
776
|
+
return runSelectedAction(() => runNode('poeditor-download.mjs'))
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
const uploadMode = await selectPrompt({
|
|
780
|
+
message: 'Upload mode',
|
|
781
|
+
choices: [
|
|
782
|
+
{ name: 'Upload (keep terms missing from file)', value: 'upload' },
|
|
783
|
+
{
|
|
784
|
+
name: 'Upload + sync terms (delete terms missing from file)',
|
|
785
|
+
value: 'upload-sync',
|
|
786
|
+
},
|
|
787
|
+
{ name: 'Cancel', value: 'cancel' },
|
|
788
|
+
],
|
|
789
|
+
default: 'upload',
|
|
790
|
+
loop: false,
|
|
791
|
+
})
|
|
792
|
+
if (uploadMode === 'cancel') continue
|
|
793
|
+
if (uploadMode === 'upload-sync') {
|
|
794
|
+
const confirmed = await confirmPrompt({
|
|
795
|
+
message:
|
|
796
|
+
'This is a destructive POEditor action: terms not present in the uploaded file will be permanently deleted from the project (including all language translations). Continue?',
|
|
797
|
+
default: false,
|
|
798
|
+
})
|
|
799
|
+
if (!confirmed) continue
|
|
800
|
+
}
|
|
801
|
+
const uploadArgs = uploadMode === 'upload-sync' ? ['--sync-terms'] : []
|
|
802
|
+
return runSelectedAction(() => runNode('poeditor-upload.mjs', uploadArgs))
|
|
773
803
|
}
|
|
774
804
|
|
|
775
805
|
if (group === 'dependency-policy') {
|
|
@@ -4,7 +4,10 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
6
|
* pnpm exec fg-devkit poeditor-upload [--config ./poeditor/.poeditor.json] [--project-id 123]
|
|
7
|
-
* [--language sv] [--file ./src/lang/sv.json] [--api-token xxx]
|
|
7
|
+
* [--language sv] [--file ./src/lang/sv.json] [--api-token xxx] [--sync-terms]
|
|
8
|
+
*
|
|
9
|
+
* --sync-terms: Pass sync_terms=1 to PoEditor so terms missing from the uploaded file
|
|
10
|
+
* are deleted from the project (destructive; use with care).
|
|
8
11
|
*/
|
|
9
12
|
|
|
10
13
|
import fs from 'node:fs'
|
|
@@ -22,6 +25,12 @@ function sleep(ms) {
|
|
|
22
25
|
})
|
|
23
26
|
}
|
|
24
27
|
|
|
28
|
+
function isTruthyFlag(value) {
|
|
29
|
+
if (value === undefined) return false
|
|
30
|
+
const normalized = String(value).trim().toLowerCase()
|
|
31
|
+
return normalized === 'true' || normalized === '1' || normalized === 'yes'
|
|
32
|
+
}
|
|
33
|
+
|
|
25
34
|
function parsePoeditorError(err) {
|
|
26
35
|
const message = err instanceof Error ? err.message : String(err)
|
|
27
36
|
const match = message.match(/Upload failed:\s*(\{.*\})$/)
|
|
@@ -46,7 +55,7 @@ function getUploadType(filePath) {
|
|
|
46
55
|
return extension
|
|
47
56
|
}
|
|
48
57
|
|
|
49
|
-
async function uploadFile({ token, projectId, language, filePath }) {
|
|
58
|
+
async function uploadFile({ token, projectId, language, filePath, syncTerms = false }) {
|
|
50
59
|
const fileBuffer = fs.readFileSync(filePath)
|
|
51
60
|
const fileName = path.basename(filePath)
|
|
52
61
|
const uploadType = getUploadType(filePath)
|
|
@@ -56,6 +65,7 @@ async function uploadFile({ token, projectId, language, filePath }) {
|
|
|
56
65
|
form.append('updating', 'terms_translations')
|
|
57
66
|
form.append('language', language)
|
|
58
67
|
form.append('overwrite', '1')
|
|
68
|
+
form.append('sync_terms', syncTerms ? '1' : '0')
|
|
59
69
|
form.append('type', uploadType)
|
|
60
70
|
form.append('file', new Blob([fileBuffer]), fileName)
|
|
61
71
|
|
|
@@ -70,18 +80,20 @@ async function uploadFile({ token, projectId, language, filePath }) {
|
|
|
70
80
|
if (data?.response?.status !== 'success') {
|
|
71
81
|
throw new Error(`Upload failed: ${JSON.stringify(data)}`)
|
|
72
82
|
}
|
|
83
|
+
const terms = data?.result?.terms
|
|
73
84
|
const translations = data?.result?.translations
|
|
85
|
+
const syncNote = syncTerms ? `, terms deleted: ${terms?.deleted ?? 0}` : ''
|
|
74
86
|
console.log(
|
|
75
|
-
`[poeditor-upload] Upload complete for "${language}" (parsed: ${translations?.parsed ?? 0}, updated: ${translations?.updated ?? 0}, added: ${translations?.added ?? 0})`,
|
|
87
|
+
`[poeditor-upload] Upload complete for "${language}" (parsed: ${translations?.parsed ?? 0}, updated: ${translations?.updated ?? 0}, added: ${translations?.added ?? 0}${syncNote})`,
|
|
76
88
|
)
|
|
77
89
|
}
|
|
78
90
|
|
|
79
|
-
async function uploadFileWithRetry({ token, projectId, language, filePath }) {
|
|
91
|
+
async function uploadFileWithRetry({ token, projectId, language, filePath, syncTerms = false }) {
|
|
80
92
|
let attempt = 0
|
|
81
93
|
while (true) {
|
|
82
94
|
attempt += 1
|
|
83
95
|
try {
|
|
84
|
-
await uploadFile({ token, projectId, language, filePath })
|
|
96
|
+
await uploadFile({ token, projectId, language, filePath, syncTerms })
|
|
85
97
|
return
|
|
86
98
|
} catch (err) {
|
|
87
99
|
const errorCode = parsePoeditorError(err)
|
|
@@ -111,20 +123,32 @@ async function main() {
|
|
|
111
123
|
}
|
|
112
124
|
|
|
113
125
|
const projectId = getFlagValue(flags, 'project-id', cfg.projectId)
|
|
126
|
+
const syncTerms = isTruthyFlag(getFlagValue(flags, 'sync-terms'))
|
|
127
|
+
if (syncTerms) {
|
|
128
|
+
console.warn(
|
|
129
|
+
'[poeditor-upload] --sync-terms enabled: terms not present in the uploaded file will be deleted from PoEditor.',
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
|
|
114
133
|
const hasExplicitLanguage = flags.has('language')
|
|
115
134
|
const hasExplicitFile = flags.has('file')
|
|
116
135
|
if (hasExplicitLanguage || hasExplicitFile) {
|
|
117
136
|
const language = getFlagValue(flags, 'language', cfg.defaultLanguage)
|
|
118
137
|
const filePath = resolvePathFromCwd(getFlagValue(flags, 'file', cfg.uploadFile))
|
|
119
|
-
await uploadFile({ token, projectId, language, filePath })
|
|
138
|
+
await uploadFile({ token, projectId, language, filePath, syncTerms })
|
|
120
139
|
console.log(`[poeditor-upload] Upload completed for ${language}: ${filePath}`)
|
|
121
140
|
return
|
|
122
141
|
}
|
|
123
142
|
|
|
124
143
|
const uploads = cfg.uploadFiles && typeof cfg.uploadFiles === 'object' ? Object.entries(cfg.uploadFiles) : []
|
|
125
144
|
if (uploads.length > 0) {
|
|
145
|
+
if (syncTerms && uploads.length > 1) {
|
|
146
|
+
console.warn(
|
|
147
|
+
'[poeditor-upload] --sync-terms with multiple uploadFiles: syncing only on the first file to avoid deleting terms present in other language files.',
|
|
148
|
+
)
|
|
149
|
+
}
|
|
126
150
|
let nextUploadAt = 0
|
|
127
|
-
for (const [language, file] of uploads) {
|
|
151
|
+
for (const [index, [language, file]] of uploads.entries()) {
|
|
128
152
|
const now = Date.now()
|
|
129
153
|
const waitBeforeUploadMs = Math.max(0, nextUploadAt - now)
|
|
130
154
|
if (waitBeforeUploadMs > 0) {
|
|
@@ -133,7 +157,8 @@ async function main() {
|
|
|
133
157
|
}
|
|
134
158
|
|
|
135
159
|
const filePath = resolvePathFromCwd(file)
|
|
136
|
-
|
|
160
|
+
const shouldSyncTerms = syncTerms && index === 0
|
|
161
|
+
await uploadFileWithRetry({ token, projectId, language, filePath, syncTerms: shouldSyncTerms })
|
|
137
162
|
console.log(`[poeditor-upload] Upload completed for ${language}: ${filePath}`)
|
|
138
163
|
nextUploadAt = Date.now() + MIN_UPLOAD_INTERVAL_MS
|
|
139
164
|
}
|
|
@@ -142,7 +167,7 @@ async function main() {
|
|
|
142
167
|
|
|
143
168
|
const language = cfg.defaultLanguage
|
|
144
169
|
const filePath = resolvePathFromCwd(cfg.uploadFile)
|
|
145
|
-
await uploadFileWithRetry({ token, projectId, language, filePath })
|
|
170
|
+
await uploadFileWithRetry({ token, projectId, language, filePath, syncTerms })
|
|
146
171
|
console.log(`[poeditor-upload] Upload completed for ${language}: ${filePath}`)
|
|
147
172
|
}
|
|
148
173
|
|