@produtype/core 0.81.0 → 0.83.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.
@@ -267,20 +267,38 @@ async function detectBackend(ctx) {
267
267
  }
268
268
  }
269
269
  }
270
- // Django
270
+ /**
271
+ * Django, which four filenames were enough to declare.
272
+ *
273
+ * Any two of `manage.py`, a file ending in `settings.py`, a file ending in `urls.py`
274
+ * and the dependency named Django, and three of those four are names other projects
275
+ * use. redash is a Flask application: it keeps a `manage.py`, and
276
+ * `redash/handlers/settings.py` is the HTTP handler for a user's settings page. Its
277
+ * report said "Backend: flask, django".
278
+ *
279
+ * The dependency is the fact — nobody runs Django without installing it — and the
280
+ * filenames only corroborate it.
281
+ *
282
+ * A fallback for the unreadable-manifest case was written and deleted: a settings
283
+ * file declaring `INSTALLED_APPS` would have stood on its own, and no repository
284
+ * measured reaches it, because every Django project declares Django. The mutation
285
+ * run said so — removing it failed nothing. A rule nothing measures is a rule to
286
+ * delete.
287
+ */
271
288
  const djangoSignals = [];
289
+ const djangoDep = (0, detectContext_1.hasRuntimePyDep)(ctx, 'django');
290
+ if (djangoDep)
291
+ djangoSignals.push({ type: 'dependency', value: 'django' });
272
292
  if (ctx.files.all.some((f) => f.endsWith('manage.py') || f === 'manage.py')) {
273
293
  djangoSignals.push({ type: 'file', value: 'manage.py' });
274
294
  }
275
295
  const settingsFile = ctx.files.all.find((f) => f.endsWith('settings.py'));
276
296
  if (settingsFile)
277
297
  djangoSignals.push({ type: 'file', value: settingsFile });
278
- if ((0, detectContext_1.hasRuntimePyDep)(ctx, 'django'))
279
- djangoSignals.push({ type: 'dependency', value: 'django' });
280
298
  if (ctx.files.all.some((f) => f.endsWith('urls.py'))) {
281
299
  djangoSignals.push({ type: 'file', value: ctx.files.all.find((f) => f.endsWith('urls.py')) });
282
300
  }
283
- if (djangoSignals.length >= 2) {
301
+ if (djangoDep && djangoSignals.length >= 2) {
284
302
  frameworks.push('django');
285
303
  evidence.push(...djangoSignals);
286
304
  }
@@ -46,6 +46,21 @@ async function detectGdpr(ctx) {
46
46
  */
47
47
  /anonymi[sz]e[_-]?(user|account)/i,
48
48
  /user[_-]?anonymi[sz]/i,
49
+ /**
50
+ * The regulation's own name, next to the act.
51
+ *
52
+ * `deleteUser(id)` was measured and withdrawn because delete is every admin
53
+ * screen ever written. GDPR is not an ordinary word: an identifier that carries
54
+ * it beside a deletion is somebody naming the article they are answering. forem
55
+ * runs its erasure through `Users::DeleteWorker` and records a
56
+ * `GDPRDeleteRequest`, and was told at `high` that it has no erasure flow.
57
+ *
58
+ * The pattern was already in this file, one capability down — the retention
59
+ * needle reads `(gdpr|privacy).*(retention|purge|delete)`, so the line was being
60
+ * found and filed under the wrong question.
61
+ */
62
+ /gdpr[_\- ]?(delete|deletion|eras|removal|forget)/i,
63
+ /(delete|deletion|eras|removal)[_\- ]?gdpr/i,
49
64
  ], 20);
