@produtype/core 0.93.0 → 0.95.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.
|
@@ -185,6 +185,25 @@ function layoutPartOf(file) {
|
|
|
185
185
|
const root = JVM_MAIN_SOURCE_ROOT.exec(file);
|
|
186
186
|
return root ? file.slice(0, root.index + root[0].length) : file;
|
|
187
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Go names its tests by file, never by directory.
|
|
190
|
+
*
|
|
191
|
+
* `_test.go` is the suffix the toolchain compiles separately; a directory called
|
|
192
|
+
* `mock`, `testing` or `fixtures` is just a package. testify's own `mock/mock.go` —
|
|
193
|
+
* half of what that library is for — was removed by the rule that drops `mocks/`, and
|
|
194
|
+
* the analyzer saw 38 of its Go files with the two biggest missing.
|
|
195
|
+
*
|
|
196
|
+
* One directory convention is left here, and it is the toolchain's: a directory whose
|
|
197
|
+
* name begins with `_` or `.` is skipped when building, which is why testify keeps
|
|
198
|
+
* `_codegen` and `_readme-gofmt` under those names.
|
|
199
|
+
*
|
|
200
|
+
* `vendor/` and `testdata/` are not repeated: the file scanner never hands them over
|
|
201
|
+
* in the first place. Both were written here and both survived being deleted in a
|
|
202
|
+
* mutation run, which is how the duplication came to light.
|
|
203
|
+
*/
|
|
204
|
+
function isGoToolchainExclusion(file) {
|
|
205
|
+
return /(^|\/)[_.][^/]+\//.test(file);
|
|
206
|
+
}
|
|
188
207
|
function isTestOrExamplePath(file) {
|
|
189
208
|
// `__mocks__` was missing, and a mock is the most misleading file in a repository:
|
|
190
209
|
// `application_fee_percent: null` inside a Stripe fixture made an open-source CRM read
|
|
@@ -192,6 +211,12 @@ function isTestOrExamplePath(file) {
|
|
|
192
211
|
// `fixtures` was listed and `fixture` was not, so `extra/fixture/authsources.php`
|
|
193
212
|
// made PHP one of the languages of an Elixir analytics product — and of cal.com,
|
|
194
213
|
// which is TypeScript.
|
|
214
|
+
/**
|
|
215
|
+
* A Go file answers to Go's conventions and to none of the directory rules below:
|
|
216
|
+
* every one of those names is a package name there.
|
|
217
|
+
*/
|
|
218
|
+
if (/\.go$/.test(file))
|
|
219
|
+
return isGoToolchainExclusion(file) || /_test\.go$/.test(file);
|
|
195
220
|
const layout = layoutPartOf(file);
|
|
196
221
|
return /(^|\/)(__tests__|__mocks__|mocks?|tests?|test-data|fixtures?|frontend-example)(\/|$)/i.test(layout)
|
|
197
222
|
/**
|
|
@@ -176,10 +176,22 @@ async function detectBackend(ctx) {
|
|
|
176
176
|
value: ctx.dotnetWebSdk ? 'a project declaring Microsoft.NET.Sdk.Web' : 'an ASP.NET Core package reference',
|
|
177
177
|
});
|
|
178
178
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
179
|
+
/**
|
|
180
|
+
* A `.csproj` with no web SDK used to be listed as a backend called "dotnet", and
|
|
181
|
+
* that is the same mistake the Rust fallback made before 0.71.0: a project file is
|
|
182
|
+
* not a server.
|
|
183
|
+
*
|
|
184
|
+
* .NET builds libraries, console tools, desktop applications and web services from
|
|
185
|
+
* the same project format, and `Microsoft.NET.Sdk.Web` is the line that says which.
|
|
186
|
+
* Moq — a mocking library, 243 C# files — was reported with "Backend: dotnet",
|
|
187
|
+
* which made it a product with a server, which kept it out of the `library` profile
|
|
188
|
+
* and got it judged as a client application: asked at `high` for state durability,
|
|
189
|
+
* asset delivery and browser crash reporting.
|
|
190
|
+
*
|
|
191
|
+
* Nothing replaces it. The language is already named in the reading-depth line and
|
|
192
|
+
* in the package manager, and "this is a .NET project" was never an answer to
|
|
193
|
+
* "what serves the requests".
|
|
194
|
+
*/
|
|
183
195
|
/**
|
|
184
196
|
* Astro, which is a backend only when it is configured to be one.
|
|
185
197
|
*
|
|
@@ -59,7 +59,17 @@ const DOCS_GENERATORS = [
|
|
|
59
59
|
* of these.
|
|
60
60
|
*/
|
|
61
61
|
exports.DOCS_DIRECTORIES = /(^|\/)(docs?|website|playground|examples?|demo|www)\//i;
|
|
62
|
-
|
|
62
|
+
/**
|
|
63
|
+
* `.html` belongs here because the front-end fact counts it.
|
|
64
|
+
*
|
|
65
|
+
* The front-end detector falls back to "a page is a front end" and counts `.html`
|
|
66
|
+
* files, so a library whose only pages are its documentation comes out with a front
|
|
67
|
+
* end. This test — is every front-end file under a docs directory — looked at
|
|
68
|
+
* framework extensions only, so it could not see those pages and could not answer
|
|
69
|
+
* yes. Moq's documentation is plain HTML under `docs/`, and Moq was profiled as a
|
|
70
|
+
* static site: a mocking library with 243 C# files, judged on its Jekyll theme.
|
|
71
|
+
*/
|
|
72
|
+
const FRONTEND_FILE = /\.(tsx|jsx|vue|svelte|astro|html?)$/;
|
|
63
73
|
/**
|
|
64
74
|
* The published package inside a workspace, if there is one.
|
|
65
75
|
*
|
|
@@ -173,7 +183,8 @@ async function detectPackaging(ctx) {
|
|
|
173
183
|
}
|
|
174
184
|
}
|
|
175
185
|
const hasManifest = ctx.packageJson !== null
|
|
176
|
-
|| all.some((f) => /(^|\/)(pyproject\.toml|setup\.py|go\.mod|Cargo\.toml|composer\.json|\w+\.gemspec)$/i.test(f))
|
|
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));
|
|
177
188
|
/**
|
|
178
189
|
* A name and a version. Without them nothing can depend on this, whatever else it
|
|
179
190
|
* does right.
|
|
@@ -181,12 +192,50 @@ async function detectPackaging(ctx) {
|
|
|
181
192
|
const named = typeof pkg.name === 'string' && typeof pkg.version === 'string';
|
|
182
193
|
const described = typeof pkg.description === 'string' && pkg.description.length > 0;
|
|
183
194
|
const sourced = pkg.repository !== undefined || pkg.homepage !== undefined;
|
|
184
|
-
|
|
195
|
+
/**
|
|
196
|
+
* The other ecosystems' way of saying "something else may depend on this".
|
|
197
|
+
*
|
|
198
|
+
* Both facts were read from `package.json` and Python packaging alone, so no
|
|
199
|
+
* library written in anything else could reach the `library` profile. Moq — a
|
|
200
|
+
* mocking library of 243 C# files, `<PackageId>Moq</PackageId>` and
|
|
201
|
+
* `<IsPackable>True</IsPackable>` in its csproj — was judged a client application
|
|
202
|
+
* and asked at `high` for state durability, asset delivery and browser crash
|
|
203
|
+
* reporting. tokio could not be classified at all.
|
|
204
|
+
*
|
|
205
|
+
* Each is the declaration its own toolchain requires to publish: a csproj that
|
|
206
|
+
* names a package id, a Cargo manifest with a `[package]` name and version beside a
|
|
207
|
+
* `src/lib.rs`. A crate that says `publish = false` is saying the opposite, and is
|
|
208
|
+
* not counted.
|
|
209
|
+
*/
|
|
210
|
+
let dotnetPackageId = false;
|
|
211
|
+
for (const file of all.filter((f) => /\.(csproj|fsproj|vbproj)$/i.test(f)).slice(0, 12)) {
|
|
212
|
+
const raw = (await (0, readTextFileSafe_1.readTextFileSafe)(ctx.root, file)) ?? '';
|
|
213
|
+
if (/<PackageId>|<IsPackable>\s*true\s*<\/IsPackable>|<GeneratePackageOnBuild>\s*true/i.test(raw)) {
|
|
214
|
+
dotnetPackageId = true;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
let rustCrate = false;
|
|
218
|
+
for (const file of all.filter((f) => /(^|\/)Cargo\.toml$/.test(f)).slice(0, 8)) {
|
|
219
|
+
const raw = (await (0, readTextFileSafe_1.readTextFileSafe)(ctx.root, file)) ?? '';
|
|
220
|
+
if (!/^\s*\[package\]/m.test(raw))
|
|
221
|
+
continue;
|
|
222
|
+
if (/^\s*publish\s*=\s*false/m.test(raw))
|
|
223
|
+
continue;
|
|
224
|
+
if (!/^\s*name\s*=/m.test(raw) || !/^\s*version\s*=/m.test(raw))
|
|
225
|
+
continue;
|
|
226
|
+
const crateRoot = file.replace(/Cargo\.toml$/, '');
|
|
227
|
+
if (all.some((f) => f === `${crateRoot}src/lib.rs`))
|
|
228
|
+
rustCrate = true;
|
|
229
|
+
}
|
|
230
|
+
const entrypointCount = nodeEntrypoints.length
|
|
231
|
+
+ (pythonEntrypoints ? 1 : 0)
|
|
232
|
+
+ (dotnetPackageId ? 1 : 0)
|
|
233
|
+
+ (rustCrate ? 1 : 0);
|
|
185
234
|
return [
|
|
186
235
|
{
|
|
187
236
|
key: 'packaging.manifest',
|
|
188
237
|
present: hasManifest,
|
|
189
|
-
complete: hasManifest && (named || pythonEntrypoints),
|
|
238
|
+
complete: hasManifest && (named || pythonEntrypoints || dotnetPackageId || rustCrate),
|
|
190
239
|
evidence: hasManifest
|
|
191
240
|
? [
|
|
192
241
|
publishedMember
|
|
@@ -203,9 +252,11 @@ async function detectPackaging(ctx) {
|
|
|
203
252
|
evidence: [
|
|
204
253
|
...nodeEntrypoints.map((key) => ({ type: 'note', value: `package.json declares "${key}"` })),
|
|
205
254
|
...(pythonEntrypoints ? [{ type: 'note', value: 'a Python package or console script declaration' }] : []),
|
|
255
|
+
...(dotnetPackageId ? [{ type: 'note', value: 'a project that declares a NuGet package id' }] : []),
|
|
256
|
+
...(rustCrate ? [{ type: 'note', value: 'a Cargo package with a library crate root' }] : []),
|
|
206
257
|
...(hasTypes ? [{ type: 'note', value: 'TypeScript types are declared' }] : []),
|
|
207
258
|
],
|
|
208
|
-
details: { nodeEntrypoints, hasTypes, pythonEntrypoints },
|
|
259
|
+
details: { nodeEntrypoints, hasTypes, pythonEntrypoints, dotnetPackageId, rustCrate },
|
|
209
260
|
},
|
|
210
261
|
{
|
|
211
262
|
key: 'packaging.license',
|
|
@@ -161,11 +161,27 @@ function buildReport(analysis, options) {
|
|
|
161
161
|
// and for the same reason must count. A Unity game and a Phaser game were each
|
|
162
162
|
// identified as games by name and then called unreadable in the same report.
|
|
163
163
|
const gameEngineDetected = analysis.detectors['game.engine']?.present === true;
|
|
164
|
+
/**
|
|
165
|
+
* A library is not an unidentified repository.
|
|
166
|
+
*
|
|
167
|
+
* The three lists describe an application, and a package has none of them, so
|
|
168
|
+
* "nothing identified" was true of every library — and it is what makes a report
|
|
169
|
+
* inconclusive. The `dotnet-library` fixture crossed that line the moment the "a
|
|
170
|
+
* .csproj is a backend" fallback was removed in the same release: a library with a
|
|
171
|
+
* NuGet manifest, read correctly, reported as a repository this analyzer could not
|
|
172
|
+
* make sense of.
|
|
173
|
+
*
|
|
174
|
+
* A package manager this analyzer parsed, beside a language it read, identifies a
|
|
175
|
+
* repository as surely as a web framework does. Loose scripts with no manifest
|
|
176
|
+
* still answer no.
|
|
177
|
+
*/
|
|
178
|
+
const packagedInAKnownEcosystem = analysis.stack.packageManager !== 'unknown' && analysis.stack.languages.length > 0;
|
|
164
179
|
const stackDetected = analysis.stack.frontend.length > 0
|
|
165
180
|
|| analysis.stack.backend.length > 0
|
|
166
181
|
|| analysis.stack.databases.length > 0
|
|
167
182
|
|| mobileDetected
|
|
168
|
-
|| gameEngineDetected
|
|
183
|
+
|| gameEngineDetected
|
|
184
|
+
|| packagedInAKnownEcosystem;
|
|
169
185
|
/**
|
|
170
186
|
* Coverage counts the expectations too, not only the observed rules.
|
|
171
187
|
*
|
package/dist/rules/rules.js
CHANGED
|
@@ -169,7 +169,26 @@ exports.rules = [
|
|
|
169
169
|
category: 'stack',
|
|
170
170
|
severity: 'low',
|
|
171
171
|
evaluate: ({ analysis }) => {
|
|
172
|
-
|
|
172
|
+
/**
|
|
173
|
+
* A library has no front end, no backend and no database, and it is not
|
|
174
|
+
* unidentifiable.
|
|
175
|
+
*
|
|
176
|
+
* The three lists are what an application is made of, and asking only about
|
|
177
|
+
* them made every library "generic or unknown to ProdKit" — which on tokio, a
|
|
178
|
+
* repository of 505 Rust files with a Cargo manifest, was enough to leave the
|
|
179
|
+
* whole report inconclusive. Removing the "a .csproj is a backend" fallback in
|
|
180
|
+
* the same release made the `dotnet-library` fixture do the same, which is how
|
|
181
|
+
* this surfaced.
|
|
182
|
+
*
|
|
183
|
+
* What this repository is built with is a fingerprint too: a package manager
|
|
184
|
+
* this analyzer actually parsed, and a language it actually read. A directory
|
|
185
|
+
* of loose scripts with no manifest still answers unknown.
|
|
186
|
+
*/
|
|
187
|
+
const named = analysis.stack.packageManager !== 'unknown' && analysis.stack.languages.length > 0;
|
|
188
|
+
const hasStack = analysis.stack.frontend.length > 0
|
|
189
|
+
|| analysis.stack.backend.length > 0
|
|
190
|
+
|| analysis.stack.databases.length > 0
|
|
191
|
+
|| named;
|
|
173
192
|
const status = hasStack ? 'passed' : 'unknown';
|
|
174
193
|
return mkFinding({
|
|
175
194
|
id: 'stack.detected',
|
|
@@ -177,14 +196,31 @@ exports.rules = [
|
|
|
177
196
|
category: 'stack',
|
|
178
197
|
status,
|
|
179
198
|
severity: sevForStatus(status, 'low'),
|
|
180
|
-
description: hasStack
|
|
199
|
+
description: hasStack
|
|
200
|
+
? 'ProdKit identified known stack signals.'
|
|
201
|
+
: 'Stack is generic or unknown to ProdKit.',
|
|
181
202
|
recommendation: hasStack
|
|
182
203
|
? 'No action required.'
|
|
183
204
|
: 'Add explicit framework manifests or keep this as generic app baseline.',
|
|
205
|
+
/**
|
|
206
|
+
* A finding that passes has to point at something.
|
|
207
|
+
*
|
|
208
|
+
* When the three stack lists are empty the arrays below are empty too, so
|
|
209
|
+
* letting a package manager satisfy this check produced twenty passing
|
|
210
|
+
* findings with nothing under them — which is the one thing
|
|
211
|
+
* `everyFindingSaysWhatItLookedFor` exists to stop. The manifest and the
|
|
212
|
+
* language are what was read, so they are what is cited.
|
|
213
|
+
*/
|
|
184
214
|
evidence: [
|
|
185
215
|
...analysis.detectors['stack.frontend']?.evidence ?? [],
|
|
186
216
|
...analysis.detectors['stack.backend']?.evidence ?? [],
|
|
187
217
|
...analysis.detectors['stack.database']?.evidence ?? [],
|
|
218
|
+
...(named
|
|
219
|
+
? [{
|
|
220
|
+
type: 'note',
|
|
221
|
+
value: `a ${analysis.stack.packageManager} manifest and ${analysis.stack.languages.join(', ')} sources`,
|
|
222
|
+
}]
|
|
223
|
+
: []),
|
|
188
224
|
],
|
|
189
225
|
});
|
|
190
226
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@produtype/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.95.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": {
|