@massu/core 1.2.0 → 1.3.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.
@@ -196,10 +196,21 @@ export function migrateV1ToV2(
196
196
  preserveNestedSubkeys(v1Framework, framework);
197
197
 
198
198
  // Paths: preserve user-set fields; fill `source` from detection if user had 'src' default.
199
+ // P1-003 (mirror of init.ts:367-390): when primary language has no source dir
200
+ // AND this is a monorepo, fall back to the common parent of workspace packages
201
+ // instead of leaving the nonexistent default 'src' in place.
199
202
  let pathsSource: string = typeof v1Paths.source === 'string' ? v1Paths.source : 'src';
200
203
  if (pathsSource === 'src' && primary) {
201
204
  const primaryDirs = detection.sourceDirs[primary]?.source_dirs ?? [];
202
- if (primaryDirs.length > 0) pathsSource = primaryDirs[0];
205
+ if (primaryDirs.length > 0) {
206
+ pathsSource = primaryDirs[0];
207
+ } else if (
208
+ detection.monorepo?.type !== undefined &&
209
+ detection.monorepo.type !== 'single' &&
210
+ detection.monorepo.packages.length > 0
211
+ ) {
212
+ pathsSource = monorepoCommonRootMigrate(detection.monorepo.packages);
213
+ }
203
214
  }
204
215
  const aliases = v1Paths.aliases && typeof v1Paths.aliases === 'object'
205
216
  ? (v1Paths.aliases as Record<string, string>)
@@ -208,6 +219,17 @@ export function migrateV1ToV2(
208
219
  source: pathsSource,
209
220
  aliases,
210
221
  };
222
+ // P1-005 mirror: emit monorepo_roots for upgraded v2 configs (additive,
223
+ // only when monorepo + not already user-specified).
224
+ if (
225
+ detection.monorepo?.type !== undefined &&
226
+ detection.monorepo.type !== 'single' &&
227
+ detection.monorepo.packages.length > 0 &&
228
+ !('monorepo_roots' in v1Paths)
229
+ ) {
230
+ const roots = monorepoDistinctRootsMigrate(detection.monorepo.packages);
231
+ if (roots.length > 0) paths.monorepo_roots = roots;
232
+ }
211
233
  for (const k of ['routers', 'routerRoot', 'pages', 'middleware', 'schema', 'components', 'hooks']) {
212
234
  if (typeof v1Paths[k] === 'string') paths[k] = v1Paths[k];
213
235
  }
@@ -309,3 +331,32 @@ export function migrateV1ToV2(
309
331
 
310
332
  return v2;
311
333
  }
334
+
335
+ /**
336
+ * Return the common top-level parent directory across every workspace
337
+ * package (mirror of init.ts:monorepoCommonRoot). Returns `'.'` when
338
+ * packages span multiple parents.
339
+ */
340
+ function monorepoCommonRootMigrate(
341
+ packages: ReadonlyArray<{ path: string }>
342
+ ): string {
343
+ const roots = monorepoDistinctRootsMigrate(packages);
344
+ return roots.length === 1 ? roots[0] : '.';
345
+ }
346
+
347
+ /**
348
+ * Return the distinct top-level parent directories of every workspace
349
+ * package (mirror of init.ts:monorepoDistinctRoots). Sorted for determinism.
350
+ */
351
+ function monorepoDistinctRootsMigrate(
352
+ packages: ReadonlyArray<{ path: string }>
353
+ ): string[] {
354
+ const set = new Set<string>();
355
+ for (const p of packages) {
356
+ const parts = p.path.split('/');
357
+ if (parts.length > 1 && parts[0] !== '' && parts[0] !== '.') {
358
+ set.add(parts[0]);
359
+ }
360
+ }
361
+ return [...set].sort();
362
+ }
@@ -109,6 +109,25 @@ function extsFor(language: SupportedLanguage): string[] {
109
109
  return EXTENSIONS[language] ?? [];
110
110
  }
111
111
 
112
+ /**
113
+ * Extensions for a language, with optional fallback for javascript-only repos
114
+ * that still contain `.ts`/`.tsx` files (no `typescript` manifest, no
115
+ * `tsconfig.json`). Fixes the bug where `apps/web/page.tsx` in a plain-JS
116
+ * monorepo is invisible to the javascript glob, causing `init --ci` to fall
117
+ * back to the nonexistent `src/`. See plan item P1-001 and incident
118
+ * `2026-04-20-massu-core-monorepo-paths-source.md`.
119
+ */
120
+ function extsWithFallback(
121
+ language: SupportedLanguage,
122
+ fallbackTsForJs: boolean
123
+ ): string[] {
124
+ const base = extsFor(language);
125
+ if (language === 'javascript' && fallbackTsForJs) {
126
+ return [...base, 'ts', 'tsx'];
127
+ }
128
+ return base;
129
+ }
130
+
112
131
  function isTestPath(language: SupportedLanguage, path: string): boolean {
113
132
  // Any dedicated test-dir keyword in the path segments
114
133
  const segments = path.split('/');
@@ -144,14 +163,21 @@ function isInsideRoot(root: string, candidate: string): boolean {
144
163
  *
145
164
  * @param projectRoot absolute path to repo root
146
165
  * @param languages list of languages to probe (derived from P1-001 manifests)
166
+ * @param opts optional flags:
167
+ * - `fallbackTsForJs`: when true AND the language is `javascript` AND no
168
+ * `typescript` manifest was discovered, also glob `.ts`/`.tsx`. This is
169
+ * the P1-001 fix for plain-JS monorepos (e.g. turbo with `next` in a
170
+ * package.json that lacks a typescript dep) that still use `.tsx`.
147
171
  */
148
172
  export function detectSourceDirs(
149
173
  projectRoot: string,
150
- languages: SupportedLanguage[]
174
+ languages: SupportedLanguage[],
175
+ opts?: { fallbackTsForJs?: boolean }
151
176
  ): SourceDirMap {
177
+ const fallbackTsForJs = opts?.fallbackTsForJs ?? false;
152
178
  const out: SourceDirMap = {};
153
179
  for (const lang of languages) {
154
- const exts = extsFor(lang);
180
+ const exts = extsWithFallback(lang, fallbackTsForJs);
155
181
  if (exts.length === 0) continue;
156
182
  const patterns = exts.map((e) => `**/*.${e}`);
157
183
  let files: string[];