@produtype/core 0.69.0 → 0.71.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.
package/README.md
CHANGED
|
@@ -174,7 +174,7 @@ prodkit plan ../my-app --output prodkit-plan.md
|
|
|
174
174
|
|
|
175
175
|
_Generated from the analyzer itself — run `npm run docs:stacks` after changing a detector._
|
|
176
176
|
|
|
177
|
-
- **Backend:** Express, Next.js, NestJS, Fastify, Hono, Elysia, Koa, AdonisJS, SvelteKit, Remix, Nuxt, Nitro, Astro, Django, Flask, FastAPI, aiohttp, Litestar, Sanic, Tornado, Starlette, Streamlit, Gradio, Dash, Chainlit, Gin, Echo, Fiber, chi, Gorilla, Beego, Go, Axum, Actix Web, Rocket, Warp, Tide, Poem, Salvo, Tower HTTP, Spring Boot, Quarkus, Micronaut, Ktor, Javalin, Vert.x, Dropwizard, Helidon, Rails, Sinatra, Hanami, Roda, Grape, Ruby, Laravel, Symfony, Slim, CodeIgniter, CakePHP, Yii, PHP, ASP.NET Core, .NET
|
|
177
|
+
- **Backend:** Express, Next.js, NestJS, Fastify, Hono, Elysia, Koa, AdonisJS, SvelteKit, Remix, Nuxt, Nitro, Astro, Django, Flask, FastAPI, aiohttp, Litestar, Sanic, Tornado, Starlette, Streamlit, Gradio, Dash, Chainlit, Gin, Echo, Fiber, chi, Gorilla, Beego, Go, Axum, Actix Web, Rocket, Warp, Tide, Poem, Salvo, Tower HTTP, Hyper, Spring Boot, Quarkus, Micronaut, Ktor, Javalin, Vert.x, Dropwizard, Helidon, Rails, Sinatra, Hanami, Roda, Grape, Ruby, Laravel, Symfony, Slim, CodeIgniter, CakePHP, Yii, PHP, ASP.NET Core, .NET
|
|
178
178
|
- **Frontend:** React, Vite, Vue, Nuxt, Svelte, Angular, Astro, Solid, Qwik, Preact, Remix, htmx, Tailwind CSS, Electron
|
|
179
179
|
- **Mobile:** Flutter, React Native, iOS (native), Android (native)
|
|
180
180
|
- **Databases:** Postgres, MySQL, SQLite, SQL Server, MongoDB, Redis, Firestore, DynamoDB, Convex
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The lines of a C-family file that sit inside a block comment.
|
|
3
|
+
*
|
|
4
|
+
* The comment rule reads one line at a time and looks for a marker at its start:
|
|
5
|
+
* a double slash, a slash-star, a lone star. A block comment written without a
|
|
6
|
+
* leading star on every line has no marker to find, and neither does code that
|
|
7
|
+
* somebody commented out by wrapping the whole thing. In AsteroidsJS a wrapped
|
|
8
|
+
* `localStorage.setItem('highScores', ...)` was cited as evidence that the game
|
|
9
|
+
* saves progress; the function it belongs to is switched off.
|
|
10
|
+
*
|
|
11
|
+
* It is the same defect the Python docstrings had, in the languages where the marker
|
|
12
|
+
* usually is there and sometimes is not.
|
|
13
|
+
*
|
|
14
|
+
* Counting the delimiters is not enough, and measuring that was the point. A first
|
|
15
|
+
* pass said 135 citations across the corpus, and the second example it offered was
|
|
16
|
+
* live code: a glob like star-star-slash-star-dot-ts, and a slash-star inside a
|
|
17
|
+
* shader string, both look like an opening. So this walks the file properly —
|
|
18
|
+
* through single quotes, double quotes, template literals and line comments — and
|
|
19
|
+
* only then decides.
|
|
20
|
+
*/
|
|
21
|
+
export declare function blockCommentLines(file: string, text: string): Set<number>;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The lines of a C-family file that sit inside a block comment.
|
|
4
|
+
*
|
|
5
|
+
* The comment rule reads one line at a time and looks for a marker at its start:
|
|
6
|
+
* a double slash, a slash-star, a lone star. A block comment written without a
|
|
7
|
+
* leading star on every line has no marker to find, and neither does code that
|
|
8
|
+
* somebody commented out by wrapping the whole thing. In AsteroidsJS a wrapped
|
|
9
|
+
* `localStorage.setItem('highScores', ...)` was cited as evidence that the game
|
|
10
|
+
* saves progress; the function it belongs to is switched off.
|
|
11
|
+
*
|
|
12
|
+
* It is the same defect the Python docstrings had, in the languages where the marker
|
|
13
|
+
* usually is there and sometimes is not.
|
|
14
|
+
*
|
|
15
|
+
* Counting the delimiters is not enough, and measuring that was the point. A first
|
|
16
|
+
* pass said 135 citations across the corpus, and the second example it offered was
|
|
17
|
+
* live code: a glob like star-star-slash-star-dot-ts, and a slash-star inside a
|
|
18
|
+
* shader string, both look like an opening. So this walks the file properly —
|
|
19
|
+
* through single quotes, double quotes, template literals and line comments — and
|
|
20
|
+
* only then decides.
|
|
21
|
+
*/
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.blockCommentLines = blockCommentLines;
|
|
24
|
+
const C_FAMILY = /\.(ts|tsx|js|jsx|mjs|cjs|go|java|cs|rs|php|scala|kt|swift|c|cc|cpp|h)$/i;
|
|
25
|
+
function blockCommentLines(file, text) {
|
|
26
|
+
const inside = new Set();
|
|
27
|
+
if (!C_FAMILY.test(file))
|
|
28
|
+
return inside;
|
|
29
|
+
if (!text.includes('/*'))
|
|
30
|
+
return inside;
|
|
31
|
+
let line = 1;
|
|
32
|
+
let state = 'code';
|
|
33
|
+
let escaped = false;
|
|
34
|
+
for (let i = 0; i < text.length; i++) {
|
|
35
|
+
const character = text[i];
|
|
36
|
+
const next = text[i + 1];
|
|
37
|
+
if (character === '\n') {
|
|
38
|
+
line++;
|
|
39
|
+
if (state === 'line-comment')
|
|
40
|
+
state = 'code';
|
|
41
|
+
if (state === 'single' || state === 'double')
|
|
42
|
+
state = 'code';
|
|
43
|
+
if (state === 'block-comment')
|
|
44
|
+
inside.add(line);
|
|
45
|
+
escaped = false;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (escaped) {
|
|
49
|
+
escaped = false;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
switch (state) {
|
|
53
|
+
case 'code':
|
|
54
|
+
if (character === '/' && next === '/') {
|
|
55
|
+
state = 'line-comment';
|
|
56
|
+
i++;
|
|
57
|
+
}
|
|
58
|
+
else if (character === '/' && next === '*') {
|
|
59
|
+
state = 'block-comment';
|
|
60
|
+
i++;
|
|
61
|
+
}
|
|
62
|
+
else if (character === "'")
|
|
63
|
+
state = 'single';
|
|
64
|
+
else if (character === '"')
|
|
65
|
+
state = 'double';
|
|
66
|
+
else if (character === '`')
|
|
67
|
+
state = 'template';
|
|
68
|
+
break;
|
|
69
|
+
case 'block-comment':
|
|
70
|
+
if (character === '*' && next === '/') {
|
|
71
|
+
state = 'code';
|
|
72
|
+
i++;
|
|
73
|
+
}
|
|
74
|
+
break;
|
|
75
|
+
case 'single':
|
|
76
|
+
case 'double':
|
|
77
|
+
case 'template':
|
|
78
|
+
if (character === '\\')
|
|
79
|
+
escaped = true;
|
|
80
|
+
else if ((state === 'single' && character === "'")
|
|
81
|
+
|| (state === 'double' && character === '"')
|
|
82
|
+
|| (state === 'template' && character === '`')) {
|
|
83
|
+
state = 'code';
|
|
84
|
+
}
|
|
85
|
+
break;
|
|
86
|
+
default:
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return inside;
|
|
91
|
+
}
|
|
@@ -59,6 +59,8 @@ exports.RUST_BACKEND_FRAMEWORKS = [
|
|
|
59
59
|
['poem', ['poem']],
|
|
60
60
|
['salvo', ['salvo']],
|
|
61
61
|
['tower-http', ['tower-http']],
|
|
62
|
+
// What a Rust service uses when it uses no framework: the HTTP layer itself.
|
|
63
|
+
['hyper', ['hyper']],
|
|
62
64
|
];
|
|
63
65
|
/**
|
|
64
66
|
* JVM frameworks, read from pom.xml and build.gradle alike.
|
|
@@ -211,6 +213,7 @@ const LABELS = {
|
|
|
211
213
|
poem: 'Poem',
|
|
212
214
|
salvo: 'Salvo',
|
|
213
215
|
'tower-http': 'Tower HTTP',
|
|
216
|
+
hyper: 'Hyper',
|
|
214
217
|
rust: 'Rust',
|
|
215
218
|
rails: 'Rails',
|
|
216
219
|
sinatra: 'Sinatra',
|
|
@@ -109,24 +109,28 @@ async function detectBackend(ctx) {
|
|
|
109
109
|
evidence.push({ type: 'dependency', value: dep });
|
|
110
110
|
}
|
|
111
111
|
/** Rust, read from Cargo.toml. */
|
|
112
|
-
let namedRustFramework = false;
|
|
113
112
|
for (const [framework, deps] of catalogue_1.RUST_BACKEND_FRAMEWORKS) {
|
|
114
113
|
const hits = (0, detectContext_1.hasAnyRuntimeRustDep)(ctx, deps);
|
|
115
114
|
if (!hits.length)
|
|
116
115
|
continue;
|
|
117
|
-
namedRustFramework = true;
|
|
118
116
|
frameworks.push(framework);
|
|
119
117
|
for (const dep of hits)
|
|
120
118
|
evidence.push({ type: 'dependency', value: dep });
|
|
121
119
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
120
|
+
/**
|
|
121
|
+
* No fallback for Rust, and removing it is a correction of my own over-reach.
|
|
122
|
+
*
|
|
123
|
+
* This was written to mirror Go's, whose justification is that plenty of production
|
|
124
|
+
* services use `net/http` and nothing else. Rust's standard library has no HTTP
|
|
125
|
+
* server at all, so the mirror does not hold: a crate with no web framework is
|
|
126
|
+
* overwhelmingly a library, a parser or a command-line tool.
|
|
127
|
+
*
|
|
128
|
+
* ruff is the measurement. A linter with `Cargo.toml` at its root reported
|
|
129
|
+
* `backend: rust`, and "a backend disqualifies a library" then profiled it as a
|
|
130
|
+
* client application — so teaching this analyzer to read Rust made it worse at
|
|
131
|
+
* reading the best-known Rust project in the corpus. `hyper` joins the framework
|
|
132
|
+
* list instead: it is what a Rust service uses when it uses no framework.
|
|
133
|
+
*/
|
|
130
134
|
/** Ruby, read from the Gemfile. */
|
|
131
135
|
let namedRubyFramework = false;
|
|
132
136
|
for (const [framework, deps] of catalogue_1.RUBY_BACKEND_FRAMEWORKS) {
|
package/dist/utils/textSearch.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.anyIncludes = anyIncludes;
|
|
|
7
7
|
const readTextFileSafe_1 = require("./readTextFileSafe");
|
|
8
8
|
const developmentOnly_1 = require("../analyzer/developmentOnly");
|
|
9
9
|
const proseLines_1 = require("../analyzer/proseLines");
|
|
10
|
+
const blockComments_1 = require("../analyzer/blockComments");
|
|
10
11
|
/**
|
|
11
12
|
* A line that declares a pattern rather than doing anything.
|
|
12
13
|
*
|
|
@@ -106,9 +107,11 @@ function matchLines(text, needles, file = '') {
|
|
|
106
107
|
const testOnly = (0, developmentOnly_1.testOnlyLines)(file, text);
|
|
107
108
|
/** A Python docstring is prose with no line marker to recognise it by. */
|
|
108
109
|
const prose = (0, proseLines_1.proseLines)(file, text);
|
|
110
|
+
/** Code somebody switched off by wrapping it, which has no marker on its lines. */
|
|
111
|
+
const commented = (0, blockComments_1.blockCommentLines)(file, text);
|
|
109
112
|
for (let i = 0; i < lines.length; i++) {
|
|
110
113
|
const line = lines[i];
|
|
111
|
-
if (testOnly.has(i + 1) || prose.has(i + 1))
|
|
114
|
+
if (testOnly.has(i + 1) || prose.has(i + 1) || commented.has(i + 1))
|
|
112
115
|
continue;
|
|
113
116
|
if (line.length > MAX_CITABLE_LINE)
|
|
114
117
|
continue;
|
|
@@ -139,9 +142,10 @@ async function searchInFiles(root, files, needles, limit = 25) {
|
|
|
139
142
|
const lines = text.split(/\r?\n/);
|
|
140
143
|
const testOnly = (0, developmentOnly_1.testOnlyLines)(file, text);
|
|
141
144
|
const prose = (0, proseLines_1.proseLines)(file, text);
|
|
145
|
+
const commented = (0, blockComments_1.blockCommentLines)(file, text);
|
|
142
146
|
for (let i = 0; i < lines.length; i++) {
|
|
143
147
|
const line = lines[i];
|
|
144
|
-
if (testOnly.has(i + 1) || prose.has(i + 1))
|
|
148
|
+
if (testOnly.has(i + 1) || prose.has(i + 1) || commented.has(i + 1))
|
|
145
149
|
continue;
|
|
146
150
|
if (line.length > MAX_CITABLE_LINE)
|
|
147
151
|
continue;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@produtype/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.71.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": {
|