@produtype/core 0.29.2 → 0.30.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.
@@ -133,6 +133,63 @@ async function androidGradleFiles(ctx) {
133
133
  }
134
134
  return found;
135
135
  }
136
+ /**
137
+ * Directory names that are part of a platform's layout rather than the project's.
138
+ *
139
+ * `src/ClientApp/Platforms/Android/AndroidManifest.xml` belongs to a project called
140
+ * ClientApp; the three segments after it are how MAUI arranges a project, not where the
141
+ * project begins. Walking past them finds the root a person would name.
142
+ */
143
+ const PLATFORM_LAYOUT = new Set([
144
+ 'platforms', 'android', 'ios', 'maccatalyst', 'app', 'src', 'main', 'res', 'xcshareddata',
145
+ ]);
146
+ function mobileProjectRoot(marker) {
147
+ const segments = marker.split('/').slice(0, -1);
148
+ /**
149
+ * `.xcodeproj` sits beside the sources, not above them.
150
+ *
151
+ * `DonGeremIA.xcodeproj/project.pbxproj` names a project whose Swift files are in
152
+ * `DonGeremIA/`, a sibling. Stopping at the bundle put the project root somewhere no
153
+ * source file lives and scored a Swift application at zero.
154
+ */
155
+ while (segments.length > 0
156
+ && (PLATFORM_LAYOUT.has(segments[segments.length - 1].toLowerCase())
157
+ || /\.(xcodeproj|xcworkspace)$/i.test(segments[segments.length - 1]))) {
158
+ segments.pop();
159
+ }
160
+ return segments.join('/');
161
+ }
162
+ /**
163
+ * How much of the repository the mobile project actually is.
164
+ *
165
+ * dotnet/eShop contains a real MAUI client — the markers are not a false positive — and
166
+ * it is one project of a dozen: 137 source files of 515, beside a web application and
167
+ * eight services. The repository was reported as a mobile application because a true
168
+ * signal about a part was read as a fact about the whole.
169
+ *
170
+ * Every repository in the verification corpus that *is* a phone application scores 1
171
+ * here: the manifest sits at its root. The gap between that and eShop's 0.27 is where
172
+ * the line goes.
173
+ */
174
+ function mobileSourceShare(sourceFiles, markers) {
175
+ if (sourceFiles.length === 0)
176
+ return 0;
177
+ /**
178
+ * No file marker means the platform came from a dependency.
179
+ *
180
+ * A Flutter project is recognised by `flutter` in its `pubspec.yaml`, and a React
181
+ * Native one by its `package.json` — a fact about the project's own manifest rather
182
+ * than about a subdirectory. Scoring those at zero made every Flutter application in
183
+ * the corpus stop being a mobile application.
184
+ */
185
+ if (markers.length === 0)
186
+ return 1;
187
+ const roots = new Set(markers.map(mobileProjectRoot));
188
+ if (roots.has(''))
189
+ return 1;
190
+ const inside = sourceFiles.filter((file) => [...roots].some((root) => file.startsWith(`${root}/`)));
191
+ return inside.length / sourceFiles.length;
192
+ }
136
193
  async function detectMobile(ctx) {
137
194
  const iosFiles = matchesAny(ctx.files.all, IOS_MARKERS);
138
195
  const androidFiles = [...matchesAny(ctx.files.all, ANDROID_MARKERS), ...(await androidGradleFiles(ctx))];
@@ -158,6 +215,7 @@ async function detectMobile(ctx) {
158
215
  platformEvidence.push({ type: 'file', value: androidFiles[0], file: androidFiles[0] });
159
216
  }
160
217
  const isMobile = platforms.length > 0;
218
+ const share = mobileSourceShare(ctx.files.source, [...iosFiles, ...androidFiles]);
161
219
  /**
162
220
  * Every capability below reports `present: false` when this is not a mobile project,
163
221
  * with no evidence — the profile that asks about them is the only one that applies
@@ -286,7 +344,7 @@ async function detectMobile(ctx) {
286
344
  key: 'mobile.platform',
287
345
  present: true,
288
346
  evidence: platformEvidence,
289
- details: { platforms },
347
+ details: { platforms, sourceShare: share },
290
348
  },
291
349
  {
292
350
  key: 'mobile.permissions',
@@ -56,6 +56,14 @@ export interface ProfileFacts {
56
56
  gameSignals: number;
57
57
  /** Ships to a phone: Flutter, React Native, or an iOS/Android project in the tree. */
58
58
  mobilePlatforms: string[];
59
+ /**
60
+ * How much of the repository the mobile project is, between 0 and 1.
61
+ *
62
+ * A true signal about one project was being read as a fact about the whole: eShop's
63
+ * MAUI client is 137 source files of 515, beside a web application and eight services,
64
+ * and the repository came back as a phone application.
65
+ */
66
+ mobileShare: number;
59
67
  sourceFiles: number;
60
68
  }
61
69
  export declare function readFacts(analysis: ProjectAnalysis): ProfileFacts;
@@ -33,6 +33,7 @@ function readFacts(analysis) {
33
33
  tests: present('quality.tests'),
34
34
  gameSignals: Number(analysis.detectors['game.engine']?.details?.supportingSignals ?? 0),
35
35
  mobilePlatforms: analysis.detectors['mobile.platform']?.details?.platforms ?? [],
36
+ mobileShare: analysis.detectors['mobile.platform']?.details?.sourceShare ?? 0,
36
37
  sourceFiles: analysis.files.source.length,
37
38
  };
38
39
  }
@@ -134,7 +135,15 @@ const RULES = [
134
135
  */
135
136
  profile: 'mobile-app',
136
137
  refines: 'client-app',
137
- admissible: (f) => f.mobilePlatforms.length > 0 && !f.tenancy,
138
+ /**
139
+ * The mobile project has to be the repository, not a project inside it.
140
+ *
141
+ * Every repository in the corpus that is a phone application has its manifest at the
142
+ * root and scores 1. eShop scores 0.27 and is a .NET system with a MAUI client in it.
143
+ * Half is the line, and it sits in the gap between those two rather than in the
144
+ * middle of a distribution.
145
+ */
146
+ admissible: (f) => f.mobilePlatforms.length > 0 && f.mobileShare >= 0.5 && !f.tenancy,
138
147
  signals: [
139
148
  { identifies: true, label: 'a mobile project in the repository', weight: 5, holds: (f) => f.mobilePlatforms.length > 0 },
140
149
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@produtype/core",
3
- "version": "0.29.2",
3
+ "version": "0.30.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": {