@omnifyjp/ts 6.8.0 → 7.0.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 CHANGED
@@ -32,8 +32,6 @@ import { generatePhp, derivePhpConfig } from './php/index.js';
32
32
  import { evaluateShadowingConflicts } from './php/shadowing-conflicts.js';
33
33
  import { hasEditableSubclassForBase, pruneDisabledScaffoldLayerBases, pruneOrphanServiceFilesIfEnabled } from './php/orphan-cleanup.js';
34
34
  import { findSiblingWithSameBasename, findAppRootAncestor } from './php/sibling-skip.js';
35
- import { runPintFormat } from './php/pint-format.js';
36
- import { runPrettierFormat } from './prettier-format.js';
37
35
  import { resolveInput, sniffInputKind } from './input-resolver.js';
38
36
  function resolveFromConfig(configPath) {
39
37
  const raw = readFileSync(configPath, 'utf-8');
@@ -195,7 +193,6 @@ program
195
193
  let tsCreated = 0;
196
194
  let tsOverwritten = 0;
197
195
  let tsSkipped = 0;
198
- const tsWrittenPaths = [];
199
196
  for (const file of files) {
200
197
  let filePath;
201
198
  if (file.category === 'enum' || file.category === 'plugin-enum') {
@@ -210,7 +207,6 @@ program
210
207
  continue;
211
208
  }
212
209
  writeFileSync(filePath, file.content, 'utf-8');
213
- tsWrittenPaths.push(filePath);
214
210
  if (file.overwrite || !existsSync(filePath)) {
215
211
  tsOverwritten++;
216
212
  }
@@ -223,22 +219,6 @@ program
223
219
  console.log(` ${tsCreated} files created (user-editable)`);
224
220
  if (tsSkipped > 0)
225
221
  console.log(` ${tsSkipped} files skipped (already exist)`);
226
- // Issue #211: format via the consumer project's Prettier (same idea as Pint for PHP).
227
- if (tsWrittenPaths.length > 0) {
228
- const prettierResult = runPrettierFormat(tsWrittenPaths);
229
- if (prettierResult.ran) {
230
- if (prettierResult.exitStatus === 0) {
231
- console.log(` Formatted ${prettierResult.formattedCount} TS file(s) via Prettier`);
232
- }
233
- else {
234
- console.warn(` [omnify] Prettier exited with status ${prettierResult.exitStatus}; generated files may not match project formatting preset.`);
235
- if (prettierResult.errorMessage) {
236
- const trimmed = prettierResult.errorMessage.trim().split('\n').slice(0, 5).join('\n');
237
- console.warn(` ${trimmed}`);
238
- }
239
- }
240
- }
241
- }
242
222
  }
243
223
  else {
244
224
  console.log(`\n[TypeScript] Skipped (disabled)`);
@@ -259,7 +239,6 @@ program
259
239
  let phpShadowedOutOfSync = 0;
260
240
  let phpShadowedAtRisk = 0;
261
241
  const skippedSiblingExamples = [];
262
- const writtenPaths = [];
263
242
  for (const file of phpFiles) {
264
243
  const filePath = resolve(configDir, file.path);
265
244
  mkdirSync(dirname(filePath), { recursive: true });
@@ -323,7 +302,6 @@ program
323
302
  }
324
303
  }
325
304
  writeFileSync(filePath, file.content, 'utf-8');
326
- writtenPaths.push(filePath);
327
305
  if (file.overwrite || !existsSync(filePath)) {
328
306
  phpOverwritten++;
329
307
  }
@@ -355,41 +333,6 @@ program
355
333
  console.log(` ... and ${phpSkippedSibling - skippedSiblingExamples.length} more`);
356
334
  }
357
335
  }
