fg-devkit 1.5.2 → 1.6.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.
Files changed (45) hide show
  1. package/configs/dependency-policy.vue-workspace.json +6 -6
  2. package/package.json +9 -1
  3. package/scaffold/vue/.browserslistrc +3 -0
  4. package/scaffold/vue/.editorconfig +5 -0
  5. package/scaffold/vue/Dockerfile +18 -0
  6. package/scaffold/vue/Dockerfile.local +16 -0
  7. package/scaffold/vue/Jenkinsfile +130 -0
  8. package/scaffold/vue/LICENSE +19 -0
  9. package/scaffold/vue/env-adjustment.sh +19 -0
  10. package/scaffold/vue/index.html +16 -0
  11. package/scaffold/vue/nginx/default.conf +23 -0
  12. package/scaffold/vue/nginx/gzip.conf +10 -0
  13. package/scaffold/vue/nginx/security.conf +6 -0
  14. package/scaffold/vue/package.json +44 -0
  15. package/scaffold/vue/poeditor/.poeditor.json +8 -0
  16. package/scaffold/vue/public/config.json +8 -0
  17. package/scaffold/vue/src/App.vue +10 -0
  18. package/scaffold/vue/src/api/client.ts +114 -0
  19. package/scaffold/vue/src/api/runtimeConfig.ts +10 -0
  20. package/scaffold/vue/src/assets/.gitkeep +0 -0
  21. package/scaffold/vue/src/components/.gitkeep +0 -0
  22. package/scaffold/vue/src/composables/.gitkeep +0 -0
  23. package/scaffold/vue/src/config/env.ts +45 -0
  24. package/scaffold/vue/src/i18n.ts +11 -0
  25. package/scaffold/vue/src/lang/sv.json +1 -0
  26. package/scaffold/vue/src/main.ts +29 -0
  27. package/scaffold/vue/src/queries/.gitkeep +0 -0
  28. package/scaffold/vue/src/router/index.ts +22 -0
  29. package/scaffold/vue/src/style.css +7 -0
  30. package/scaffold/vue/src/types/.gitkeep +0 -0
  31. package/scaffold/vue/src/views/HomeView.vue +10 -0
  32. package/scaffold/vue/src/vite-env.d.ts +1 -0
  33. package/scaffold/vue/start-container.sh +15 -0
  34. package/scaffold/vue/tailwind.config.js +21 -0
  35. package/scaffold/vue/vite.config.ts +41 -0
  36. package/scripts/fg-devkit.mjs +28 -3
  37. package/scripts/fleet-bump-fg-devkit.mjs +135 -0
  38. package/scripts/poeditor-common.mjs +118 -0
  39. package/scripts/poeditor-download.mjs +101 -0
  40. package/scripts/poeditor-upload.mjs +58 -0
  41. package/scripts/policy-upgrade.mjs +3 -0
  42. package/scripts/refresh-dependency-policy-from-repos.mjs +7 -0
  43. package/scripts/scaffold-vue.mjs +183 -0
  44. package/scripts/sync-scaffold-vue-deps.mjs +68 -0
  45. package/tsconfig.json +13 -0
