@shortlink-org/portolan 0.1.0 → 0.2.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 (381) hide show
  1. package/README.md +69 -205
  2. package/cli/init.mjs +285 -0
  3. package/cli/init.test.mjs +158 -0
  4. package/cli/portolan.mjs +37 -73
  5. package/index.html +19 -0
  6. package/package.json +5 -10
  7. package/plugins/README.md +66 -27
  8. package/plugins/portolan-go.wasm +0 -0
  9. package/portolan.json +52 -73
  10. package/public/landing/cats/bug-hunter.webp +0 -0
  11. package/public/landing/cats/compass-nap.webp +0 -0
  12. package/public/landing/cats/diagrammer.webp +0 -0
  13. package/public/landing/cats/docs-reader.webp +0 -0
  14. package/public/landing/cats/laptop.webp +0 -0
  15. package/public/landing/cats/map-inspector.webp +0 -0
  16. package/public/landing/cats/server-break.webp +0 -0
  17. package/public/landing/cats/star-mapper.webp +0 -0
  18. package/public/landing/cats/system-builder.webp +0 -0
  19. package/public/landing/cats/thread-tangle.webp +0 -0
  20. package/public/og.png +0 -0
  21. package/public/readme-header.webp +0 -0
  22. package/schema/portolan.schema.json +180 -0
  23. package/scripts/builtin-plugins.mjs +14 -0
  24. package/scripts/delivery-presets.mjs +353 -0
  25. package/scripts/gen.mjs +37 -4
  26. package/scripts/history.mjs +126 -0
  27. package/scripts/history.test.mjs +110 -0
  28. package/scripts/host-plugins/fetch-bsr.mjs +351 -0
  29. package/{plugins/fetch-bsr/options.schema.json → scripts/host-plugins/fetch-bsr.options.json} +4 -0
  30. package/scripts/host-plugins/fetch-bsr.test.mjs +212 -0
  31. package/scripts/host-plugins/fetch-csr.mjs +386 -0
  32. package/scripts/host-plugins/fetch-csr.test.mjs +178 -0
  33. package/scripts/host-plugins/fetch-git.mjs +379 -0
  34. package/scripts/host-plugins/fetch-git.test.mjs +210 -0
  35. package/scripts/local-api.mjs +22 -4
  36. package/scripts/local-api.test.mjs +84 -2
  37. package/scripts/manifest.mjs +3 -2
  38. package/scripts/package-smoke.mjs +6 -2
  39. package/scripts/plugin-host.mjs +77 -18
  40. package/scripts/plugin-host.test.mjs +50 -1
  41. package/scripts/plugin-wasm-worker.mjs +6 -3
  42. package/scripts/run-builtin.mjs +6 -26
  43. package/scripts/schema.mjs +6 -1
  44. package/src/app/App.tsx +24 -429
  45. package/src/app/Breadcrumbs.test.ts +7 -0
  46. package/src/app/Breadcrumbs.tsx +15 -2
  47. package/src/app/CatalogApp.tsx +436 -0
  48. package/src/app/Sidebar.tsx +0 -1
  49. package/src/chat/ChatPanel.tsx +98 -41
  50. package/src/chat/Composer.tsx +4 -2
  51. package/src/chat/Conversation.tsx +79 -15
  52. package/src/chat/Header.tsx +43 -9
  53. package/src/chat/Starter.tsx +64 -3
  54. package/src/chat/Steps.tsx +1 -1
  55. package/src/chat/flags.test.ts +51 -14
  56. package/src/chat/flags.ts +8 -2
  57. package/src/chat/page-context.test.ts +51 -0
  58. package/src/chat/page-context.ts +128 -0
  59. package/src/chat/prompt.test.ts +49 -1
  60. package/src/chat/prompt.ts +42 -1
  61. package/src/chat/tools.ts +7 -2
  62. package/src/chat/transport.ts +16 -4
  63. package/src/components/ProblemRow.tsx +182 -0
  64. package/src/data.ts +20 -5
  65. package/src/er/ErCanvas.tsx +1 -1
  66. package/src/graph/DependencyGraph.tsx +10 -1
  67. package/src/graph/FocusedEventGraph.tsx +1 -1
  68. package/src/graph/GraphToolbar.tsx +2 -4
  69. package/src/index.css +220 -2
  70. package/src/landing/DraggableReveal.tsx +168 -0
  71. package/src/landing/EstateGraph.tsx +28 -0
  72. package/src/landing/FlowPlayback.tsx +282 -0
  73. package/src/landing/HeroMap.tsx +128 -0
  74. package/src/landing/LandingPage.tsx +757 -0
  75. package/src/landing/ProductFrame.tsx +68 -0
  76. package/src/landing/ProductTour.tsx +371 -0
  77. package/src/landing/catalog.ts +25 -0
  78. package/src/landing/motion.tsx +52 -0
  79. package/src/lib/all-problems.ts +28 -0
  80. package/src/lib/local-api.ts +35 -1
  81. package/src/lib/setup-info.test.ts +8 -0
  82. package/src/lib/setup-info.ts +3 -2
  83. package/src/lib/source-code.ts +2 -1
  84. package/src/likec4/C4View.tsx +5 -0
  85. package/src/likec4/InteractiveView.tsx +5 -1
  86. package/src/map/ContextMapGraph.tsx +47 -20
  87. package/src/pages/ContextMap.tsx +5 -1
  88. package/src/pages/FlowDetail.tsx +24 -7
  89. package/src/pages/Overview.tsx +68 -12
  90. package/src/pages/Problems.tsx +6 -193
  91. package/src/pages/Settings.tsx +133 -108
  92. package/src/pages/settings/DeliverySettings.tsx +229 -0
  93. package/src/pages/settings/PreferencesSettings.tsx +101 -0
  94. package/src/routes.test.ts +20 -0
  95. package/src/routes.ts +13 -1
  96. package/vite.config.ts +12 -8
  97. package/catalog/enum_test.go +0 -46
  98. package/catalog/model.go +0 -932
  99. package/catalog/roundtrip_test.go +0 -185
  100. package/catalog/via_test.go +0 -38
  101. package/go.mod +0 -14
  102. package/go.sum +0 -14
  103. package/internal/commands/cargo.go +0 -154
  104. package/internal/commands/commands.go +0 -143
  105. package/internal/commands/commands_test.go +0 -323
  106. package/internal/commands/gradle.go +0 -77
  107. package/internal/commands/justfile.go +0 -82
  108. package/internal/commands/makefile.go +0 -106
  109. package/internal/commands/maven.go +0 -93
  110. package/internal/commands/packagejson.go +0 -159
  111. package/internal/commands/pyproject.go +0 -169
  112. package/internal/commands/taskfile.go +0 -101
  113. package/internal/commands/testdata/estate/.cargo/config.toml +0 -7
  114. package/internal/commands/testdata/estate/Makefile +0 -33
  115. package/internal/commands/testdata/estate/Taskfile.yml +0 -21
  116. package/internal/commands/testdata/estate/build.gradle.kts +0 -21
  117. package/internal/commands/testdata/estate/justfile +0 -20
  118. package/internal/commands/testdata/estate/package.json +0 -13
  119. package/internal/commands/testdata/estate/pom.xml +0 -29
  120. package/internal/commands/testdata/estate/pyproject.toml +0 -27
  121. package/internal/commands/testdata/estate/xtask/src/main.rs +0 -27
  122. package/internal/commands/testdata/golden/commands.json +0 -305
  123. package/internal/gohttp/analyze.go +0 -2500
  124. package/internal/gohttp/endpoints.go +0 -1067
  125. package/internal/gohttp/roots.go +0 -320
  126. package/internal/gohttp/typed.go +0 -143
  127. package/internal/goscan/constants.go +0 -85
  128. package/internal/goscan/goscan_test.go +0 -227
  129. package/internal/goscan/names.go +0 -52
  130. package/internal/goscan/parse_test.go +0 -11
  131. package/internal/goscan/source.go +0 -36
  132. package/internal/goscan/tree.go +0 -155
  133. package/internal/goscan/types.go +0 -99
  134. package/internal/wsdl/ids.go +0 -127
  135. package/internal/wsdl/ids_test.go +0 -21
  136. package/internal/wsdl/model.go +0 -62
  137. package/internal/wsdl/parse.go +0 -920
  138. package/internal/wsdl/parse_test.go +0 -133
  139. package/plugin/describe.go +0 -107
  140. package/plugin/describe_test.go +0 -114
  141. package/plugin/protocol.go +0 -117
  142. package/plugin/schematest/schematest.go +0 -126
  143. package/plugins/extract-adr/describe.go +0 -19
  144. package/plugins/extract-adr/describe_test.go +0 -11
  145. package/plugins/extract-adr/extract.go +0 -153
  146. package/plugins/extract-adr/extract_test.go +0 -350
  147. package/plugins/extract-adr/history.go +0 -99
  148. package/plugins/extract-adr/main.go +0 -65
  149. package/plugins/extract-adr/parse.go +0 -642
  150. package/plugins/extract-adr/parse_test.go +0 -405
  151. package/plugins/extract-asyncapi/describe.go +0 -19
  152. package/plugins/extract-asyncapi/describe_test.go +0 -11
  153. package/plugins/extract-asyncapi/extract.go +0 -315
  154. package/plugins/extract-asyncapi/extract_test.go +0 -197
  155. package/plugins/extract-asyncapi/main.go +0 -48
  156. package/plugins/extract-asyncapi/spec.go +0 -151
  157. package/plugins/extract-commands/describe.go +0 -19
  158. package/plugins/extract-commands/describe_test.go +0 -11
  159. package/plugins/extract-commands/extract.go +0 -79
  160. package/plugins/extract-commands/extract_test.go +0 -92
  161. package/plugins/extract-commands/main.go +0 -55
  162. package/plugins/extract-csr/avro.go +0 -258
  163. package/plugins/extract-csr/describe.go +0 -19
  164. package/plugins/extract-csr/describe_test.go +0 -11
  165. package/plugins/extract-csr/extract.go +0 -338
  166. package/plugins/extract-csr/extract_test.go +0 -374
  167. package/plugins/extract-csr/jsonschema.go +0 -343
  168. package/plugins/extract-csr/lock.go +0 -25
  169. package/plugins/extract-csr/main.go +0 -84
  170. package/plugins/extract-csr/subject.go +0 -102
  171. package/plugins/extract-flows/describe.go +0 -19
  172. package/plugins/extract-flows/describe_test.go +0 -11
  173. package/plugins/extract-flows/extract.go +0 -91
  174. package/plugins/extract-flows/main.go +0 -45
  175. package/plugins/extract-flows/parse.go +0 -593
  176. package/plugins/extract-flows/parse_test.go +0 -204
  177. package/plugins/extract-glossary/describe.go +0 -19
  178. package/plugins/extract-glossary/describe_test.go +0 -11
  179. package/plugins/extract-glossary/extract.go +0 -115
  180. package/plugins/extract-glossary/extract_test.go +0 -220
  181. package/plugins/extract-glossary/main.go +0 -59
  182. package/plugins/extract-glossary/parse.go +0 -214
  183. package/plugins/extract-glossary/parse_test.go +0 -203
  184. package/plugins/extract-go/aggregate.go +0 -214
  185. package/plugins/extract-go/client.go +0 -409
  186. package/plugins/extract-go/client_test.go +0 -266
  187. package/plugins/extract-go/describe.go +0 -19
  188. package/plugins/extract-go/describe_test.go +0 -11
  189. package/plugins/extract-go/enum.go +0 -195
  190. package/plugins/extract-go/enum_test.go +0 -82
  191. package/plugins/extract-go/event.go +0 -99
  192. package/plugins/extract-go/extract.go +0 -191
  193. package/plugins/extract-go/extract_test.go +0 -261
  194. package/plugins/extract-go/flow.go +0 -1441
  195. package/plugins/extract-go/flow_test.go +0 -609
  196. package/plugins/extract-go/httpclient.go +0 -174
  197. package/plugins/extract-go/httpclient_test.go +0 -296
  198. package/plugins/extract-go/ids.go +0 -92
  199. package/plugins/extract-go/layout.go +0 -225
  200. package/plugins/extract-go/layout_test.go +0 -125
  201. package/plugins/extract-go/lifecycle.go +0 -324
  202. package/plugins/extract-go/lifecycle_test.go +0 -108
  203. package/plugins/extract-go/main.go +0 -79
  204. package/plugins/extract-go/operation.go +0 -157
  205. package/plugins/extract-go/source.go +0 -316
  206. package/plugins/extract-go/transport.go +0 -321
  207. package/plugins/extract-go/transport_test.go +0 -145
  208. package/plugins/extract-go/wiring.go +0 -434
  209. package/plugins/extract-go-nats/describe.go +0 -19
  210. package/plugins/extract-go-nats/describe_test.go +0 -11
  211. package/plugins/extract-go-nats/extract.go +0 -177
  212. package/plugins/extract-go-nats/extract_test.go +0 -389
  213. package/plugins/extract-go-nats/index.go +0 -379
  214. package/plugins/extract-go-nats/main.go +0 -42
  215. package/plugins/extract-go-nats/resolve.go +0 -161
  216. package/plugins/extract-go-nats/sites.go +0 -224
  217. package/plugins/extract-graphql/describe.go +0 -19
  218. package/plugins/extract-graphql/describe_test.go +0 -11
  219. package/plugins/extract-graphql/extract.go +0 -433
  220. package/plugins/extract-graphql/extract_test.go +0 -256
  221. package/plugins/extract-graphql/ids.go +0 -49
  222. package/plugins/extract-graphql/lex.go +0 -237
  223. package/plugins/extract-graphql/main.go +0 -51
  224. package/plugins/extract-graphql/parse.go +0 -621
  225. package/plugins/extract-graphql/parse_test.go +0 -122
  226. package/plugins/extract-http-clients/describe.go +0 -19
  227. package/plugins/extract-http-clients/describe_test.go +0 -11
  228. package/plugins/extract-http-clients/extract.go +0 -705
  229. package/plugins/extract-http-clients/extract_test.go +0 -1263
  230. package/plugins/extract-http-clients/main.go +0 -42
  231. package/plugins/extract-openapi/describe.go +0 -19
  232. package/plugins/extract-openapi/describe_test.go +0 -11
  233. package/plugins/extract-openapi/discover.go +0 -243
  234. package/plugins/extract-openapi/extract.go +0 -526
  235. package/plugins/extract-openapi/extract_test.go +0 -545
  236. package/plugins/extract-openapi/main.go +0 -75
  237. package/plugins/extract-openapi/spec.go +0 -350
  238. package/plugins/extract-project/describe.go +0 -19
  239. package/plugins/extract-project/describe_test.go +0 -11
  240. package/plugins/extract-project/extract.go +0 -221
  241. package/plugins/extract-project/extract_test.go +0 -109
  242. package/plugins/extract-project/main.go +0 -41
  243. package/plugins/extract-proto/ast.go +0 -125
  244. package/plugins/extract-proto/consumes.go +0 -77
  245. package/plugins/extract-proto/describe.go +0 -19
  246. package/plugins/extract-proto/extract.go +0 -293
  247. package/plugins/extract-proto/extract_test.go +0 -459
  248. package/plugins/extract-proto/ids.go +0 -89
  249. package/plugins/extract-proto/ids_test.go +0 -57
  250. package/plugins/extract-proto/lex.go +0 -285
  251. package/plugins/extract-proto/main.go +0 -100
  252. package/plugins/extract-proto/module.go +0 -120
  253. package/plugins/extract-proto/parse.go +0 -720
  254. package/plugins/extract-proto/parse_test.go +0 -307
  255. package/plugins/extract-proto/provides.go +0 -236
  256. package/plugins/extract-proto/resolve.go +0 -222
  257. package/plugins/extract-proto/resolve_test.go +0 -119
  258. package/plugins/extract-redis/describe.go +0 -19
  259. package/plugins/extract-redis/describe_test.go +0 -11
  260. package/plugins/extract-redis/extract.go +0 -183
  261. package/plugins/extract-redis/extract_test.go +0 -168
  262. package/plugins/extract-redis/keyspaces.go +0 -469
  263. package/plugins/extract-redis/main.go +0 -44
  264. package/plugins/extract-river/describe.go +0 -19
  265. package/plugins/extract-river/describe_test.go +0 -11
  266. package/plugins/extract-river/extract.go +0 -507
  267. package/plugins/extract-river/extract_test.go +0 -132
  268. package/plugins/extract-river/main.go +0 -42
  269. package/plugins/extract-sql/ddl.go +0 -893
  270. package/plugins/extract-sql/ddl_test.go +0 -401
  271. package/plugins/extract-sql/describe.go +0 -19
  272. package/plugins/extract-sql/describe_test.go +0 -11
  273. package/plugins/extract-sql/layout.go +0 -221
  274. package/plugins/extract-sql/layout_test.go +0 -55
  275. package/plugins/extract-sql/lineage.go +0 -107
  276. package/plugins/extract-sql/main.go +0 -141
  277. package/plugins/extract-sql/maps.go +0 -564
  278. package/plugins/extract-sql/maps_java.go +0 -117
  279. package/plugins/extract-sql/maps_rust.go +0 -333
  280. package/plugins/extract-sql/maps_rust_test.go +0 -70
  281. package/plugins/extract-sql/maps_test.go +0 -204
  282. package/plugins/extract-sql/maps_ts.go +0 -398
  283. package/plugins/extract-sql/maps_ts_test.go +0 -136
  284. package/plugins/extract-sql/projection.go +0 -70
  285. package/plugins/extract-sql/projection_test.go +0 -50
  286. package/plugins/extract-sql/store.go +0 -420
  287. package/plugins/extract-sql/store_test.go +0 -233
  288. package/plugins/extract-sql/view.go +0 -295
  289. package/plugins/extract-watermill/describe.go +0 -19
  290. package/plugins/extract-watermill/describe_test.go +0 -11
  291. package/plugins/extract-watermill/extract.go +0 -1228
  292. package/plugins/extract-watermill/extract_test.go +0 -234
  293. package/plugins/extract-watermill/main.go +0 -41
  294. package/plugins/extract-wsdl/describe.go +0 -19
  295. package/plugins/extract-wsdl/describe_test.go +0 -11
  296. package/plugins/extract-wsdl/extract.go +0 -148
  297. package/plugins/extract-wsdl/extract_test.go +0 -45
  298. package/plugins/extract-wsdl/main.go +0 -53
  299. package/plugins/fetch-bsr/auth.go +0 -114
  300. package/plugins/fetch-bsr/bsr.go +0 -240
  301. package/plugins/fetch-bsr/cache.go +0 -66
  302. package/plugins/fetch-bsr/describe.go +0 -19
  303. package/plugins/fetch-bsr/fetch.go +0 -192
  304. package/plugins/fetch-bsr/fetch_test.go +0 -465
  305. package/plugins/fetch-bsr/lock.go +0 -71
  306. package/plugins/fetch-bsr/main.go +0 -117
  307. package/plugins/fetch-csr/auth.go +0 -95
  308. package/plugins/fetch-csr/cache.go +0 -69
  309. package/plugins/fetch-csr/describe.go +0 -19
  310. package/plugins/fetch-csr/describe_test.go +0 -11
  311. package/plugins/fetch-csr/fetch.go +0 -254
  312. package/plugins/fetch-csr/fetch_test.go +0 -494
  313. package/plugins/fetch-csr/lock.go +0 -95
  314. package/plugins/fetch-csr/main.go +0 -122
  315. package/plugins/fetch-csr/registry.go +0 -209
  316. package/plugins/fetch-git/cache.go +0 -65
  317. package/plugins/fetch-git/describe.go +0 -19
  318. package/plugins/fetch-git/describe_test.go +0 -11
  319. package/plugins/fetch-git/fetch.go +0 -181
  320. package/plugins/fetch-git/fetch_test.go +0 -332
  321. package/plugins/fetch-git/git.go +0 -169
  322. package/plugins/fetch-git/lock.go +0 -72
  323. package/plugins/fetch-git/main.go +0 -127
  324. package/plugins/fetch-git/offline.go +0 -39
  325. package/plugins/fetch-git/pin.go +0 -90
  326. package/plugins/fetch-git/pin_test.go +0 -74
  327. package/plugins/gen-backstage/describe.go +0 -17
  328. package/plugins/gen-backstage/main.go +0 -22
  329. package/plugins/gen-backstage/plugin.go +0 -473
  330. package/plugins/gen-backstage/plugin_test.go +0 -145
  331. package/plugins/gen-backstage.wasm +0 -0
  332. package/plugins/gen-markdown/adr.go +0 -162
  333. package/plugins/gen-markdown/aggregate.go +0 -362
  334. package/plugins/gen-markdown/canonical.go +0 -93
  335. package/plugins/gen-markdown/context.go +0 -90
  336. package/plugins/gen-markdown/coverage_test.go +0 -89
  337. package/plugins/gen-markdown/describe.go +0 -19
  338. package/plugins/gen-markdown/describe_test.go +0 -11
  339. package/plugins/gen-markdown/external.go +0 -71
  340. package/plugins/gen-markdown/flow.go +0 -277
  341. package/plugins/gen-markdown/glossary.go +0 -76
  342. package/plugins/gen-markdown/glossary_test.go +0 -148
  343. package/plugins/gen-markdown/llms.go +0 -302
  344. package/plugins/gen-markdown/main.go +0 -22
  345. package/plugins/gen-markdown/markdown.go +0 -254
  346. package/plugins/gen-markdown/markdown_test.go +0 -100
  347. package/plugins/gen-markdown/module.go +0 -107
  348. package/plugins/gen-markdown/plugin.go +0 -39
  349. package/plugins/gen-markdown/quality_test.go +0 -176
  350. package/plugins/gen-markdown/redis_test.go +0 -38
  351. package/plugins/gen-markdown/render.go +0 -344
  352. package/plugins/gen-markdown/render_test.go +0 -186
  353. package/plugins/gen-markdown/service.go +0 -456
  354. package/plugins/gen-markdown/source.go +0 -177
  355. package/plugins/gen-markdown/store.go +0 -201
  356. package/plugins/gen-markdown.wasm +0 -0
  357. package/plugins/gen-mermaid/describe.go +0 -17
  358. package/plugins/gen-mermaid/main.go +0 -22
  359. package/plugins/gen-mermaid/plugin.go +0 -104
  360. package/plugins/gen-mermaid/plugin_test.go +0 -33
  361. package/plugins/gen-mermaid.wasm +0 -0
  362. package/plugins/openapi/ids.go +0 -261
  363. package/plugins/openapi/ids_test.go +0 -98
  364. package/plugins/verify-codeowners/describe.go +0 -19
  365. package/plugins/verify-codeowners/describe_test.go +0 -11
  366. package/plugins/verify-codeowners/main.go +0 -75
  367. package/plugins/verify-codeowners/match.go +0 -85
  368. package/plugins/verify-codeowners/match_test.go +0 -47
  369. package/plugins/verify-codeowners/owners.go +0 -164
  370. package/plugins/verify-codeowners/owners_test.go +0 -225
  371. package/plugins/verify-codeowners/parse.go +0 -90
  372. package/plugins/verify-codeowners/parse_test.go +0 -62
  373. package/plugins/verify-otel/describe.go +0 -19
  374. package/plugins/verify-otel/describe_test.go +0 -11
  375. package/plugins/verify-otel/main.go +0 -53
  376. package/plugins/verify-otel/match.go +0 -336
  377. package/plugins/verify-otel/otlp.go +0 -200
  378. package/plugins/verify-otel/verify.go +0 -734
  379. package/plugins/verify-otel/verify_test.go +0 -460
  380. /package/{plugins/fetch-csr/options.schema.json → scripts/host-plugins/fetch-csr.options.json} +0 -0
  381. /package/{plugins/fetch-git/options.schema.json → scripts/host-plugins/fetch-git.options.json} +0 -0
