@omnifyjp/omnify 5.8.17 → 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 +6 -6
- package/ts-dist/cli.js +44 -1
- package/ts-dist/php/sibling-skip.d.ts +42 -0
- package/ts-dist/php/sibling-skip.js +93 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omnifyjp/omnify",
|
|
3
|
-
"version": "5.8.
|
|
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.
|
|
40
|
-
"@omnifyjp/omnify-darwin-x64": "5.8.
|
|
41
|
-
"@omnifyjp/omnify-linux-x64": "5.8.
|
|
42
|
-
"@omnifyjp/omnify-linux-arm64": "5.8.
|
|
43
|
-
"@omnifyjp/omnify-win32-x64": "5.8.
|
|
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
|
@@ -24,12 +24,13 @@
|
|
|
24
24
|
* omnify-ts --update # force re-fetch remote input
|
|
25
25
|
*/
|
|
26
26
|
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
27
|
-
import { resolve, dirname, join } from 'node:path';
|
|
27
|
+
import { resolve, dirname, basename, join, relative } from 'node:path';
|
|
28
28
|
import { Command } from 'commander';
|
|
29
29
|
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, findAppRootAncestor } from './php/sibling-skip.js';
|
|
33
34
|
import { resolveInput, sniffInputKind } from './input-resolver.js';
|
|
34
35
|
function resolveFromConfig(configPath) {
|
|
35
36
|
const raw = readFileSync(configPath, 'utf-8');
|
|
@@ -219,6 +220,8 @@ program
|
|
|
219
220
|
let phpCreated = 0;
|
|
220
221
|
let phpOverwritten = 0;
|
|
221
222
|
let phpSkipped = 0;
|
|
223
|
+
let phpSkippedSibling = 0;
|
|
224
|
+
const skippedSiblingExamples = [];
|
|
222
225
|
for (const file of phpFiles) {
|
|
223
226
|
const filePath = resolve(configDir, file.path);
|
|
224
227
|
mkdirSync(dirname(filePath), { recursive: true });
|
|
@@ -226,6 +229,37 @@ program
|
|
|
226
229
|
phpSkipped++;
|
|
227
230
|
continue;
|
|
228
231
|
}
|
|
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.
|
|
252
|
+
if (!file.overwrite) {
|
|
253
|
+
const scanRoot = findAppRootAncestor(filePath) ?? dirname(filePath);
|
|
254
|
+
const collision = findSiblingWithSameBasename(scanRoot, basename(filePath), filePath);
|
|
255
|
+
if (collision) {
|
|
256
|
+
phpSkippedSibling++;
|
|
257
|
+
if (skippedSiblingExamples.length < 3) {
|
|
258
|
+
skippedSiblingExamples.push(`${relative(configDir, filePath)} (sibling: ${relative(configDir, collision)})`);
|
|
259
|
+
}
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
229
263
|
writeFileSync(filePath, file.content, 'utf-8');
|
|
230
264
|
if (file.overwrite || !existsSync(filePath)) {
|
|
231
265
|
phpOverwritten++;
|
|
@@ -239,6 +273,15 @@ program
|
|
|
239
273
|
console.log(` ${phpCreated} files created (user-editable)`);
|
|
240
274
|
if (phpSkipped > 0)
|
|
241
275
|
console.log(` ${phpSkipped} files skipped (already exist)`);
|
|
276
|
+
if (phpSkippedSibling > 0) {
|
|
277
|
+
console.log(` ${phpSkippedSibling} user-editable stub(s) skipped (project sibling with same name found in subfolder)`);
|
|
278
|
+
for (const ex of skippedSiblingExamples) {
|
|
279
|
+
console.log(` e.g. ${ex}`);
|
|
280
|
+
}
|
|
281
|
+
if (phpSkippedSibling > skippedSiblingExamples.length) {
|
|
282
|
+
console.log(` ... and ${phpSkippedSibling - skippedSiblingExamples.length} more`);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
242
285
|
// Orphan cleanup: when a schema is removed, renamed, flipped from
|
|
243
286
|
// kind:object → kind:pivot, or opted out via `options.service: false`,
|
|
244
287
|
// its previously-emitted `*ServiceBase.php` becomes orphan and
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User-editable stub collision detection. Issue #98 v5.8.18 / #99 v5.8.19.
|
|
3
|
+
*
|
|
4
|
+
* Many Laravel projects organize Requests / Resources / Services by
|
|
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.
|
|
11
|
+
*
|
|
12
|
+
* Detection: scan a configurable root directory recursively for any
|
|
13
|
+
* file with the same basename. If found at a path != target, skip
|
|
14
|
+
* emission.
|
|
15
|
+
*
|
|
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).
|
|
22
|
+
*
|
|
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.
|
|
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;
|
|
37
|
+
/**
|
|
38
|
+
* Scan `rootDir` recursively for a file whose basename matches
|
|
39
|
+
* `baseName`, excluding `excludePath` itself. Returns the first
|
|
40
|
+
* match's full path or null.
|
|
41
|
+
*/
|
|
42
|
+
export declare function findSiblingWithSameBasename(rootDir: string, baseName: string, excludePath: string): string | null;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User-editable stub collision detection. Issue #98 v5.8.18 / #99 v5.8.19.
|
|
3
|
+
*
|
|
4
|
+
* Many Laravel projects organize Requests / Resources / Services by
|
|
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.
|
|
11
|
+
*
|
|
12
|
+
* Detection: scan a configurable root directory recursively for any
|
|
13
|
+
* file with the same basename. If found at a path != target, skip
|
|
14
|
+
* emission.
|
|
15
|
+
*
|
|
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).
|
|
22
|
+
*
|
|
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.
|
|
26
|
+
*/
|
|
27
|
+
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
28
|
+
import { basename, dirname, join } from 'node:path';
|
|
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
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Scan `rootDir` recursively for a file whose basename matches
|
|
53
|
+
* `baseName`, excluding `excludePath` itself. Returns the first
|
|
54
|
+
* match's full path or null.
|
|
55
|
+
*/
|
|
56
|
+
export function findSiblingWithSameBasename(rootDir, baseName, excludePath) {
|
|
57
|
+
let scanned = 0;
|
|
58
|
+
const stack = [rootDir];
|
|
59
|
+
while (stack.length > 0) {
|
|
60
|
+
if (scanned >= MAX_FILES_SCANNED)
|
|
61
|
+
return null;
|
|
62
|
+
const dir = stack.pop();
|
|
63
|
+
if (!existsSync(dir))
|
|
64
|
+
continue;
|
|
65
|
+
let entries;
|
|
66
|
+
try {
|
|
67
|
+
entries = readdirSync(dir);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
for (const entry of entries) {
|
|
73
|
+
scanned++;
|
|
74
|
+
if (scanned >= MAX_FILES_SCANNED)
|
|
75
|
+
return null;
|
|
76
|
+
const fullPath = join(dir, entry);
|
|
77
|
+
let stats;
|
|
78
|
+
try {
|
|
79
|
+
stats = statSync(fullPath);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (stats.isDirectory()) {
|
|
85
|
+
stack.push(fullPath);
|
|
86
|
+
}
|
|
87
|
+
else if (entry === baseName && fullPath !== excludePath) {
|
|
88
|
+
return fullPath;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|