@omnifyjp/ts 6.3.4 → 6.3.5
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.
|
@@ -24,7 +24,7 @@ export function generateModels(reader, config) {
|
|
|
24
24
|
if (extended.length > 0) {
|
|
25
25
|
files.push(generateExtendAttributesTrait(name, schema, extended, reader, config));
|
|
26
26
|
}
|
|
27
|
-
files.push(generatePackageUserModel(name, pkgNs, config, extended.length > 0));
|
|
27
|
+
files.push(generatePackageUserModel(name, pkgNs, config, extended.length > 0, schema.kind === 'pivot'));
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
return files;
|
|
@@ -1057,10 +1057,17 @@ function buildAccessors(expandedProperties) {
|
|
|
1057
1057
|
return methods.join('\n');
|
|
1058
1058
|
}
|
|
1059
1059
|
/** Generate a user model for a package schema that extends the package's model class. */
|
|
1060
|
-
function generatePackageUserModel(name, packageNamespace, config, hasExtendAttributes = false) {
|
|
1060
|
+
function generatePackageUserModel(name, packageNamespace, config, hasExtendAttributes = false, isPivot = false) {
|
|
1061
1061
|
const modelName = toPascalCase(name);
|
|
1062
1062
|
const modelNamespace = config.models.namespace;
|
|
1063
|
-
|
|
1063
|
+
// #178: a `kind: pivot` schema's editable model is emitted under the
|
|
1064
|
+
// dedicated `Pivot/` subfolder on the package side too (v5.8.5), so the
|
|
1065
|
+
// parent this stub extends is `<Pkg>\Models\Pivot\<Name>`. Naming the
|
|
1066
|
+
// flat `<Pkg>\Models\<Name>` pointed at a class no generator ever writes,
|
|
1067
|
+
// and the stub fataled with "Class not found" the moment it autoloaded.
|
|
1068
|
+
const packageModelClass = isPivot
|
|
1069
|
+
? `${packageNamespace}\\Pivot\\${modelName}`
|
|
1070
|
+
: `${packageNamespace}\\${modelName}`;
|
|
1064
1071
|
const traitName = extendAttributesTraitName(name);
|
|
1065
1072
|
// This file is created once and never rewritten, so the `use` line can only
|
|
1066
1073
|
// be put here for a model that does not exist yet. A project whose model
|
|
@@ -19,6 +19,8 @@ const ACTION_METHOD_MAP = {
|
|
|
19
19
|
};
|
|
20
20
|
/** All standard actions (used for wildcard expansion). */
|
|
21
21
|
const ALL_ACTIONS = ['view', 'list', 'create', 'edit', 'delete'];
|
|
22
|
+
/** FQCN of the acting user every policy method takes. */
|
|
23
|
+
const ACTOR_FQN = 'App\\Models\\User';
|
|
22
24
|
/** Actions that receive both $user and $record parameters. */
|
|
23
25
|
const RECORD_ACTIONS = new Set(['view', 'edit', 'delete']);
|
|
24
26
|
// ============================================================================
|
|
@@ -93,6 +95,19 @@ function generateBasePolicyClass(name, schema, reader, config) {
|
|
|
93
95
|
: (config.models.userEditableGroupByFolder
|
|
94
96
|
? nestByGroup({ path: '', namespace: config.models.userEditableNamespace }, schema.group).namespace
|
|
95
97
|
: config.models.userEditableNamespace);
|
|
98
|
+
// Issue #177: the acting user is always `App\\Models\\User`, but the policed
|
|
99
|
+
// model is imported from its own namespace — and for a schema named `User`
|
|
100
|
+
// outside `App\\Models` the two short names collide. PHP rejects the file at
|
|
101
|
+
// compile time (`Cannot use ... as User because the name is already in use`),
|
|
102
|
+
// which is a fatal, not a catchable Throwable. Alias the actor so both stay
|
|
103
|
+
// distinct, and keep the signatures pointing at the class each parameter
|
|
104
|
+
// actually needs. When the policed model IS `App\\Models\\User` the two are
|
|
105
|
+
// the same class: one import, no alias.
|
|
106
|
+
const modelFqn = `${modelNamespace}\\${modelName}`;
|
|
107
|
+
const actorClass = modelName === 'User' && modelFqn !== ACTOR_FQN ? 'ActingUser' : 'User';
|
|
108
|
+
const actorImport = actorClass === 'User'
|
|
109
|
+
? `use ${ACTOR_FQN};`
|
|
110
|
+
: `use ${ACTOR_FQN} as ${actorClass};`;
|
|
96
111
|
// Issue #98 v5.8.14: when no Cedar-style `policies:` array is defined,
|
|
97
112
|
// emit the standard CRUD scaffold with `return true;` bodies.
|
|
98
113
|
// The project secures the policy by overriding methods in the
|
|
@@ -112,23 +127,23 @@ function generateBasePolicyClass(name, schema, reader, config) {
|
|
|
112
127
|
const body = hasExplicitPolicies
|
|
113
128
|
? generateMethodBody(action, expandedPolicies, schema, reader, hasRecord)
|
|
114
129
|
: ' return true;';
|
|
115
|
-
methods.push(buildMethod(methodName, modelName, hasRecord, body));
|
|
130
|
+
methods.push(buildMethod(methodName, modelName, hasRecord, body, actorClass));
|
|
116
131
|
}
|
|
117
132
|
// SoftDelete: restore + forceDelete
|
|
118
133
|
if (hasSoftDelete) {
|
|
119
134
|
const restoreBody = hasExplicitPolicies
|
|
120
135
|
? generateMethodBody('delete', expandedPolicies, schema, reader)
|
|
121
136
|
: ' return true;';
|
|
122
|
-
methods.push(buildMethod('restore', modelName, true, restoreBody));
|
|
137
|
+
methods.push(buildMethod('restore', modelName, true, restoreBody, actorClass));
|
|
123
138
|
const forceDeleteBody = hasExplicitPolicies
|
|
124
139
|
? generateMethodBody('delete', expandedPolicies, schema, reader)
|
|
125
140
|
: ' return true;';
|
|
126
|
-
methods.push(buildMethod('forceDelete', modelName, true, forceDeleteBody));
|
|
141
|
+
methods.push(buildMethod('forceDelete', modelName, true, forceDeleteBody, actorClass));
|
|
127
142
|
}
|
|
128
143
|
const cidrHelper = needsCidrHelper ? buildCidrHelper() : '';
|
|
129
144
|
const imports = [...new Set([
|
|
130
|
-
`use ${
|
|
131
|
-
|
|
145
|
+
`use ${modelFqn};`,
|
|
146
|
+
actorImport,
|
|
132
147
|
'use Illuminate\\Auth\\Access\\HandlesAuthorization;',
|
|
133
148
|
])].join('\n');
|
|
134
149
|
const content = `<?php
|
|
@@ -280,14 +295,16 @@ function generateMethodBody(action, policies, schema, reader, hasRecord = true)
|
|
|
280
295
|
// ============================================================================
|
|
281
296
|
// Method builder
|
|
282
297
|
// ============================================================================
|
|
283
|
-
function buildMethod(methodName, modelName, hasRecord, body
|
|
298
|
+
function buildMethod(methodName, modelName, hasRecord, body,
|
|
299
|
+
/** Short name the acting user is imported under — aliased on collision (#177). */
|
|
300
|
+
actorClass = 'User') {
|
|
284
301
|
const recordParam = hasRecord ? `, ${modelName} $record` : '';
|
|
285
302
|
const returnType = 'bool';
|
|
286
303
|
return `
|
|
287
304
|
/**
|
|
288
305
|
* Determine whether the user can ${methodName}.
|
|
289
306
|
*/
|
|
290
|
-
public function ${methodName}(
|
|
307
|
+
public function ${methodName}(${actorClass} $user${recordParam}): ${returnType}
|
|
291
308
|
{
|
|
292
309
|
${body}
|
|
293
310
|
}`;
|
|
@@ -72,12 +72,23 @@ function generateBaseResource(name, schema, reader, config) {
|
|
|
72
72
|
const timestamps = resolveTimestamps(options);
|
|
73
73
|
const hasSoftDelete = options.softDelete ?? false;
|
|
74
74
|
const hasId = options.id ?? true;
|
|
75
|
+
// #179: the class this file declares owns its own short name. With
|
|
76
|
+
// `flatBase` the base class is `<Name>Resource`, the same short name a
|
|
77
|
+
// self-referencing association (`parent: Department`) wants to import from
|
|
78
|
+
// the editable namespace — and `use App\\Http\\Resources\\DepartmentResource;`
|
|
79
|
+
// next to `class DepartmentResource` is a compile-time fatal. Reserving the
|
|
80
|
+
// declared FQN makes the importer fall back to the inline FQCN, which is the
|
|
81
|
+
// collision handling #174 already specified.
|
|
82
|
+
const declaredClassName = config.structure === 'modular'
|
|
83
|
+
? `${modelName}ResourceBase`
|
|
84
|
+
: resolveBaseClass(config.resources, `${modelName}Resource`, 'Base').className;
|
|
75
85
|
const fields = [];
|
|
76
86
|
// #174: reference other Resources by short name + `use` import, the way Pint
|
|
77
87
|
// would rewrite them, so `generate` and the formatter stop disagreeing.
|
|
78
88
|
const importer = createClassImporter(baseNamespace, [
|
|
79
89
|
'Illuminate\\Http\\Request',
|
|
80
90
|
'Illuminate\\Http\\Resources\\Json\\JsonResource',
|
|
91
|
+
`${baseNamespace}\\${declaredClassName}`,
|
|
81
92
|
]);
|
|
82
93
|
if (hasId) {
|
|
83
94
|
fields.push(" 'id' => $this->id,");
|
|
@@ -34,6 +34,19 @@
|
|
|
34
34
|
* malformed input.
|
|
35
35
|
*/
|
|
36
36
|
export declare function findAppRootAncestor(filePath: string): string | null;
|
|
37
|
+
/**
|
|
38
|
+
* The omnify root that governs `filePath`: the closest ancestor directory
|
|
39
|
+
* holding an `omnify.yaml`. Null when there is none within 12 levels.
|
|
40
|
+
*
|
|
41
|
+
* Issue #178: a project with omnify packages inside `app/` (`app/Idp/`,
|
|
42
|
+
* `app/Auth/`, each its own omnify root) runs `generate` once per root, and
|
|
43
|
+
* the `app/`-wide basename scan reaches straight across those boundaries. The
|
|
44
|
+
* root run then skipped `app/Models/<Pivot>.php` because the package had
|
|
45
|
+
* emitted `app/Idp/Models/Pivot/<Pivot>.php`, and the package run skipped its
|
|
46
|
+
* own stub because the root had emitted the flat one — two different classes
|
|
47
|
+
* suppressing each other, permanently, since neither is ever rewritten.
|
|
48
|
+
*/
|
|
49
|
+
export declare function findOmnifyRoot(filePath: string): string | null;
|
|
37
50
|
/**
|
|
38
51
|
* Scan `rootDir` recursively for a file whose basename matches
|
|
39
52
|
* `baseName`, excluding `excludePath` itself. Returns the first
|
package/dist/php/sibling-skip.js
CHANGED
|
@@ -48,6 +48,30 @@ export function findAppRootAncestor(filePath) {
|
|
|
48
48
|
}
|
|
49
49
|
return null;
|
|
50
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* The omnify root that governs `filePath`: the closest ancestor directory
|
|
53
|
+
* holding an `omnify.yaml`. Null when there is none within 12 levels.
|
|
54
|
+
*
|
|
55
|
+
* Issue #178: a project with omnify packages inside `app/` (`app/Idp/`,
|
|
56
|
+
* `app/Auth/`, each its own omnify root) runs `generate` once per root, and
|
|
57
|
+
* the `app/`-wide basename scan reaches straight across those boundaries. The
|
|
58
|
+
* root run then skipped `app/Models/<Pivot>.php` because the package had
|
|
59
|
+
* emitted `app/Idp/Models/Pivot/<Pivot>.php`, and the package run skipped its
|
|
60
|
+
* own stub because the root had emitted the flat one — two different classes
|
|
61
|
+
* suppressing each other, permanently, since neither is ever rewritten.
|
|
62
|
+
*/
|
|
63
|
+
export function findOmnifyRoot(filePath) {
|
|
64
|
+
let cur = dirname(filePath);
|
|
65
|
+
for (let i = 0; i < 12; i++) {
|
|
66
|
+
if (existsSync(join(cur, 'omnify.yaml')))
|
|
67
|
+
return cur;
|
|
68
|
+
const parent = dirname(cur);
|
|
69
|
+
if (parent === cur)
|
|
70
|
+
break;
|
|
71
|
+
cur = parent;
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
51
75
|
/**
|
|
52
76
|
* Scan `rootDir` recursively for a file whose basename matches
|
|
53
77
|
* `baseName`, excluding `excludePath` itself. Returns the first
|
|
@@ -64,6 +88,11 @@ export function findAppRootAncestor(filePath) {
|
|
|
64
88
|
* codegen-owned", which matches `Omnify/`, `OmnifyBase/`, etc.
|
|
65
89
|
*/
|
|
66
90
|
export function findSiblingWithSameBasename(rootDir, baseName, excludePath) {
|
|
91
|
+
// #178: only a file generated from the SAME omnify root can be a relocated
|
|
92
|
+
// version of this stub. One from another root is a different class that
|
|
93
|
+
// merely shares a filename, and skipping on it leaves the class nothing
|
|
94
|
+
// declares.
|
|
95
|
+
const ownerRoot = findOmnifyRoot(excludePath);
|
|
67
96
|
let scanned = 0;
|
|
68
97
|
const stack = [rootDir];
|
|
69
98
|
while (stack.length > 0) {
|
|
@@ -104,6 +133,8 @@ export function findSiblingWithSameBasename(rootDir, baseName, excludePath) {
|
|
|
104
133
|
stack.push(fullPath);
|
|
105
134
|
}
|
|
106
135
|
else if (entry === baseName && fullPath !== excludePath) {
|
|
136
|
+
if (findOmnifyRoot(fullPath) !== ownerRoot)
|
|
137
|
+
continue;
|
|
107
138
|
return fullPath;
|
|
108
139
|
}
|
|
109
140
|
}
|