@omnifyjp/omnify 5.8.18 → 5.8.19

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnifyjp/omnify",
3
- "version": "5.8.18",
3
+ "version": "5.8.19",
4
4
  "description": "Schema-driven code generation for Laravel, TypeScript, and SQL",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -36,10 +36,10 @@
36
36
  "zod": "^3.24.0"
37
37
  },
38
38
  "optionalDependencies": {
39
- "@omnifyjp/omnify-darwin-arm64": "5.8.18",
40
- "@omnifyjp/omnify-darwin-x64": "5.8.18",
41
- "@omnifyjp/omnify-linux-x64": "5.8.18",
42
- "@omnifyjp/omnify-linux-arm64": "5.8.18",
43
- "@omnifyjp/omnify-win32-x64": "5.8.18"
39
+ "@omnifyjp/omnify-darwin-arm64": "5.8.19",
40
+ "@omnifyjp/omnify-darwin-x64": "5.8.19",
41
+ "@omnifyjp/omnify-linux-x64": "5.8.19",
42
+ "@omnifyjp/omnify-linux-arm64": "5.8.19",
43
+ "@omnifyjp/omnify-win32-x64": "5.8.19"
44
44
  }
45
45
  }
package/ts-dist/cli.js CHANGED
@@ -30,7 +30,7 @@ import { parse as parseYaml } from 'yaml';
30
30
  import { generateTypeScript } from './generator.js';
31
31
  import { generatePhp, derivePhpConfig } from './php/index.js';
32
32
  import { pruneOrphanServiceFiles } from './php/orphan-cleanup.js';
33
- import { findSiblingWithSameBasename } from './php/sibling-skip.js';
33
+ import { findSiblingWithSameBasename, findAppRootAncestor } from './php/sibling-skip.js';
34
34
  import { resolveInput, sniffInputKind } from './input-resolver.js';
35
35
  function resolveFromConfig(configPath) {
36
36
  const raw = readFileSync(configPath, 'utf-8');
@@ -229,21 +229,29 @@ program
229
229
  phpSkipped++;
230
230
  continue;
231
231
  }