358
- // Issue #100 v5.8.20: auto-format every written PHP file via
359
- // Laravel Pint when it's installed in the project. Same preset
360
- // the project's CI already uses, so codegen output stops failing
361
- // `pint --test` and devs no longer need a manual sweep after
362
- // every `omnify generate`.
363
- //
364
- // NOT a silent no-op when Pint is missing any more. It used to be, and that cost a
365
- // consumer several investigation sessions: their CI regenerated without Pint on PATH,
366
- // wrote 927 files differing from the committed (Pint-formatted) tree, and the only
367
- // evidence was one ABSENT log line. It was read as a Linux/Darwin build divergence and
368
- // chased as far as "pick a canonical platform" before anyone ran the two commands that
369
- // settle it (godx-jp/godx-corebooks#108, reproduced exactly: hide `vendor/bin/pint`,
370
- // regenerate, 927 files; restore it, 0).
371
- //
372
- // The opt-out (`OMNIFY_NO_PINT=1`) stays quiet — that one is a choice, not a surprise.
373
- if (writtenPaths.length > 0) {
374
- const pintResult = runPintFormat(writtenPaths);
375
- if (pintResult.ran) {
376
- if (pintResult.exitStatus === 0) {
377
- console.log(` Formatted ${pintResult.formattedCount} PHP file(s) via Laravel Pint`);
378
- }
379
- else {
380
- console.warn(` [omnify] Laravel Pint exited with status ${pintResult.exitStatus}; generated files may not match project formatting preset.`);
381
- if (pintResult.errorMessage) {
382
- const trimmed = pintResult.errorMessage.trim().split('\n').slice(0, 5).join('\n');
383
- console.warn(` ${trimmed}`);
384
- }
385
- }
386
- }
387
- else if (pintResult.skipped === 'pint-not-found') {
388
- console.warn(` [omnify] Laravel Pint not found — ${writtenPaths.length} PHP file(s) written UNFORMATTED.`);
389
- console.warn(' If this project commits generated PHP, the output will differ from a tree formatted by Pint.');
390
- console.warn(' Run `composer install` before `omnify generate`, or set OMNIFY_NO_PINT=1 to silence this deliberately.');
391
- }
392
- }
393
336
  // Orphan cleanup: when a schema is removed, renamed, flipped from
394
337
  // kind:object → kind:pivot, or opted out via `options.service: false`,
395
338
  // its previously-emitted `*ServiceBase.php` becomes orphan and
@@ -208,7 +208,7 @@ function generateUserController(name, schema, config) {
208
208
  className: `${modelName}ControllerBase`,
209
209
  fqn: `${resolveModularBaseNamespace(config, name, 'Controllers', config.controllers.baseNamespace)}\\${modelName}ControllerBase`,
210
210
  }
211
- : resolveBaseClass(config.controllers, `${modelName}Controller`, 'Base');
211
+ : legacyGroupedBase(config, `${modelName}Controller`, schema.group);
212
212
  const editable = config.structure === 'modular'
213
213
  ? { className: `${modelName}Controller`, fileName: `${modelName}Controller.php`, namespace: config.controllers.namespace, path: config.controllers.path }
214
214
  : resolveEditableClass(config.controllers, modelName, 'Controller');
