@omnifyjp/ts 5.2.2 → 5.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/dist/cli.js +21 -1
- package/dist/php/orphan-cleanup.d.ts +36 -0
- package/dist/php/orphan-cleanup.js +124 -0
- package/dist/php/service-generator.d.ts +15 -0
- package/dist/php/service-generator.js +55 -18
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -28,7 +28,8 @@ import { resolve, dirname, join } 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
|
-
import { generatePhp } from './php/index.js';
|
|
31
|
+
import { generatePhp, derivePhpConfig } from './php/index.js';
|
|
32
|
+
import { pruneOrphanServiceFiles } from './php/orphan-cleanup.js';
|
|
32
33
|
import { resolveInput, sniffInputKind } from './input-resolver.js';
|
|
33
34
|
function resolveFromConfig(configPath) {
|
|
34
35
|
const raw = readFileSync(configPath, 'utf-8');
|
|
@@ -238,6 +239,25 @@ program
|
|
|
238
239
|
console.log(` ${phpCreated} files created (user-editable)`);
|
|
239
240
|
if (phpSkipped > 0)
|
|
240
241
|
console.log(` ${phpSkipped} files skipped (already exist)`);
|
|
242
|
+
// Orphan cleanup: when a schema is removed, renamed, flipped from
|
|
243
|
+
// kind:object → kind:pivot, or opted out via `options.service: false`,
|
|
244
|
+
// its previously-emitted `*ServiceBase.php` becomes orphan and
|
|
245
|
+
// pollutes Composer autoload. Prune base files automatically; warn
|
|
246
|
+
// about editable `*Service.php` (may contain user code).
|
|
247
|
+
const phpConfig = derivePhpConfig(laravelOverrides);
|
|
248
|
+
const cleanup = pruneOrphanServiceFiles(configDir, phpConfig, phpFiles);
|
|
249
|
+
if (cleanup.prunedBases.length > 0) {
|
|
250
|
+
console.log(` ${cleanup.prunedBases.length} orphan ServiceBase pruned (schema removed/renamed/opted-out)`);
|
|
251
|
+
for (const p of cleanup.prunedBases) {
|
|
252
|
+
console.log(` pruned: ${p}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
for (const p of cleanup.warnedEditables) {
|
|
256
|
+
console.warn(`[omnify-ts] orphan editable service ${p} — backing schema is no ` +
|
|
257
|
+
`longer eligible for service codegen (removed / renamed / opted out / ` +
|
|
258
|
+
`flipped to non-object kind). Safe to delete by hand if it has no ` +
|
|
259
|
+
`custom code inside; preserved for now to protect any hand-edits.`);
|
|
260
|
+
}
|
|
241
261
|
}
|
|
242
262
|
console.log(`\nGeneration complete.`);
|
|
243
263
|
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orphan cleanup for service codegen.
|
|
3
|
+
*
|
|
4
|
+
* Service generation is opt-out (#81): every project `kind: object` schema
|
|
5
|
+
* gets a `<Name>ServiceBase.php` (regenerated wholesale, no user content)
|
|
6
|
+
* and a `<Name>Service.php` (sinh-1-lần userFile, may contain hand code).
|
|
7
|
+
*
|
|
8
|
+
* When a schema flips from `kind: object` → `kind: pivot`, gets renamed,
|
|
9
|
+
* removed, opted out via `options.service: false`, or hidden, the
|
|
10
|
+
* previously-emitted base service stays on disk forever and pollutes
|
|
11
|
+
* Composer autoload (a class file with no backing schema = mystery code
|
|
12
|
+
* for the next dev to inherit).
|
|
13
|
+
*
|
|
14
|
+
* This module mirrors the existing pivot YAML cleanup pattern
|
|
15
|
+
* (`RemoveOrphanAutoPivots` in the Go layer): scan known service
|
|
16
|
+
* directories, diff against the current generation's expected output set,
|
|
17
|
+
* delete the orphan base files. Editable `*Service.php` files are NEVER
|
|
18
|
+
* deleted — they may contain hand-written user code — instead a console
|
|
19
|
+
* warning is printed so the user can decide whether to clean up by hand.
|
|
20
|
+
*/
|
|
21
|
+
import type { GeneratedFile, PhpConfig } from './types.js';
|
|
22
|
+
export interface ServiceCleanupResult {
|
|
23
|
+
/** Absolute paths to `*ServiceBase.php` files that were deleted. */
|
|
24
|
+
prunedBases: string[];
|
|
25
|
+
/** Absolute paths to `*Service.php` files that look orphan but are
|
|
26
|
+
* preserved (may contain user code). Caller should print a warning. */
|
|
27
|
+
warnedEditables: string[];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Delete orphan `*ServiceBase.php` files; flag orphan editable services
|
|
31
|
+
* for warning. Pure-ish: takes the generated file list (= source of
|
|
32
|
+
* truth for "what should exist") and the resolved PhpConfig (= where to
|
|
33
|
+
* look). Filesystem mutations are limited to `unlinkSync` on base files
|
|
34
|
+
* that are demonstrably absent from the generated set.
|
|
35
|
+
*/
|
|
36
|
+
export declare function pruneOrphanServiceFiles(configDir: string, config: PhpConfig, generated: readonly GeneratedFile[]): ServiceCleanupResult;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orphan cleanup for service codegen.
|
|
3
|
+
*
|
|
4
|
+
* Service generation is opt-out (#81): every project `kind: object` schema
|
|
5
|
+
* gets a `<Name>ServiceBase.php` (regenerated wholesale, no user content)
|
|
6
|
+
* and a `<Name>Service.php` (sinh-1-lần userFile, may contain hand code).
|
|
7
|
+
*
|
|
8
|
+
* When a schema flips from `kind: object` → `kind: pivot`, gets renamed,
|
|
9
|
+
* removed, opted out via `options.service: false`, or hidden, the
|
|
10
|
+
* previously-emitted base service stays on disk forever and pollutes
|
|
11
|
+
* Composer autoload (a class file with no backing schema = mystery code
|
|
12
|
+
* for the next dev to inherit).
|
|
13
|
+
*
|
|
14
|
+
* This module mirrors the existing pivot YAML cleanup pattern
|
|
15
|
+
* (`RemoveOrphanAutoPivots` in the Go layer): scan known service
|
|
16
|
+
* directories, diff against the current generation's expected output set,
|
|
17
|
+
* delete the orphan base files. Editable `*Service.php` files are NEVER
|
|
18
|
+
* deleted — they may contain hand-written user code — instead a console
|
|
19
|
+
* warning is printed so the user can decide whether to clean up by hand.
|
|
20
|
+
*/
|
|
21
|
+
import { existsSync, readdirSync, statSync, unlinkSync } from 'node:fs';
|
|
22
|
+
import { join, resolve, basename } from 'node:path';
|
|
23
|
+
/**
|
|
24
|
+
* Delete orphan `*ServiceBase.php` files; flag orphan editable services
|
|
25
|
+
* for warning. Pure-ish: takes the generated file list (= source of
|
|
26
|
+
* truth for "what should exist") and the resolved PhpConfig (= where to
|
|
27
|
+
* look). Filesystem mutations are limited to `unlinkSync` on base files
|
|
28
|
+
* that are demonstrably absent from the generated set.
|
|
29
|
+
*/
|
|
30
|
+
export function pruneOrphanServiceFiles(configDir, config, generated) {
|
|
31
|
+
// Build the absolute-path set of files THIS generation pass produced.
|
|
32
|
+
// Anything matching `*ServiceBase.php` outside this set is orphan.
|
|
33
|
+
const expectedAbs = new Set();
|
|
34
|
+
for (const f of generated) {
|
|
35
|
+
expectedAbs.add(resolve(configDir, f.path));
|
|
36
|
+
}
|
|
37
|
+
const result = { prunedBases: [], warnedEditables: [] };
|
|
38
|
+
// ---- Base service cleanup -----------------------------------------------
|
|
39
|
+
//
|
|
40
|
+
// Search roots cover both layout modes:
|
|
41
|
+
// - Modular: `<modules.path>/<Schema>/Services/<Schema>ServiceBase.php`
|
|
42
|
+
// (one folder per schema; orphan schemas have orphan folders)
|
|
43
|
+
// - Legacy: `<services.basePath>/<Schema>ServiceBase.php`
|
|
44
|
+
// (flat folder; orphan schemas leave loose files)
|
|
45
|
+
//
|
|
46
|
+
// We always scan BOTH so a project that switched modes mid-flight gets
|
|
47
|
+
// the leftover-from-the-other-mode files cleaned too.
|
|
48
|
+
const baseSearchRoots = [];
|
|
49
|
+
if (config.structure === 'modular') {
|
|
50
|
+
const modulesAbs = resolve(configDir, config.modules.path);
|
|
51
|
+
if (existsSync(modulesAbs) && statSync(modulesAbs).isDirectory()) {
|
|
52
|
+
for (const entry of readdirSync(modulesAbs)) {
|
|
53
|
+
const svcDir = join(modulesAbs, entry, 'Services');
|
|
54
|
+
if (existsSync(svcDir) && statSync(svcDir).isDirectory()) {
|
|
55
|
+
baseSearchRoots.push(svcDir);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
baseSearchRoots.push(resolve(configDir, config.services.basePath));
|
|
61
|
+
for (const root of baseSearchRoots) {
|
|
62
|
+
if (!existsSync(root))
|
|
63
|
+
continue;
|
|
64
|
+
for (const entry of readdirSync(root)) {
|
|
65
|
+
if (!entry.endsWith('ServiceBase.php'))
|
|
66
|
+
continue;
|
|
67
|
+
const fullPath = join(root, entry);
|
|
68
|
+
if (expectedAbs.has(fullPath))
|
|
69
|
+
continue;
|
|
70
|
+
try {
|
|
71
|
+
unlinkSync(fullPath);
|
|
72
|
+
result.prunedBases.push(fullPath);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// EPERM / EBUSY / ENOENT — skip. The next generate pass will
|
|
76
|
+
// retry; meanwhile we don't crash the whole codegen pipeline
|
|
77
|
+
// on a single uncleanable file.
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// ---- Editable service warning -------------------------------------------
|
|
82
|
+
//
|
|
83
|
+
// Editable services live at `<services.path>/<Group>/<Schema>Service.php`
|
|
84
|
+
// (group-nested) or `<services.path>/<Schema>Service.php` (no group).
|
|
85
|
+
// The recursive walk skips the `OmnifyBase` subfolder (already covered
|
|
86
|
+
// by the base cleanup above) so we don't double-process or warn on
|
|
87
|
+
// base files masquerading as editable.
|
|
88
|
+
const editableRoot = resolve(configDir, config.services.path);
|
|
89
|
+
walkServiceFiles(editableRoot, (path) => {
|
|
90
|
+
const name = basename(path);
|
|
91
|
+
if (name.endsWith('ServiceBase.php'))
|
|
92
|
+
return;
|
|
93
|
+
if (!name.endsWith('Service.php'))
|
|
94
|
+
return;
|
|
95
|
+
if (expectedAbs.has(path))
|
|
96
|
+
return;
|
|
97
|
+
result.warnedEditables.push(path);
|
|
98
|
+
});
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
function walkServiceFiles(root, visit) {
|
|
102
|
+
if (!existsSync(root))
|
|
103
|
+
return;
|
|
104
|
+
for (const entry of readdirSync(root)) {
|
|
105
|
+
const full = join(root, entry);
|
|
106
|
+
let info;
|
|
107
|
+
try {
|
|
108
|
+
info = statSync(full);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (info.isDirectory()) {
|
|
114
|
+
// OmnifyBase holds the base files we already process above —
|
|
115
|
+
// skip to avoid the editable scan re-flagging them.
|
|
116
|
+
if (entry === 'OmnifyBase')
|
|
117
|
+
continue;
|
|
118
|
+
walkServiceFiles(full, visit);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
visit(full);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -27,3 +27,18 @@ import type { GeneratedFile, PhpConfig } from './types.js';
|
|
|
27
27
|
* - package-owned schemas (those get services in their owning package)
|
|
28
28
|
*/
|
|
29
29
|
export declare function generateServices(reader: SchemaReader, config: PhpConfig): GeneratedFile[];
|
|
30
|
+
/**
|
|
31
|
+
* Returns the set of `*ServiceBase.php` filenames the current schema set
|
|
32
|
+
* is expected to produce. The cli.ts orphan-cleanup compares this set
|
|
33
|
+
* against the actual files in the base-services directory and deletes
|
|
34
|
+
* any mismatches — handles the case where a schema is renamed, removed,
|
|
35
|
+
* flipped from `kind: object` to `kind: pivot`, or opted out via
|
|
36
|
+
* `options.service: false`.
|
|
37
|
+
*
|
|
38
|
+
* Filename format mirrors `generateBaseService`: `{PascalSchemaName}ServiceBase.php`.
|
|
39
|
+
*
|
|
40
|
+
* Editable `*Service.php` files are NOT in scope — those are user-owned
|
|
41
|
+
* (sinh-1-lần userFile semantics) and may contain hand-written code; the
|
|
42
|
+
* cli prints a warning instead of deleting them.
|
|
43
|
+
*/
|
|
44
|
+
export declare function expectedBaseServiceFileNames(reader: SchemaReader): Set<string>;
|
|
@@ -31,17 +31,40 @@ import { baseFile, userFile, resolveModularBasePath, resolveModularBaseNamespace
|
|
|
31
31
|
*/
|
|
32
32
|
export function generateServices(reader, config) {
|
|
33
33
|
const files = [];
|
|
34
|
+
const candidates = collectServiceCandidates(reader);
|
|
35
|
+
// Emit deprecation warnings once per schema for legacy service-block keys
|
|
36
|
+
// that now duplicate property-level metadata.
|
|
37
|
+
for (const [name, schema] of Object.entries(candidates)) {
|
|
38
|
+
warnLegacyServiceKeys(name, schema);
|
|
39
|
+
}
|
|
40
|
+
for (const [name, schema] of Object.entries(candidates)) {
|
|
41
|
+
files.push(...generateForSchema(name, schema, reader, config));
|
|
42
|
+
}
|
|
43
|
+
return files;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Collect the set of schemas that should produce a service (base + editable
|
|
47
|
+
* pair) on this generation pass. Shared by `generateServices` (which emits
|
|
48
|
+
* the files) and `expectedBaseServiceFileNames` (which drives the orphan
|
|
49
|
+
* cleanup in cli.ts). Keeping a single source of truth for the candidate
|
|
50
|
+
* filter means the cleanup can never disagree with the emitter — when a
|
|
51
|
+
* schema flips from `kind: object` to `kind: pivot`, the previously-emitted
|
|
52
|
+
* `*ServiceBase.php` becomes orphan and the cleanup deletes it on the next
|
|
53
|
+
* regeneration.
|
|
54
|
+
*
|
|
55
|
+
* #81 + #80 interaction (v3.23.1): iterate every project-owned schema
|
|
56
|
+
* directly instead of going through `getProjectObjectSchemas()`, which
|
|
57
|
+
* filters out KindPartial. Phantom-upstream extends (#80 self-reference
|
|
58
|
+
* fallback — e.g. `kind: extend, target: Product` with no upstream
|
|
59
|
+
* package loaded) are kept as KindPartial so downstream generators
|
|
60
|
+
* skip emitting tables for them, but their SERVICES still belong in
|
|
61
|
+
* the consumer project and must be generated here. Kind=object stays
|
|
62
|
+
* the opt-out default; Kind=partial/extend is included when the user
|
|
63
|
+
* has signalled service intent (legacy options.service block or any
|
|
64
|
+
* property-level service flag).
|
|
65
|
+
*/
|
|
66
|
+
function collectServiceCandidates(reader) {
|
|
34
67
|
const candidates = {};
|
|
35
|
-
// #81 + #80 interaction (v3.23.1): iterate every project-owned schema
|
|
36
|
-
// directly instead of going through `getProjectObjectSchemas()`, which
|
|
37
|
-
// filters out KindPartial. Phantom-upstream extends (#80 self-reference
|
|
38
|
-
// fallback — e.g. `kind: extend, target: Product` with no upstream
|
|
39
|
-
// package loaded) are kept as KindPartial so downstream generators
|
|
40
|
-
// skip emitting tables for them, but their SERVICES still belong in
|
|
41
|
-
// the consumer project and must be generated here. Kind=object stays
|
|
42
|
-
// the opt-out default; Kind=partial/extend is included when the user
|
|
43
|
-
// has signalled service intent (legacy options.service block or any
|
|
44
|
-
// property-level service flag).
|
|
45
68
|
const allSchemas = reader.getSchemas();
|
|
46
69
|
for (const [name, schema] of Object.entries(allSchemas)) {
|
|
47
70
|
// Project-owned only — packages emit their own services in the
|
|
@@ -89,15 +112,29 @@ export function generateServices(reader, config) {
|
|
|
89
112
|
candidates[name] = schema;
|
|
90
113
|
}
|
|
91
114
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
115
|
+
return candidates;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Returns the set of `*ServiceBase.php` filenames the current schema set
|
|
119
|
+
* is expected to produce. The cli.ts orphan-cleanup compares this set
|
|
120
|
+
* against the actual files in the base-services directory and deletes
|
|
121
|
+
* any mismatches — handles the case where a schema is renamed, removed,
|
|
122
|
+
* flipped from `kind: object` to `kind: pivot`, or opted out via
|
|
123
|
+
* `options.service: false`.
|
|
124
|
+
*
|
|
125
|
+
* Filename format mirrors `generateBaseService`: `{PascalSchemaName}ServiceBase.php`.
|
|
126
|
+
*
|
|
127
|
+
* Editable `*Service.php` files are NOT in scope — those are user-owned
|
|
128
|
+
* (sinh-1-lần userFile semantics) and may contain hand-written code; the
|
|
129
|
+
* cli prints a warning instead of deleting them.
|
|
130
|
+
*/
|
|
131
|
+
export function expectedBaseServiceFileNames(reader) {
|
|
132
|
+
const candidates = collectServiceCandidates(reader);
|
|
133
|
+
const out = new Set();
|
|
134
|
+
for (const name of Object.keys(candidates)) {
|
|
135
|
+
out.add(`${toPascalCase(name)}ServiceBase.php`);
|
|
99
136
|
}
|
|
100
|
-
return
|
|
137
|
+
return out;
|
|
101
138
|
}
|
|
102
139
|
/**
|
|
103
140
|
* #81 + #80 (v3.23.1): detect "user wants a service for this schema" signal
|