232
- // Issue #98 v5.8.18: skip user-editable stub when a project-owned
233
- // sibling with the same basename exists elsewhere in the layer's
234
- // editable subtree (e.g. project organizes Requests by domain
235
- // folder `Admin/BrandStoreRequest.php` while omnify wanted to
236
- // emit the FLAT default stub `BrandStoreRequest.php` at the
237
- // layer root). The flat stub is unused noise in that layout,
238
- // and every `omnify generate` would otherwise re-create it,
239
- // forcing the dev to delete it manually after each run.
240
- // Only applies to user-editable files (overwrite=false); base
241
- // files always regenerate. Scope: scan ONLY the file's parent
242
- // directory recursively — not sibling directories — so a
243
- // grouped `Cms/<Name>.php` stub doesn't get skipped by an
244
- // unrelated `Admin/<Name>.php`.
232
+ // Issue #98 v5.8.18 / #99 v5.8.19: skip user-editable stub
233
+ // when a project-owned sibling with the same basename exists
234
+ // elsewhere under the project's `app/` root. Two patterns
235
+ // covered:
236
+ // 1. Domain subfolder organization (#98 v5.8.18): project
237
+ // hand-writes `app/Http/Requests/Admin/BrandStoreRequest.php`;
238
+ // omnify wants to emit the flat default
239
+ // `app/Http/Requests/BrandStoreRequest.php`. The flat
240
+ // stub is unused noise.
241
+ // 2. v4 v5 upgrade (#99 v5.8.19): project has v4 wrapper at
242
+ // `app/Services/Omnify/<Name>Service.php`; v5 wants to
243
+ // emit at the new modular path
244
+ // `app/Omnify/Services/<Name>Service.php`. Two wrappers
245
+ // compete; the v4 one carries the project's actual logic.
246
+ //
247
+ // Scope: scan from the project's `app/` directory (walked up
248
+ // from the target). Falls back to the file's parent dir when
249
+ // no `app/` ancestor is found (e.g. unusual rootPath layout).
250
+ // Only applies to user-editable files (`overwrite=false`); base
251
+ // files always regenerate.
245
252
  if (!file.overwrite) {
246
- const collision = findSiblingWithSameBasename(dirname(filePath), basename(filePath), filePath);
253
+ const scanRoot = findAppRootAncestor(filePath) ?? dirname(filePath);
254
+ const collision = findSiblingWithSameBasename(scanRoot, basename(filePath), filePath);
247
255
  if (collision) {
248
256
  phpSkippedSibling++;
249
257
  if (skippedSiblingExamples.length < 3) {
@@ -1,26 +1,39 @@
1
1
  /**
2
- * User-editable stub collision detection. Issue #98 v5.8.18.
2
+ * User-editable stub collision detection. Issue #98 v5.8.18 / #99 v5.8.19.
3
3
  *
4
4
  * Many Laravel projects organize Requests / Resources / Services by
5
- * domain subfolder (`Admin/`, `Cms/`, `Audit/`, etc.) BEFORE adopting
6
- * omnify. When omnify then emits the FLAT default stub at the layer
7
- * root (e.g. `app/Http/Requests/BrandStoreRequest.php`), the stub
8
- * collides ergonomically with the project's hand-written
9
- * `app/Http/Requests/Admin/BrandStoreRequest.php` same basename,
10
- * unused by any caller, gets re-created every `omnify generate`,
11
- * forcing the dev to delete it manually.
5
+ * domain subfolder (`Admin/`, `Cms/`, `Audit/`) BEFORE adopting omnify,
6
+ * OR they upgrade from v4 with wrappers at v4 paths
7
+ * (`app/Services/Omnify/<Name>Service.php`) and v5 wants to emit at
8
+ * the new modular path (`app/Omnify/Services/<Name>Service.php`). In
9
+ * both cases the auto-emitted user-editable stub is unused noise that
10
+ * regenerates every `omnify generate`, forcing the dev to delete it.
12
11
  *
13
- * Detection: scan the file's PARENT DIRECTORY recursively for any
12
+ * Detection: scan a configurable root directory recursively for any
14
13
  * file with the same basename. If found at a path != target, skip
15
14
  * emission.
16
15
  *
17
- * Scope rule: scan ONLY the file's parent directory (and its subdirs),
18
- * NOT sibling directories. So a grouped editable at `Cms/Banner.php`
19
- * isn't skipped by an unrelated `Admin/Banner.php`.
16
+ * Scope decision:
17
+ * - Wide scope (e.g. project's `app/`): finds cross-layer wrappers
18
+ * like the v4 v5 upgrade case (#99).
19
+ * - Narrow scope (file's parent dir): finds same-layer siblings only,
20
+ * like a domain-organized `Admin/Brand.php` next to the flat
21
+ * default (#98 v5.8.18).
20
22
  *
21
- * Bounded by `MAX_FILES_SCANNED` so a project root with tens of
22
- * thousands of files doesn't grind generate to a halt.
23
+ * The CLI passes whichever scope matches the layout. Bounded by
24
+ * `MAX_FILES_SCANNED` so a project with tens of thousands of files
25
+ * doesn't grind generate to a halt.
23
26
  */
27
+ /**
28
+ * Walk up from `filePath` looking for the closest ancestor directory
29
+ * named `app`. Returns its absolute path or null. Used by the CLI to
30
+ * pick a project-wide search root for sibling-skip — covers v4 → v5
31
+ * cross-layer wrappers (e.g. v4 wrapper at `app/Services/Omnify/<X>Service.php`
32
+ * vs v5 modular emit at `app/Omnify/Services/<X>Service.php`). Capped
33
+ * at 12 levels of climbing so we don't traverse the entire FS on a
34
+ * malformed input.
35
+ */
36
+ export declare function findAppRootAncestor(filePath: string): string | null;
24
37
  /**
25
38
  * Scan `rootDir` recursively for a file whose basename matches
26
39
  * `baseName`, excluding `excludePath` itself. Returns the first
@@ -1,29 +1,53 @@
1
1
  /**
2
- * User-editable stub collision detection. Issue #98 v5.8.18.
2
+ * User-editable stub collision detection. Issue #98 v5.8.18 / #99 v5.8.19.
3
3
  *
4
4
  * Many Laravel projects organize Requests / Resources / Services by
5
- * domain subfolder (`Admin/`, `Cms/`, `Audit/`, etc.) BEFORE adopting
6
- * omnify. When omnify then emits the FLAT default stub at the layer
7
- * root (e.g. `app/Http/Requests/BrandStoreRequest.php`), the stub
8
- * collides ergonomically with the project's hand-written
9
- * `app/Http/Requests/Admin/BrandStoreRequest.php` same basename,
10
- * unused by any caller, gets re-created every `omnify generate`,
11
- * forcing the dev to delete it manually.
5
+ * domain subfolder (`Admin/`, `Cms/`, `Audit/`) BEFORE adopting omnify,
6
+ * OR they upgrade from v4 with wrappers at v4 paths
7
+ * (`app/Services/Omnify/<Name>Service.php`) and v5 wants to emit at
8
+ * the new modular path (`app/Omnify/Services/<Name>Service.php`). In
9
+ * both cases the auto-emitted user-editable stub is unused noise that
10
+ * regenerates every `omnify generate`, forcing the dev to delete it.
12
11
  *
13
- * Detection: scan the file's PARENT DIRECTORY recursively for any
12
+ * Detection: scan a configurable root directory recursively for any
14
13
  * file with the same basename. If found at a path != target, skip
15
14
  * emission.
16
15
  *
17
- * Scope rule: scan ONLY the file's parent directory (and its subdirs),
18
- * NOT sibling directories. So a grouped editable at `Cms/Banner.php`
19
- * isn't skipped by an unrelated `Admin/Banner.php`.
16
+ * Scope decision:
17
+ * - Wide scope (e.g. project's `app/`): finds cross-layer wrappers
18
+ * like the v4 v5 upgrade case (#99).
19
+ * - Narrow scope (file's parent dir): finds same-layer siblings only,
20
+ * like a domain-organized `Admin/Brand.php` next to the flat
21
+ * default (#98 v5.8.18).
20
22
  *
21
- * Bounded by `MAX_FILES_SCANNED` so a project root with tens of
22
- * thousands of files doesn't grind generate to a halt.
23
+ * The CLI passes whichever scope matches the layout. Bounded by
24
+ * `MAX_FILES_SCANNED` so a project with tens of thousands of files
25
+ * doesn't grind generate to a halt.
23
26
  */
24
27
  import { existsSync, readdirSync, statSync } from 'node:fs';
25
- import { join } from 'node:path';
28
+ import { basename, dirname, join } from 'node:path';
26
29
  const MAX_FILES_SCANNED = 5000;
30
+ /**
31
+ * Walk up from `filePath` looking for the closest ancestor directory
32
+ * named `app`. Returns its absolute path or null. Used by the CLI to
33
+ * pick a project-wide search root for sibling-skip — covers v4 → v5
34
+ * cross-layer wrappers (e.g. v4 wrapper at `app/Services/Omnify/<X>Service.php`
35
+ * vs v5 modular emit at `app/Omnify/Services/<X>Service.php`). Capped
36
+ * at 12 levels of climbing so we don't traverse the entire FS on a
37
+ * malformed input.
38
+ */
39
+ export function findAppRootAncestor(filePath) {
40
+ let cur = dirname(filePath);
41
+ for (let i = 0; i < 12; i++) {
42
+ if (basename(cur) === 'app')
43
+ return cur;
44
+ const parent = dirname(cur);
45
+ if (parent === cur)
46
+ break;
47
+ cur = parent;
48
+ }
49
+ return null;
50
+ }
27
51
  /**
28
52
  * Scan `rootDir` recursively for a file whose basename matches
29
53
  * `baseName`, excluding `excludePath` itself. Returns the first