@joshuafolkken/kit 1.872.0 → 1.881.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 (256) hide show
  1. package/.github/workflows/publish.yml +39 -2
  2. package/README.md +3 -3
  3. package/dist/hooks/batch-guard.js +4 -4
  4. package/dist/hooks/{chunk-KABG6AEW.js → chunk-264QY6KS.js} +7 -0
  5. package/dist/hooks/{chunk-I4D3NVUD.js → chunk-36UE53LS.js} +2 -2
  6. package/dist/hooks/{chunk-WCYPBPJC.js → chunk-4OXFU3MK.js} +8 -8
  7. package/dist/hooks/{chunk-ABVEVV2M.js → chunk-6MQCXEEA.js} +1 -1
  8. package/dist/hooks/{chunk-46EEEHC3.js → chunk-FNFOLFFV.js} +1 -1
  9. package/dist/hooks/{chunk-5U5EZYG6.js → chunk-FRJKVSK3.js} +2 -2
  10. package/dist/hooks/{chunk-2RUPMGGV.js → chunk-PRDCNGA6.js} +4 -4
  11. package/dist/hooks/{chunk-WMN4TSVF.js → chunk-VGYSA2NT.js} +2 -2
  12. package/dist/hooks/{chunk-A5YJ3H7E.js → chunk-Y5OYCU2F.js} +3 -3
  13. package/dist/hooks/{chunk-4QHBNAI4.js → chunk-ZBK4XRAS.js} +3 -3
  14. package/dist/hooks/codex-hook-adapter.js +10 -10
  15. package/dist/hooks/duplicate-read-guard.js +5 -5
  16. package/dist/hooks/format-edited.js +2 -2
  17. package/dist/hooks/investigation-guard.js +5 -5
  18. package/dist/hooks/pretool-guard.js +9 -9
  19. package/dist/hooks/stop-guard.js +3 -3
  20. package/dist/josh.js +7 -0
  21. package/package.json +6 -2
  22. package/scripts/init/distributed-paths.ts +14 -11
  23. package/scripts/init/hook-command-bootstrap.ts +51 -0
  24. package/scripts/init/hook-command-rewrite.ts +9 -1
  25. package/scripts/init/init-logic.ts +2 -8
  26. package/scripts/josh/__snapshots__/josh-command-map.test.ts.snap +10 -0
  27. package/scripts/josh/josh-command-map.ts +1 -0
  28. package/scripts/josh/josh-commands-project.ts +6 -0
  29. package/scripts/registry-migration/migrate-logic.ts +229 -0
  30. package/scripts/registry-migration/migrate.ts +280 -0
  31. package/scripts/registry-migration/user-npmrc.ts +15 -0
  32. package/templates/workflows/ci.yml +6 -2
  33. package/dist/hooks/chunk-25JL6FMS.js +0 -4970
  34. package/dist/hooks/chunk-2ANGT3XM.js +0 -375
  35. package/dist/hooks/chunk-2EI5GBSW.js +0 -42
  36. package/dist/hooks/chunk-2OT5XE2N.js +0 -5014
  37. package/dist/hooks/chunk-2XIGZQMQ.js +0 -3299
  38. package/dist/hooks/chunk-33ELOKEV.js +0 -910
  39. package/dist/hooks/chunk-33YRTKM7.js +0 -230
  40. package/dist/hooks/chunk-36JQU5NN.js +0 -77
  41. package/dist/hooks/chunk-3EJBTWVS.js +0 -1769
  42. package/dist/hooks/chunk-3FK5SFAC.js +0 -432
  43. package/dist/hooks/chunk-3PWPYDD2.js +0 -240
  44. package/dist/hooks/chunk-3TQU7TDH.js +0 -375
  45. package/dist/hooks/chunk-3TVLSFZ6.js +0 -432
  46. package/dist/hooks/chunk-3VXYCB4E.js +0 -77
  47. package/dist/hooks/chunk-44M6JXGV.js +0 -3398
  48. package/dist/hooks/chunk-4A7UVP2P.js +0 -364
  49. package/dist/hooks/chunk-4LFQEZS4.js +0 -137
  50. package/dist/hooks/chunk-4OCX3V2T.js +0 -469
  51. package/dist/hooks/chunk-4OHA5DE7.js +0 -49
  52. package/dist/hooks/chunk-4PMDW3YD.js +0 -68
  53. package/dist/hooks/chunk-4SSB3FEE.js +0 -3273
  54. package/dist/hooks/chunk-5A2WQ2CQ.js +0 -5390
  55. package/dist/hooks/chunk-5EVA2JQB.js +0 -5390
  56. package/dist/hooks/chunk-5P7VF5VE.js +0 -3230
  57. package/dist/hooks/chunk-5QWJGGZP.js +0 -49
  58. package/dist/hooks/chunk-5VUUF3ZV.js +0 -240
  59. package/dist/hooks/chunk-5WA7UBT3.js +0 -374
  60. package/dist/hooks/chunk-6EBGRQ3Q.js +0 -301
  61. package/dist/hooks/chunk-6TVHWMD3.js +0 -432
  62. package/dist/hooks/chunk-6VAGLEMB.js +0 -68
  63. package/dist/hooks/chunk-75BTXKFJ.js +0 -3424
  64. package/dist/hooks/chunk-75RHN67S.js +0 -137
  65. package/dist/hooks/chunk-76KC3BDD.js +0 -3414
  66. package/dist/hooks/chunk-76SLS7CZ.js +0 -898
  67. package/dist/hooks/chunk-7DHZ4DWT.js +0 -5015
  68. package/dist/hooks/chunk-7INSJDGE.js +0 -458
  69. package/dist/hooks/chunk-7VJ4FTDX.js +0 -268
  70. package/dist/hooks/chunk-7XCYNHQK.js +0 -77
  71. package/dist/hooks/chunk-AAEXXUUL.js +0 -268
  72. package/dist/hooks/chunk-ACN3YXFL.js +0 -68
  73. package/dist/hooks/chunk-AD3XE3QZ.js +0 -397
  74. package/dist/hooks/chunk-AENFZPZH.js +0 -230
  75. package/dist/hooks/chunk-AFKRFQ6W.js +0 -68
  76. package/dist/hooks/chunk-AHD5IPWI.js +0 -375
  77. package/dist/hooks/chunk-AMK5JGV7.js +0 -77
  78. package/dist/hooks/chunk-ARN7NCQI.js +0 -425
  79. package/dist/hooks/chunk-AUFY653N.js +0 -3425
  80. package/dist/hooks/chunk-AXQBNFEF.js +0 -375
  81. package/dist/hooks/chunk-AYC7R6U4.js +0 -3316
  82. package/dist/hooks/chunk-AYEA2FJ7.js +0 -5015
  83. package/dist/hooks/chunk-B2LIXYFL.js +0 -77
  84. package/dist/hooks/chunk-B4HPNY2I.js +0 -375
  85. package/dist/hooks/chunk-B4YNCUGB.js +0 -469
  86. package/dist/hooks/chunk-B57S3VWT.js +0 -922
  87. package/dist/hooks/chunk-BINZ3PLI.js +0 -364
  88. package/dist/hooks/chunk-BIWLABFK.js +0 -432
  89. package/dist/hooks/chunk-BODUSXKK.js +0 -410
  90. package/dist/hooks/chunk-BQBAT2YK.js +0 -375
  91. package/dist/hooks/chunk-BUJEBW75.js +0 -391
  92. package/dist/hooks/chunk-BVNWORKA.js +0 -72
  93. package/dist/hooks/chunk-BWI47LQS.js +0 -5205
  94. package/dist/hooks/chunk-C25HRMJX.js +0 -137
  95. package/dist/hooks/chunk-CBFZNC74.js +0 -375
  96. package/dist/hooks/chunk-CECYXS4Y.js +0 -3424
  97. package/dist/hooks/chunk-CELGUP5F.js +0 -49
  98. package/dist/hooks/chunk-CMHANU3F.js +0 -3442
  99. package/dist/hooks/chunk-CMN372LI.js +0 -68
  100. package/dist/hooks/chunk-CPGZLMMK.js +0 -3299
  101. package/dist/hooks/chunk-CTQKXDC7.js +0 -268
  102. package/dist/hooks/chunk-CYAT4RIT.js +0 -68
  103. package/dist/hooks/chunk-CYTWYCH2.js +0 -5014
  104. package/dist/hooks/chunk-CZSOLRBA.js +0 -77
  105. package/dist/hooks/chunk-D2PIDYJR.js +0 -3438
  106. package/dist/hooks/chunk-DE57HI6T.js +0 -137
  107. package/dist/hooks/chunk-DGXDGAPG.js +0 -5306
  108. package/dist/hooks/chunk-DNYRSYQG.js +0 -5014
  109. package/dist/hooks/chunk-DTAXH2A2.js +0 -240
  110. package/dist/hooks/chunk-E2IZUWI5.js +0 -375
  111. package/dist/hooks/chunk-E4L2K2BL.js +0 -397
  112. package/dist/hooks/chunk-E55NKX5F.js +0 -68
  113. package/dist/hooks/chunk-ECXVCCVD.js +0 -3079
  114. package/dist/hooks/chunk-EPHO443D.js +0 -68
  115. package/dist/hooks/chunk-EUFK2BI2.js +0 -375
  116. package/dist/hooks/chunk-EYCD5ZAV.js +0 -137
  117. package/dist/hooks/chunk-F2SDYJU4.js +0 -240
  118. package/dist/hooks/chunk-F4B5D5TF.js +0 -68
  119. package/dist/hooks/chunk-FGEO3NDX.js +0 -432
  120. package/dist/hooks/chunk-FJYI52RE.js +0 -137
  121. package/dist/hooks/chunk-FYTZHIY4.js +0 -3085
  122. package/dist/hooks/chunk-G7JL2A5K.js +0 -375
  123. package/dist/hooks/chunk-GA3ITDBW.js +0 -42
  124. package/dist/hooks/chunk-GDR4H4SV.js +0 -469
  125. package/dist/hooks/chunk-GFKDXPSM.js +0 -2855
  126. package/dist/hooks/chunk-GIMRTKFU.js +0 -566
  127. package/dist/hooks/chunk-GOPHRLGO.js +0 -240
  128. package/dist/hooks/chunk-GX4HO7SJ.js +0 -230
  129. package/dist/hooks/chunk-GXKKK23T.js +0 -240
  130. package/dist/hooks/chunk-GZ6FUVIK.js +0 -566
  131. package/dist/hooks/chunk-H5HPY5M5.js +0 -2869
  132. package/dist/hooks/chunk-HCF3OGEE.js +0 -1829
  133. package/dist/hooks/chunk-HDEXAOUM.js +0 -469
  134. package/dist/hooks/chunk-HM2TY3TN.js +0 -469
  135. package/dist/hooks/chunk-HOA7NXA2.js +0 -55
  136. package/dist/hooks/chunk-HOFEWH6H.js +0 -240
  137. package/dist/hooks/chunk-HUUI6S5J.js +0 -2669
  138. package/dist/hooks/chunk-I4Q6WAJE.js +0 -68
  139. package/dist/hooks/chunk-IB6N4CR2.js +0 -432
  140. package/dist/hooks/chunk-IFLDYJMD.js +0 -469
  141. package/dist/hooks/chunk-IGU466YD.js +0 -77
  142. package/dist/hooks/chunk-IJCQUHKY.js +0 -375
  143. package/dist/hooks/chunk-IKUMBDJB.js +0 -230
  144. package/dist/hooks/chunk-IM3BDIBJ.js +0 -5005
  145. package/dist/hooks/chunk-IMYMAODK.js +0 -3431
  146. package/dist/hooks/chunk-IN2ZVGXA.js +0 -5005
  147. package/dist/hooks/chunk-ITVJ2MKQ.js +0 -18
  148. package/dist/hooks/chunk-J5RIJYTM.js +0 -2669
  149. package/dist/hooks/chunk-JFLMH4K5.js +0 -3460
  150. package/dist/hooks/chunk-JR25IFML.js +0 -77
  151. package/dist/hooks/chunk-JT5AUXFI.js +0 -432
  152. package/dist/hooks/chunk-JY5YVN6N.js +0 -3234
  153. package/dist/hooks/chunk-K5Q4ETVI.js +0 -391
  154. package/dist/hooks/chunk-K67V2F24.js +0 -137
  155. package/dist/hooks/chunk-KATZESEC.js +0 -3273
  156. package/dist/hooks/chunk-KFH4H7W7.js +0 -469
  157. package/dist/hooks/chunk-KMUEI6RX.js +0 -910
  158. package/dist/hooks/chunk-KMV4OY77.js +0 -3438
  159. package/dist/hooks/chunk-KQAZNWEY.js +0 -137
  160. package/dist/hooks/chunk-KZZR6JER.js +0 -5015
  161. package/dist/hooks/chunk-LAR6DREP.js +0 -77
  162. package/dist/hooks/chunk-LNCJWEZD.js +0 -5015
  163. package/dist/hooks/chunk-LRYD2GLM.js +0 -6
  164. package/dist/hooks/chunk-LXBKYHDV.js +0 -77
  165. package/dist/hooks/chunk-LYLKIUNE.js +0 -5024
  166. package/dist/hooks/chunk-LZKCFOVC.js +0 -432
  167. package/dist/hooks/chunk-M4FCIYRI.js +0 -77
  168. package/dist/hooks/chunk-M5M7327Q.js +0 -3425
  169. package/dist/hooks/chunk-MF72MHSX.js +0 -3455
  170. package/dist/hooks/chunk-MK56LKDO.js +0 -397
  171. package/dist/hooks/chunk-N3MJADY7.js +0 -240
  172. package/dist/hooks/chunk-NFQSUDBN.js +0 -458
  173. package/dist/hooks/chunk-NHREKY45.js +0 -137
  174. package/dist/hooks/chunk-NP5TTAAW.js +0 -375
  175. package/dist/hooks/chunk-NQTLTKTJ.js +0 -2993
  176. package/dist/hooks/chunk-NVRCZPBZ.js +0 -391
  177. package/dist/hooks/chunk-O46HMMLJ.js +0 -469
  178. package/dist/hooks/chunk-OFJGH2L4.js +0 -240
  179. package/dist/hooks/chunk-OLFMCZH5.js +0 -240
  180. package/dist/hooks/chunk-OMCZS46I.js +0 -49
  181. package/dist/hooks/chunk-OQZEZW5G.js +0 -469
  182. package/dist/hooks/chunk-P2MDAT2Y.js +0 -1897
  183. package/dist/hooks/chunk-P3M2JJIF.js +0 -68
  184. package/dist/hooks/chunk-PDVHQITQ.js +0 -3453
  185. package/dist/hooks/chunk-PNT2AMDH.js +0 -391
  186. package/dist/hooks/chunk-PR2LVSY6.js +0 -3445
  187. package/dist/hooks/chunk-PRR5R4JK.js +0 -137
  188. package/dist/hooks/chunk-PXT44QVN.js +0 -566
  189. package/dist/hooks/chunk-PZLWJLAP.js +0 -469
  190. package/dist/hooks/chunk-Q2FEAZJ2.js +0 -77
  191. package/dist/hooks/chunk-Q32WETDO.js +0 -3265
  192. package/dist/hooks/chunk-Q45DUBLE.js +0 -3600
  193. package/dist/hooks/chunk-Q7NZSWQ5.js +0 -240
  194. package/dist/hooks/chunk-QH2JNAMK.js +0 -3085
  195. package/dist/hooks/chunk-QNNWYQ2R.js +0 -375
  196. package/dist/hooks/chunk-QPU5H2OS.js +0 -68
  197. package/dist/hooks/chunk-QRAHZ5TO.js +0 -1829
  198. package/dist/hooks/chunk-QUZ5HPIE.js +0 -469
  199. package/dist/hooks/chunk-QVCRG34O.js +0 -2893
  200. package/dist/hooks/chunk-R3MY4E3S.js +0 -1675
  201. package/dist/hooks/chunk-R6TXJJXP.js +0 -77
  202. package/dist/hooks/chunk-REJU6VT7.js +0 -1829
  203. package/dist/hooks/chunk-REQAQLRC.js +0 -2993
  204. package/dist/hooks/chunk-RFCXPAHH.js +0 -391
  205. package/dist/hooks/chunk-RHI6Y22X.js +0 -137
  206. package/dist/hooks/chunk-RKZVOBBS.js +0 -469
  207. package/dist/hooks/chunk-RL57TT2Y.js +0 -566
  208. package/dist/hooks/chunk-RORDOD4S.js +0 -3321
  209. package/dist/hooks/chunk-RZKYW5P2.js +0 -77
  210. package/dist/hooks/chunk-SJPJ6VSI.js +0 -391
  211. package/dist/hooks/chunk-SKSIJQQQ.js +0 -432
  212. package/dist/hooks/chunk-SSQDZKXG.js +0 -268
  213. package/dist/hooks/chunk-SYHFKE4C.js +0 -77
  214. package/dist/hooks/chunk-TCGNI2ZY.js +0 -42
  215. package/dist/hooks/chunk-TG3WXJF5.js +0 -137
  216. package/dist/hooks/chunk-TH7IJAY5.js +0 -77
  217. package/dist/hooks/chunk-TLZ27PCY.js +0 -240
  218. package/dist/hooks/chunk-U652FXTN.js +0 -566
  219. package/dist/hooks/chunk-UEK47YUF.js +0 -268
  220. package/dist/hooks/chunk-UIO45H3O.js +0 -432
  221. package/dist/hooks/chunk-UNLZEEVH.js +0 -5005
  222. package/dist/hooks/chunk-UNRVF3SZ.js +0 -432
  223. package/dist/hooks/chunk-USLFAFOI.js +0 -3453
  224. package/dist/hooks/chunk-VAEELMLL.js +0 -442
  225. package/dist/hooks/chunk-VALSWLDQ.js +0 -3453
  226. package/dist/hooks/chunk-VC5JEPNY.js +0 -3398
  227. package/dist/hooks/chunk-VEZVK6C7.js +0 -230
  228. package/dist/hooks/chunk-VK5JKFAR.js +0 -49
  229. package/dist/hooks/chunk-VNF2KP7Z.js +0 -469
  230. package/dist/hooks/chunk-VWWCUMW6.js +0 -2669
  231. package/dist/hooks/chunk-W4JARUQV.js +0 -137
  232. package/dist/hooks/chunk-WEOBMNJG.js +0 -2993
  233. package/dist/hooks/chunk-WKJU3J5Y.js +0 -5390
  234. package/dist/hooks/chunk-WNON54PT.js +0 -3469
  235. package/dist/hooks/chunk-WOVKBPPX.js +0 -137
  236. package/dist/hooks/chunk-WRVFORYJ.js +0 -432
  237. package/dist/hooks/chunk-WUBVYGQZ.js +0 -240
  238. package/dist/hooks/chunk-WWULTUOM.js +0 -68
  239. package/dist/hooks/chunk-WXHKKB45.js +0 -4962
  240. package/dist/hooks/chunk-XAI3YTHJ.js +0 -469
  241. package/dist/hooks/chunk-XFZH6JZJ.js +0 -77
  242. package/dist/hooks/chunk-XGPWX6IA.js +0 -137
  243. package/dist/hooks/chunk-XIXXIVGY.js +0 -566
  244. package/dist/hooks/chunk-XKDOYJGA.js +0 -922
  245. package/dist/hooks/chunk-YALTVBHM.js +0 -432
  246. package/dist/hooks/chunk-YEBEPBFK.js +0 -5014
  247. package/dist/hooks/chunk-YI2BQ3DK.js +0 -3441
  248. package/dist/hooks/chunk-YVNBCFGG.js +0 -1829
  249. package/dist/hooks/chunk-YXBN3RGG.js +0 -432
  250. package/dist/hooks/chunk-Z2OOJSW3.js +0 -3163
  251. package/dist/hooks/chunk-ZA3HVBOY.js +0 -2644
  252. package/dist/hooks/chunk-ZKID46U3.js +0 -137
  253. package/dist/hooks/chunk-ZPMU2JV6.js +0 -49
  254. package/dist/hooks/chunk-ZRKY4W7O.js +0 -432
  255. package/dist/hooks/chunk-ZTMRB7LJ.js +0 -268
  256. package/dist/hooks/chunk-ZVPPBQ3L.js +0 -3413
