@inlang/paraglide-js 2.23.2 → 2.24.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 (72) hide show
  1. package/dist/bundler-plugins/per-locale-build/vite-environments.js +12 -7
  2. package/dist/bundler-plugins/per-locale-build/vite-environments.js.map +1 -1
  3. package/dist/bundler-plugins/unplugin.js +134 -4
  4. package/dist/bundler-plugins/unplugin.js.map +1 -1
  5. package/dist/bundler-plugins/unplugin.test.js +77 -0
  6. package/dist/bundler-plugins/unplugin.test.js.map +1 -1
  7. package/dist/cli/commands/compile/command.js +8 -1
  8. package/dist/cli/commands/compile/command.js.map +1 -1
  9. package/dist/cli/commands/compile/command.test.js +45 -0
  10. package/dist/cli/commands/compile/command.test.js.map +1 -1
  11. package/dist/compiler/compile.js +0 -15
  12. package/dist/compiler/compile.js.map +1 -1
  13. package/dist/compiler/compile.test.js +0 -52
  14. package/dist/compiler/compile.test.js.map +1 -1
  15. package/dist/compiler/compiler-options.d.ts +8 -0
  16. package/dist/compiler/compiler-options.js.map +1 -1
  17. package/dist/compiler/runtime/create-runtime.d.ts +1 -0
  18. package/dist/compiler/runtime/create-runtime.js +11 -0
  19. package/dist/compiler/runtime/create-runtime.js.map +1 -1
  20. package/dist/compiler/runtime/exec-url-pattern.d.ts +9 -0
  21. package/dist/compiler/runtime/exec-url-pattern.js +31 -0
  22. package/dist/compiler/runtime/exec-url-pattern.js.map +1 -0
  23. package/dist/compiler/runtime/extract-locale-from-url.js +8 -3
  24. package/dist/compiler/runtime/extract-locale-from-url.js.map +1 -1
  25. package/dist/compiler/runtime/generate-static-localized-urls.js +12 -6
  26. package/dist/compiler/runtime/generate-static-localized-urls.js.map +1 -1
  27. package/dist/compiler/runtime/generate-static-localized-urls.test.js +46 -0
  28. package/dist/compiler/runtime/generate-static-localized-urls.test.js.map +1 -1
  29. package/dist/compiler/runtime/localize-href.bench.d.ts +1 -0
  30. package/dist/compiler/runtime/localize-href.bench.js +77 -0
  31. package/dist/compiler/runtime/localize-href.bench.js.map +1 -0
  32. package/dist/compiler/runtime/localize-href.test.js +43 -0
  33. package/dist/compiler/runtime/localize-href.test.js.map +1 -1
  34. package/dist/compiler/runtime/localize-url.d.ts +14 -0
  35. package/dist/compiler/runtime/localize-url.js +376 -18
  36. package/dist/compiler/runtime/localize-url.js.map +1 -1
  37. package/dist/compiler/runtime/localize-url.test.js +163 -0
  38. package/dist/compiler/runtime/localize-url.test.js.map +1 -1
  39. package/dist/compiler/runtime/normalize-trailing-slash.d.ts +10 -0
  40. package/dist/compiler/runtime/normalize-trailing-slash.js +23 -0
  41. package/dist/compiler/runtime/normalize-trailing-slash.js.map +1 -0
  42. package/dist/compiler/runtime/route-strategy.js +4 -2
  43. package/dist/compiler/runtime/route-strategy.js.map +1 -1
  44. package/dist/compiler/runtime/should-redirect.js +4 -1
  45. package/dist/compiler/runtime/should-redirect.js.map +1 -1
  46. package/dist/compiler/runtime/should-redirect.test.js +30 -2
  47. package/dist/compiler/runtime/should-redirect.test.js.map +1 -1
  48. package/dist/compiler/runtime/type.d.ts +1 -0
  49. package/dist/compiler/runtime/type.js.map +1 -1
  50. package/dist/compiler/runtime/variables.d.ts +6 -0
  51. package/dist/compiler/runtime/variables.js +6 -0
  52. package/dist/compiler/runtime/variables.js.map +1 -1
  53. package/dist/compiler/server/middleware.js +6 -2
  54. package/dist/compiler/server/middleware.js.map +1 -1
  55. package/dist/compiler/server/middleware.test.js +34 -0
  56. package/dist/compiler/server/middleware.test.js.map +1 -1
  57. package/dist/index.test.d.ts +1 -0
  58. package/dist/index.test.js +9 -0
  59. package/dist/index.test.js.map +1 -0
  60. package/dist/services/env-variables/index.js +1 -1
  61. package/dist/services/env-variables/index.js.map +1 -1
  62. package/dist/services/file-handling/write-output.bench.d.ts +1 -0
  63. package/dist/services/file-handling/write-output.bench.js +24 -0
  64. package/dist/services/file-handling/write-output.bench.js.map +1 -0
  65. package/dist/services/file-handling/write-output.js +16 -20
  66. package/dist/services/file-handling/write-output.js.map +1 -1
  67. package/dist/services/file-handling/write-output.test.js +27 -0
  68. package/dist/services/file-handling/write-output.test.js.map +1 -1
  69. package/package.json +2 -2
  70. package/dist/services/account/index.d.ts +0 -14
  71. package/dist/services/account/index.js +0 -43
  72. package/dist/services/account/index.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"write-output.js","sourceRoot":"","sources":["../../../src/services/file-handling/write-output.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAG7B,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAMjC;IACA,MAAM,mBAAmB,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC;IAE1E,+CAA+C;IAC/C,MAAM,YAAY,GAAG,IAAI,GAAG,EAAE,CAAC;IAE/B,KAAK,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,mBAAmB,CAAC,EAAE,CAAC;QACpE,IAAI,IAAI,CAAC,oBAAoB,EAAE,CAAC,QAAQ,CAAC,KAAK,IAAI,EAAE,CAAC;YACpD,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC5B,CAAC;IACF,CAAC;IAED,0DAA0D;IAC1D,MAAM,aAAa,GAAG,IAAI,GAAG,EAAU,CAAC;IACxC,IAAI,IAAI,CAAC,oBAAoB,EAAE,CAAC;QAC/B,KAAK,MAAM,QAAQ,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,oBAAoB,CAAC,EAAE,CAAC;YAC/D,IAAI,CAAC,mBAAmB,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACpC,aAAa,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAC7B,CAAC;QACF,CAAC;IACF,CAAC;IAED,IAAI,YAAY,CAAC,IAAI,KAAK,CAAC,IAAI,aAAa,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACzD,OAAO,mBAAmB,CAAC;IAC5B,CAAC;IAED,6BAA6B;IAC7B,EAAE;IACF,8EAA8E;IAC9E,oFAAoF;IACpF,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;QACzB,MAAM,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACpE,CAAC;SAAM,CAAC;QACP,MAAM,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1D,CAAC;IACD,sCAAsC;IACtC,gFAAgF;IAChF,IAAI,aAAa,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC;QACpD,MAAM,kBAAkB,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;IAClE,CAAC;IAED,wDAAwD;IACxD,MAAM,OAAO,CAAC,UAAU,CACvB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE;QAC/C,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QACxD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QACzC,MAAM,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACrD,CAAC,CAAC,CACF,CAAC;IAEF,aAAa;IACb,MAAM,OAAO,CAAC,UAAU,CACvB,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,QAAQ,EAAE,WAAW,CAAC,EAAE,EAAE;QACjE,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YACjC,OAAO;QACR,CAAC;QACD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QACxD,MAAM,IAAI,CAAC,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IAChD,CAAC,CAAC,CACF,CAAC;IAEF,kEAAkE;IAClE,OAAO,mBAAmB,CAAC;AAC5B,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,kBAAkB,CAChC,EAAiB,EACjB,aAAqB,EACrB,aAA0B;IAE1B,8CAA8C;IAC9C,MAAM,kBAAkB,GAAG,IAAI,GAAG,EAAU,CAAC;IAE7C,8DAA8D;IAC9D,MAAM,OAAO,CAAC,UAAU,CACvB,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE;QAChD,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;QACvD,IAAI,CAAC;YACJ,MAAM,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAE1B,6CAA6C;YAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;YACvC,IAAI,OAAO,KAAK,aAAa,EAAE,CAAC;gBAC/B,kBAAkB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACjC,CAAC;QACF,CAAC;QAAC,MAAM,CAAC;YACR,0CAA0C;QAC3C,CAAC;IACF,CAAC,CAAC,CACF,CAAC;IAEF,uEAAuE;IACvE,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,IAAI,CACrD,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,MAAM,CAC7B,CAAC;IAEF,KAAK,MAAM,OAAO,IAAI,UAAU,EAAE,CAAC;QAClC,qDAAqD;QACrD,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,IAAI,OAAO,KAAK,aAAa,EAAE,CAAC;YACrE,SAAS;QACV,CAAC;QAED,IAAI,CAAC;YACJ,MAAM,WAAW,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YAC9C,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBAC9B,MAAM,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;gBAExB,6CAA6C;gBAC7C,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;gBACxC,IAAI,SAAS,KAAK,aAAa,EAAE,CAAC;oBACjC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;gBAC5B,CAAC;YACF,CAAC;QACF,CAAC;QAAC,MAAM,CAAC;YACR,yCAAyC;QAC1C,CAAC;IACF,CAAC;AACF,CAAC;AAED,KAAK,UAAU,UAAU,CAAC,KAAa;IACtC,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;IAClC,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACnC,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IAC/D,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC;IACzD,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACvE,CAAC;AAED,KAAK,UAAU,UAAU,CACxB,MAA8B,EAC9B,eAAuB;IAEvB,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,KAAK,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC9D,MAAM,eAAe,GACpB,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,QAAQ,CAAC,CAAC;QACvD,MAAM,CAAC,QAAQ,CAAC,GAAG,MAAM,UAAU,CAAC,eAAe,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,MAAM,CAAC;AACf,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAClC,SAAiB,EACjB,EAAiB;IAEjB,IAAI,CAAC;QACJ,MAAM,IAAI,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACtC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE;YAAE,OAAO,SAAS,CAAC;IAC3C,CAAC;IAAC,MAAM,CAAC;QACR,OAAO,SAAS,CAAC;IAClB,CAAC;IAED,MAAM,MAAM,GAA2B,EAAE,CAAC;IAE1C,KAAK,UAAU,IAAI,CAAC,UAAkB,EAAE,cAAsB;QAC7D,IAAI,OAAO,CAAC;QACZ,IAAI,CAAC;YACJ,OAAO,GAAG,MAAM,EAAE,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QACjE,CAAC;QAAC,MAAM,CAAC;YACR,OAAO;QACR,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC7B,MAAM,iBAAiB,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;YAC/D,MAAM,YAAY,GAAG,cAAc;gBAClC,CAAC,CAAC,GAAG,cAAc,IAAI,KAAK,CAAC,IAAI,EAAE;gBACnC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC;YACd,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;gBACzB,MAAM,IAAI,CAAC,iBAAiB,EAAE,YAAY,CAAC,CAAC;YAC7C,CAAC;iBAAM,IAAI,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;gBAC3B,IAAI,CAAC;oBACJ,MAAM,WAAW,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC,CAAC;oBAClE,MAAM,eAAe,GACpB,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;oBACrD,MAAM,CAAC,YAAY,CAAC,GAAG,MAAM,UAAU,CAAC,eAAe,CAAC,CAAC;gBAC1D,CAAC;gBAAC,MAAM,CAAC;oBACR,iEAAiE;oBACjE,mEAAmE;gBACpE,CAAC;YACF,CAAC;QACF,CAAC;IACF,CAAC;IAED,MAAM,IAAI,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;IAC1B,OAAO,MAAM,CAAC;AACf,CAAC","sourcesContent":["import path from \"node:path\";\nimport type nodeFs from \"node:fs/promises\";\n\nexport async function writeOutput(args: {\n\tdirectory: string;\n\toutput: Record<string, string>;\n\tcleanDirectory?: boolean;\n\tfs: typeof nodeFs;\n\tpreviousOutputHashes?: Record<string, string>;\n}) {\n\tconst currentOutputHashes = await hashOutput(args.output, args.directory);\n\n\t// if the output hasn't changed, don't write it\n\tconst changedFiles = new Set();\n\n\tfor (const [filePath, hash] of Object.entries(currentOutputHashes)) {\n\t\tif (args.previousOutputHashes?.[filePath] !== hash) {\n\t\t\tchangedFiles.add(filePath);\n\t\t}\n\t}\n\n\t// Find files that have been removed in the current output\n\tconst filesToDelete = new Set<string>();\n\tif (args.previousOutputHashes) {\n\t\tfor (const filePath of Object.keys(args.previousOutputHashes)) {\n\t\t\tif (!currentOutputHashes[filePath]) {\n\t\t\t\tfilesToDelete.add(filePath);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (changedFiles.size === 0 && filesToDelete.size === 0) {\n\t\treturn currentOutputHashes;\n\t}\n\n\t// clear the output directory\n\t//\n\t// disabled because of https://github.com/opral/inlang-paraglide-js/issues/350\n\t// and re-enabled because of https://github.com/opral/inlang-paraglide-js/issues/420\n\tif (args.cleanDirectory) {\n\t\tawait args.fs.rm(args.directory, { recursive: true, force: true });\n\t} else {\n\t\tawait args.fs.mkdir(args.directory, { recursive: true });\n\t}\n\t// Delete files that have been removed\n\t// ignore if cleanDirectory is true because the directory will be cleaned anyway\n\tif (filesToDelete.size > 0 && !args.cleanDirectory) {\n\t\tawait deleteRemovedFiles(args.fs, args.directory, filesToDelete);\n\t}\n\n\t//Create missing directories inside the output directory\n\tawait Promise.allSettled(\n\t\tObject.keys(args.output).map(async (filePath) => {\n\t\t\tconst fullPath = path.resolve(args.directory, filePath);\n\t\t\tconst directory = path.dirname(fullPath);\n\t\t\tawait args.fs.mkdir(directory, { recursive: true });\n\t\t})\n\t);\n\n\t//Write files\n\tawait Promise.allSettled(\n\t\tObject.entries(args.output).map(async ([filePath, fileContent]) => {\n\t\t\tif (!changedFiles.has(filePath)) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst fullPath = path.resolve(args.directory, filePath);\n\t\t\tawait args.fs.writeFile(fullPath, fileContent);\n\t\t})\n\t);\n\n\t//Only update the previousOutputHashes if the write was successful\n\treturn currentOutputHashes;\n}\n\n/**\n * Delete files that have been removed and clean up empty directories\n */\nasync function deleteRemovedFiles(\n\tfs: typeof nodeFs,\n\tbaseDirectory: string,\n\tfilesToDelete: Set<string>\n) {\n\t// Collect directories that might need cleanup\n\tconst potentialEmptyDirs = new Set<string>();\n\n\t// First pass: delete all files and collect parent directories\n\tawait Promise.allSettled(\n\t\tArray.from(filesToDelete).map(async (filePath) => {\n\t\t\tconst fullPath = path.resolve(baseDirectory, filePath);\n\t\t\ttry {\n\t\t\t\tawait fs.unlink(fullPath);\n\n\t\t\t\t// Add parent directory for potential cleanup\n\t\t\t\tconst dirPath = path.dirname(fullPath);\n\t\t\t\tif (dirPath !== baseDirectory) {\n\t\t\t\t\tpotentialEmptyDirs.add(dirPath);\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Ignore errors if the file doesn't exist\n\t\t\t}\n\t\t})\n\t);\n\n\t// Second pass: clean up empty directories, starting from deepest paths\n\tconst sortedDirs = Array.from(potentialEmptyDirs).sort(\n\t\t(a, b) => b.length - a.length\n\t);\n\n\tfor (const dirPath of sortedDirs) {\n\t\t// Only process directories within the base directory\n\t\tif (!dirPath.startsWith(baseDirectory) || dirPath === baseDirectory) {\n\t\t\tcontinue;\n\t\t}\n\n\t\ttry {\n\t\t\tconst dirContents = await fs.readdir(dirPath);\n\t\t\tif (dirContents.length === 0) {\n\t\t\t\tawait fs.rmdir(dirPath);\n\n\t\t\t\t// Add parent directory for potential cleanup\n\t\t\t\tconst parentDir = path.dirname(dirPath);\n\t\t\t\tif (parentDir !== baseDirectory) {\n\t\t\t\t\tsortedDirs.push(parentDir);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore errors during directory cleanup\n\t\t}\n\t}\n}\n\nasync function hashString(input: string): Promise<string> {\n\tconst encoder = new TextEncoder();\n\tconst data = encoder.encode(input);\n\tconst hashBuffer = await crypto.subtle.digest(\"SHA-256\", data);\n\tconst hashArray = Array.from(new Uint8Array(hashBuffer));\n\treturn hashArray.map((b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\n\nasync function hashOutput(\n\toutput: Record<string, string>,\n\toutputDirectory: string\n): Promise<Record<string, string>> {\n\tconst hashes: Record<string, string> = {};\n\tfor (const [filePath, fileContent] of Object.entries(output)) {\n\t\tconst combinedContent =\n\t\t\tfileContent + path.resolve(outputDirectory, filePath);\n\t\thashes[filePath] = await hashString(combinedContent);\n\t}\n\treturn hashes;\n}\n\n/**\n * Walk an existing output directory and produce the same hash map that\n * {@link writeOutput} would have returned on a previous compile.\n *\n * Used by the bundler plugins to seed `previousCompilation` on a fresh\n * process so the first compile's diff against unchanged inputs is empty\n * and `writeOutput` short-circuits — letting us avoid wiping the directory\n * out from under concurrent SSR/prerender readers (#659).\n *\n * Returns `undefined` if the directory does not exist. Keys are\n * forward-slash relative paths to match {@link hashOutput}.\n */\nexport async function hashDirectory(\n\tdirectory: string,\n\tfs: typeof nodeFs\n): Promise<Record<string, string> | undefined> {\n\ttry {\n\t\tconst stat = await fs.stat(directory);\n\t\tif (!stat.isDirectory()) return undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n\n\tconst hashes: Record<string, string> = {};\n\n\tasync function walk(currentDir: string, relativePrefix: string) {\n\t\tlet entries;\n\t\ttry {\n\t\t\tentries = await fs.readdir(currentDir, { withFileTypes: true });\n\t\t} catch {\n\t\t\treturn;\n\t\t}\n\t\tfor (const entry of entries) {\n\t\t\tconst entryAbsolutePath = path.resolve(currentDir, entry.name);\n\t\t\tconst relativePath = relativePrefix\n\t\t\t\t? `${relativePrefix}/${entry.name}`\n\t\t\t\t: entry.name;\n\t\t\tif (entry.isDirectory()) {\n\t\t\t\tawait walk(entryAbsolutePath, relativePath);\n\t\t\t} else if (entry.isFile()) {\n\t\t\t\ttry {\n\t\t\t\t\tconst fileContent = await fs.readFile(entryAbsolutePath, \"utf-8\");\n\t\t\t\t\tconst combinedContent =\n\t\t\t\t\t\tfileContent + path.resolve(directory, relativePath);\n\t\t\t\t\thashes[relativePath] = await hashString(combinedContent);\n\t\t\t\t} catch {\n\t\t\t\t\t// Another compiler process may be rewriting this file. A partial\n\t\t\t\t\t// seed is safe; the following compile will repair missing outputs.\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tawait walk(directory, \"\");\n\treturn hashes;\n}\n"]}