@@ -1,332 +0,0 @@
1
- package main
2
-
3
- import (
4
- "os"
5
- "os/exec"
6
- "path/filepath"
7
- "sort"
8
- "strings"
9
- "testing"
10
-
11
- "github.com/shortlink-org/portolan/plugin"
12
- )
13
-
14
- // A repository to fetch from, made on the spot: git itself is the fake, and
15
- // what it serves is what a forge would serve.
16
- type remote struct {
17
- url string
18
- commit string
19
- }
20
-
21
- func repository(t *testing.T) remote {
22
- t.Helper()
23
- if _, err := exec.LookPath("git"); err != nil {
24
- t.Skip("no git on PATH")
25
- }
26
- dir := t.TempDir()
27
- run := func(args ...string) string {
28
- t.Helper()
29
- cmd := exec.Command("git", append([]string{
30
- "-c", "user.name=test", "-c", "user.email=test@example.com",
31
- "-c", "commit.gpgsign=false", "-c", "init.defaultBranch=main",
32
- }, args...)...)
33
- cmd.Dir = dir
34
- out, err := cmd.CombinedOutput()
35
- if err != nil {
36
- t.Fatalf("git %s: %v\n%s", strings.Join(args, " "), err, out)
37
- }
38
-
39
- return strings.TrimSpace(string(out))
40
- }
41
- write := func(rel, contents string) {
42
- t.Helper()
43
- if err := os.MkdirAll(filepath.Dir(filepath.Join(dir, rel)), 0o755); err != nil {
44
- t.Fatal(err)
45
- }
46
- if err := os.WriteFile(filepath.Join(dir, rel), []byte(contents), 0o644); err != nil {
47
- t.Fatal(err)
48
- }
49
- }
50
-
51
- run("init", "--quiet")
52
- write("services/oms/internal/domain/order/order.go", "package order\n")
53
- write("services/oms/README.md", "# OMS\n")
54
- write("proto/shop/v1/orders.proto", "syntax = \"proto3\";\n")
55
- write("README.md", "# shop\n")
56
- run("add", ".")
57
- run("commit", "--quiet", "-m", "the estate")
58
- // A forge lets a client fetch a commit by name; a plain file remote does
59
- // not unless told to, and the fallback to branches is tested separately.
60
- run("config", "uploadpack.allowAnySHA1InWant", "true")
61
-
62
- return remote{url: "file://" + dir, commit: run("rev-parse", "HEAD")}
63
- }
64
-
65
- func options(r remote, cache string, paths ...string) Options {
66
- return Options{
67
- Cache: cache,
68
- Repos: []Repo{{Repo: r.url, Commit: r.commit, Paths: paths}},
69
- }
70
- }
71
-
72
- // Every test decides for itself whether it is online.
73
- func isolate(t *testing.T) {
74
- t.Helper()
75
- t.Setenv(OfflineEnv, "")
76
- t.Setenv("CI", "")
77
- }
78
-
79
- func names(resp plugin.Response) []string {
80
- out := make([]string, 0, len(resp.Files))
81
- for _, f := range resp.Files {
82
- out = append(out, f.Name)
83
- }
84
- sort.Strings(out)
85
-
86
- return out
87
- }
88
-
89
- func contentsOf(resp plugin.Response, name string) string {
90
- for _, f := range resp.Files {
91
- if f.Name == name {
92
- return f.Contents
93
- }
94
- }
95
-
96
- return ""
97
- }
98
-
99
- // write does what the host does with a response.
100
- func write(t *testing.T, dir string, resp plugin.Response) {
101
- t.Helper()
102
- for _, f := range resp.Files {
103
- at := filepath.Join(dir, filepath.FromSlash(f.Name))
104
- if err := os.MkdirAll(filepath.Dir(at), 0o755); err != nil {
105
- t.Fatal(err)
106
- }
107
- if err := os.WriteFile(at, []byte(f.Contents), 0o644); err != nil {
108
- t.Fatal(err)
109
- }
110
- }
111
- }
112
-
113
- func TestFetchWritesTheNarrowedCopyAndALock(t *testing.T) {
114
- isolate(t)
115
- r := repository(t)
116
-
117
- resp, err := fetch(options(r, t.TempDir(), "services/oms", "proto"))
118
- if err != nil {
119
- t.Fatal(err)
120
- }
121
- // The directory is owner/name, read off the URL; the paths inside are
122
- // the repository's own, so an extractor can read the copy as a checkout.
123
- dir := filepath.Base(filepath.Dir(strings.TrimPrefix(r.url, "file://"))) + "/" + filepath.Base(strings.TrimPrefix(r.url, "file://"))
124
- want := []string{
125
- dir + "/git.lock.json",
126
- dir + "/git.repo.json",
127
- dir + "/proto/shop/v1/orders.proto",
128
- dir + "/services/oms/README.md",
129
- dir + "/services/oms/internal/domain/order/order.go",
130
- }
131
- if got := names(resp); strings.Join(got, "\n") != strings.Join(want, "\n") {
132
- t.Fatalf("files = %v, want %v", got, want)
133
- }
134
- lock := contentsOf(resp, dir+"/git.lock.json")
135
- if !strings.Contains(lock, `"commit": "`+r.commit+`"`) || !strings.Contains(lock, `"sha256"`) || !strings.Contains(lock, `"services/oms"`) {
136
- t.Errorf("lock = %s", lock)
137
- }
138
- if len(resp.Warnings()) != 0 {
139
- t.Errorf("diagnostics = %+v", resp.Warnings())
140
- }
141
-
142
- // The fragment is the lock's commit said in the shape the catalog reads,
143
- // and it is a catalog: empty lists rather than absent ones, so the merge
144
- // never has to tell "no contexts" from "not a source".
145
- fragment := contentsOf(resp, dir+"/git.repo.json")
146
- if !strings.Contains(fragment, `"commit": "`+r.commit+`"`) {
147
- t.Errorf("fragment does not pin the fetched commit: %s", fragment)
148
- }
149
- for _, empty := range []string{`"contexts": []`, `"defs": {}`, `"flows": []`, `"adrs": []`} {
150
- if !strings.Contains(fragment, empty) {
151
- t.Errorf("fragment is missing %s: %s", empty, fragment)
152
- }
153
- }
154
- }
155
-
156
- func TestOfflineReplayIsByteIdentical(t *testing.T) {
157
- isolate(t)
158
- r := repository(t)
159
- cache := t.TempDir()
160
-
161
- online, err := fetch(options(r, cache, "services/oms"))
162
- if err != nil {
163
- t.Fatal(err)
164
- }
165
- write(t, cache, online)
166
-
167
- t.Setenv(OfflineEnv, "1")
168
- replayed, err := fetch(options(r, cache, "services/oms"))
169
- if err != nil {
170
- t.Fatal(err)
171
- }
172
- if strings.Join(names(online), "\n") != strings.Join(names(replayed), "\n") {
173
- t.Fatalf("offline names = %v, online %v", names(replayed), names(online))
174
- }
175
- for _, f := range online.Files {
176
- if contentsOf(replayed, f.Name) != f.Contents {
177
- t.Errorf("%s differs offline", f.Name)
178
- }
179
- }
180
- if len(replayed.Warnings()) != 1 || !strings.Contains(replayed.Warnings()[0].Message, "offline") {
181
- t.Errorf("diagnostics = %+v, want the one saying the copy was used", replayed.Warnings())
182
- }
183
- }
184
-
185
- func TestCiImpliesOffline(t *testing.T) {
186
- isolate(t)
187
- r := repository(t)
188
- cache := t.TempDir()
189
- online, err := fetch(options(r, cache, "proto"))
190
- if err != nil {
191
- t.Fatal(err)
192
- }
193
- write(t, cache, online)
194
-
195
- t.Setenv("CI", "true")
196
- replayed, err := fetch(options(r, cache, "proto"))
197
- if err != nil {
198
- t.Fatal(err)
199
- }
200
- if len(replayed.Warnings()) != 1 {
201
- t.Errorf("CI should replay and say so: %+v", replayed.Warnings())
202
- }
203
- }
204
-
205
- func TestEditedCacheIsReportedByPath(t *testing.T) {
206
- isolate(t)
207
- r := repository(t)
208
- cache := t.TempDir()
209
- online, err := fetch(options(r, cache, "services/oms"))
210
- if err != nil {
211
- t.Fatal(err)
212
- }
213
- write(t, cache, online)
214
-
215
- var edited string
216
- for _, name := range names(online) {
217
- if strings.HasSuffix(name, "order.go") {
218
- edited = name
219
- }
220
- }
221
- if err := os.WriteFile(filepath.Join(cache, filepath.FromSlash(edited)), []byte("package order // edited\n"), 0o644); err != nil {
222
- t.Fatal(err)
223
- }
224
-
225
- t.Setenv(OfflineEnv, "1")
226
- _, err = fetch(options(r, cache, "services/oms"))
227
- if err == nil || !strings.Contains(err.Error(), "order.go") || !strings.Contains(err.Error(), "edited by hand") {
228
- t.Fatalf("err = %v, want the edited file named", err)
229
- }
230
- }
231
-
232
- func TestOfflineWithNoCacheFailsRatherThanEmittingNothing(t *testing.T) {
233
- isolate(t)
234
- t.Setenv(OfflineEnv, "1")
235
- resp, err := fetch(options(remote{url: "file:///nowhere/acme/shop", commit: "0000000000000000000000000000000000000000"}, t.TempDir()))
236
- if err == nil || len(resp.Files) != 0 {
237
- t.Fatalf("err = %v, files = %v", err, names(resp))
238
- }
239
- }
240
-
241
- func TestFailedFetchWithNoCacheFails(t *testing.T) {
242
- isolate(t)
243
- _, err := fetch(options(remote{url: "file://" + filepath.Join(t.TempDir(), "acme", "gone"), commit: "0000000000000000000000000000000000000000"}, t.TempDir()))
244
- if err == nil || !strings.Contains(err.Error(), "no usable copy") {
245
- t.Fatalf("err = %v", err)
246
- }
247
- }
248
-
249
- func TestFailedFetchFallsBackToTheCommittedCopy(t *testing.T) {
250
- isolate(t)
251
- r := repository(t)
252
- cache := t.TempDir()
253
- online, err := fetch(options(r, cache, "proto"))
254
- if err != nil {
255
- t.Fatal(err)
256
- }
257
- write(t, cache, online)
258
-
259
- // The forge is gone; the URL is the same, so the copy is in the same place.
260
- if err := os.RemoveAll(strings.TrimPrefix(r.url, "file://")); err != nil {
261
- t.Fatal(err)
262
- }
263
- fallback, err := fetch(options(r, cache, "proto"))
264
- if err != nil {
265
- t.Fatal(err)
266
- }
267
- if strings.Join(names(fallback), "\n") != strings.Join(names(online), "\n") {
268
- t.Errorf("fallback = %v", names(fallback))
269
- }
270
- if len(fallback.Warnings()) != 1 || !strings.Contains(fallback.Warnings()[0].Message, "not fetched") {
271
- t.Errorf("diagnostics = %+v", fallback.Warnings())
272
- }
273
- }
274
-
275
- func TestUnpinnedRepositoryIsResolvedOnlineAndRefusedOffline(t *testing.T) {
276
- isolate(t)
277
- r := repository(t)
278
- opts := Options{Cache: t.TempDir(), Repos: []Repo{{Repo: r.url, Ref: "main", Paths: []string{"proto"}}}}
279
-
280
- resp, err := fetch(opts)
281
- if err != nil {
282
- t.Fatal(err)
283
- }
284
- if len(resp.Warnings()) != 1 || !strings.Contains(resp.Warnings()[0].Message, "not pinned") || !strings.Contains(resp.Warnings()[0].Message, r.commit) {
285
- t.Errorf("diagnostics = %+v", resp.Warnings())
286
- }
287
-
288
- t.Setenv(OfflineEnv, "1")
289
- if _, err := fetch(opts); err == nil || !strings.Contains(err.Error(), "not pinned") {
290
- t.Fatalf("err = %v", err)
291
- }
292
- }
293
-
294
- // A remote that does not serve commits by name still serves its branches,
295
- // and the commit is found among them.
296
- func TestACommitIsFetchedThroughItsBranchWhenTheRemoteRefusesIt(t *testing.T) {
297
- isolate(t)
298
- r := repository(t)
299
- cmd := exec.Command("git", "config", "--unset", "uploadpack.allowAnySHA1InWant")
300
- cmd.Dir = strings.TrimPrefix(r.url, "file://")
301
- if out, err := cmd.CombinedOutput(); err != nil {
302
- t.Fatalf("%v: %s", err, out)
303
- }
304
-
305
- resp, err := fetch(options(r, t.TempDir(), "README.md"))
306
- if err != nil {
307
- t.Fatal(err)
308
- }
309
- // The file, the lock, and the fragment naming the commit.
310
- if got := names(resp); len(got) != 3 {
311
- t.Errorf("files = %v", got)
312
- }
313
- }
314
-
315
- func TestSplitRepo(t *testing.T) {
316
- cases := map[string]string{
317
- "github.com/acme/shop": "https://github.com/acme/shop|acme/shop",
318
- "https://gitlab.com/acme/shop.git": "https://gitlab.com/acme/shop.git|acme/shop",
319
- "git@github.com:acme/shop.git": "git@github.com:acme/shop.git|acme/shop",
320
- "ssh://git@github.com/acme/shop": "ssh://git@github.com/acme/shop|acme/shop",
321
- "https://gitlab.com/org/group/shop": "https://gitlab.com/org/group/shop|group/shop",
322
- }
323
- for name, want := range cases {
324
- url, dir, err := splitRepo(name)
325
- if err != nil || url+"|"+dir != want {
326
- t.Errorf("%s = %s|%s %v, want %s", name, url, dir, err, want)
327
- }
328
- }
329
- if _, _, err := splitRepo("shop"); err == nil {
330
- t.Error("a bare name has no owner and should be refused")
331
- }
332
- }
@@ -1,169 +0,0 @@
1
- package main
2
-
3
- // The only file that runs git.
4
- //
5
- // The host already needs a git binary to stamp fragments, so this leans on
6
- // the same one rather than on a library: whatever git is configured to do
7
- // about credentials and hosts, it does here too, and this plugin has no
8
- // opinion about it. Everything happens in a directory that exists for one
9
- // call: a fetch of the one commit, and an archive of the paths wanted, read
10
- // straight into memory.
11
-
12
- import (
13
- "archive/tar"
14
- "bytes"
15
- "errors"
16
- "fmt"
17
- "io"
18
- "os"
19
- "os/exec"
20
- "path"
21
- "strings"
22
- )
23
-
24
- // splitRepo turns the name the manifest uses into a URL git can fetch and
25
- // the directory the copy lives in: owner/name, the same shape as fetch-bsr.
26
- func splitRepo(name string) (url, dir string, err error) {
27
- name = strings.TrimSpace(name)
28
- if name == "" {
29
- return "", "", fmt.Errorf("a repo entry names no repository")
30
- }
31
-
32
- var where string
33
- switch {
34
- case strings.HasPrefix(name, "git@"):
35
- // git@host:owner/name
36
- url = name
37
- _, where, _ = strings.Cut(name, ":")
38
- case strings.Contains(name, "://"):
39
- url = name
40
- _, where, _ = strings.Cut(name, "://")
41
- if i := strings.Index(where, "/"); i >= 0 {
42
- where = where[i+1:]
43
- }
44
- default:
45
- // host/owner/name, the way go.mod spells it.
46
- url = "https://" + name
47
- if i := strings.Index(name, "/"); i >= 0 {
48
- where = name[i+1:]
49
- }
50
- }
51
-
52
- where = strings.TrimSuffix(strings.Trim(where, "/"), ".git")
53
- segments := strings.Split(where, "/")
54
- if len(segments) < 2 {
55
- return "", "", fmt.Errorf("%q does not name an owner and a repository", name)
56
- }
57
- dir = path.Join(segments[len(segments)-2], segments[len(segments)-1])
58
-
59
- return url, dir, nil
60
- }
61
-
62
- // git runs one command and answers with its output, or with what it printed
63
- // on stderr when it failed. Prompts are off: a fetch that needs a credential
64
- // git does not have is a failure to report, not a question to hang on.
65
- func git(dir string, args ...string) ([]byte, error) {
66
- cmd := exec.Command("git", args...)
67
- cmd.Dir = dir
68
- cmd.Env = append(os.Environ(), "GIT_TERMINAL_PROMPT=0")
69
- var stderr bytes.Buffer
70
- cmd.Stderr = &stderr
71
- out, err := cmd.Output()
72
- if err != nil {
73
- msg := strings.TrimSpace(stderr.String())
74
- if msg == "" {
75
- msg = err.Error()
76
- }
77
-
78
- return nil, errors.New("git " + args[0] + ": " + firstLine(msg))
79
- }
80
-
81
- return out, nil
82
- }
83
-
84
- func firstLine(s string) string {
85
- if i := strings.IndexByte(s, '\n'); i >= 0 {
86
- return s[:i]
87
- }
88
-
89
- return s
90
- }
91
-
92
- // resolve asks the remote what a ref points at right now.
93
- func resolve(url, ref string) (string, error) {
94
- args := []string{"ls-remote", "--quiet", url}
95
- if ref != "" {
96
- args = append(args, ref)
97
- } else {
98
- args = append(args, "HEAD")
99
- }
100
- out, err := git("", args...)
101
- if err != nil {
102
- return "", err
103
- }
104
- for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
105
- fields := strings.Fields(line)
106
- if len(fields) >= 1 && len(fields[0]) == 40 {
107
- return fields[0], nil
108
- }
109
- }
110
-
111
- return "", fmt.Errorf("%s has no ref %q", url, firstNonEmpty(ref, "HEAD"))
112
- }
113
-
114
- // download fetches one commit and reads the wanted paths out of it.
115
- //
116
- // The commit is fetched by name where the forge allows it - GitHub and GitLab
117
- // do - and by its branches where it does not, which is the case for a plain
118
- // file:// remote; either way what is read is verified to be that commit.
119
- func download(url, commit string, paths []string) (map[string][]byte, error) {
120
- tmp, err := os.MkdirTemp("", "portolan-fetch-git-")
121
- if err != nil {
122
- return nil, err
123
- }
124
- defer os.RemoveAll(tmp)
125
-
126
- if _, err := git(tmp, "init", "--quiet"); err != nil {
127
- return nil, err
128
- }
129
- if _, err := git(tmp, "fetch", "--quiet", "--depth", "1", url, commit); err != nil {
130
- if _, fallback := git(tmp, "fetch", "--quiet", "--depth", "1", url, "+refs/heads/*:refs/remotes/src/*"); fallback != nil {
131
- return nil, err
132
- }
133
- }
134
- if _, err := git(tmp, "cat-file", "-e", commit+"^{commit}"); err != nil {
135
- return nil, fmt.Errorf("%s is not a commit %s has, or not one reachable from a branch", commit, url)
136
- }
137
-
138
- args := []string{"archive", "--format=tar", commit}
139
- if len(paths) > 0 {
140
- args = append(args, "--")
141
- args = append(args, paths...)
142
- }
143
- archive, err := git(tmp, args...)
144
- if err != nil {
145
- return nil, err
146
- }
147
-
148
- files := map[string][]byte{}
149
- reader := tar.NewReader(bytes.NewReader(archive))
150
- for {
151
- header, err := reader.Next()
152
- if errors.Is(err, io.EOF) {
153
- break
154
- }
155
- if err != nil {
156
- return nil, err
157
- }
158
- if header.Typeflag != tar.TypeReg {
159
- continue
160
- }
161
- content, err := io.ReadAll(reader)
162
- if err != nil {
163
- return nil, err
164
- }
165
- files[path.Clean(header.Name)] = content
166
- }
167
-
168
- return files, nil
169
- }
@@ -1,72 +0,0 @@
1
- package main
2
-
3
- // What was fetched, so it can be checked without fetching again.
4
- //
5
- // One lock per repository directory rather than one for the whole step, so a
6
- // vendored copy is self-describing: the files and the statement of where they
7
- // came from sit together.
8
-
9
- import (
10
- "crypto/sha256"
11
- "encoding/hex"
12
- "encoding/json"
13
- "sort"
14
- )
15
-
16
- // LockName is the file written beside each repository's copy.
17
- const LockName = "git.lock.json"
18
-
19
- type Lock struct {
20
- Repos []LockRepo `json:"repos"`
21
- }
22
-
23
- type LockRepo struct {
24
- Repo string `json:"repo"`
25
-
26
- // Commit is what was actually fetched. A commit is immutable, so a pinned
27
- // fetch is byte-reproducible and this is what makes replaying from disk
28
- // equivalent to fetching again.
29
- Commit string `json:"commit"`
30
-
31
- // Paths is the narrowing the copy was made with, so a wider or narrower
32
- // manifest is a change to the copy rather than a mystery.
33
- Paths []string `json:"paths,omitempty"`
34
-
35
- Files []LockEntry `json:"files"`
36
- }
37
-
38
- type LockEntry struct {
39
- Path string `json:"path"`
40
-
41
- // SHA256 is over the bytes as written. It is what turns a cache into
42
- // something checkable: a vendored copy someone edited by hand stops
43
- // matching, which is exactly the drift docs/adr/org.0001.md wants visible.
44
- SHA256 string `json:"sha256"`
45
- Size int `json:"size"`
46
- }
47
-
48
- func digestOf(content []byte) string {
49
- sum := sha256.Sum256(content)
50
-
51
- return hex.EncodeToString(sum[:])
52
- }
53
-
54
- // encode writes the lock the way every generated file in this repository is
55
- // written: sorted, indented, newline-terminated. It is committed and compared
56
- // by `gen:check`, so an unstable order would rewrite it on every run.
57
- func (l Lock) encode() (string, error) {
58
- for i := range l.Repos {
59
- sort.Slice(l.Repos[i].Files, func(a, b int) bool {
60
- return l.Repos[i].Files[a].Path < l.Repos[i].Files[b].Path
61
- })
62
- sort.Strings(l.Repos[i].Paths)
63
- }
64
- sort.Slice(l.Repos, func(a, b int) bool { return l.Repos[a].Repo < l.Repos[b].Repo })
65
-
66
- out, err := json.MarshalIndent(l, "", " ")
67
- if err != nil {
68
- return "", err
69
- }
70
-
71
- return string(out) + "\n", nil
72
- }
@@ -1,127 +0,0 @@
1
- // Package main is portolan-fetch-git: a git repository on one side, a narrowed
2
- // copy of it committed to this repository on the other.
3
- //
4
- // It exists for the estate whose services live in repositories of their own.
5
- // An extractor reads one tree, and reads it as a pure function of what is on
6
- // disk; fetching is the step that can fail, need a credential, or come back
7
- // with something different than it did yesterday. Keeping the two apart is
8
- // what lets CI verify an estate it never clones.
9
- //
10
- // The fetched files are the plugin's Response.Files, not a side effect. The
11
- // host writes them, so they get a manifest entry, are compared by `gen:check`
12
- // like any other generated file, and are removed when the step stops naming
13
- // them. Paths inside the copy are the repository's own, so the extract step
14
- // that follows points its `in` at the vendored service and reads it exactly
15
- // as it would read the service's own checkout. Refreshing a pin then produces
16
- // ONE pull request holding the pin bump, the source diff, the lock diff and
17
- // the fragment diff - which is the review worth having.
18
- //
19
- // Two files land beside each copy. `git.lock.json` is for the next run of this
20
- // step - the commit and every file's digest, which is what makes replaying it
21
- // equivalent to fetching again. `git.repo.json` is for the estate: a catalog
22
- // fragment naming the repository and the commit, which is the only way that
23
- // fact reaches a page. Nothing downstream can work it out - a service says
24
- // which repository it lives in, and an extractor reads a directory as a pure
25
- // function of what is on disk - so without it a vendored service's source
26
- // paths are text nobody can open. It has to be matched by `sources` in the
27
- // manifest, and by SOURCE_GLOBS in src/data.ts, to be read.
28
- //
29
- // It is not wired into portolan.json until there is a second repository. The
30
- // manifest it expects:
31
- //
32
- // {
33
- // "sources": ["data/*.json", "vendor/repos/*/*/git.repo.json"],
34
- // "plugins": [
35
- // { "name": "git", "process": { "command": "go", "args": ["run", "./plugins/fetch-git"] } }
36
- // ],
37
- // "extract": [
38
- // {
39
- // "plugin": "git",
40
- // "in": "vendor",
41
- // "out": "vendor/repos",
42
- // "options": {
43
- // "cache": "vendor/repos",
44
- // "repos": [
45
- // {
46
- // "repo": "github.com/acme/shop",
47
- // "commit": "c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0",
48
- // "paths": ["services/oms", "proto"]
49
- // }
50
- // ]
51
- // }
52
- // },
53
- // {
54
- // "plugin": "go-domain",
55
- // "in": "vendor/repos/acme/shop/services/oms",
56
- // "out": "data/shop",
57
- // "options": { "context": "shop", "service": "oms", "store": "pg" }
58
- // }
59
- // ]
60
- // }
61
- //
62
- // The git step is declared FIRST: extract steps run in list order, so the copy
63
- // and its lock are on disk by the time the extractors read them.
64
- //
65
- // In CI, set PORTOLAN_OFFLINE=1. The step then replays the committed copies,
66
- // checks them against their locks and emits an identical file list, so a fork's
67
- // pull request needs no credential and a forge being down cannot turn the tree
68
- // red.
69
- package main
70
-
71
- import (
72
- "fmt"
73
- "io"
74
- "os"
75
-
76
- "github.com/shortlink-org/portolan/plugin"
77
- )
78
-
79
- // Options are what the manifest tells the fetcher.
80
- //
81
- // No credential, ever. portolan.json is committed; git reaches the forge with
82
- // whatever it is configured with - a credential helper, a netrc entry, an ssh
83
- // agent - and this plugin reads none of it.
84
- type Options struct {
85
- Repos []Repo `json:"repos"`
86
-
87
- // Cache is where the last run's files were written, repo-relative, and
88
- // should be the same path as the step's `out`.
89
- //
90
- // Stated twice because a plugin is never told where its output goes. That
91
- // is the whole shape of the protocol - a plugin returns files and the host
92
- // decides what to do with them - and not an oversight to work around.
93
- Cache string `json:"cache"`
94
- }
95
-
96
- type Repo struct {
97
- // Repo is where the repository is: "github.com/acme/shop", or any URL git
98
- // accepts - https://, ssh://, git@host:owner/name.
99
- Repo string `json:"repo"`
100
-
101
- // Commit pins it. A commit is immutable, so a pinned fetch is
102
- // byte-reproducible - which is the only reason replaying from disk is
103
- // equivalent to fetching again. Leaving it out is allowed and warned about;
104
- // offline it is refused, because there is nothing to replay against.
105
- Commit string `json:"commit,omitempty"`
106
-
107
- // Ref is the branch or tag to resolve when there is no commit. Defaults to
108
- // the remote's HEAD.
109
- Ref string `json:"ref,omitempty"`
110
-
111
- // Paths narrows the copy to the directories actually read, which is how a
112
- // vendored repository stays small. Left out, the whole tree.
113
- Paths []string `json:"paths,omitempty"`
114
- }
115
-
116
- func main() {
117
- if err := run(os.Stdin, os.Stdout); err != nil {
118
- fmt.Fprintln(os.Stderr, "portolan-fetch-git:", err)
119
- os.Exit(1)
120
- }
121
- }
122
-
123
- func run(stdin io.Reader, stdout io.Writer) error {
124
- return plugin.Serve(stdin, stdout, descriptor(), func(_ plugin.Request, opts Options) (plugin.Response, error) {
125
- return fetch(opts)
126
- })
127
- }