@@ -54,6 +54,7 @@ const ALIASES: Record<string, string> = {
54
54
  pt: 'port',
55
55
  i: 'init',
56
56
  sy: 'sync',
57
+ rmi: 'registry:migrate',
57
58
  sys: 'sync:scope',
58
59
  g: 'git',
59
60
  gp: 'pr',
@@ -17,6 +17,12 @@ const PROJECT_COMMANDS: Record<string, CommandEntry> = {
17
17
  category: 'Project',
18
18
  reference: ['', 'developer', ['files']],
19
19
  },
20
+ 'registry:migrate': {
21
+ script: 'scripts/registry-migration/migrate.ts',
22
+ description: 'Migrate a kit-only project from GitHub Packages to public npm',
23
+ category: 'Project',
24
+ reference: ['', 'developer', ['files', 'network', 'processes']],
25
+ },
20
26
  'sync:scope': {
21
27
  script: 'scripts/sync/managed-config-scope-cli.ts',
22
28
  description: 'Say whether this change touches a file josh sync distributes',
@@ -0,0 +1,229 @@
1
+ import { fix_gh_packages_logic } from '#scripts/gh/fix-gh-packages-logic'
2
+
3
+ const SCOPE = '@joshuafolkken'
4
+ const REGISTRY_KEY = `${SCOPE}:registry`
5
+ const PUBLIC_REGISTRY = 'https://registry.npmjs.org/'
6
+ const GITHUB_REGISTRY = 'https://npm.pkg.github.com/'
7
+ const MAPPING = `${REGISTRY_KEY}=${PUBLIC_REGISTRY}`
8
+ const REGISTRY_VALUE_INDEX = 1
9
+ const SPLIT_LIMIT = 2
10
+
11
+ interface MigrationPlan {
12
+ content: string
13
+ packages: ReadonlyArray<string>
14
+ blocked: ReadonlyArray<string>
15
+ change: string
16
+ }
17
+
18
+ type IntegrityPair = readonly [string, string]
19
+
20
+ function normalized_registry(value: string): string {
21
+ let normalized = value.trim()
22
+ while (normalized.endsWith('/')) normalized = normalized.slice(0, -1)
23
+
24
+ return `${normalized}/`
25
+ }
26
+
27
+ function mapping_lines(content: string): ReadonlyArray<string> {
28
+ return content.split(/\r?\n/u).filter((line) => line.trimStart().startsWith(`${REGISTRY_KEY}=`))
29
+ }
30
+
31
+ function registry_of(content: string): string | undefined {
32
+ const lines = mapping_lines(content)
33
+ if (lines.length !== 1) return undefined
34
+
35
+ return normalized_registry(lines[0]?.split('=', SPLIT_LIMIT)[REGISTRY_VALUE_INDEX] ?? '')
36
+ }
37
+
38
+ function scoped_packages(lockfile: string): ReadonlyArray<string> {
39
+ const packages = fix_gh_packages_logic.parse_lockfile_packages(lockfile)
40
+
41
+ return Object.keys(packages)
42
+ .filter((key) => key.startsWith(`${SCOPE}/`))
43
+ .map((key) => fix_gh_packages_logic.package_path_from_key(key))
44
+ .filter((name, index, names) => names.indexOf(name) === index)
45
+ }
46
+
47
+ function scoped_versions(lockfile: string): ReadonlyArray<readonly [string, string]> {
48
+ const packages = fix_gh_packages_logic.parse_lockfile_packages(lockfile)
49
+
50
+ return Object.keys(packages)
51
+ .filter((key) => key.startsWith(`${SCOPE}/`))
52
+ .map(
53
+ (key) =>
54
+ [
55
+ fix_gh_packages_logic.package_path_from_key(key),
56
+ fix_gh_packages_logic.package_version_from_key(key),
57
+ ] as const,
58
+ )
59
+ }
60
+
61
+ function is_github_tarball(value: string): boolean {
62
+ try {
63
+ const url = new URL(value)
64
+
65
+ return url.protocol === 'https:' && url.hostname === 'npm.pkg.github.com'
66
+ } catch {
67
+ return false
68
+ }
69
+ }
70
+
71
+ function github_tarballs(lockfile: string): ReadonlyArray<string> {
72
+ const packages = fix_gh_packages_logic.parse_lockfile_packages(lockfile)
73
+
74
+ return Object.entries(packages)
75
+ .filter(
76
+ ([key, entry]) =>
77
+ key.startsWith(`${SCOPE}/`) &&
78
+ entry.resolution?.tarball !== undefined &&
79
+ is_github_tarball(entry.resolution.tarball),
80
+ )
81
+ .map(([key]) => key)
82
+ }
83
+
84
+ function remove_flow_tarball(line: string): string {
85
+ const marker = ', tarball: https://npm.pkg.github.com/'
86
+ const start = line.indexOf(marker)
87
+ if (start === -1) return line
88
+ const end = line.indexOf('}', start)
89
+ if (end === -1) return line
90
+
91
+ return `${line.slice(0, start)}${line.slice(end)}`
92
+ }
93
+
94
+ function is_package_entry(line: string): boolean {
95
+ return /^ {2}[^ ]/u.test(line)
96
+ }
97
+
98
+ function is_github_tarball_line(line: string): boolean {
99
+ return line.trimStart().startsWith('tarball: https://npm.pkg.github.com/')
100
+ }
101
+
102
+ function clean_kit_line(
103
+ line: string,
104
+ version: string,
105
+ integrities: ReadonlyMap<string, IntegrityPair>,
106
+ ): string | undefined {
107
+ if (!version) return line
108
+ if (is_github_tarball_line(line)) return undefined
109
+ const cleaned = remove_flow_tarball(line)
110
+ const pair = integrities.get(version)
111
+ if (pair === undefined) return cleaned
112
+
113
+ return cleaned.split(`integrity: ${pair[0]}`).join(`integrity: ${pair[1]}`)
114
+ }
115
+
116
+ function next_kit_version(line: string, version: string): string {
117
+ if (!is_package_entry(line)) return version
118
+ const prefix = " '@joshuafolkken/kit@"
119
+
120
+ return line.startsWith(prefix) ? (line.slice(prefix.length).split("'", 1)[0] ?? '') : ''
121
+ }
122
+
123
+ function integrity_pair(
124
+ old_integrity: string | undefined,
125
+ npm_integrity: string | undefined,
126
+ ): IntegrityPair | undefined {
127
+ if (old_integrity === undefined || npm_integrity === undefined) return undefined
128
+
129
+ return [old_integrity, npm_integrity]
130
+ }
131
+
132
+ function lock_integrities(
133
+ lockfile: string,
134
+ npm_integrities: ReadonlyMap<string, string>,
135
+ ): Map<string, IntegrityPair> {
136
+ const packages = fix_gh_packages_logic.parse_lockfile_packages(lockfile)
137
+ const pairs = new Map<string, IntegrityPair>()
138
+ const kit_entries = Object.entries(packages).filter(([key]) => key.startsWith(`${SCOPE}/kit@`))
139
+
140
+ for (const [key, entry] of kit_entries) {
141
+ const version = fix_gh_packages_logic.package_version_from_key(key)
142
+ const pair = integrity_pair(entry.resolution?.integrity, npm_integrities.get(version))
143
+ if (pair !== undefined) pairs.set(version, pair)
144
+ }
145
+
146
+ return pairs
147
+ }
148
+
149
+ function push_if_defined(lines: Array<string>, cleaned: string | undefined): void {
150
+ if (cleaned !== undefined) lines.push(cleaned)
151
+ }
152
+
153
+ function rewrite_kit_lockfile(
154
+ lockfile: string,
155
+ npm_integrities: ReadonlyMap<string, string>,
156
+ ): string {
157
+ let kit_version = ''
158
+ const lines: Array<string> = []
159
+ const integrities = lock_integrities(lockfile, npm_integrities)
160
+
161
+ for (const line of lockfile.split('\n')) {
162
+ kit_version = next_kit_version(line, kit_version)
163
+ const cleaned = clean_kit_line(line, kit_version, integrities)
164
+
165
+ push_if_defined(lines, cleaned)
166
+ }
167
+
168
+ return lines.join('\n')
169
+ }
170
+
171
+ function append_mapping(content: string, eol: string): string {
172
+ const separator = content && !content.endsWith('\n') ? eol : ''
173
+
174
+ return `${content}${separator}${MAPPING}${eol}`
175
+ }
176
+
177
+ function replace_mapping(content: string): string {
178
+ const lines = mapping_lines(content)
179
+ const eol = content.includes('\r\n') ? '\r\n' : '\n'
180
+
181
+ if (lines.length === 0) return append_mapping(content, eol)
182
+
183
+ return content
184
+ .split(/\r?\n/u)
185
+ .map((line) => (line === lines[0] ? MAPPING : line))
186
+ .join(eol)
187
+ }
188
+
189
+ function unsupported_registry(project_npmrc: string): string | undefined {
190
+ const registry = registry_of(project_npmrc)
191
+
192
+ if (registry === undefined || [GITHUB_REGISTRY, PUBLIC_REGISTRY].includes(registry)) {
193
+ return undefined
194
+ }
195
+
196
+ return `unsupported project registry: ${registry}`
197
+ }
198
+
199
+ function has_duplicate_mapping(project_npmrc: string, user_npmrc: string): boolean {
200
+ return mapping_lines(project_npmrc).length > 1 || mapping_lines(user_npmrc).length > 1
201
+ }
202
+
203
+ function previous_registry(project_npmrc: string, user_npmrc: string): string {
204
+ return registry_of(project_npmrc) ?? registry_of(user_npmrc) ?? 'default npm'
205
+ }
206
+
207
+ function plan(project_npmrc: string, user_npmrc: string, lockfile: string): MigrationPlan {
208
+ const packages = scoped_packages(lockfile)
209
+ const blocked = packages.filter((name) => name !== `${SCOPE}/kit`)
210
+ if (has_duplicate_mapping(project_npmrc, user_npmrc)) blocked.push('duplicate registry mappings')
211
+ const unsupported = unsupported_registry(project_npmrc)
212
+ if (unsupported !== undefined) blocked.push(unsupported)
213
+ const before = previous_registry(project_npmrc, user_npmrc)
214
+ const change = `${before} → ${PUBLIC_REGISTRY}`
215
+
216
+ return { content: replace_mapping(project_npmrc), packages, blocked, change }
217
+ }
218
+
219
+ const migrate_logic = {
220
+ plan,
221
+ github_tarballs,
222
+ rewrite_kit_lockfile,
223
+ registry_of,
224
+ scoped_packages,
225
+ scoped_versions,
226
+ }
227
+
228
+ export { migrate_logic }
229
+ export type { MigrationPlan }
@@ -0,0 +1,280 @@
1
+ import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
2
+ import path from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+ import { execa } from 'execa'
5
+ import { z } from 'zod'
6
+ import { migrate_logic, type MigrationPlan } from './migrate-logic'
7
+ import { user_npmrc } from './user-npmrc'
8
+
9
+ const NPMRC = '.npmrc'
10
+ const LOCKFILE = 'pnpm-lock.yaml'
11
+ const WORKSPACE = 'pnpm-workspace.yaml'
12
+ const PUBLIC_REGISTRY = 'https://registry.npmjs.org/'
13
+ const FETCH_TIMEOUT_MS = 10_000
14
+ const SCOPE = '@joshuafolkken/'
15
+ const manifest_schema = z.looseObject({
16
+ dependencies: z.record(z.string(), z.string()).optional(),
17
+ devDependencies: z.record(z.string(), z.string()).optional(),
18
+ optionalDependencies: z.record(z.string(), z.string()).optional(),
19
+ peerDependencies: z.record(z.string(), z.string()).optional(),
20
+ })
21
+ const version_schema = z.looseObject({
22
+ dist: z.looseObject({ integrity: z.string(), tarball: z.string() }),
23
+ })
24
+
25
+ function is_public_npm_url(value: string): boolean {
26
+ try {
27
+ const url = new URL(value)
28
+
29
+ return url.origin === 'https://registry.npmjs.org' && url.username === '' && url.password === ''
30
+ } catch {
31
+ return false
32
+ }
33
+ }
34
+
35
+ interface VersionCheck {
36
+ missing: ReadonlyArray<string>
37
+ integrities: ReadonlyMap<string, string>
38
+ }
39
+
40
+ async function probe_integrity(
41
+ name: string,
42
+ version: string,
43
+ probe_version: MigrationDependencies['fetch_version'],
44
+ ): Promise<string | undefined> {
45
+ if (!version) return undefined
46
+
47
+ return await probe_version(name, version)
48
+ }
49
+
50
+ interface MigrationDependencies {
51
+ fetch_version: (name: string, version: string) => Promise<string | undefined>
52
+ install: (cwd: string) => Promise<void>
53
+ effective_registry: (cwd: string) => Promise<string>
54
+ user_npmrc: string
55
+ environment: NodeJS.ProcessEnv
56
+ }
57
+
58
+ interface MigrationContext {
59
+ cwd: string
60
+ npmrc_path: string
61
+ lockfile_path: string
62
+ workspace_path: string
63
+ original_npmrc: string
64
+ original_lockfile: string
65
+ original_workspace: string
66
+ has_npmrc: boolean
67
+ has_lockfile: boolean
68
+ has_workspace: boolean
69
+ plan: MigrationPlan
70
+ }
71
+
72
+ function read_file(file_path: string): string {
73
+ return existsSync(file_path) ? readFileSync(file_path, 'utf8') : ''
74
+ }
75
+
76
+ function has_registry_override(environment: NodeJS.ProcessEnv): boolean {
77
+ return Object.keys(environment).some((key) =>
78
+ /^(?:npm|pnpm)_config_.*(?:joshuafolkken.*registry|userconfig)/iu.test(key),
79
+ )
80
+ }
81
+
82
+ function manifest_packages(content: string): ReadonlyArray<string> {
83
+ const manifest = manifest_schema.parse(JSON.parse(content))
84
+ const sections = ['dependencies', 'devDependencies', 'optionalDependencies', 'peerDependencies']
85
+
86
+ return sections.flatMap((section) => {
87
+ const value: unknown = Reflect.get(manifest, section)
88
+ if (typeof value !== 'object' || value === null) return []
89
+
90
+ return Object.keys(value).filter((name) => name.startsWith(SCOPE))
91
+ })
92
+ }
93
+
94
+ function restore(context: MigrationContext): void {
95
+ if (context.has_npmrc) writeFileSync(context.npmrc_path, context.original_npmrc)
96
+ else rmSync(context.npmrc_path, { force: true })
97
+ writeFileSync(context.lockfile_path, context.original_lockfile)
98
+ if (context.has_workspace) writeFileSync(context.workspace_path, context.original_workspace)
99
+ else rmSync(context.workspace_path, { force: true })
100
+ }
101
+
102
+ async function verify_public_versions(
103
+ lockfile: string,
104
+ probe_version: MigrationDependencies['fetch_version'],
105
+ ): Promise<VersionCheck> {
106
+ const missing: Array<string> = []
107
+ const integrities = new Map<string, string>()
108
+
109
+ for (const [name, version] of migrate_logic.scoped_versions(lockfile)) {
110
+ const integrity = await probe_integrity(name, version, probe_version)
111
+ if (integrity === undefined) missing.push(`${name}@${version}`)
112
+ else integrities.set(version, integrity)
113
+ }
114
+
115
+ return { missing, integrities }
116
+ }
117
+
118
+ function report_context(context: MigrationContext): void {
119
+ console.info(`Current registry and planned change: ${context.plan.change}`)
120
+ console.info(`Installed scoped packages: ${context.plan.packages.join(', ') || '(none)'}`)
121
+ }
122
+
123
+ function create_context(cwd: string, dependencies: MigrationDependencies): MigrationContext {
124
+ const npmrc_path = path.join(cwd, NPMRC)
125
+ const lockfile_path = path.join(cwd, LOCKFILE)
126
+ const workspace_path = path.join(cwd, WORKSPACE)
127
+ const original_npmrc = read_file(npmrc_path)
128
+ const original_lockfile = read_file(lockfile_path)
129
+ const original_workspace = read_file(workspace_path)
130
+ const plan = migrate_logic.plan(original_npmrc, dependencies.user_npmrc, original_lockfile)
131
+
132
+ return {
133
+ cwd,
134
+ npmrc_path,
135
+ lockfile_path,
136
+ workspace_path,
137
+ original_npmrc,
138
+ original_lockfile,
139
+ original_workspace,
140
+ plan,
141
+ has_npmrc: existsSync(npmrc_path),
142
+ has_lockfile: existsSync(lockfile_path),
143
+ has_workspace: existsSync(workspace_path),
144
+ }
145
+ }
146
+
147
+ function initial_block(
148
+ context: MigrationContext,
149
+ dependencies: MigrationDependencies,
150
+ ): string | undefined {
151
+ if (!context.has_lockfile) return 'No lockfile; run pnpm install before migrating.'
152
+ const manifest = manifest_packages(read_file(path.join(context.cwd, 'package.json')))
153
+ const missing_lock = manifest.filter((name) => !context.plan.packages.includes(name))
154
+ const blocked = [...context.plan.blocked, ...missing_lock]
155
+ if (!context.plan.packages.includes(`${SCOPE}kit`)) blocked.push('kit is absent from lockfile')
156
+ if (has_registry_override(dependencies.environment)) blocked.push('registry environment override')
157
+ if (blocked.length === 0) return undefined
158
+
159
+ return `Migration blocked: ${blocked.join(', ')}. Keep GitHub Packages until every package is available on npm.`
160
+ }
161
+
162
+ async function verify_resolution(
163
+ context: MigrationContext,
164
+ dependencies: MigrationDependencies,
165
+ integrities: ReadonlyMap<string, string>,
166
+ ): Promise<void> {
167
+ await dependencies.install(context.cwd)
168
+
169
+ if (read_file(context.workspace_path) !== context.original_workspace) {
170
+ throw new Error('pnpm changed pnpm-workspace.yaml outside the migration')
171
+ }
172
+
173
+ const actual = await dependencies.effective_registry(context.cwd)
174
+ if (!is_public_npm_url(actual)) throw new Error(`effective registry is ${actual}`)
175
+ const lockfile = read_file(context.lockfile_path)
176
+ const old_tarballs = migrate_logic.github_tarballs(lockfile)
177
+ if (old_tarballs.length > 0) throw new Error(`GitHub tarballs remain: ${old_tarballs.join(', ')}`)
178
+
179
+ if (migrate_logic.rewrite_kit_lockfile(lockfile, integrities) !== lockfile) {
180
+ throw new Error('lockfile integrity does not match public npm')
181
+ }
182
+ }
183
+
184
+ async function apply(
185
+ context: MigrationContext,
186
+ dependencies: MigrationDependencies,
187
+ integrities: ReadonlyMap<string, string>,
188
+ ): Promise<string> {
189
+ try {
190
+ writeFileSync(context.npmrc_path, context.plan.content)
191
+ writeFileSync(
192
+ context.lockfile_path,
193
+ migrate_logic.rewrite_kit_lockfile(context.original_lockfile, integrities),
194
+ )
195
+ await verify_resolution(context, dependencies, integrities)
196
+ } catch (error) {
197
+ restore(context)
198
+
199
+ return `Migration blocked; original settings restored: ${String(error)}`
200
+ }
201
+
202
+ return 'Migrated to public npm. The project registry and lockfile now resolve from npm.'
203
+ }
204
+
205
+ function is_already_migrated(
206
+ context: MigrationContext,
207
+ integrities: ReadonlyMap<string, string>,
208
+ ): boolean {
209
+ return (
210
+ context.plan.content === context.original_npmrc &&
211
+ migrate_logic.rewrite_kit_lockfile(context.original_lockfile, integrities) ===
212
+ context.original_lockfile
213
+ )
214
+ }
215
+
216
+ async function migrate(cwd: string, dependencies: MigrationDependencies): Promise<string> {
217
+ const context = create_context(cwd, dependencies)
218
+
219
+ report_context(context)
220
+ const block = initial_block(context, dependencies)
221
+ if (block !== undefined) return block
222
+ const version_check = await verify_public_versions(
223
+ context.original_lockfile,
224
+ dependencies.fetch_version,
225
+ )
226
+
227
+ if (version_check.missing.length > 0) {
228
+ return `Migration blocked: unpublished on npm: ${version_check.missing.join(', ')}.`
229
+ }
230
+
231
+ if (is_already_migrated(context, version_check.integrities)) {
232
+ return 'Already using public npm; no changes made.'
233
+ }
234
+
235
+ return await apply(context, dependencies, version_check.integrities)
236
+ }
237
+
238
+ async function fetch_version(name: string, version: string): Promise<string | undefined> {
239
+ const encoded = encodeURIComponent(name)
240
+ const response = await fetch(`${PUBLIC_REGISTRY}${encoded}/${encodeURIComponent(version)}`, {
241
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
242
+ })
243
+
244
+ if (!response.ok) return undefined
245
+ const parsed = version_schema.safeParse(await response.json())
246
+ if (!parsed.success || !is_public_npm_url(parsed.data.dist.tarball)) return undefined
247
+
248
+ return parsed.data.dist.integrity
249
+ }
250
+
251
+ async function install(cwd: string): Promise<void> {
252
+ await execa('pnpm', ['install', '--lockfile-only', '--ignore-scripts', '--force'], { cwd })
253
+ }
254
+
255
+ async function effective_registry(cwd: string): Promise<string> {
256
+ const result = await execa('pnpm', ['config', 'get', '@joshuafolkken:registry'], { cwd })
257
+
258
+ return result.stdout.trim()
259
+ }
260
+
261
+ async function main(): Promise<void> {
262
+ const result = await migrate(process.cwd(), {
263
+ fetch_version,
264
+ install,
265
+ effective_registry,
266
+ user_npmrc: user_npmrc.read(),
267
+ environment: process.env,
268
+ })
269
+
270
+ console.info(result)
271
+
272
+ if (result.startsWith('Migration blocked') || result.startsWith('No lockfile')) process.exit(1)
273
+ }
274
+
275
+ if (process.argv[1] === fileURLToPath(import.meta.url)) await main()
276
+
277
+ const registry_migration = { migrate }
278
+
279
+ export { registry_migration }
280
+ export type { MigrationDependencies }
@@ -0,0 +1,15 @@
1
+ import { existsSync, readFileSync } from 'node:fs'
2
+ import os from 'node:os'
3
+ import path from 'node:path'
4
+
5
+ const NPMRC = '.npmrc'
6
+
7
+ function read(home: string = os.homedir()): string {
8
+ const file = path.join(home, NPMRC)
9
+
10
+ return existsSync(file) ? readFileSync(file, 'utf8') : ''
11
+ }
12
+
13
+ const user_npmrc = { read }
14
+
15
+ export { user_npmrc }
@@ -118,7 +118,9 @@ jobs:
118
118
  uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
119
119
  with:
120
120
  node-version: '24'
121
- registry-url: 'https://npm.pkg.github.com'
121
+
122
+ - name: Configure GitHub Packages authentication for existing projects
123
+ run: printf '//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}\n' >> "$HOME/.npmrc"
122
124
 
123
125
  - name: Resolve pnpm version
124
126
  id: pnpm-version
@@ -358,7 +360,9 @@ jobs:
358
360
  uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
359
361
  with:
360
362
  node-version: '24'
361
- registry-url: 'https://npm.pkg.github.com'
363
+
364
+ - name: Configure GitHub Packages authentication for existing projects
365
+ run: printf '//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}\n' >> "$HOME/.npmrc"
362
366
 
363
367
  - name: Resolve pnpm version
364
368
  id: pnpm-version