1
+ {"version":3,"file":"write-output.js","sourceRoot":"","sources":["../../../src/services/file-handling/write-output.ts"],"names":[],"mappings":"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","sourcesContent":["import path from \"node:path\";\nimport { createHash } from \"node:crypto\";\nimport type nodeFs from \"node:fs/promises\";\n\nexport async function writeOutput(args: {\n\tdirectory: string;\n\toutput: Record<string, string>;\n\tcleanDirectory?: boolean;\n\tfs: typeof nodeFs;\n\tpreviousOutputHashes?: Record<string, string>;\n}) {\n\tconst currentOutputHashes = await hashOutput(args.output, args.directory);\n\n\t// if the output hasn't changed, don't write it\n\tconst changedFiles = new Set<string>();\n\n\tfor (const [filePath, hash] of Object.entries(currentOutputHashes)) {\n\t\tif (args.previousOutputHashes?.[filePath] !== hash) {\n\t\t\tchangedFiles.add(filePath);\n\t\t}\n\t}\n\n\t// Find files that have been removed in the current output\n\tconst filesToDelete = new Set<string>();\n\tif (args.previousOutputHashes) {\n\t\tfor (const filePath of Object.keys(args.previousOutputHashes)) {\n\t\t\tif (!currentOutputHashes[filePath]) {\n\t\t\t\tfilesToDelete.add(filePath);\n\t\t\t}\n\t\t}\n\t}\n\n\tif (changedFiles.size === 0 && filesToDelete.size === 0) {\n\t\treturn currentOutputHashes;\n\t}\n\n\t// clear the output directory\n\t//\n\t// disabled because of https://github.com/opral/inlang-paraglide-js/issues/350\n\t// and re-enabled because of https://github.com/opral/inlang-paraglide-js/issues/420\n\tif (args.cleanDirectory) {\n\t\tawait args.fs.rm(args.directory, { recursive: true, force: true });\n\t}\n\t// Delete files that have been removed\n\t// ignore if cleanDirectory is true because the directory will be cleaned anyway\n\tif (filesToDelete.size > 0 && !args.cleanDirectory) {\n\t\tawait deleteRemovedFiles(args.fs, args.directory, filesToDelete);\n\t}\n\n\t// Create only the directories needed by files that are about to be written.\n\t// On incremental compiles, `output` can contain thousands of unchanged\n\t// message modules; creating every parent directory again adds avoidable\n\t// filesystem work even though only a handful of files changed.\n\tconst directoriesToCreate = new Set<string>();\n\tfor (const filePath of changedFiles) {\n\t\tdirectoriesToCreate.add(\n\t\t\tpath.dirname(path.resolve(args.directory, filePath))\n\t\t);\n\t}\n\tawait Promise.allSettled(\n\t\tArray.from(directoriesToCreate, (directory) =>\n\t\t\targs.fs.mkdir(directory, { recursive: true })\n\t\t)\n\t);\n\n\t//Write files\n\tawait Promise.allSettled(\n\t\tObject.entries(args.output).map(async ([filePath, fileContent]) => {\n\t\t\tif (!changedFiles.has(filePath)) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst fullPath = path.resolve(args.directory, filePath);\n\t\t\tawait args.fs.writeFile(fullPath, fileContent);\n\t\t})\n\t);\n\n\t//Only update the previousOutputHashes if the write was successful\n\treturn currentOutputHashes;\n}\n\n/**\n * Delete files that have been removed and clean up empty directories\n */\nasync function deleteRemovedFiles(\n\tfs: typeof nodeFs,\n\tbaseDirectory: string,\n\tfilesToDelete: Set<string>\n) {\n\t// Collect directories that might need cleanup\n\tconst potentialEmptyDirs = new Set<string>();\n\n\t// First pass: delete all files and collect parent directories\n\tawait Promise.allSettled(\n\t\tArray.from(filesToDelete).map(async (filePath) => {\n\t\t\tconst fullPath = path.resolve(baseDirectory, filePath);\n\t\t\ttry {\n\t\t\t\tawait fs.unlink(fullPath);\n\n\t\t\t\t// Add parent directory for potential cleanup\n\t\t\t\tconst dirPath = path.dirname(fullPath);\n\t\t\t\tif (dirPath !== baseDirectory) {\n\t\t\t\t\tpotentialEmptyDirs.add(dirPath);\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Ignore errors if the file doesn't exist\n\t\t\t}\n\t\t})\n\t);\n\n\t// Second pass: clean up empty directories, starting from deepest paths\n\tconst sortedDirs = Array.from(potentialEmptyDirs).sort(\n\t\t(a, b) => b.length - a.length\n\t);\n\n\tfor (const dirPath of sortedDirs) {\n\t\t// Only process directories within the base directory\n\t\tif (!dirPath.startsWith(baseDirectory) || dirPath === baseDirectory) {\n\t\t\tcontinue;\n\t\t}\n\n\t\ttry {\n\t\t\tconst dirContents = await fs.readdir(dirPath);\n\t\t\tif (dirContents.length === 0) {\n\t\t\t\tawait fs.rmdir(dirPath);\n\n\t\t\t\t// Add parent directory for potential cleanup\n\t\t\t\tconst parentDir = path.dirname(dirPath);\n\t\t\t\tif (parentDir !== baseDirectory) {\n\t\t\t\t\tsortedDirs.push(parentDir);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore errors during directory cleanup\n\t\t}\n\t}\n}\n\nfunction hashString(input: string): string {\n\treturn createHash(\"sha256\").update(input).digest(\"hex\");\n}\n\nasync function hashOutput(\n\toutput: Record<string, string>,\n\toutputDirectory: string\n): Promise<Record<string, string>> {\n\tconst entries = Object.entries(output).map(([filePath, fileContent]) => {\n\t\tconst combinedContent =\n\t\t\tfileContent + path.resolve(outputDirectory, filePath);\n\t\treturn [filePath, hashString(combinedContent)] as const;\n\t});\n\treturn Object.fromEntries(entries);\n}\n\n/**\n * Walk an existing output directory and produce the same hash map that\n * {@link writeOutput} would have returned on a previous compile.\n *\n * Used by the bundler plugins to seed `previousCompilation` on a fresh\n * process so the first compile's diff against unchanged inputs is empty\n * and `writeOutput` short-circuits — letting us avoid wiping the directory\n * out from under concurrent SSR/prerender readers (#659).\n *\n * Returns `undefined` if the directory does not exist. Keys are\n * forward-slash relative paths to match {@link hashOutput}.\n */\nexport async function hashDirectory(\n\tdirectory: string,\n\tfs: typeof nodeFs\n): Promise<Record<string, string> | undefined> {\n\ttry {\n\t\tconst stat = await fs.stat(directory);\n\t\tif (!stat.isDirectory()) return undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n\n\tconst hashes: Record<string, string> = {};\n\n\tasync function walk(currentDir: string, relativePrefix: string) {\n\t\tlet entries;\n\t\ttry {\n\t\t\tentries = await fs.readdir(currentDir, { withFileTypes: true });\n\t\t} catch {\n\t\t\treturn;\n\t\t}\n\t\tfor (const entry of entries) {\n\t\t\tconst entryAbsolutePath = path.resolve(currentDir, entry.name);\n\t\t\tconst relativePath = relativePrefix\n\t\t\t\t? `${relativePrefix}/${entry.name}`\n\t\t\t\t: entry.name;\n\t\t\tif (entry.isDirectory()) {\n\t\t\t\tawait walk(entryAbsolutePath, relativePath);\n\t\t\t} else if (entry.isFile()) {\n\t\t\t\ttry {\n\t\t\t\t\tconst fileContent = await fs.readFile(entryAbsolutePath, \"utf-8\");\n\t\t\t\t\tconst combinedContent =\n\t\t\t\t\t\tfileContent + path.resolve(directory, relativePath);\n\t\t\t\t\thashes[relativePath] = await hashString(combinedContent);\n\t\t\t\t} catch {\n\t\t\t\t\t// Another compiler process may be rewriting this file. A partial\n\t\t\t\t\t// seed is safe; the following compile will repair missing outputs.\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tawait walk(directory, \"\");\n\treturn hashes;\n}\n"]}
@@ -79,6 +79,33 @@ test("should write again if the output has changed", async () => {
79
79
  expect(await fs.readFile("/output/test.txt", { encoding: "utf-8" })).toBe("test2");
80
80
  expect(writeFileSpy).toHaveBeenCalledTimes(2);
81
81
  });