@@ -0,0 +1,22 @@
1
+ import { createRouter, createWebHistory } from 'vue-router'
2
+
3
+ const routes = [
4
+ {
5
+ path: '/',
6
+ name: 'home',
7
+ meta: { title: 'Home' },
8
+ component: () => import('@/views/HomeView.vue'),
9
+ },
10
+ ]
11
+
12
+ const router = createRouter({
13
+ history: createWebHistory(import.meta.env.BASE_URL),
14
+ routes,
15
+ })
16
+
17
+ router.afterEach((to) => {
18
+ const title = typeof to.meta.title === 'string' ? to.meta.title : 'App'
19
+ document.title = `${title} | PLACEHOLDER_APP_NAME`
20
+ })
21
+
22
+ export default router
@@ -0,0 +1,7 @@
1
+ @import 'fds-vue-core/tokens.css';
2
+ @import 'tailwindcss';
3
+ @config '../tailwind.config.js';
4
+ @source '../node_modules/fds-vue-core';
5
+ @import 'fds-vue-core/slot-styles.css';
6
+ @import 'fds-vue-core/apply.css';
7
+ @import 'fds-vue-core/fonts.css';
File without changes
@@ -0,0 +1,10 @@
1
+ <script setup lang="ts">
2
+ import env from '@/config/env'
3
+ </script>
4
+
5
+ <template>
6
+ <main class="p-6">
7
+ <h1 class="text-xl font-bold">PLACEHOLDER_APP_NAME</h1>
8
+ <p class="mt-2 text-sm text-gray-700">Environment: {{ env.environment }}</p>
9
+ </main>
10
+ </template>
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />
@@ -0,0 +1,15 @@
1
+ #!/bin/bash
2
+
3
+ if [[ ${APP_API_URL} ]]; then
4
+ cat > ${CONFIG_FILE_PATH} <<EOL
5
+ {
6
+ "APP_API_URL": "${APP_API_URL}",
7
+ "VERSION": {
8
+ "hash": "$(<build_hash.txt)",
9
+ "date": "$(<build_date.txt)"
10
+ }
11
+ }
12
+ EOL
13
+ fi
14
+
15
+ exec "$@"
@@ -0,0 +1,21 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ import preset from 'fds-vue-core/tailwind.preset.cjs'
3
+
4
+ export default {
5
+ presets: [preset],
6
+ content: [
7
+ './index.html',
8
+ './src/**/*.{vue,js,ts,jsx,tsx}',
9
+ ],
10
+ safelist: [
11
+ 'max-w-screen-xs',
12
+ 'max-w-screen-sm',
13
+ 'max-w-screen-md',
14
+ 'max-w-screen-lg',
15
+ 'max-w-screen-xl',
16
+ 'max-w-screen-2xl',
17
+ 'max-w-screen-3xl',
18
+ 'max-w-screen-4xl',
19
+ ],
20
+ plugins: [],
21
+ }
@@ -0,0 +1,41 @@
1
+ import { defineConfig } from 'vite'
2
+ import path, { dirname, resolve } from 'node:path'
3
+ import vue from '@vitejs/plugin-vue'
4
+ import { fileURLToPath } from 'node:url'
5
+ import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite'
6
+ import tailwindcss from '@tailwindcss/vite'
7
+
8
+ export default defineConfig({
9
+ plugins: [
10
+ vue({
11
+ template: {
12
+ compilerOptions: {
13
+ isCustomElement: (tag: string) => tag.startsWith('fds-'),
14
+ },
15
+ },
16
+ }),
17
+ VueI18nPlugin({
18
+ include: resolve(dirname(fileURLToPath(import.meta.url)), './src/lang/**'),
19
+ strictMessage: false,
20
+ }),
21
+ tailwindcss(),
22
+ ],
23
+ resolve: {
24
+ alias: {
25
+ '@': path.resolve(__dirname, 'src'),
26
+ },
27
+ },
28
+ server: {
29
+ port: 5173,
30
+ strictPort: true,
31
+ watch: {
32
+ usePolling: true,
33
+ },
34
+ fs: {
35
+ allow: ['..'],
36
+ },
37
+ },
38
+ css: {
39
+ devSourcemap: true,
40
+ },
41
+ })
@@ -9,6 +9,7 @@
9
9
  * pnpm exec fg-devkit policy-vulns [policy-file]
10
10
  * pnpm exec fg-devkit repo-vulns
11
11
  * pnpm exec fg-devkit fleet-vulns [projects-root] [--summary-only] [--delay-ms=<ms>]
12
+ * pnpm exec fg-devkit fleet-bump-fg-devkit [projects-root] [--dry-run]
12
13
  * pnpm exec fg-devkit policy-upgrade [policy-file] [ncu-args...]
13
14
  * pnpm exec fg-devkit sync-project-configs (VS Code + tooling sync; overwrites config files)
14
15
  * pnpm exec fg-devkit sync-tooling-configs (tooling sync only; overwrites config files)
@@ -26,11 +27,14 @@
26
27
  * pnpm exec fg-devkit format-check [prettier-args...] (same as format plus --check)
27
28
  * pnpm exec fg-devkit typecheck [vue-tsc-args...] (run vue-tsc with fg-devkit bundled package)
28
29
  * pnpm exec fg-devkit typecheck-watch [vue-tsc-args...] (same as typecheck plus --watch)
30
+ * pnpm exec fg-devkit poeditor-download [args...] (download translations using poeditor/.poeditor.json + env token)
31
+ * pnpm exec fg-devkit poeditor-upload [args...] (upload translations using poeditor/.poeditor.json + env token)
29
32
  * Interactive menu (no subcommand):
