@omnifyjp/omnify 6.2.0 → 6.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.
- package/package.json +6 -6
- package/ts-dist/cli.js +25 -1
- package/ts-dist/php/model-generator.js +6 -2
- package/ts-dist/php/types.d.ts +51 -1
- package/ts-dist/php/types.js +64 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omnifyjp/omnify",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.3.0",
|
|
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": "6.
|
|
40
|
-
"@omnifyjp/omnify-darwin-x64": "6.
|
|
41
|
-
"@omnifyjp/omnify-linux-x64": "6.
|
|
42
|
-
"@omnifyjp/omnify-linux-arm64": "6.
|
|
43
|
-
"@omnifyjp/omnify-win32-x64": "6.
|
|
39
|
+
"@omnifyjp/omnify-darwin-arm64": "6.3.0",
|
|
40
|
+
"@omnifyjp/omnify-darwin-x64": "6.3.0",
|
|
41
|
+
"@omnifyjp/omnify-linux-x64": "6.3.0",
|
|
42
|
+
"@omnifyjp/omnify-linux-arm64": "6.3.0",
|
|
43
|
+
"@omnifyjp/omnify-win32-x64": "6.3.0"
|
|
44
44
|
}
|
|
45
45
|
}
|
package/ts-dist/cli.js
CHANGED
|
@@ -29,6 +29,7 @@ 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
|
+
import { stripPhpComments } from './php/types.js';
|
|
32
33
|
import { pruneOrphanServiceFilesIfEnabled } from './php/orphan-cleanup.js';
|
|
33
34
|
import { findSiblingWithSameBasename, findAppRootAncestor } from './php/sibling-skip.js';
|
|
34
35
|
import { runPintFormat } from './php/pint-format.js';
|
|
@@ -100,6 +101,11 @@ function resolveFromConfig(configPath) {
|
|
|
100
101
|
route: laravelConfig?.route,
|
|
101
102
|
config: laravelConfig?.config,
|
|
102
103
|
nestedset: laravelConfig?.nestedset,
|
|
104
|
+
modules: laravelConfig?.modules,
|
|
105
|
+
shared: laravelConfig?.shared,
|
|
106
|
+
enums: laravelConfig?.enums,
|
|
107
|
+
traits: laravelConfig?.traits,
|
|
108
|
+
openapi: laravelConfig?.openapi,
|
|
103
109
|
} : undefined;
|
|
104
110
|
return {
|
|
105
111
|
inputSpec,
|
|
@@ -226,6 +232,7 @@ program
|
|
|
226
232
|
let phpOverwritten = 0;
|
|
227
233
|
let phpSkipped = 0;
|
|
228
234
|
let phpSkippedSibling = 0;
|
|
235
|
+
let phpShadowed = 0;
|
|
229
236
|
const skippedSiblingExamples = [];
|
|
230
237
|
const writtenPaths = [];
|
|
231
238
|
for (const file of phpFiles) {
|
|
@@ -233,11 +240,24 @@ program
|
|
|
233
240
|
mkdirSync(dirname(filePath), { recursive: true });
|
|
234
241
|
if (!file.overwrite && existsSync(filePath)) {
|
|
235
242
|
phpSkipped++;
|
|
243
|
+
const existing = file.expects || (file.conflicts && file.conflicts.length > 0)
|
|
244
|
+
? readFileSync(filePath, 'utf-8')
|
|
245
|
+
: '';
|
|
236
246
|
// The file predates something the template now needs. Say so once,
|
|
237
247
|
// naming the file and the line, rather than rewriting user code.
|
|
238
|
-
if (file.expects && !
|
|
248
|
+
if (file.expects && !existing.includes(file.expects.needle)) {
|
|
239
249
|
console.warn(`[omnify-ts] ${relative(configDir, filePath)}: ${file.expects.hint}`);
|
|
240
250
|
}
|
|
251
|
+
// The mirror: the file contains an edit that silently disables what
|
|
252
|
+
// the base generates. #171 — a redeclared $fillable shadows the
|
|
253
|
+
// parent's, so every property added afterwards is stripped on mass
|
|
254
|
+
// assignment with no error anywhere.
|
|
255
|
+
for (const conflict of file.conflicts ?? []) {
|
|
256
|
+
if (new RegExp(conflict.pattern).test(stripPhpComments(existing))) {
|
|
257
|
+
phpShadowed++;
|
|
258
|
+
console.warn(`[omnify-ts] ${relative(configDir, filePath)}: ${conflict.hint}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
241
261
|
continue;
|
|
242
262
|
}
|
|
243
263
|
// Issue #98 v5.8.18 / #99 v5.8.19: skip user-editable stub
|
|
@@ -285,6 +305,10 @@ program
|
|
|
285
305
|
console.log(` ${phpCreated} files created (user-editable)`);
|
|
286
306
|
if (phpSkipped > 0)
|
|
287
307
|
console.log(` ${phpSkipped} files skipped (already exist)`);
|
|
308
|
+
if (phpShadowed > 0) {
|
|
309
|
+
console.warn(` ${phpShadowed} editable file(s) shadow a generated property — ` +
|
|
310
|
+
`schema changes will NOT reach them until that is fixed (see warnings above).`);
|
|
311
|
+
}
|
|
288
312
|
if (phpSkippedSibling > 0) {
|
|
289
313
|
console.log(` ${phpSkippedSibling} user-editable stub(s) skipped (project sibling with same name found in subfolder)`);
|
|
290
314
|
for (const ex of skippedSiblingExamples) {
|
|
@@ -5,7 +5,7 @@ import { toPascalCase, toSnakeCase, toCamelCase, toFkColumnName, pluralize } fro
|
|
|
5
5
|
import { resolveTimestamps } from './timestamps.js';
|
|
6
6
|
import { toCast, toPhpDocType, isHiddenByDefault } from './type-mapper.js';
|
|
7
7
|
import { buildRelation } from './relation-builder.js';
|
|
8
|
-
import { baseFile, userFile, resolveModularBasePath, resolveModularBaseNamespace, resolveSharedBaseNamespace, resolveGlobalTraitNamespace, resolveGlobalEnumNamespace, resolveBaseClass, resolveEditableClass, nestByGroup, nestEditableByGroup, } from './types.js';
|
|
8
|
+
import { baseFile, userFile, shadowingPropertyConflicts, resolveModularBasePath, resolveModularBaseNamespace, resolveSharedBaseNamespace, resolveGlobalTraitNamespace, resolveGlobalEnumNamespace, resolveBaseClass, resolveEditableClass, nestByGroup, nestEditableByGroup, } from './types.js';
|
|
9
9
|
import { enumClassName } from './enum-generator.js';
|
|
10
10
|
/** Generate base model and user model for all project-owned object schemas,
|
|
11
11
|
* plus user models for package schemas (extending the package model). */
|
|
@@ -474,7 +474,11 @@ ${traits.join('\n')}
|
|
|
474
474
|
//
|
|
475
475
|
}
|
|
476
476
|
`;
|
|
477
|
-
|
|
477
|
+
// #171: this subclass is created once and then owned by the project. The
|
|
478
|
+
// most common edit to it — redeclaring $fillable — silently disables every
|
|
479
|
+
// property omnify adds to the base afterwards. We cannot prevent the edit,
|
|
480
|
+
// but we can stop it from being invisible.
|
|
481
|
+
return userFile(`${editable.path}/${editable.fileName}`, content, undefined, shadowingPropertyConflicts(baseAlias));
|
|
478
482
|
}
|
|
479
483
|
function buildImports(baseNamespace, modelName, hasSoftDelete, isAuthenticatable, hasTranslatable, properties, needsUuidTrait = false, needsUlidTrait = false, hasNestedSet = false, nestedSetNamespace = 'Aimeos\\Nestedset', hasFiles = false, modelNamespace = '', localesNamespace = '', traitsNamespace = '', sharedModelsNamespace = '', config, hasAuditLog = false) {
|
|
480
484
|
const lines = [];
|
package/ts-dist/php/types.d.ts
CHANGED
|
@@ -21,6 +21,23 @@ export interface GeneratedFile {
|
|
|
21
21
|
readonly needle: string;
|
|
22
22
|
readonly hint: string;
|
|
23
23
|
};
|
|
24
|
+
/**
|
|
25
|
+
* For a user file only: things the file must NOT contain, and why.
|
|
26
|
+
*
|
|
27
|
+
* The mirror of `expects`. Some edits to a user-editable subclass silently
|
|
28
|
+
* disable what the base class generates — the worst being a redeclared
|
|
29
|
+
* `protected $fillable`, which in PHP REPLACES the parent's rather than
|
|
30
|
+
* merging, so every property added to the schema afterwards is stripped by
|
|
31
|
+
* Eloquent on mass assignment. Nothing throws; `save()` returns true and the
|
|
32
|
+
* column stays NULL (issue #171).
|
|
33
|
+
*
|
|
34
|
+
* Each entry is matched against the content of the file we skipped, and
|
|
35
|
+
* `hint` is printed when it matches.
|
|
36
|
+
*/
|
|
37
|
+
readonly conflicts?: readonly {
|
|
38
|
+
readonly pattern: string;
|
|
39
|
+
readonly hint: string;
|
|
40
|
+
}[];
|
|
24
41
|
}
|
|
25
42
|
/** Create a base file (always overwritten). */
|
|
26
43
|
export declare function baseFile(path: string, content: string): GeneratedFile;
|
|
@@ -28,7 +45,28 @@ export declare function baseFile(path: string, content: string): GeneratedFile;
|
|
|
28
45
|
export declare function userFile(path: string, content: string, expects?: {
|
|
29
46
|
needle: string;
|
|
30
47
|
hint: string;
|
|
31
|
-
}
|
|
48
|
+
}, conflicts?: readonly {
|
|
49
|
+
pattern: string;
|
|
50
|
+
hint: string;
|
|
51
|
+
}[]): GeneratedFile;
|
|
52
|
+
/**
|
|
53
|
+
* Eloquent array properties that REPLACE the parent's when redeclared in a
|
|
54
|
+
* subclass, rather than merging. Redeclaring any of these in a user-editable
|
|
55
|
+
* model shadows whatever the generated base puts there, and the failure is
|
|
56
|
+
* completely silent. Issue #171.
|
|
57
|
+
*/
|
|
58
|
+
export declare const SHADOWING_ELOQUENT_PROPERTIES: readonly ["fillable", "guarded", "casts", "hidden", "visible", "appends", "dates", "touches", "with"];
|
|
59
|
+
/**
|
|
60
|
+
* Build the `conflicts` entries for a generated model's editable subclass.
|
|
61
|
+
*
|
|
62
|
+
* `baseClass` is named in the hint because the whole point is that the base is
|
|
63
|
+
* still correct — the generator did emit the property — and the reader needs to
|
|
64
|
+
* know which file they are shadowing.
|
|
65
|
+
*/
|
|
66
|
+
export declare function shadowingPropertyConflicts(baseClass: string): {
|
|
67
|
+
pattern: string;
|
|
68
|
+
hint: string;
|
|
69
|
+
}[];
|
|
32
70
|
/** Category of base file for modular path resolution. */
|
|
33
71
|
export type BaseCategory = 'Models' | 'Controllers' | 'Requests' | 'Resources' | 'Services' | 'Enums' | 'Traits' | 'Locales' | 'Policies';
|
|
34
72
|
/**
|
|
@@ -418,3 +456,15 @@ export declare function nestEditableByGroup(layer: BaseEditableLayer, loc: {
|
|
|
418
456
|
* All paths and namespaces fall back to sensible defaults.
|
|
419
457
|
*/
|
|
420
458
|
export declare function derivePhpConfig(overrides?: LaravelCodegenOverrides): PhpConfig;
|
|
459
|
+
/**
|
|
460
|
+
* Strip PHP comments so a commented-out declaration does not read as a real
|
|
461
|
+
* one. Without this, the very common
|
|
462
|
+
*
|
|
463
|
+
* // protected $fillable = ['name']; // left from a refactor
|
|
464
|
+
*
|
|
465
|
+
* would be reported as shadowing the base — and a warning that fires on code
|
|
466
|
+
* that is not running is worse than no warning, because people learn to ignore
|
|
467
|
+
* it. Naive on purpose: it can strip a `//` inside a string literal, which only
|
|
468
|
+
* ever costs us a warning we would otherwise have printed, never a false one.
|
|
469
|
+
*/
|
|
470
|
+
export declare function stripPhpComments(source: string): string;
|
package/ts-dist/php/types.js
CHANGED
|
@@ -6,8 +6,53 @@ export function baseFile(path, content) {
|
|
|
6
6
|
return { path, content, overwrite: true, type: 'base' };
|
|
7
7
|
}
|
|
8
8
|
/** Create a user file (created once, skip if exists). */
|
|
9
|
-
export function userFile(path, content, expects) {
|
|
10
|
-
return {
|
|
9
|
+
export function userFile(path, content, expects, conflicts) {
|
|
10
|
+
return {
|
|
11
|
+
path,
|
|
12
|
+
content,
|
|
13
|
+
overwrite: false,
|
|
14
|
+
type: 'user',
|
|
15
|
+
...(expects ? { expects } : {}),
|
|
16
|
+
...(conflicts && conflicts.length > 0 ? { conflicts } : {}),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Eloquent array properties that REPLACE the parent's when redeclared in a
|
|
21
|
+
* subclass, rather than merging. Redeclaring any of these in a user-editable
|
|
22
|
+
* model shadows whatever the generated base puts there, and the failure is
|
|
23
|
+
* completely silent. Issue #171.
|
|
24
|
+
*/
|
|
25
|
+
export const SHADOWING_ELOQUENT_PROPERTIES = [
|
|
26
|
+
'fillable',
|
|
27
|
+
'guarded',
|
|
28
|
+
'casts',
|
|
29
|
+
'hidden',
|
|
30
|
+
'visible',
|
|
31
|
+
'appends',
|
|
32
|
+
'dates',
|
|
33
|
+
'touches',
|
|
34
|
+
'with',
|
|
35
|
+
];
|
|
36
|
+
/**
|
|
37
|
+
* Build the `conflicts` entries for a generated model's editable subclass.
|
|
38
|
+
*
|
|
39
|
+
* `baseClass` is named in the hint because the whole point is that the base is
|
|
40
|
+
* still correct — the generator did emit the property — and the reader needs to
|
|
41
|
+
* know which file they are shadowing.
|
|
42
|
+
*/
|
|
43
|
+
export function shadowingPropertyConflicts(baseClass) {
|
|
44
|
+
return SHADOWING_ELOQUENT_PROPERTIES.map((prop) => ({
|
|
45
|
+
// Matches `protected $fillable`, `public array $casts`, `protected static
|
|
46
|
+
// $with` — any redeclaration, whatever the visibility or type hint.
|
|
47
|
+
pattern: String.raw `(?:public|protected|private)\s+(?:static\s+)?(?:array\s+)?\$` + prop + String.raw `\s*=`,
|
|
48
|
+
hint: `redeclares $${prop}, which REPLACES ` +
|
|
49
|
+
`${baseClass}::$${prop} rather than merging with it (PHP does not merge ` +
|
|
50
|
+
`redeclared properties). Anything omnify adds to $${prop} from now on is ` +
|
|
51
|
+
`silently dropped: mass assignment strips it, save() still returns true, ` +
|
|
52
|
+
`and the column stays NULL. Fix: delete the redeclaration and let the base ` +
|
|
53
|
+
`provide it, or build it from the parent, e.g. ` +
|
|
54
|
+
`__construct() { $this->${prop} = array_merge(parent::$${prop} ?? [], [...]); }`,
|
|
55
|
+
}));
|
|
11
56
|
}
|
|
12
57
|
/**
|
|
13
58
|
* Resolve base file path for a schema based on structure.
|
|
@@ -382,3 +427,20 @@ export function derivePhpConfig(overrides) {
|
|
|
382
427
|
},
|
|
383
428
|
};
|
|
384
429
|
}
|
|
430
|
+
/**
|
|
431
|
+
* Strip PHP comments so a commented-out declaration does not read as a real
|
|
432
|
+
* one. Without this, the very common
|
|
433
|
+
*
|
|
434
|
+
* // protected $fillable = ['name']; // left from a refactor
|
|
435
|
+
*
|
|
436
|
+
* would be reported as shadowing the base — and a warning that fires on code
|
|
437
|
+
* that is not running is worse than no warning, because people learn to ignore
|
|
438
|
+
* it. Naive on purpose: it can strip a `//` inside a string literal, which only
|
|
439
|
+
* ever costs us a warning we would otherwise have printed, never a false one.
|
|
440
|
+
*/
|
|
441
|
+
export function stripPhpComments(source) {
|
|
442
|
+
return source
|
|
443
|
+
.replace(/\/\*[\s\S]*?\*\//g, '')
|
|
444
|
+
.replace(/(^|\s)\/\/[^\n]*/g, '$1')
|
|
445
|
+
.replace(/(^|\s)#[^\n]*/g, '$1');
|
|
446
|
+
}
|