82
+ test("should only create directories for files that changed", async () => {
83
+ const { writeOutput } = await import("./write-output.js");
84
+ const fs = mockFs({});
85
+ const mkdirSpy = vi.spyOn(fs, "mkdir");
86
+ const hashes = await writeOutput({
87
+ directory: "/output",
88
+ output: {
89
+ "messages/first.js": "first",
90
+ "messages/second.js": "second",
91
+ },
92
+ fs,
93
+ });
94
+ mkdirSpy.mockClear();
95
+ await writeOutput({
96
+ directory: "/output",
97
+ output: {
98
+ "messages/first.js": "changed",
99
+ "messages/second.js": "second",
100
+ },
101
+ fs,
102
+ previousOutputHashes: hashes,
103
+ });
104
+ expect(mkdirSpy).toHaveBeenCalledTimes(1);
105
+ expect(mkdirSpy).toHaveBeenCalledWith("/output/messages", {
106
+ recursive: true,
107
+ });
108
+ });
82
109
  test("should write files if output has partially changed", async () => {
83
110
  const { writeOutput } = await import("./write-output.js");
84
111
  const fs = mockFs({});
@@ -1 +1 @@
1
- {"version":3,"file":"write-output.test.js","sourceRoot":"","sources":["../../../src/services/file-handling/write-output.test.ts"],"names":[],"mappings":"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","sourcesContent":["import memfs from \"memfs\";\nimport type fs from \"node:fs/promises\";\nimport { test, expect, vi, beforeEach } from \"vitest\";\n\nbeforeEach(() => {\n\tvi.resetModules();\n});\n\ntest(\"should write the output to a non-existing directory\", async () => {\n\tconst { writeOutput } = await import(\"./write-output.js\");\n\tconst fs = mockFs({});\n\n\tawait writeOutput({\n\t\tdirectory: \"/output\",\n\t\toutput: { \"test.txt\": \"test\" },\n\t\tfs,\n\t});\n\texpect(await fs.readFile(\"/output/test.txt\", { encoding: \"utf-8\" })).toBe(\n\t\t\"test\"\n\t);\n});\n\ntest(\"should clear & overwrite output that's already there\", async () => {\n\tconst { writeOutput } = await import(\"./write-output.js\");\n\tconst fs = mockFs({\n\t\t\"/output/test.txt\": \"old\",\n\t\t\"/output/other.txt\": \"other\",\n\t});\n\n\tawait writeOutput({\n\t\tdirectory: \"/output\",\n\t\toutput: { \"test.txt\": \"new\" },\n\t\tcleanDirectory: true,\n\t\tfs,\n\t});\n\n\texpect(await fs.readFile(\"/output/test.txt\", { encoding: \"utf-8\" })).toBe(\n\t\t\"new\"\n\t);\n\tawait expect(\n\t\tasync () => await fs.readFile(\"/output/other.txt\", { encoding: \"utf-8\" })\n\t).rejects.toBeDefined();\n});\n\ntest(\"should create any missing directories\", async () => {\n\tconst { writeOutput } = await import(\"./write-output.js\");\n\tconst fs = mockFs({});\n\n\tawait writeOutput({\n\t\tdirectory: \"/output/messages\",\n\t\toutput: {\n\t\t\t\"de/test.txt\": \"de\",\n\t\t\t\"en/test.txt\": \"en\",\n\t\t},\n\t\tfs,\n\t});\n\texpect(\n\t\tawait fs.readFile(\"/output/messages/de/test.txt\", { encoding: \"utf-8\" })\n\t).toBe(\"de\");\n\texpect(\n\t\tawait fs.readFile(\"/output/messages/en/test.txt\", { encoding: \"utf-8\" })\n\t).toBe(\"en\");\n});\n\ntest(\"should only write once if the output hasn't changed\", async () => {\n\tconst { writeOutput } = await import(\"./write-output.js\");\n\tconst fs = mockFs({});\n\n\tconst writeFileSpy = vi.spyOn(fs as any, \"writeFile\");\n\n\tconst hashes = await writeOutput({\n\t\tdirectory: \"/output\",\n\t\toutput: { \"test.txt\": \"test\" },\n\t\tfs,\n\t});\n\tconst hashes2 = await writeOutput({\n\t\tdirectory: \"/output\",\n\t\toutput: { \"test.txt\": \"test\" },\n\t\tfs,\n\t\tpreviousOutputHashes: hashes,\n\t});\n\texpect(hashes).toEqual(hashes2);\n\texpect(await fs.readFile(\"/output/test.txt\", { encoding: \"utf-8\" })).toBe(\n\t\t\"test\"\n\t);\n\texpect(writeFileSpy).toHaveBeenCalledTimes(1);\n});\n\ntest(\"should write again if the output has changed\", async () => {\n\tconst { writeOutput } = await import(\"./write-output.js\");\n\tconst fs = mockFs({});\n\n\tconst writeFileSpy = vi.spyOn(fs as any, \"writeFile\");\n\n\tconst hashes = await writeOutput({\n\t\tdirectory: \"/output\",\n\t\toutput: { \"test.txt\": \"test\" },\n\t\tfs,\n\t});\n\tawait writeOutput({\n\t\tdirectory: \"/output\",\n\t\toutput: { \"test.txt\": \"test2\" },\n\t\tfs,\n\t\tpreviousOutputHashes: hashes,\n\t});\n\texpect(await fs.readFile(\"/output/test.txt\", { encoding: \"utf-8\" })).toBe(\n\t\t\"test2\"\n\t);\n\texpect(writeFileSpy).toHaveBeenCalledTimes(2);\n});\n\ntest(\"should write files if output has partially changed\", async () => {\n\tconst { writeOutput } = await import(\"./write-output.js\");\n\tconst fs = mockFs({});\n\n\tconst writeFileSpy = vi.spyOn(fs as any, \"writeFile\");\n\n\tconst hashes = await writeOutput({\n\t\tdirectory: \"/output\",\n\t\toutput: { \"file1.txt\": \"test\", \"file2.txt\": \"test\" },\n\t\tfs,\n\t});\n\n\tawait writeOutput({\n\t\tdirectory: \"/output\",\n\t\toutput: { \"file1.txt\": \"test\", \"file2.txt\": \"test2\" },\n\t\tfs,\n\t\tpreviousOutputHashes: hashes,\n\t});\n\texpect(writeFileSpy).toHaveBeenCalledWith(\"/output/file2.txt\", \"test2\");\n\texpect(writeFileSpy).toHaveBeenCalledTimes(3);\n});\n\ntest(\"should delete files that have been removed from the output\", async () => {\n\tconst { writeOutput } = await import(\"./write-output.js\");\n\tconst fs = mockFs({});\n\n\t// First write with three files\n\tconst hashes = await writeOutput({\n\t\tdirectory: \"/output\",\n\t\toutput: {\n\t\t\t\"file1.txt\": \"content1\",\n\t\t\t\"file2.txt\": \"content2\",\n\t\t\t\"subdir/file3.txt\": \"content3\",\n\t\t},\n\t\tfs,\n\t});\n\n\t// Verify all files were written\n\texpect(await fs.readFile(\"/output/file1.txt\", { encoding: \"utf-8\" })).toBe(\n\t\t\"content1\"\n\t);\n\texpect(await fs.readFile(\"/output/file2.txt\", { encoding: \"utf-8\" })).toBe(\n\t\t\"content2\"\n\t);\n\texpect(\n\t\tawait fs.readFile(\"/output/subdir/file3.txt\", { encoding: \"utf-8\" })\n\t).toBe(\"content3\");\n\n\t// Second write with file2.txt removed\n\tawait writeOutput({\n\t\tdirectory: \"/output\",\n\t\toutput: {\n\t\t\t\"file1.txt\": \"content1\",\n\t\t\t\"subdir/file3.txt\": \"content3updated\",\n\t\t},\n\t\tfs,\n\t\tpreviousOutputHashes: hashes,\n\t});\n\n\t// Verify file1.txt still exists\n\texpect(await fs.readFile(\"/output/file1.txt\", { encoding: \"utf-8\" })).toBe(\n\t\t\"content1\"\n\t);\n\t// Verify file2.txt has been deleted\n\tawait expect(\n\t\tasync () => await fs.readFile(\"/output/file2.txt\", { encoding: \"utf-8\" })\n\t).rejects.toBeDefined();\n\t// Verify file3.txt was updated\n\texpect(\n\t\tawait fs.readFile(\"/output/subdir/file3.txt\", { encoding: \"utf-8\" })\n\t).toBe(\"content3updated\");\n\n\t// Get the new hashes\n\tconst newHashes = await writeOutput({\n\t\tdirectory: \"/output\",\n\t\toutput: {\n\t\t\t\"file1.txt\": \"content1\",\n\t\t\t\"subdir/file3.txt\": \"content3updated\",\n\t\t},\n\t\tfs,\n\t\tpreviousOutputHashes: hashes,\n\t});\n\n\t// Third write with subdir/file3.txt removed\n\tawait writeOutput({\n\t\tdirectory: \"/output\",\n\t\toutput: {\n\t\t\t\"file1.txt\": \"content1\",\n\t\t},\n\t\tfs,\n\t\tpreviousOutputHashes: newHashes,\n\t});\n\n\t// Verify file1.txt still exists\n\texpect(await fs.readFile(\"/output/file1.txt\", { encoding: \"utf-8\" })).toBe(\n\t\t\"content1\"\n\t);\n\t// Verify subdir/file3.txt has been deleted\n\tawait expect(\n\t\tasync () =>\n\t\t\tawait fs.readFile(\"/output/subdir/file3.txt\", { encoding: \"utf-8\" })\n\t).rejects.toBeDefined();\n\t// Verify the subdir directory has also been removed\n\tawait expect(\n\t\tasync () => await fs.readdir(\"/output/subdir\")\n\t).rejects.toBeDefined();\n});\n\ntest(\"hashDirectory matches the hashes writeOutput returns\", async () => {\n\tconst { writeOutput, hashDirectory } = await import(\"./write-output.js\");\n\tconst fs = mockFs({});\n\n\tconst output = {\n\t\t\"a.txt\": \"alpha\",\n\t\t\"nested/b.txt\": \"beta\",\n\t\t\"nested/deep/c.txt\": \"gamma\",\n\t};\n\n\tconst writtenHashes = await writeOutput({\n\t\tdirectory: \"/output\",\n\t\toutput,\n\t\tfs,\n\t});\n\n\tconst seededHashes = await hashDirectory(\"/output\", fs);\n\n\texpect(seededHashes).toEqual(writtenHashes);\n});\n\ntest(\"hashDirectory returns undefined when directory does not exist\", async () => {\n\tconst { hashDirectory } = await import(\"./write-output.js\");\n\tconst fs = mockFs({});\n\n\texpect(await hashDirectory(\"/nope\", fs)).toBeUndefined();\n});\n\ntest(\"hashDirectory skips files that disappear while walking\", async () => {\n\tconst { hashDirectory } = await import(\"./write-output.js\");\n\tconst fs = mockFs({\n\t\t\"/output/a.txt\": \"alpha\",\n\t\t\"/output/b.txt\": \"beta\",\n\t});\n\tconst readFile = fs.readFile.bind(fs);\n\tvi.spyOn(fs, \"readFile\").mockImplementation((async (...args) => {\n\t\tif (args[0] === \"/output/b.txt\") {\n\t\t\tthrow new Error(\"file disappeared\");\n\t\t}\n\t\treturn readFile(args[0], args[1]);\n\t}) as typeof fs.readFile);\n\n\tconst seededHashes = await hashDirectory(\"/output\", fs);\n\n\texpect(seededHashes).toEqual({\n\t\t\"a.txt\": expect.any(String),\n\t});\n});\n\ntest(\"hashDirectory skips directories that disappear while walking\", async () => {\n\tconst { hashDirectory } = await import(\"./write-output.js\");\n\tconst fs = mockFs({\n\t\t\"/output/nested/b.txt\": \"beta\",\n\t});\n\tconst readdir = fs.readdir.bind(fs);\n\tvi.spyOn(fs, \"readdir\").mockImplementation((async (...args) => {\n\t\tif (args[0] === \"/output/nested\") {\n\t\t\tthrow new Error(\"directory disappeared\");\n\t\t}\n\t\treturn readdir(args[0], args[1] as never);\n\t}) as typeof fs.readdir);\n\n\tconst seededHashes = await hashDirectory(\"/output\", fs);\n\n\texpect(seededHashes).toEqual({});\n});\n\nconst mockFs = (files: memfs.DirectoryJSON) => {\n\tconst _memfs = memfs.createFsFromVolume(memfs.Volume.fromJSON(files));\n\treturn _memfs.promises as unknown as typeof fs;\n};\n"]}
1
+ {"version":3,"file":"write-output.test.js","sourceRoot":"","sources":["../../../src/services/file-handling/write-output.test.ts"],"names":[],"mappings":"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","sourcesContent":["import memfs from \"memfs\";\nimport type fs from \"node:fs/promises\";\nimport { test, expect, vi, beforeEach } from \"vitest\";\n\nbeforeEach(() => {\n\tvi.resetModules();\n});\n\ntest(\"should write the output to a non-existing directory\", async () => {\n\tconst { writeOutput } = await import(\"./write-output.js\");\n\tconst fs = mockFs({});\n\n\tawait writeOutput({\n\t\tdirectory: \"/output\",\n\t\toutput: { \"test.txt\": \"test\" },\n\t\tfs,\n\t});\n\texpect(await fs.readFile(\"/output/test.txt\", { encoding: \"utf-8\" })).toBe(\n\t\t\"test\"\n\t);\n});\n\ntest(\"should clear & overwrite output that's already there\", async () => {\n\tconst { writeOutput } = await import(\"./write-output.js\");\n\tconst fs = mockFs({\n\t\t\"/output/test.txt\": \"old\",\n\t\t\"/output/other.txt\": \"other\",\n\t});\n\n\tawait writeOutput({\n\t\tdirectory: \"/output\",\n\t\toutput: { \"test.txt\": \"new\" },\n\t\tcleanDirectory: true,\n\t\tfs,\n\t});\n\n\texpect(await fs.readFile(\"/output/test.txt\", { encoding: \"utf-8\" })).toBe(\n\t\t\"new\"\n\t);\n\tawait expect(\n\t\tasync () => await fs.readFile(\"/output/other.txt\", { encoding: \"utf-8\" })\n\t).rejects.toBeDefined();\n});\n\ntest(\"should create any missing directories\", async () => {\n\tconst { writeOutput } = await import(\"./write-output.js\");\n\tconst fs = mockFs({});\n\n\tawait writeOutput({\n\t\tdirectory: \"/output/messages\",\n\t\toutput: {\n\t\t\t\"de/test.txt\": \"de\",\n\t\t\t\"en/test.txt\": \"en\",\n\t\t},\n\t\tfs,\n\t});\n\texpect(\n\t\tawait fs.readFile(\"/output/messages/de/test.txt\", { encoding: \"utf-8\" })\n\t).toBe(\"de\");\n\texpect(\n\t\tawait fs.readFile(\"/output/messages/en/test.txt\", { encoding: \"utf-8\" })\n\t).toBe(\"en\");\n});\n\ntest(\"should only write once if the output hasn't changed\", async () => {\n\tconst { writeOutput } = await import(\"./write-output.js\");\n\tconst fs = mockFs({});\n\n\tconst writeFileSpy = vi.spyOn(fs as any, \"writeFile\");\n\n\tconst hashes = await writeOutput({\n\t\tdirectory: \"/output\",\n\t\toutput: { \"test.txt\": \"test\" },\n\t\tfs,\n\t});\n\tconst hashes2 = await writeOutput({\n\t\tdirectory: \"/output\",\n\t\toutput: { \"test.txt\": \"test\" },\n\t\tfs,\n\t\tpreviousOutputHashes: hashes,\n\t});\n\texpect(hashes).toEqual(hashes2);\n\texpect(await fs.readFile(\"/output/test.txt\", { encoding: \"utf-8\" })).toBe(\n\t\t\"test\"\n\t);\n\texpect(writeFileSpy).toHaveBeenCalledTimes(1);\n});\n\ntest(\"should write again if the output has changed\", async () => {\n\tconst { writeOutput } = await import(\"./write-output.js\");\n\tconst fs = mockFs({});\n\n\tconst writeFileSpy = vi.spyOn(fs as any, \"writeFile\");\n\n\tconst hashes = await writeOutput({\n\t\tdirectory: \"/output\",\n\t\toutput: { \"test.txt\": \"test\" },\n\t\tfs,\n\t});\n\tawait writeOutput({\n\t\tdirectory: \"/output\",\n\t\toutput: { \"test.txt\": \"test2\" },\n\t\tfs,\n\t\tpreviousOutputHashes: hashes,\n\t});\n\texpect(await fs.readFile(\"/output/test.txt\", { encoding: \"utf-8\" })).toBe(\n\t\t\"test2\"\n\t);\n\texpect(writeFileSpy).toHaveBeenCalledTimes(2);\n});\n\ntest(\"should only create directories for files that changed\", async () => {\n\tconst { writeOutput } = await import(\"./write-output.js\");\n\tconst fs = mockFs({});\n\tconst mkdirSpy = vi.spyOn(fs, \"mkdir\");\n\n\tconst hashes = await writeOutput({\n\t\tdirectory: \"/output\",\n\t\toutput: {\n\t\t\t\"messages/first.js\": \"first\",\n\t\t\t\"messages/second.js\": \"second\",\n\t\t},\n\t\tfs,\n\t});\n\tmkdirSpy.mockClear();\n\n\tawait writeOutput({\n\t\tdirectory: \"/output\",\n\t\toutput: {\n\t\t\t\"messages/first.js\": \"changed\",\n\t\t\t\"messages/second.js\": \"second\",\n\t\t},\n\t\tfs,\n\t\tpreviousOutputHashes: hashes,\n\t});\n\n\texpect(mkdirSpy).toHaveBeenCalledTimes(1);\n\texpect(mkdirSpy).toHaveBeenCalledWith(\"/output/messages\", {\n\t\trecursive: true,\n\t});\n});\n\ntest(\"should write files if output has partially changed\", async () => {\n\tconst { writeOutput } = await import(\"./write-output.js\");\n\tconst fs = mockFs({});\n\n\tconst writeFileSpy = vi.spyOn(fs as any, \"writeFile\");\n\n\tconst hashes = await writeOutput({\n\t\tdirectory: \"/output\",\n\t\toutput: { \"file1.txt\": \"test\", \"file2.txt\": \"test\" },\n\t\tfs,\n\t});\n\n\tawait writeOutput({\n\t\tdirectory: \"/output\",\n\t\toutput: { \"file1.txt\": \"test\", \"file2.txt\": \"test2\" },\n\t\tfs,\n\t\tpreviousOutputHashes: hashes,\n\t});\n\texpect(writeFileSpy).toHaveBeenCalledWith(\"/output/file2.txt\", \"test2\");\n\texpect(writeFileSpy).toHaveBeenCalledTimes(3);\n});\n\ntest(\"should delete files that have been removed from the output\", async () => {\n\tconst { writeOutput } = await import(\"./write-output.js\");\n\tconst fs = mockFs({});\n\n\t// First write with three files\n\tconst hashes = await writeOutput({\n\t\tdirectory: \"/output\",\n\t\toutput: {\n\t\t\t\"file1.txt\": \"content1\",\n\t\t\t\"file2.txt\": \"content2\",\n\t\t\t\"subdir/file3.txt\": \"content3\",\n\t\t},\n\t\tfs,\n\t});\n\n\t// Verify all files were written\n\texpect(await fs.readFile(\"/output/file1.txt\", { encoding: \"utf-8\" })).toBe(\n\t\t\"content1\"\n\t);\n\texpect(await fs.readFile(\"/output/file2.txt\", { encoding: \"utf-8\" })).toBe(\n\t\t\"content2\"\n\t);\n\texpect(\n\t\tawait fs.readFile(\"/output/subdir/file3.txt\", { encoding: \"utf-8\" })\n\t).toBe(\"content3\");\n\n\t// Second write with file2.txt removed\n\tawait writeOutput({\n\t\tdirectory: \"/output\",\n\t\toutput: {\n\t\t\t\"file1.txt\": \"content1\",\n\t\t\t\"subdir/file3.txt\": \"content3updated\",\n\t\t},\n\t\tfs,\n\t\tpreviousOutputHashes: hashes,\n\t});\n\n\t// Verify file1.txt still exists\n\texpect(await fs.readFile(\"/output/file1.txt\", { encoding: \"utf-8\" })).toBe(\n\t\t\"content1\"\n\t);\n\t// Verify file2.txt has been deleted\n\tawait expect(\n\t\tasync () => await fs.readFile(\"/output/file2.txt\", { encoding: \"utf-8\" })\n\t).rejects.toBeDefined();\n\t// Verify file3.txt was updated\n\texpect(\n\t\tawait fs.readFile(\"/output/subdir/file3.txt\", { encoding: \"utf-8\" })\n\t).toBe(\"content3updated\");\n\n\t// Get the new hashes\n\tconst newHashes = await writeOutput({\n\t\tdirectory: \"/output\",\n\t\toutput: {\n\t\t\t\"file1.txt\": \"content1\",\n\t\t\t\"subdir/file3.txt\": \"content3updated\",\n\t\t},\n\t\tfs,\n\t\tpreviousOutputHashes: hashes,\n\t});\n\n\t// Third write with subdir/file3.txt removed\n\tawait writeOutput({\n\t\tdirectory: \"/output\",\n\t\toutput: {\n\t\t\t\"file1.txt\": \"content1\",\n\t\t},\n\t\tfs,\n\t\tpreviousOutputHashes: newHashes,\n\t});\n\n\t// Verify file1.txt still exists\n\texpect(await fs.readFile(\"/output/file1.txt\", { encoding: \"utf-8\" })).toBe(\n\t\t\"content1\"\n\t);\n\t// Verify subdir/file3.txt has been deleted\n\tawait expect(\n\t\tasync () =>\n\t\t\tawait fs.readFile(\"/output/subdir/file3.txt\", { encoding: \"utf-8\" })\n\t).rejects.toBeDefined();\n\t// Verify the subdir directory has also been removed\n\tawait expect(\n\t\tasync () => await fs.readdir(\"/output/subdir\")\n\t).rejects.toBeDefined();\n});\n\ntest(\"hashDirectory matches the hashes writeOutput returns\", async () => {\n\tconst { writeOutput, hashDirectory } = await import(\"./write-output.js\");\n\tconst fs = mockFs({});\n\n\tconst output = {\n\t\t\"a.txt\": \"alpha\",\n\t\t\"nested/b.txt\": \"beta\",\n\t\t\"nested/deep/c.txt\": \"gamma\",\n\t};\n\n\tconst writtenHashes = await writeOutput({\n\t\tdirectory: \"/output\",\n\t\toutput,\n\t\tfs,\n\t});\n\n\tconst seededHashes = await hashDirectory(\"/output\", fs);\n\n\texpect(seededHashes).toEqual(writtenHashes);\n});\n\ntest(\"hashDirectory returns undefined when directory does not exist\", async () => {\n\tconst { hashDirectory } = await import(\"./write-output.js\");\n\tconst fs = mockFs({});\n\n\texpect(await hashDirectory(\"/nope\", fs)).toBeUndefined();\n});\n\ntest(\"hashDirectory skips files that disappear while walking\", async () => {\n\tconst { hashDirectory } = await import(\"./write-output.js\");\n\tconst fs = mockFs({\n\t\t\"/output/a.txt\": \"alpha\",\n\t\t\"/output/b.txt\": \"beta\",\n\t});\n\tconst readFile = fs.readFile.bind(fs);\n\tvi.spyOn(fs, \"readFile\").mockImplementation((async (...args) => {\n\t\tif (args[0] === \"/output/b.txt\") {\n\t\t\tthrow new Error(\"file disappeared\");\n\t\t}\n\t\treturn readFile(args[0], args[1]);\n\t}) as typeof fs.readFile);\n\n\tconst seededHashes = await hashDirectory(\"/output\", fs);\n\n\texpect(seededHashes).toEqual({\n\t\t\"a.txt\": expect.any(String),\n\t});\n});\n\ntest(\"hashDirectory skips directories that disappear while walking\", async () => {\n\tconst { hashDirectory } = await import(\"./write-output.js\");\n\tconst fs = mockFs({\n\t\t\"/output/nested/b.txt\": \"beta\",\n\t});\n\tconst readdir = fs.readdir.bind(fs);\n\tvi.spyOn(fs, \"readdir\").mockImplementation((async (...args) => {\n\t\tif (args[0] === \"/output/nested\") {\n\t\t\tthrow new Error(\"directory disappeared\");\n\t\t}\n\t\treturn readdir(args[0], args[1] as never);\n\t}) as typeof fs.readdir);\n\n\tconst seededHashes = await hashDirectory(\"/output\", fs);\n\n\texpect(seededHashes).toEqual({});\n});\n\nconst mockFs = (files: memfs.DirectoryJSON) => {\n\tconst _memfs = memfs.createFsFromVolume(memfs.Volume.fromJSON(files));\n\treturn _memfs.promises as unknown as typeof fs;\n};\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@inlang/paraglide-js",
3
3
  "type": "module",
4
- "version": "2.23.2",
4
+ "version": "2.24.0",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
7
7
  "access": "public",
@@ -27,7 +27,7 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "@inlang/recommend-sherlock": "^0.2.1",
30
- "@inlang/sdk": "^2.10.0",
30
+ "@inlang/sdk": "^3.0.0",
31
31
  "commander": "11.1.0",
32
32
  "consola": "3.4.0",
33
33
  "json5": "2.2.3",
@@ -1,14 +0,0 @@
1
- import type { Account } from "@inlang/sdk/lix";
2
- export declare function getLocalAccount(args: {
3
- fs: typeof import("node:fs");
4
- }): Account | undefined;
5
- export declare function saveLocalAccount(args: {
6
- fs: typeof import("node:fs");
7
- account: Account;
8
- }): void;
9
- /**
10
- * Returns the path to the local account file.
11
- *
12
- * Based on the env-paths package https://github.com/sindresorhus/env-paths/blob/main/index.js
13
- */
14
- export declare function getAccountFilePath(): string;
@@ -1,43 +0,0 @@
1
- import path from "node:path";
2
- import os from "node:os";
3
- export function getLocalAccount(args) {
4
- try {
5
- const accountPath = getAccountFilePath();
6
- if (!args.fs.existsSync(accountPath)) {
7
- return;
8
- }
9
- return JSON.parse(args.fs.readFileSync(accountPath, "utf8"));
10
- }
11
- catch {
12
- return;
13
- }
14
- }
15
- export function saveLocalAccount(args) {
16
- try {
17
- const accountPath = getAccountFilePath();
18
- args.fs.mkdirSync(path.dirname(accountPath), { recursive: true });
19
- args.fs.writeFileSync(accountPath, JSON.stringify(args.account, null, 2));
20
- }
21
- catch {
22
- // do nothing
23
- }
24
- }
25
- /**
26
- * Returns the path to the local account file.
27
- *
28
- * Based on the env-paths package https://github.com/sindresorhus/env-paths/blob/main/index.js
29
- */
30
- export function getAccountFilePath() {
31
- const homedir = os.homedir();
32
- if (process.platform === "darwin") {
33
- return path.join(homedir, "Library", "Application Support", "lix", "account.json");
34
- }
35
- else if (process.platform === "win32") {
36
- return path.join(homedir, "AppData", "Roaming", "lix", "account.json");
37
- }
38
- // linux
39
- else {
40
- return path.join(homedir, ".local", "share", "lix", "account.json");
41
- }
42
- }
43
- //# sourceMappingURL=index.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/services/account/index.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,SAAS,CAAC;AAGzB,MAAM,UAAU,eAAe,CAAC,IAE/B;IACA,IAAI,CAAC;QACJ,MAAM,WAAW,GAAG,kBAAkB,EAAE,CAAC;QAEzC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;YACtC,OAAO;QACR,CAAC;QAED,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC;IAC9D,CAAC;IAAC,MAAM,CAAC;QACR,OAAO;IACR,CAAC;AACF,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,IAGhC;IACA,IAAI,CAAC;QACJ,MAAM,WAAW,GAAG,kBAAkB,EAAE,CAAC;QACzC,IAAI,CAAC,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAClE,IAAI,CAAC,EAAE,CAAC,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAC3E,CAAC;IAAC,MAAM,CAAC;QACR,aAAa;IACd,CAAC;AACF,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB;IACjC,MAAM,OAAO,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC;IAE7B,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACnC,OAAO,IAAI,CAAC,IAAI,CACf,OAAO,EACP,SAAS,EACT,qBAAqB,EACrB,KAAK,EACL,cAAc,CACd,CAAC;IACH,CAAC;SAAM,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACzC,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,cAAc,CAAC,CAAC;IACxE,CAAC;IACD,QAAQ;SACH,CAAC;QACL,OAAO,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE,cAAc,CAAC,CAAC;IACrE,CAAC;AACF,CAAC","sourcesContent":["import path from \"node:path\";\nimport os from \"node:os\";\nimport type { Account } from \"@inlang/sdk/lix\";\n\nexport function getLocalAccount(args: {\n\tfs: typeof import(\"node:fs\");\n}): Account | undefined {\n\ttry {\n\t\tconst accountPath = getAccountFilePath();\n\n\t\tif (!args.fs.existsSync(accountPath)) {\n\t\t\treturn;\n\t\t}\n\n\t\treturn JSON.parse(args.fs.readFileSync(accountPath, \"utf8\"));\n\t} catch {\n\t\treturn;\n\t}\n}\n\nexport function saveLocalAccount(args: {\n\tfs: typeof import(\"node:fs\");\n\taccount: Account;\n}): void {\n\ttry {\n\t\tconst accountPath = getAccountFilePath();\n\t\targs.fs.mkdirSync(path.dirname(accountPath), { recursive: true });\n\t\targs.fs.writeFileSync(accountPath, JSON.stringify(args.account, null, 2));\n\t} catch {\n\t\t// do nothing\n\t}\n}\n\n/**\n * Returns the path to the local account file.\n *\n * Based on the env-paths package https://github.com/sindresorhus/env-paths/blob/main/index.js\n */\nexport function getAccountFilePath() {\n\tconst homedir = os.homedir();\n\n\tif (process.platform === \"darwin\") {\n\t\treturn path.join(\n\t\t\thomedir,\n\t\t\t\"Library\",\n\t\t\t\"Application Support\",\n\t\t\t\"lix\",\n\t\t\t\"account.json\"\n\t\t);\n\t} else if (process.platform === \"win32\") {\n\t\treturn path.join(homedir, \"AppData\", \"Roaming\", \"lix\", \"account.json\");\n\t}\n\t// linux\n\telse {\n\t\treturn path.join(homedir, \".local\", \"share\", \"lix\", \"account.json\");\n\t}\n}\n"]}