@produtype/core 0.95.0 → 0.96.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.
@@ -72,9 +72,44 @@ async function detectBackend(ctx) {
72
72
  * has to be a real part of what is written here before it names the backend.
73
73
  */
74
74
  const goShare = (0, languageShare_1.languageShare)(ctx, /\.go$/);
75
- if (!namedGoFramework && goShare >= languageShare_1.MINIMUM_LANGUAGE_SHARE && ctx.files.all.some((f) => /(^|\/)go\.mod$/.test(f))) {
75
+ /**
76
+ * A Go module is not a server for being a Go module.
77
+ *
78
+ * The fallback asked for a `go.mod` and enough Go to be the product, and named the
79
+ * backend `go`. testify is 100% Go and is a testing library: it came out with a
80
+ * backend, which made it a product with a server, which kept it out of the
81
+ * `library` profile — the same chain the `.csproj` fallback produced for Moq.
82
+ *
83
+ * Go's standard library does have an HTTP server, which is why this fallback exists
84
+ * at all and why Rust's was deleted in 0.71.0. So the test is the server side of
85
+ * it: `ListenAndServe`, an `http.Server` value, a handler registered on a mux.
86
+ * `net/http` alone is not enough — testify imports it to build a round tripper,
87
+ * which is the client.
88
+ */
89
+ const servesOverHttp = goShare >= languageShare_1.MINIMUM_LANGUAGE_SHARE
90
+ ? await (0, textSearch_1.searchInFiles)(ctx.root, ctx.files.source.filter((f) => /\.go$/.test(f)), [
91
+ /\bhttp\.ListenAndServe(TLS)?\s*\(/,
92
+ /\bhttp\.Server\s*\{/,
93
+ /\bhttp\.(Handle|HandleFunc)\s*\(/,
94
+ /\bhttp\.NewServeMux\s*\(/,
95
+ /**
96
+ * The handler signature, which is the contract itself.
97
+ *
98
+ * A Go service often keeps its handlers in one package and its
99
+ * `ListenAndServe` in another, so requiring the server call missed the
100
+ * commonest shape: the `password-recovery-wording` fixture is one handler
101
+ * taking `http.ResponseWriter` and nothing else, and it stopped being a
102
+ * backend. Nothing on the client side is handed a ResponseWriter — testify
103
+ * defines a `TestResponseWriter` of its own and never names the interface.
104
+ */
105
+ /\bhttp\.ResponseWriter\b/,
106
+ ], 3)
107
+ : [];
108
+ if (!namedGoFramework && servesOverHttp.length > 0 && ctx.files.all.some((f) => /(^|\/)go\.mod$/.test(f))) {
76
109
  frameworks.push('go');
77
- evidence.push({ type: 'note', value: 'a Go module with no web framework named in go.mod' });
110
+ for (const hit of servesOverHttp.slice(0, 1)) {
111
+ evidence.push({ type: 'snippet', value: hit.snippet, file: hit.file, line: hit.line });
112
+ }
78
113
  }
79
114
  /** The JVM, read from pom.xml and build.gradle alike: coordinates are the same shape. */
80
115
  for (const [framework, deps] of catalogue_1.JVM_BACKEND_FRAMEWORKS) {
@@ -184,7 +184,8 @@ async function detectPackaging(ctx) {
184
184
  }
185
185
  const hasManifest = ctx.packageJson !== null
186
186
  || all.some((f) => /(^|\/)(pyproject\.toml|setup\.py|go\.mod|Cargo\.toml|composer\.json|\w+\.gemspec)$/i.test(f))
187
- || all.some((f) => /\.(csproj|fsproj|vbproj)$/i.test(f));
187
+ || all.some((f) => /\.(csproj|fsproj|vbproj|pom\.xml)$/i.test(f))
188
+ || all.some((f) => /(^|\/)pom\.xml$/.test(f));
188
189
  /**
189
190
  * A name and a version. Without them nothing can depend on this, whatever else it
190
191
  * does right.
@@ -227,15 +228,56 @@ async function detectPackaging(ctx) {
227
228
  if (all.some((f) => f === `${crateRoot}src/lib.rs`))
228
229
  rustCrate = true;
229
230
  }
231
+ /**
232
+ * Go says it with the absence of a `main` package.
233
+ *
234
+ * A module is importable by anything that knows its path; what makes it a program
235
+ * instead is a `package main`. testify is a module of 37 files with no `main`
236
+ * anywhere, and it came out `client-app` — below the floor, so in practice no
237
+ * profile at all.
238
+ *
239
+ * A repository that ships both a library and a `cmd/` binary answers no here, which
240
+ * is the cautious direction: a tool that also exposes packages is judged as a tool.
241
+ */
242
+ let goLibrary = false;
243
+ if (all.some((f) => /(^|\/)go\.mod$/.test(f))) {
244
+ const goFiles = ctx.files.source.filter((f) => /\.go$/.test(f));
245
+ let sawMain = false;
246
+ for (const file of goFiles.slice(0, 80)) {
247
+ const raw = (await (0, readTextFileSafe_1.readTextFileSafe)(ctx.root, file)) ?? '';
248
+ if (/^\s*package\s+main\s*$/m.test(raw)) {
249
+ sawMain = true;
250
+ break;
251
+ }
252
+ }
253
+ goLibrary = goFiles.length > 0 && !sawMain;
254
+ }
255
+ /**
256
+ * The JVM says it by configuring a publication.
257
+ *
258
+ * Every pom carries a groupId, an artifactId and a version, so those say nothing:
259
+ * an application has them too. What separates a published artifact is the plugin
260
+ * that uploads it — gson configures `central-publishing-maven-plugin`, Gradle
261
+ * projects apply `maven-publish`, older poms name a `distributionManagement`.
262
+ */
263
+ let jvmPublication = false;
264
+ for (const file of all.filter((f) => /(^|\/)(pom\.xml|build\.gradle(\.kts)?)$/.test(f)).slice(0, 8)) {
265
+ const raw = (await (0, readTextFileSafe_1.readTextFileSafe)(ctx.root, file)) ?? '';
266
+ if (/maven-publish|<distributionManagement>|central-publishing-maven-plugin|maven-deploy-plugin|nexus-staging/.test(raw)) {
267
+ jvmPublication = true;
268
+ }
269
+ }
230
270
  const entrypointCount = nodeEntrypoints.length
231
271
  + (pythonEntrypoints ? 1 : 0)
232
272
  + (dotnetPackageId ? 1 : 0)
233
- + (rustCrate ? 1 : 0);
273
+ + (rustCrate ? 1 : 0)
274
+ + (goLibrary ? 1 : 0)
275
+ + (jvmPublication ? 1 : 0);
234
276
  return [
235
277
  {
236
278
  key: 'packaging.manifest',
237
279
  present: hasManifest,
238
- complete: hasManifest && (named || pythonEntrypoints || dotnetPackageId || rustCrate),
280
+ complete: hasManifest && (named || pythonEntrypoints || dotnetPackageId || rustCrate || goLibrary || jvmPublication),
239
281
  evidence: hasManifest
240
282
  ? [
241
283
  publishedMember
@@ -254,9 +296,11 @@ async function detectPackaging(ctx) {
254
296
  ...(pythonEntrypoints ? [{ type: 'note', value: 'a Python package or console script declaration' }] : []),
255
297
  ...(dotnetPackageId ? [{ type: 'note', value: 'a project that declares a NuGet package id' }] : []),
256
298
  ...(rustCrate ? [{ type: 'note', value: 'a Cargo package with a library crate root' }] : []),
299
+ ...(goLibrary ? [{ type: 'note', value: 'a Go module with no main package' }] : []),
300
+ ...(jvmPublication ? [{ type: 'note', value: 'a build that configures a Maven publication' }] : []),
257
301
  ...(hasTypes ? [{ type: 'note', value: 'TypeScript types are declared' }] : []),
258
302
  ],
259
- details: { nodeEntrypoints, hasTypes, pythonEntrypoints, dotnetPackageId, rustCrate },
303
+ details: { nodeEntrypoints, hasTypes, pythonEntrypoints, dotnetPackageId, rustCrate, goLibrary, jvmPublication },
260
304
  },
261
305
  {
262
306
  key: 'packaging.license',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@produtype/core",
3
- "version": "0.95.0",
3
+ "version": "0.96.0",
4
4
  "description": "Deterministic CLI and library that analyzes a web application repository and reports how far it is from production-ready for the kind of product it is meant to be.",
5
5
  "license": "MIT",
6
6
  "bin": {