30
33
  * pnpm exec fg-devkit
31
34
  *
32
35
  * Maintainer-only policy regeneration is not exposed here; run
33
- * node node_modules/fg-devkit/scripts/refresh-dependency-policy-from-repos.mjs from a checkout when needed.
36
+ * node node_modules/fg-devkit/scripts/refresh-dependency-policy-from-repos.mjs from a checkout when needed
37
+ * (that script also refreshes scaffold/vue/package.json versions from the policy).
34
38
  */
35
39
 
36
40
  import { spawnSync } from 'node:child_process'
@@ -250,7 +254,7 @@ function runMaintainerPolicyUpgradeAndCommit() {
250
254
  }
251
255
 
252
256
  const [only] = changed
253
- const stageCode = stageFiles(['configs/dependency-policy.vue-workspace.json'])
257
+ const stageCode = stageFiles(['configs/dependency-policy.vue-workspace.json', 'scaffold/vue/package.json'])
254
258
  if (stageCode !== 0) return stageCode
255
259
 
256
260
  const commitMessage = `dep(${only.name}): bump to ${only.to}`
@@ -408,6 +412,7 @@ const DIRECT_COMMANDS = {
408
412
  'policy-vulns': 'policy-vulns.mjs',
409
413
  'repo-vulns': 'repo-vulns.mjs',
410
414
  'fleet-vulns': 'fleet-vulns.mjs',
415
+ 'fleet-bump-fg-devkit': 'fleet-bump-fg-devkit.mjs',
411
416
  'policy-upgrade': 'policy-upgrade.mjs',
412
417
  'sync-project-configs': 'sync-project-configs.mjs',
413
418
  'sync-tooling-configs': 'sync-tooling-configs.mjs',
@@ -415,6 +420,9 @@ const DIRECT_COMMANDS = {
415
420
  'sync-consumer-scripts': 'sync-consumer-scripts.mjs',
416
421
  'setup-lefthook': 'setup-lefthook',
417
422
  'prune-consumer-deps': 'prune-consumer-deps.mjs',
423
+ 'sync-scaffold-vue-deps': 'sync-scaffold-vue-deps.mjs',
424
+ 'poeditor-download': 'poeditor-download.mjs',
425
+ 'poeditor-upload': 'poeditor-upload.mjs',
418
426
  }
419
427
 
420
428
  function printHelp() {
@@ -429,6 +437,8 @@ Commands:
429
437
  repo-vulns Check all repo dependencies for vulnerabilities via pnpm audit
430
438
  fleet-vulns [projects-root] [--summary-only] [--delay-ms=<ms>]
431
439
  Run pnpm audit across Vue repos (full output + combined summary)
440
+ fleet-bump-fg-devkit [projects-root] [--dry-run]
441
+ Bump fg-devkit to latest across Vue repos, then run pnpm install
432
442
  policy-upgrade [policy-file] [ncu-args...]
433
443
  Upgrade policy packages via npm-check-updates (default interactive)
434
444
  sync-project-configs VS Code + tooling sync (overwrites config files)
@@ -448,6 +458,12 @@ Commands:
448
458
  format-check [prettier-args...] Run Prettier with --check using fg-devkit bundled package
449
459
  typecheck [vue-tsc-args...] Run vue-tsc using fg-devkit bundled package
450
460
  typecheck-watch [vue-tsc-args...] Run vue-tsc with --watch using fg-devkit bundled package
461
+ poeditor-download [args...] Download locale files via PoEditor (reads poeditor/.poeditor.json by default)
462
+ poeditor-upload [args...] Upload locale file via PoEditor (reads poeditor/.poeditor.json by default)
463
+ scaffold-vue <target-dir> [--name …] [--slug …] [--force] …
464
+ Copy the bundled Vue scaffold into target-dir (see script header)
465
+ sync-scaffold-vue-deps [policy-file]
466
+ Align scaffold/vue/package.json versions to the dependency policy
451
467
 
452
468
  With no command, opens an interactive menu.
453
469
 
@@ -517,7 +533,7 @@ const MAINTAINER_MENU = [
517
533
  },
518
534
  {
519
535
  key: 'm3',
520
- label: 'Maintainer: Refresh dependency policy from repos',
536
+ label: 'Maintainer: Refresh dependency policy from repos (and scaffold/vue package.json)',
521
537
  run: () => runNode('refresh-dependency-policy-from-repos.mjs'),
522
538
  },
523
539
  {
@@ -540,6 +556,11 @@ const MAINTAINER_MENU = [
540
556
  label: 'Maintainer: Check vulnerabilities across all Vue repos',
541
557
  run: () => runNode('fleet-vulns.mjs'),
542
558
  },
559
+ {
560
+ key: 'm8',
561
+ label: 'Maintainer: Bump fg-devkit in all Vue repos and run pnpm install',
562
+ run: () => runNode('fleet-bump-fg-devkit.mjs'),
563
+ },
543
564
  ]
544
565
 
545
566
  function isFgDevkitMaintainerContext() {
@@ -638,6 +659,10 @@ async function main() {
638
659
  process.exitCode = runNode('vuln-commit.mjs', rest)
639
660
  return
640
661
  }
662
+ if (sub === 'scaffold-vue') {
663
+ process.exitCode = runNode('scaffold-vue.mjs', rest)
664
+ return
665
+ }
641
666
  if (sub === 'lint') {
642
667
  process.exitCode = runBundledEslint(rest.length > 0 ? rest : ['.'])
643
668
  return
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Bumps fg-devkit to latest in Vue repositories and runs pnpm install.
4
+ *
5
+ * Usage:
6
+ * pnpm exec fg-devkit fleet-bump-fg-devkit [projects-root] [--dry-run]
7
+ */
8
+
9
+ import fs from 'node:fs'
10
+ import path from 'node:path'
11
+ import { spawnSync } from 'node:child_process'
12
+ import { fileURLToPath } from 'node:url'
13
+
14
+ const scriptDir = path.dirname(fileURLToPath(import.meta.url))
15
+ const cliArgs = process.argv.slice(2)
16
+ const dryRun = cliArgs.includes('--dry-run')
17
+ const projectsRootArg = cliArgs.find((a) => !a.startsWith('-'))
18
+ const projectsRoot = projectsRootArg ? path.resolve(process.cwd(), projectsRootArg) : path.resolve(scriptDir, '../..')
19
+
20
+ function walk(dir, acc = []) {
21
+ for (const item of fs.readdirSync(dir, { withFileTypes: true })) {
22
+ if (item.name === 'node_modules' || item.name === '.git') continue
23
+ const p = path.join(dir, item.name)
24
+ if (item.isDirectory()) walk(p, acc)
25
+ else if (item.name === 'package.json') acc.push(p)
26
+ }
27
+ return acc
28
+ }
29
+
30
+ function readJson(p) {
31
+ return JSON.parse(fs.readFileSync(p, 'utf8'))
32
+ }
33
+
34
+ function isVueProject(pkg) {
35
+ return Boolean(pkg.dependencies?.vue || pkg.devDependencies?.vue)
36
+ }
37
+
38
+ function hasFgDevkit(pkg) {
39
+ return Boolean(pkg.dependencies?.['fg-devkit'] || pkg.devDependencies?.['fg-devkit'])
40
+ }
41
+
42
+ function isFgDevkitRepo(pkg) {
43
+ return pkg?.name === 'fg-devkit'
44
+ }
45
+
46
+ const packageJsonFiles = walk(projectsRoot)
47
+ const repos = []
48
+ for (const packageJsonPath of packageJsonFiles) {
49
+ try {
50
+ const pkg = readJson(packageJsonPath)
51
+ if (!isVueProject(pkg)) continue
52
+ if (isFgDevkitRepo(pkg)) continue
53
+ if (!hasFgDevkit(pkg)) continue
54
+ repos.push(path.dirname(packageJsonPath))
55
+ } catch {
56
+ // Ignore invalid package.json files.
57
+ }
58
+ }
59
+
60
+ const uniqueRepos = [...new Set(repos)].sort((a, b) => a.localeCompare(b))
61
+ if (uniqueRepos.length === 0) {
62
+ console.log(`[fleet-bump-fg-devkit] No Vue repositories with fg-devkit found under ${projectsRoot}`)
63
+ process.exit(0)
64
+ }
65
+
66
+ console.log(`[fleet-bump-fg-devkit] projectsRoot=${projectsRoot}`)
67
+ console.log(`[fleet-bump-fg-devkit] repositories=${uniqueRepos.length}`)
68
+
69
+ let failures = 0
70
+ let updated = 0
71
+
72
+ for (const repoDir of uniqueRepos) {
73
+ const repoName = path.basename(repoDir)
74
+ const bumpArgs = ['up', 'fg-devkit@latest']
75
+ const installArgs = ['install']
76
+
77
+ if (dryRun) {
78
+ console.log(`[fleet-bump-fg-devkit] DRY RUN ${repoName}: pnpm ${bumpArgs.join(' ')}`)
79
+ console.log(`[fleet-bump-fg-devkit] DRY RUN ${repoName}: pnpm ${installArgs.join(' ')}`)
80
+ continue
81
+ }
82
+
83
+ console.log(`\n[fleet-bump-fg-devkit] Updating ${repoName}`)
84
+ const bump = spawnSync('pnpm', bumpArgs, {
85
+ cwd: repoDir,
86
+ stdio: 'inherit',
87
+ env: {
88
+ ...process.env,
89
+ npm_config_save_prefix: '',
90
+ npm_config_save_exact: 'true',
91
+ },
92
+ })
93
+
94
+ if (bump.error) {
95
+ failures += 1
96
+ console.error(`[fleet-bump-fg-devkit] FAIL ${repoName}: ${bump.error.message}`)
97
+ continue
98
+ }
99
+
100
+ const bumpStatus = bump.status ?? 1
101
+ if (bumpStatus !== 0) {
102
+ failures += 1
103
+ console.error(`[fleet-bump-fg-devkit] FAIL ${repoName}: pnpm up exited with code ${bumpStatus}`)
104
+ continue
105
+ }
106
+
107
+ const install = spawnSync('pnpm', installArgs, {
108
+ cwd: repoDir,
109
+ stdio: 'inherit',
110
+ env: {
111
+ ...process.env,
112
+ npm_config_save_prefix: '',
113
+ npm_config_save_exact: 'true',
114
+ },
115
+ })
116
+
117
+ if (install.error) {
118
+ failures += 1
119
+ console.error(`[fleet-bump-fg-devkit] FAIL ${repoName}: ${install.error.message}`)
120
+ continue
121
+ }
122
+
123
+ const status = install.status ?? 1
124
+ if (status !== 0) {
125
+ failures += 1
126
+ console.error(`[fleet-bump-fg-devkit] FAIL ${repoName}: pnpm install exited with code ${status}`)
127
+ continue
128
+ }
129
+
130
+ updated += 1
131
+ console.log(`[fleet-bump-fg-devkit] OK ${repoName}`)
132
+ }
133
+
134
+ console.log(`\n[fleet-bump-fg-devkit] Done: updated=${updated}, failed=${failures}`)
135
+ process.exit(failures > 0 ? 1 : 0)
@@ -0,0 +1,118 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+
4
+ function readJsonIfExists(filePath) {
5
+ if (!fs.existsSync(filePath)) return null
6
+ try {
7
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'))
8
+ } catch {
9
+ return null
10
+ }
11
+ }
12
+
13
+ function resolveFromCwd(p) {
14
+ return path.isAbsolute(p) ? p : path.resolve(process.cwd(), p)
15
+ }
16
+
17
+ function resolveLegacyConfig(raw) {
18
+ // Legacy format: { "<projectId>": { "path": "./src/lang/" } }
19
+ const [firstKey] = Object.keys(raw ?? {})
20
+ if (!firstKey) return null
21
+ const value = raw[firstKey]
22
+ if (!value || typeof value !== 'object') return null
23
+ return {
24
+ projectId: firstKey,
25
+ downloadPath: typeof value.path === 'string' ? value.path : './src/lang/',
26
+ languages: Array.isArray(value.languages) ? value.languages.filter((x) => typeof x === 'string' && x.trim() !== '') : undefined,
27
+ defaultLanguage: typeof value.defaultLanguage === 'string' ? value.defaultLanguage : 'sv',
28
+ defaultFormat: typeof value.defaultFormat === 'string' ? value.defaultFormat : 'json',
29
+ uploadFile: typeof value.uploadFile === 'string' ? value.uploadFile : './src/lang/sv.json',
30
+ }
31
+ }
32
+
33
+ function resolveObjectConfig(raw) {
34
+ if (!raw || typeof raw !== 'object') return null
35
+ if (typeof raw.projectId !== 'string') return null
36
+ return {
37
+ projectId: raw.projectId,
38
+ downloadPath: typeof raw.downloadPath === 'string' ? raw.downloadPath : './src/lang/',
39
+ languages: Array.isArray(raw.languages) ? raw.languages.filter((x) => typeof x === 'string' && x.trim() !== '') : undefined,
40
+ defaultLanguage: typeof raw.defaultLanguage === 'string' ? raw.defaultLanguage : 'sv',
41
+ defaultFormat: typeof raw.defaultFormat === 'string' ? raw.defaultFormat : 'json',
42
+ uploadFile: typeof raw.uploadFile === 'string' ? raw.uploadFile : './src/lang/sv.json',
43
+ }
44
+ }
45
+
46
+ export function parseFlags(argv) {
47
+ const positional = []
48
+ const flags = new Map()
49
+ for (let i = 0; i < argv.length; i += 1) {
50
+ const a = argv[i]
51
+ if (!a.startsWith('--')) {
52
+ positional.push(a)
53
+ continue
54
+ }
55
+ const key = a.slice(2)
56
+ const next = argv[i + 1]
57
+ if (next && !next.startsWith('--')) {
58
+ if (flags.has(key)) {
59
+ const prev = flags.get(key)
60
+ flags.set(key, Array.isArray(prev) ? [...prev, next] : [prev, next])
61
+ } else {
62
+ flags.set(key, next)
63
+ }
64
+ i += 1
65
+ } else {
66
+ flags.set(key, 'true')
67
+ }
68
+ }
69
+ return { positional, flags }
70
+ }
71
+
72
+ export function getFlagValue(flags, key, fallback = undefined) {
73
+ const value = flags.get(key)
74
+ if (value === undefined) return fallback
75
+ if (Array.isArray(value)) return value[value.length - 1]
76
+ return value
77
+ }
78
+
79
+ export function getFlagList(flags, key) {
80
+ const value = flags.get(key)
81
+ if (value === undefined) return []
82
+ return Array.isArray(value) ? value : [value]
83
+ }
84
+
85
+ export function loadPoeditorConfig(flags) {
86
+ const configPath = resolveFromCwd(getFlagValue(flags, 'config', './poeditor/.poeditor.json'))
87
+ const raw = readJsonIfExists(configPath) ?? {}
88
+ const resolved = resolveObjectConfig(raw) ?? resolveLegacyConfig(raw) ?? {}
89
+ return {
90
+ configPath,
91
+ projectId: resolved.projectId ?? '000000',
92
+ downloadPath: resolved.downloadPath ?? './src/lang/',
93
+ languages: resolved.languages ?? undefined,
94
+ defaultLanguage: resolved.defaultLanguage ?? 'sv',
95
+ defaultFormat: resolved.defaultFormat ?? 'json',
96
+ uploadFile: resolved.uploadFile ?? './src/lang/sv.json',
97
+ }
98
+ }
99
+
100
+ export function getPoeditorToken(flags) {
101
+ const fromFlag = getFlagValue(flags, 'api-token')
102
+ if (fromFlag) return fromFlag
103
+ if (process.env.POEDITOR_API_KEY) return process.env.POEDITOR_API_KEY
104
+ const publicConfigPath = resolveFromCwd('./public/config.json')
105
+ const publicConfig = readJsonIfExists(publicConfigPath)
106
+ if (typeof publicConfig?.POEDITOR_API_KEY === 'string') {
107
+ return publicConfig.POEDITOR_API_KEY
108
+ }
109
+ return ''
110
+ }
111
+
112
+ export function ensureDir(dirPath) {
113
+ fs.mkdirSync(dirPath, { recursive: true })
114
+ }
115
+
116
+ export function resolvePathFromCwd(p) {
117
+ return resolveFromCwd(p)
118
+ }
@@ -0,0 +1,101 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Download translation files from PoEditor.
4
+ *
5
+ * Usage:
6
+ * pnpm exec fg-devkit poeditor-download [--config ./poeditor/.poeditor.json] [--project-id 123]
7
+ * [--language sv --language en] [--format json] [--output-dir ./src/lang] [--api-token xxx]
8
+ */
9
+
10
+ import fs from 'node:fs'
11
+ import path from 'node:path'
12
+
13
+ import {
14
+ ensureDir,
15
+ getFlagList,
16
+ getFlagValue,
17
+ getPoeditorToken,
18
+ loadPoeditorConfig,
19
+ parseFlags,
20
+ resolvePathFromCwd,
21
+ } from './poeditor-common.mjs'
22
+
23
+ async function exportLanguage({ token, projectId, language, format }) {
24
+ const params = new URLSearchParams({
25
+ api_token: token,
26
+ id: String(projectId),
27
+ language,
28
+ type: format === 'json' ? 'key_value_json' : format,
29
+ })
30
+ const res = await fetch('https://api.poeditor.com/v2/projects/export', {
31
+ method: 'POST',
32
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
33
+ body: params,
34
+ })
35
+ if (!res.ok) {
36
+ throw new Error(`PoEditor export failed (${res.status})`)
37
+ }
38
+ const data = await res.json()
39
+ const url = data?.result?.url
40
+ if (!url) {
41
+ throw new Error(`PoEditor export missing URL for language ${language}`)
42
+ }
43
+ return url
44
+ }
45
+
46
+ function prettifyJsonFile(filePath) {
47
+ let raw = fs.readFileSync(filePath, 'utf8')
48
+ if (raw.charCodeAt(0) === 0xfeff) raw = raw.slice(1)
49
+ const parsed = JSON.parse(raw)
50
+ const sorted = Object.keys(parsed)
51
+ .sort((a, b) => a.localeCompare(b))
52
+ .reduce((acc, key) => {
53
+ acc[key] = parsed[key]
54
+ return acc
55
+ }, {})
56
+ fs.writeFileSync(filePath, `${JSON.stringify(sorted, null, 2)}\n`, 'utf8')
57
+ }
58
+
59
+ async function downloadToFile(url, filePath, format) {
60
+ const res = await fetch(url)
61
+ if (!res.ok) {
62
+ throw new Error(`Failed to download ${url} (${res.status})`)
63
+ }
64
+ const text = await res.text()
65
+ fs.writeFileSync(filePath, text, 'utf8')
66
+ if (format === 'json') {
67
+ prettifyJsonFile(filePath)
68
+ }
69
+ }
70
+
71
+ async function main() {
72
+ const { flags } = parseFlags(process.argv.slice(2))
73
+ const cfg = loadPoeditorConfig(flags)
74
+
75
+ const token = getPoeditorToken(flags)
76
+ if (!token) {
77
+ console.error('[poeditor-download] Missing POEDITOR_API_KEY (env), --api-token, or public/config.json value.')
78
+ process.exit(1)
79
+ }
80
+
81
+ const projectId = getFlagValue(flags, 'project-id', cfg.projectId)
82
+ const format = getFlagValue(flags, 'format', cfg.defaultFormat)
83
+ const outputDir = resolvePathFromCwd(getFlagValue(flags, 'output-dir', cfg.downloadPath))
84
+ const requested = getFlagList(flags, 'language')
85
+ const fromConfig = Array.isArray(cfg.languages) ? cfg.languages : []
86
+ const languages = requested.length > 0 ? requested : fromConfig.length > 0 ? fromConfig : [cfg.defaultLanguage]
87
+
88
+ ensureDir(outputDir)
89
+
90
+ for (const language of languages) {
91
+ const url = await exportLanguage({ token, projectId, language, format })
92
+ const outPath = path.resolve(outputDir, `${language}.${format}`)
93
+ await downloadToFile(url, outPath, format)
94
+ console.log(`[poeditor-download] Saved ${outPath}`)
95
+ }
96
+ }
97
+
98
+ main().catch((err) => {
99
+ console.error('[poeditor-download]', err.message)
100
+ process.exit(1)
101
+ })
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Upload translation files to PoEditor.
4
+ *
5
+ * Usage:
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]
8
+ */
9
+
10
+ import fs from 'node:fs'
11
+
12
+ import { getFlagValue, getPoeditorToken, loadPoeditorConfig, parseFlags, resolvePathFromCwd } from './poeditor-common.mjs'
13
+
14
+ async function uploadFile({ token, projectId, language, updating }) {
15
+ const params = new URLSearchParams({
16
+ api_token: token,
17
+ id: String(projectId),
18
+ language,
19
+ updating,
20
+ })
21
+
22
+ const res = await fetch('https://api.poeditor.com/v2/projects/upload', {
23
+ method: 'POST',
24
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
25
+ body: params,
26
+ })
27
+ if (!res.ok) {
28
+ throw new Error(`PoEditor upload failed (${res.status})`)
29
+ }
30
+ const data = await res.json()
31
+ if (data?.response?.status !== 'success') {
32
+ throw new Error(`Upload failed: ${JSON.stringify(data)}`)
33
+ }
34
+ }
35
+
36
+ async function main() {
37
+ const { flags } = parseFlags(process.argv.slice(2))
38
+ const cfg = loadPoeditorConfig(flags)
39
+
40
+ const token = getPoeditorToken(flags)
41
+ if (!token) {
42
+ console.error('[poeditor-upload] Missing POEDITOR_API_KEY (env), --api-token, or public/config.json value.')
43
+ process.exit(1)
44
+ }
45
+
46
+ const projectId = getFlagValue(flags, 'project-id', cfg.projectId)
47
+ const language = getFlagValue(flags, 'language', cfg.defaultLanguage)
48
+ const filePath = resolvePathFromCwd(getFlagValue(flags, 'file', cfg.uploadFile))
49
+ const updating = fs.readFileSync(filePath, 'utf8')
50
+
51
+ await uploadFile({ token, projectId, language, updating })
52
+ console.log(`[poeditor-upload] Upload completed for ${filePath}`)
53
+ }
54
+
55
+ main().catch((err) => {
56
+ console.error('[poeditor-upload]', err.message)
57
+ process.exit(1)
58
+ })
@@ -18,6 +18,7 @@ import { spawnSync } from 'node:child_process'
18
18
  import { fileURLToPath } from 'node:url'
19
19
  import { createRequire } from 'node:module'
20
20
  import { resolveDependencyPolicyPath } from './resolve-dependency-policy-path.mjs'
21
+ import { syncScaffoldVueDepsFromPolicy } from './sync-scaffold-vue-deps.mjs'
21
22
 
22
23
  const require = createRequire(import.meta.url)
23
24
  const scriptDir = path.dirname(fileURLToPath(import.meta.url))
@@ -114,4 +115,6 @@ for (const [name, version] of Object.entries(updatedDevDeps)) {
114
115
  }
115
116
 
116
117
  fs.writeFileSync(policyPath, `${JSON.stringify(policy, null, 2)}\n`, 'utf8')
118
+ const fgDevkitRoot = path.resolve(scriptDir, '..')
119
+ syncScaffoldVueDepsFromPolicy({ fgDevkitRoot, policyPath })
117
120
  console.log('[policy-upgrade] Updated dependency policy from selected ncu changes.')
@@ -17,6 +17,9 @@
17
17
  *
18
18
  * Runs entirely on the local filesystem (no registry or network).
19
19
  *
20
+ * After writing the policy, also updates scaffold/vue/package.json dependency pins from that policy
21
+ * (see sync-scaffold-vue-deps.mjs).
22
+ *
20
23
  * Usage:
21
24
  * node scripts/refresh-dependency-policy-from-repos.mjs [projectsRoot] [policyFile]
22
25
  */
@@ -26,6 +29,7 @@ import path from 'node:path'
26
29
  import { fileURLToPath } from 'node:url'
27
30
 
28
31
  import { collectPinViolations } from './dependency-policy-pins.mjs'
32
+ import { syncScaffoldVueDepsFromPolicy } from './sync-scaffold-vue-deps.mjs'
29
33
 
30
34
  const scriptDir = path.dirname(fileURLToPath(import.meta.url))
31
35
  const fgDevkitRoot = path.resolve(scriptDir, '..')
@@ -207,6 +211,9 @@ if (policyErrors.length > 0) {
207
211
 
208
212
  fs.writeFileSync(policyPath, `${JSON.stringify(policy, null, 2)}\n`)
209
213
 
214
+ const fgDevkitRoot = path.resolve(scriptDir, '..')
215
+ syncScaffoldVueDepsFromPolicy({ fgDevkitRoot, policyPath })
216
+
210
217
  console.log(`[refresh-policy] projectsRoot=${projectsRoot}`)
211
218
  console.log(`[refresh-policy] policy=${policyPath}`)
212
219
  console.log(