50
65
  /**
51
66
  * `deleteUser(id)` is not article 17 — it is every admin screen ever written, and
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.detectSecurity = detectSecurity;
4
4
  const detectContext_1 = require("./detectContext");
5
5
  const readTextFileSafe_1 = require("../utils/readTextFileSafe");
6
+ const djangoSettings_1 = require("./djangoSettings");
6
7
  const textSearch_1 = require("../utils/textSearch");
7
8
  const absenceEvidence_1 = require("./absenceEvidence");
8
9
  const valuesFromPackage_1 = require("./structural/valuesFromPackage");
@@ -111,35 +112,6 @@ function detectCorsConfig(text, file, boundNames) {
111
112
  }
112
113
  return { loose, strict };
113
114
  }
114
- /**
115
- * A settings file that is Django's, rather than one that shares its name.
116
- *
117
- * `diet_hub/api/settings.py` is a FastAPI router that lets an administrator change
118
- * application options from the browser. It was read as Django's configuration, and on
119
- * the strength of the filename alone the project was credited with having security
120
- * middleware it does not have — the one direction of error that matters here, because
121
- * it hides a missing control rather than inventing a present one.
122
- *
123
- * Two conditions, because either alone is wrong. A repository can depend on Django and
124
- * still own a dozen files called settings.py; a file can declare INSTALLED_APPS in a
125
- * tutorial that the product does not run. And the first file named settings.py is not
126
- * the right one: a project with `settings/base.py` and `settings/production.py` has
127
- * several, so every candidate is examined and the first that is Django's is used.
128
- */
129
- async function findDjangoSettings(ctx) {
130
- if (!ctx.pythonDeps.includes('django'))
131
- return null;
132
- const candidates = ctx.files.all.filter((f) => /(^|\/)settings(_[a-z]+)?\.py$/i.test(f) || /(^|\/)settings\/[a-z_]+\.py$/i.test(f));
133
- for (const file of candidates) {
134
- const text = await (0, readTextFileSafe_1.readTextFileSafe)(ctx.root, file);
135
- if (!text)
136
- continue;
137
- if (/^\s*INSTALLED_APPS\s*=/m.test(text) || /^\s*MIDDLEWARE\s*=/m.test(text) || /DJANGO_SETTINGS_MODULE/.test(text)) {
138
- return { file, text };
139
- }
140
- }
141
- return null;
142
- }
143
115
  const RATE_LIMIT_PACKAGES = [
144
116
  'express-rate-limit',
145
117
  '@upstash/ratelimit',
@@ -394,7 +366,7 @@ async function detectSecurity(ctx) {
394
366
  * `CORS_ORIGIN_ALLOW_ALL`, which is what it was called before version 3.5 — is the
395
367
  * one line that opens it.
396
368
  */
397
- const django = await findDjangoSettings(ctx);
369
+ const django = await (0, djangoSettings_1.findDjangoSettings)(ctx);
398
370
  if (django) {
399
371
  const middlewareLine = django.text
400
372
  .split(/\r?\n/)
@@ -453,7 +425,7 @@ async function detectSecurity(ctx) {
453
425
  * this repository keeps as a fixture precisely because it is unprotected. What is
454
426
  * left is the `SECURE_*` settings above: lines somebody chose to write.
455
427
  */
456
- const djangoSettings = await findDjangoSettings(ctx);
428
+ const djangoSettings = await (0, djangoSettings_1.findDjangoSettings)(ctx);
457
429
  let djangoDebugTrue = false;
458
430
  let djangoSecureCookies = true;
459
431
  if (djangoSettings) {
@@ -0,0 +1,20 @@
1
+ import type { DetectContext } from './detectContext';
2
+ /**
3
+ * A settings file that is Django's, rather than one that shares its name.
4
+ *
5
+ * `diet_hub/api/settings.py` is a FastAPI router that lets an administrator change
6
+ * application options from the browser. It was read as Django's configuration, and on
7
+ * the strength of the filename alone the project was credited with having security
8
+ * middleware it does not have — the one direction of error that matters here, because
9
+ * it hides a missing control rather than inventing a present one.
10
+ *
11
+ * Two conditions, because either alone is wrong. A repository can depend on Django and
12
+ * still own a dozen files called settings.py; a file can declare INSTALLED_APPS in a
13
+ * tutorial that the product does not run. And the first file named settings.py is not
14
+ * the right one: a project with `settings/base.py` and `settings/production.py` has
15
+ * several, so every candidate is examined and the first that is Django's is used.
16
+ */
17
+ export declare function findDjangoSettings(ctx: DetectContext): Promise<{
18
+ file: string;
19
+ text: string;
20
+ } | null>;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findDjangoSettings = findDjangoSettings;
4
+ const readTextFileSafe_1 = require("../utils/readTextFileSafe");
5
+ /**
6
+ * A settings file that is Django's, rather than one that shares its name.
7
+ *
8
+ * `diet_hub/api/settings.py` is a FastAPI router that lets an administrator change
9
+ * application options from the browser. It was read as Django's configuration, and on
10
+ * the strength of the filename alone the project was credited with having security
11
+ * middleware it does not have — the one direction of error that matters here, because
12
+ * it hides a missing control rather than inventing a present one.
13
+ *
14
+ * Two conditions, because either alone is wrong. A repository can depend on Django and
15
+ * still own a dozen files called settings.py; a file can declare INSTALLED_APPS in a
16
+ * tutorial that the product does not run. And the first file named settings.py is not
17
+ * the right one: a project with `settings/base.py` and `settings/production.py` has
18
+ * several, so every candidate is examined and the first that is Django's is used.
19
+ */
20
+ async function findDjangoSettings(ctx) {
21
+ if (!ctx.pythonDeps.includes('django'))
22
+ return null;
23
+ const candidates = ctx.files.all.filter((f) => /(^|\/)settings(_[a-z]+)?\.py$/i.test(f) || /(^|\/)settings\/[a-z_]+\.py$/i.test(f));
24
+ for (const file of candidates) {
25
+ const text = await (0, readTextFileSafe_1.readTextFileSafe)(ctx.root, file);
26
+ if (!text)
27
+ continue;
28
+ if (/^\s*INSTALLED_APPS\s*=/m.test(text) || /^\s*MIDDLEWARE\s*=/m.test(text) || /DJANGO_SETTINGS_MODULE/.test(text)) {
29
+ return { file, text };
30
+ }
31
+ }
32
+ return null;
33
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@produtype/core",
3
- "version": "0.81.0",
3
+ "version": "0.83.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": {