@omnifyjp/omnify 5.8.20 → 5.8.22
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/php/architecture-doc-generator.d.ts +21 -0
- package/ts-dist/php/architecture-doc-generator.js +156 -0
- package/ts-dist/php/index.js +7 -0
- package/ts-dist/php/service-generator.js +13 -1
- package/ts-dist/php/sibling-skip.d.ts +10 -0
- package/ts-dist/php/sibling-skip.js +19 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omnifyjp/omnify",
|
|
3
|
-
"version": "5.8.
|
|
3
|
+
"version": "5.8.22",
|
|
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.22",
|
|
40
|
+
"@omnifyjp/omnify-darwin-x64": "5.8.22",
|
|
41
|
+
"@omnifyjp/omnify-linux-x64": "5.8.22",
|
|
42
|
+
"@omnifyjp/omnify-linux-arm64": "5.8.22",
|
|
43
|
+
"@omnifyjp/omnify-win32-x64": "5.8.22"
|
|
44
44
|
}
|
|
45
45
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Architecture documentation generator. Issue #102 v5.8.21.
|
|
3
|
+
*
|
|
4
|
+
* Auto-emits a `docs/contributing/omnify-architecture.md` (or whichever
|
|
5
|
+
* path the project configured) describing the project's Omnify layout:
|
|
6
|
+
*
|
|
7
|
+
* - Two-layer structure (auto-gen base zone + project-owned editable)
|
|
8
|
+
* - Per-schema FQN reference table (computed from schemas.json + the
|
|
9
|
+
* project's resolved codegen config)
|
|
10
|
+
* - Hard rules + anti-patterns the project must follow
|
|
11
|
+
* - Astrotomic translatable hookup, Pivot/Translation always-grouped
|
|
12
|
+
*
|
|
13
|
+
* The content is fully derivable from the codegen state — every Omnify
|
|
14
|
+
* project hand-wrote some version of this doc and let it drift the
|
|
15
|
+
* moment a schema changed. Auto-generating it keeps the doc
|
|
16
|
+
* consistent with the actual file layout, version after version.
|
|
17
|
+
*/
|
|
18
|
+
import { SchemaReader } from './schema-reader.js';
|
|
19
|
+
import type { GeneratedFile, PhpConfig } from './types.js';
|
|
20
|
+
/** Generate `omnify-architecture.md` describing the project's Omnify layout. */
|
|
21
|
+
export declare function generateArchitectureDoc(reader: SchemaReader, config: PhpConfig): GeneratedFile[];
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Architecture documentation generator. Issue #102 v5.8.21.
|
|
3
|
+
*
|
|
4
|
+
* Auto-emits a `docs/contributing/omnify-architecture.md` (or whichever
|
|
5
|
+
* path the project configured) describing the project's Omnify layout:
|
|
6
|
+
*
|
|
7
|
+
* - Two-layer structure (auto-gen base zone + project-owned editable)
|
|
8
|
+
* - Per-schema FQN reference table (computed from schemas.json + the
|
|
9
|
+
* project's resolved codegen config)
|
|
10
|
+
* - Hard rules + anti-patterns the project must follow
|
|
11
|
+
* - Astrotomic translatable hookup, Pivot/Translation always-grouped
|
|
12
|
+
*
|
|
13
|
+
* The content is fully derivable from the codegen state — every Omnify
|
|
14
|
+
* project hand-wrote some version of this doc and let it drift the
|
|
15
|
+
* moment a schema changed. Auto-generating it keeps the doc
|
|
16
|
+
* consistent with the actual file layout, version after version.
|
|
17
|
+
*/
|
|
18
|
+
import { toPascalCase } from './naming-helper.js';
|
|
19
|
+
import { baseFile, nestByGroup } from './types.js';
|
|
20
|
+
/** Generate `omnify-architecture.md` describing the project's Omnify layout. */
|
|
21
|
+
export function generateArchitectureDoc(reader, config) {
|
|
22
|
+
const schemas = reader.getProjectVisibleObjectSchemas();
|
|
23
|
+
const allSchemaNames = Object.keys(schemas).sort();
|
|
24
|
+
if (allSchemaNames.length === 0)
|
|
25
|
+
return [];
|
|
26
|
+
// Group schemas by yaml group folder.
|
|
27
|
+
const byGroup = new Map();
|
|
28
|
+
for (const name of allSchemaNames) {
|
|
29
|
+
const schema = schemas[name];
|
|
30
|
+
const group = schema.group ?? '_root';
|
|
31
|
+
const arr = byGroup.get(group) ?? [];
|
|
32
|
+
arr.push(name);
|
|
33
|
+
byGroup.set(group, arr);
|
|
34
|
+
}
|
|
35
|
+
const groups = Array.from(byGroup.keys()).sort();
|
|
36
|
+
const lines = [];
|
|
37
|
+
// ── Header ────────────────────────────────────────────────────────────────
|
|
38
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
39
|
+
lines.push('---');
|
|
40
|
+
lines.push('title: Omnify Architecture (auto-generated)');
|
|
41
|
+
lines.push(`generated_at: ${today}`);
|
|
42
|
+
lines.push('generated_by: omnify');
|
|
43
|
+
lines.push(`schema_count: ${allSchemaNames.length}`);
|
|
44
|
+
lines.push(`group_count: ${groups.length}`);
|
|
45
|
+
lines.push('---');
|
|
46
|
+
lines.push('');
|
|
47
|
+
lines.push('# Omnify Architecture');
|
|
48
|
+
lines.push('');
|
|
49
|
+
lines.push('> AUTO-GENERATED — do not edit. Regenerate via `omnify generate`.');
|
|
50
|
+
lines.push('');
|
|
51
|
+
lines.push('This project follows the Omnify v5.8.x canonical Laravel layout. The');
|
|
52
|
+
lines.push('`app/Omnify/` zone is owned by the codegen; `app/Models/`, `app/Http/`,');
|
|
53
|
+
lines.push('`app/Services/`, `app/Policies/` are project-owned and extend the');
|
|
54
|
+
lines.push('auto-gen bases.');
|
|
55
|
+
lines.push('');
|
|
56
|
+
// ── Layer map ─────────────────────────────────────────────────────────────
|
|
57
|
+
lines.push('## Layer map');
|
|
58
|
+
lines.push('');
|
|
59
|
+
lines.push('| Layer | Auto-gen base | User-editable (Laravel-canonical) |');
|
|
60
|
+
lines.push('|-------|---------------|-----------------------------------|');
|
|
61
|
+
lines.push(`| Model | \`${config.models.basePath}/<Group>/<Name>.php\` | \`${config.models.userEditablePath}/<Name>.php\` (FLAT) |`);
|
|
62
|
+
lines.push(`| Translation | \`${config.models.basePath}/Translation/<Name>Translation.php\` | \`${config.models.userEditablePath}/Translation/<Name>Translation.php\` |`);
|
|
63
|
+
lines.push(`| Pivot | \`${config.models.basePath}/Pivot/<Name>.php\` | \`${config.models.userEditablePath}/Pivot/<Name>.php\` |`);
|
|
64
|
+
lines.push(`| Enum | \`${config.globalEnums.path}/<Name>Enum.php\` | _(no user-editable)_ |`);
|
|
65
|
+
lines.push(`| Request | \`${config.requests.basePath}/<Group>/<Name>StoreRequest.php\` | \`${config.requests.userEditablePath}/<Name>StoreRequest.php\` (FLAT) |`);
|
|
66
|
+
lines.push(`| Resource | \`${config.resources.basePath}/<Group>/<Name>Resource.php\` | \`${config.resources.userEditablePath}/<Name>Resource.php\` (FLAT) |`);
|
|
67
|
+
lines.push(`| Service | \`${config.services.basePath}/<Group>/<Name>Service.php\` | \`${config.services.userEditablePath}/<Name>Service.php\` (FLAT) |`);
|
|
68
|
+
lines.push(`| Policy | \`${config.policies.basePath}/<Group>/<Name>Policy.php\` | \`${config.policies.userEditablePath}/<Name>Policy.php\` (FLAT) |`);
|
|
69
|
+
lines.push('');
|
|
70
|
+
// ── Per-schema FQN reference ──────────────────────────────────────────────
|
|
71
|
+
lines.push('## Schema → Base FQN reference');
|
|
72
|
+
lines.push('');
|
|
73
|
+
lines.push('Every schema in the project, organized by YAML group folder. The');
|
|
74
|
+
lines.push('FQN columns are the **base** classes — extend these from your');
|
|
75
|
+
lines.push('user-editable wrappers.');
|
|
76
|
+
lines.push('');
|
|
77
|
+
for (const group of groups) {
|
|
78
|
+
const groupNames = byGroup.get(group).sort();
|
|
79
|
+
const heading = group === '_root' ? '_ungrouped_' : group;
|
|
80
|
+
lines.push(`### ${heading} (${groupNames.length} schema${groupNames.length === 1 ? '' : 's'})`);
|
|
81
|
+
lines.push('');
|
|
82
|
+
lines.push('| Schema | Model base | Service base | Resource base | Request base | Policy base |');
|
|
83
|
+
lines.push('|---|---|---|---|---|---|');
|
|
84
|
+
for (const name of groupNames) {
|
|
85
|
+
const schema = schemas[name];
|
|
86
|
+
const modelName = toPascalCase(name);
|
|
87
|
+
const groupSegment = schema.group;
|
|
88
|
+
const isPivot = schema.kind === 'pivot';
|
|
89
|
+
const nestNs = (baseNs) => {
|
|
90
|
+
if (config.structure === 'modular')
|
|
91
|
+
return baseNs;
|
|
92
|
+
if (isPivot)
|
|
93
|
+
return `${baseNs}\\Pivot`;
|
|
94
|
+
if (groupSegment) {
|
|
95
|
+
return nestByGroup({ path: '', namespace: baseNs }, groupSegment).namespace;
|
|
96
|
+
}
|
|
97
|
+
return baseNs;
|
|
98
|
+
};
|
|
99
|
+
const modelFqn = `${nestNs(config.models.baseNamespace)}\\${modelName}${config.models.flatBase ? '' : 'BaseModel'}`;
|
|
100
|
+
const serviceFqn = `${nestNs(config.services.baseNamespace)}\\${modelName}Service${config.services.flatBase ? '' : 'Base'}`;
|
|
101
|
+
const resourceFqn = `${nestNs(config.resources.baseNamespace)}\\${modelName}Resource${config.resources.flatBase ? '' : 'Base'}`;
|
|
102
|
+
const requestFqn = `${nestNs(config.requests.baseNamespace)}\\${modelName}StoreRequest${config.requests.flatBase ? '' : 'Base'}`;
|
|
103
|
+
const policyFqn = `${nestNs(config.policies.baseNamespace)}\\${modelName}Policy${config.policies.flatBase ? '' : 'Base'}`;
|
|
104
|
+
lines.push(`| ${modelName} | \`${modelFqn}\` | \`${serviceFqn}\` | \`${resourceFqn}\` | \`${requestFqn}\` | \`${policyFqn}\` |`);
|
|
105
|
+
}
|
|
106
|
+
lines.push('');
|
|
107
|
+
}
|
|
108
|
+
// ── Hard rules ────────────────────────────────────────────────────────────
|
|
109
|
+
lines.push('## Hard rules');
|
|
110
|
+
lines.push('');
|
|
111
|
+
lines.push('1. **Never edit anything under the `Omnify/` auto-gen zone.** Files');
|
|
112
|
+
lines.push(' here carry a `DO NOT EDIT` header and are overwritten on every');
|
|
113
|
+
lines.push(' `omnify generate`.');
|
|
114
|
+
lines.push('2. **Schema-derived classes MUST extend the omnify base.** Do not');
|
|
115
|
+
lines.push(' extend `Eloquent\\Model`, `JsonResource`, `FormRequest`, etc.');
|
|
116
|
+
lines.push(' directly when a base exists for that schema.');
|
|
117
|
+
lines.push('3. **User-editable lives at canonical Laravel paths** — flat at the');
|
|
118
|
+
lines.push(' top level (`app/Models/<Name>.php`, NOT `app/Models/<Group>/<Name>.php`).');
|
|
119
|
+
lines.push('4. **Custom (non-schema) classes go in subfolders**, not at the FLAT');
|
|
120
|
+
lines.push(' top (`app/Services/Auth/PasswordResetService.php`, etc.).');
|
|
121
|
+
lines.push('5. **Translation / Pivot / Enum always grouped** — even when other');
|
|
122
|
+
lines.push(' layers are flat, these three keep their dedicated subfolder.');
|
|
123
|
+
lines.push('6. **`config/translatable.php` must set `translation_model_namespace`**');
|
|
124
|
+
lines.push(` to \`${config.models.userEditableNamespace}\\Translation\` so Astrotomic auto-resolves.`);
|
|
125
|
+
lines.push('');
|
|
126
|
+
// ── Anti-patterns ─────────────────────────────────────────────────────────
|
|
127
|
+
lines.push('## Anti-patterns');
|
|
128
|
+
lines.push('');
|
|
129
|
+
lines.push('- Editing files under the `Omnify/` auto-gen zone');
|
|
130
|
+
lines.push('- Re-declaring `$table` / `$fillable` / `$casts` in user-editable');
|
|
131
|
+
lines.push(' models (duplicates schema, drifts on next regeneration)');
|
|
132
|
+
lines.push('- Importing the auto-gen base namespace directly from controllers /');
|
|
133
|
+
lines.push(' services when a user-editable wrapper exists');
|
|
134
|
+
lines.push('- Adding manual `Gate::policy()` registrations for schema-derived');
|
|
135
|
+
lines.push(' models (Laravel auto-discovery handles them)');
|
|
136
|
+
lines.push('- Writing the same schema definition twice (once in YAML, once');
|
|
137
|
+
lines.push(' hand-written)');
|
|
138
|
+
lines.push('- Putting custom (non-schema) resources at the FLAT top-level path');
|
|
139
|
+
lines.push('');
|
|
140
|
+
// ── Footer ────────────────────────────────────────────────────────────────
|
|
141
|
+
lines.push('## Regenerate');
|
|
142
|
+
lines.push('');
|
|
143
|
+
lines.push('```bash');
|
|
144
|
+
lines.push('omnify generate');
|
|
145
|
+
lines.push('```');
|
|
146
|
+
lines.push('');
|
|
147
|
+
lines.push(`Last updated: ${today} by omnify.`);
|
|
148
|
+
lines.push('');
|
|
149
|
+
// Honor `rootPath` so monorepo layouts (`rootPath: backend`) get the
|
|
150
|
+
// doc emitted under `backend/docs/contributing/...` like the rest of
|
|
151
|
+
// the codegen output.
|
|
152
|
+
const docPath = config.rootPath
|
|
153
|
+
? `${config.rootPath}/docs/contributing/omnify-architecture.md`
|
|
154
|
+
: 'docs/contributing/omnify-architecture.md';
|
|
155
|
+
return [baseFile(docPath, lines.join('\n'))];
|
|
156
|
+
}
|
package/ts-dist/php/index.js
CHANGED
|
@@ -36,6 +36,7 @@ import { generateServices } from './service-generator.js';
|
|
|
36
36
|
import { generateRoutes } from './route-generator.js';
|
|
37
37
|
import { generateEnums } from './enum-generator.js';
|
|
38
38
|
import { generateOpenApi } from './openapi-generator.js';
|
|
39
|
+
import { generateArchitectureDoc } from './architecture-doc-generator.js';
|
|
39
40
|
export { derivePhpConfig } from './types.js';
|
|
40
41
|
/** Generate all PHP files from schemas.json data. */
|
|
41
42
|
export function generatePhp(data, overrides) {
|
|
@@ -104,5 +105,11 @@ export function generatePhp(data, overrides) {
|
|
|
104
105
|
files.push(...generateResources(reader, config));
|
|
105
106
|
files.push(...generateFactories(reader, config));
|
|
106
107
|
files.push(...generatePolicies(reader, config));
|
|
108
|
+
// Issue #102 v5.8.21: emit `docs/contributing/omnify-architecture.md`
|
|
109
|
+
// describing the project's resolved Omnify layout — layer paths,
|
|
110
|
+
// per-schema FQN reference, hard rules, anti-patterns. Pure additive;
|
|
111
|
+
// no behavior change for existing consumers, and the doc updates
|
|
112
|
+
// automatically as schemas / config change so it never drifts.
|
|
113
|
+
files.push(...generateArchitectureDoc(reader, config));
|
|
107
114
|
return files;
|
|
108
115
|
}
|
|
@@ -200,9 +200,21 @@ function warnLegacyServiceKeys(name, schema) {
|
|
|
200
200
|
}
|
|
201
201
|
}
|
|
202
202
|
function generateForSchema(name, schema, reader, config) {
|
|
203
|
+
// Issue #101 v5.8.22: only emit the service BASE. The user-editable
|
|
204
|
+
// service stub is pure scaffolding (extends base, no body) for every
|
|
205
|
+
// schema in every project — 290+ noise files in a typical project.
|
|
206
|
+
// Consumers inject the BASE FQN directly:
|
|
207
|
+
//
|
|
208
|
+
// public function __construct(
|
|
209
|
+
// private \App\Omnify\Services\<Group>\<Name>ServiceBase \$service,
|
|
210
|
+
// ) {}
|
|
211
|
+
//
|
|
212
|
+
// When custom logic IS needed, the dev creates the editable file by
|
|
213
|
+
// hand under the `userEditablePath` (`app/Services/<Name>Service.php`)
|
|
214
|
+
// — Laravel autoloads it like any other class. Omnify won't recreate
|
|
215
|
+
// it on subsequent generates because we no longer emit it at all.
|
|
203
216
|
return [
|
|
204
217
|
generateBaseService(name, schema, reader, config),
|
|
205
|
-
generateUserService(name, schema, config),
|
|
206
218
|
];
|
|
207
219
|
}
|
|
208
220
|
/**
|
|
@@ -38,5 +38,15 @@ export declare function findAppRootAncestor(filePath: string): string | null;
|
|
|
38
38
|
* Scan `rootDir` recursively for a file whose basename matches
|
|
39
39
|
* `baseName`, excluding `excludePath` itself. Returns the first
|
|
40
40
|
* match's full path or null.
|
|
41
|
+
*
|
|
42
|
+
* Issue #99 v5.8.19 follow-up: skip the auto-gen `Omnify/` subtree
|
|
43
|
+
* (its files are codegen output, not project-owned siblings) so the
|
|
44
|
+
* editable layer at `app/Models/<Name>.php` doesn't get blocked by
|
|
45
|
+
* the sibling base at `app/Omnify/Models/<Group>/<Name>.php` under
|
|
46
|
+
* canonical+flatBase layout. Project-owned siblings under
|
|
47
|
+
* `app/Services/Omnify/<Name>Service.php` (v4 wrapper layout) DO still
|
|
48
|
+
* trigger a skip because they live OUTSIDE the auto-gen zone — the
|
|
49
|
+
* skip rule is "any directory whose name starts with `Omnify` is
|
|
50
|
+
* codegen-owned", which matches `Omnify/`, `OmnifyBase/`, etc.
|
|
41
51
|
*/
|
|
42
52
|
export declare function findSiblingWithSameBasename(rootDir: string, baseName: string, excludePath: string): string | null;
|
|
@@ -52,6 +52,16 @@ export function findAppRootAncestor(filePath) {
|
|
|
52
52
|
* Scan `rootDir` recursively for a file whose basename matches
|
|
53
53
|
* `baseName`, excluding `excludePath` itself. Returns the first
|
|
54
54
|
* match's full path or null.
|
|
55
|
+
*
|
|
56
|
+
* Issue #99 v5.8.19 follow-up: skip the auto-gen `Omnify/` subtree
|
|
57
|
+
* (its files are codegen output, not project-owned siblings) so the
|
|
58
|
+
* editable layer at `app/Models/<Name>.php` doesn't get blocked by
|
|
59
|
+
* the sibling base at `app/Omnify/Models/<Group>/<Name>.php` under
|
|
60
|
+
* canonical+flatBase layout. Project-owned siblings under
|
|
61
|
+
* `app/Services/Omnify/<Name>Service.php` (v4 wrapper layout) DO still
|
|
62
|
+
* trigger a skip because they live OUTSIDE the auto-gen zone — the
|
|
63
|
+
* skip rule is "any directory whose name starts with `Omnify` is
|
|
64
|
+
* codegen-owned", which matches `Omnify/`, `OmnifyBase/`, etc.
|
|
55
65
|
*/
|
|
56
66
|
export function findSiblingWithSameBasename(rootDir, baseName, excludePath) {
|
|
57
67
|
let scanned = 0;
|
|
@@ -82,6 +92,15 @@ export function findSiblingWithSameBasename(rootDir, baseName, excludePath) {
|
|
|
82
92
|
continue;
|
|
83
93
|
}
|
|
84
94
|
if (stats.isDirectory()) {
|
|
95
|
+
// Skip the auto-gen `app/Omnify/` zone — its files are codegen
|
|
96
|
+
// outputs, not user-owned siblings. Detect via "directory is
|
|
97
|
+
// named `Omnify` AND its parent is named `app`": this catches
|
|
98
|
+
// the canonical `app/Omnify/` zone but does NOT skip
|
|
99
|
+
// `app/Services/Omnify/` (a v4 wrapper layout where `Omnify`
|
|
100
|
+
// is project-owned and should still trigger sibling-skip per
|
|
101
|
+
// issue #99 v5.8.19).
|
|
102
|
+
if (entry === 'Omnify' && basename(dir) === 'app')
|
|
103
|
+
continue;
|
|
85
104
|
stack.push(fullPath);
|
|
86
105
|
}
|
|
87
106
|
else if (entry === baseName && fullPath !== excludePath) {
|