@@ -244,3 +244,13 @@ class ${editable.className} extends ${baseAlias}
244
244
  `;
245
245
  return userFile(`${userPath}/${editable.fileName}`, content);
246
246
  }
247
+ /**
248
+ * Legacy layout nests the base controller under its schema group (see
249
+ * `generateBaseController`), so the stub must import it from the same nested
250
+ * namespace — a flat import names a class that does not exist.
251
+ */
252
+ function legacyGroupedBase(config, className, group) {
253
+ const raw = resolveBaseClass(config.controllers, className, 'Base');
254
+ const { namespace } = nestByGroup({ path: '', namespace: raw.namespace }, group);
255
+ return { className: raw.className, fqn: `${namespace}\\${raw.className}` };
256
+ }
@@ -66,25 +66,26 @@ function collectModularLayerDirs(configDir, config, category) {
66
66
  return dirs;
67
67
  }
68
68
  const BASE_SUBDIRS_SKIP = new Set(['Base', 'OmnifyBase']);
69
- function findEditableSubclassFile(roots, fileName) {
69
+ function findEditableSubclassFile(roots, fileName, excludeAbsPath) {
70
70
  for (const root of roots) {
71
- if (findFileByName(root, fileName))
71
+ const found = findFileAbsolutePath(root, fileName);
72
+ if (found && found !== excludeAbsPath)
72
73
  return true;
73
74
  }
74
75
  return false;
75
76
  }
76
- function findFileByName(dir, fileName) {
77
+ function findFileAbsolutePath(dir, fileName) {
77
78
  if (!existsSync(dir))
78
- return false;
79
+ return null;
79
80
  let info;
80
81
  try {
81
82
  info = statSync(dir);
82
83
  }
83
84
  catch {
84
- return false;
85
+ return null;
85
86
  }
86
87
  if (!info.isDirectory()) {
87
- return basename(dir) === fileName;
88
+ return basename(dir) === fileName ? resolve(dir) : null;
88
89
  }
89
90
  for (const entry of readdirSync(dir)) {
90
91
  if (BASE_SUBDIRS_SKIP.has(entry))
@@ -97,14 +98,15 @@ function findFileByName(dir, fileName) {
97
98
  continue;
98
99
  }
99
100
  if (info.isDirectory()) {
100
- if (findFileByName(full, fileName))
101
- return true;
101
+ const nested = findFileAbsolutePath(full, fileName);
102
+ if (nested)
103
+ return nested;
102
104
  }
103
105
  else if (entry === fileName) {
104
- return true;
106
+ return resolve(full);
105
107
  }
106
108
  }
107
- return false;
109
+ return null;
108
110
  }
109
111
  function collectEditableSearchRoots(configDir, config, layer, modularCategory, baseAbsPath) {
110
112
  const roots = [];
@@ -151,7 +153,7 @@ function pruneDisabledLayerBasesInRoots(configDir, config, spec, roots, expected
151
153
  const editableName = spec.toEditableFileName(entry);
152
154
  if (editableName) {
153
155
  const editableRoots = collectEditableSearchRoots(configDir, config, spec.layerConfig, spec.modularCategory, fullPath);
154
- if (findEditableSubclassFile(editableRoots, editableName)) {
156
+ if (findEditableSubclassFile(editableRoots, editableName, fullPath)) {
155
157
  kept += 1;
156
158
  continue;
157
159
  }
@@ -210,12 +212,46 @@ export function hasEditableSubclassForBase(configDir, config, file) {
210
212
  if (!editableName)
211
213
  return false;
212
214
  const roots = collectEditableSearchRoots(configDir, config, spec.layerConfig, spec.modularCategory, resolve(configDir, file.path));
213
- return findEditableSubclassFile(roots, editableName);
215
+ return findEditableSubclassFile(roots, editableName, resolve(configDir, file.path));
214
216
  }
215
217
  return false;
216
218
  }
219
+ const SCAFFOLD_LAYER_BASE_SUFFIX = {
220
+ request: 'RequestBase.php',
221
+ resource: 'ResourceBase.php',
222
+ policy: 'PolicyBase.php',
223
+ controller: 'ControllerBase.php',
224
+ service: 'ServiceBase.php',
225
+ };
226
+ const SCAFFOLD_LAYER_EDITABLE_SUFFIX = {
227
+ request: 'Request.php',
228
+ resource: 'Resource.php',
229
+ policy: 'Policy.php',
230
+ controller: 'Controller.php',
231
+ service: 'Service.php',
232
+ };
233
+ /**
234
+ * Legacy `flatBase` puts regenerated bases directly at `layer.basePath` with
235
+ * the same filename as the user-editable stub (different directory). That
236
+ * directory is generated-only: resolveBaseClass() always targets basePath and
237
+ * only per-schema base classes are written there (issue #96).
238
+ */
239
+ function disabledLayerFileMatchers(config, layerConfig, baseSuffix, editableSuffix) {
240
+ const modularMatches = (name) => name.endsWith(baseSuffix);
241
+ const modularToEditable = (name) => baseToEditableFileName(name, baseSuffix, editableSuffix);
242
+ if (config.structure === 'modular') {
243
+ return { matchesBaseFile: modularMatches, toEditableFileName: modularToEditable };
244
+ }
245
+ if (layerConfig.flatBase) {
246
+ return {
247
+ matchesBaseFile: (name) => name.endsWith('.php'),
248
+ toEditableFileName: (name) => (name.endsWith('.php') ? name : null),
249
+ };
250
+ }
251
+ return { matchesBaseFile: modularMatches, toEditableFileName: modularToEditable };
252
+ }
217
253
  function scaffoldLayerSpecs(config) {
218
- return [
254
+ const layers = [
219
255
  {
220
256
  layer: 'request',
221
257
  label: 'request base',
@@ -223,8 +259,6 @@ function scaffoldLayerSpecs(config) {
223
259
  layerConfig: config.requests,
224
260
  legacyBasePath: config.requests.basePath,
225
261
  modularCategory: 'Requests',
226
- matchesBaseFile: (name) => name.endsWith('RequestBase.php'),
227
- toEditableFileName: (name) => baseToEditableFileName(name, 'RequestBase.php', 'Request.php'),
228
262
  },
229
263
  {
230
264
  layer: 'resource',
@@ -233,8 +267,6 @@ function scaffoldLayerSpecs(config) {
233
267
  layerConfig: config.resources,
234
268
  legacyBasePath: config.resources.basePath,
235
269
  modularCategory: 'Resources',
236
- matchesBaseFile: (name) => name.endsWith('ResourceBase.php'),
237
- toEditableFileName: (name) => baseToEditableFileName(name, 'ResourceBase.php', 'Resource.php'),
238
270
  },
239
271
  {
240
272
  layer: 'policy',
@@ -243,8 +275,6 @@ function scaffoldLayerSpecs(config) {
243
275
  layerConfig: config.policies,
244
276
  legacyBasePath: config.policies.basePath,
245
277
  modularCategory: 'Policies',
246
- matchesBaseFile: (name) => name.endsWith('PolicyBase.php'),
247
- toEditableFileName: (name) => baseToEditableFileName(name, 'PolicyBase.php', 'Policy.php'),
248
278
  },
249
279
  {
250
280
  layer: 'controller',
@@ -253,8 +283,6 @@ function scaffoldLayerSpecs(config) {
253
283
  layerConfig: config.controllers,
254
284
  legacyBasePath: config.controllers.basePath,
255
285
  modularCategory: 'Controllers',
256
- matchesBaseFile: (name) => name.endsWith('ControllerBase.php'),
257
- toEditableFileName: (name) => baseToEditableFileName(name, 'ControllerBase.php', 'Controller.php'),
258
286
  },
259
287
  {
260
288
  layer: 'service',
@@ -263,10 +291,14 @@ function scaffoldLayerSpecs(config) {
263
291
  layerConfig: config.services,
264
292
  legacyBasePath: config.services.basePath,
265
293
  modularCategory: 'Services',
266
- matchesBaseFile: (name) => name.endsWith('ServiceBase.php'),
267
- toEditableFileName: (name) => baseToEditableFileName(name, 'ServiceBase.php', 'Service.php'),
268
294
  },
269
295
  ];
296
+ return layers.map((entry) => {
297
+ const baseSuffix = SCAFFOLD_LAYER_BASE_SUFFIX[entry.layer];
298
+ const editableSuffix = SCAFFOLD_LAYER_EDITABLE_SUFFIX[entry.layer];
299
+ const matchers = disabledLayerFileMatchers(config, entry.layerConfig, baseSuffix, editableSuffix);
300
+ return { ...entry, ...matchers };
301
+ });
270
302
  }
271
303
  /**
272
304
  * Delete orphan `*ServiceBase.php` files; flag orphan editable services
@@ -43,7 +43,8 @@ export function buildRelation(propName, property, modelNamespace, context) {
43
43
  const related = relation === 'MorphTo'
44
44
  ? '\\Illuminate\\Database\\Eloquent\\Model'
45
45
  : '\\' + modelNamespace + '\\' + target;
46
- method = method.replace(' */', ` * @return ${type}<${related}, $this>\n */`);
46
+ // Blank line before the tag: Pint's `phpdoc_separation` (laravel preset).
47
+ method = method.replace(' */', ` *\n * @return ${type}<${related}, $this>\n */`);
47
48
  return { method, imports };
48
49
  }
49
50
  /** Generate the table name Laravel's joiningTable() infers from model basenames. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnifyjp/ts",
3
- "version": "6.8.0",
3
+ "version": "7.0.0",
4
4
  "description": "TypeScript model type generator from Omnify schemas.json",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",