@ogygie/generate 0.24.0 → 0.25.1
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/map-entry.d.ts +32 -0
- package/dist/cli/run.js +127 -46
- package/package.json +3 -3
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type OgygieError } from "@ogygie/core/errors";
|
|
2
|
+
import { type GenerateProject } from "./project.js";
|
|
3
|
+
export interface ScreenMapEntry {
|
|
4
|
+
name: string;
|
|
5
|
+
intent: string;
|
|
6
|
+
app: string;
|
|
7
|
+
folder: string;
|
|
8
|
+
spec: string;
|
|
9
|
+
routes: never[];
|
|
10
|
+
dbTables: never[];
|
|
11
|
+
dependsOn: string[];
|
|
12
|
+
status: "skeleton" | "in-progress";
|
|
13
|
+
}
|
|
14
|
+
/** The features folder of an app, as its sibling features are laid out (`src/features` when none says otherwise). */
|
|
15
|
+
export declare function featureFolderOfApp(project: GenerateProject, appName: string, featureId: string): string;
|
|
16
|
+
export type ScreenEntryOutcome = {
|
|
17
|
+
ok: true;
|
|
18
|
+
entry: ScreenMapEntry;
|
|
19
|
+
} | {
|
|
20
|
+
ok: false;
|
|
21
|
+
problem: OgygieError;
|
|
22
|
+
} | {
|
|
23
|
+
ok: false;
|
|
24
|
+
problem: null;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* The entry a screen spec off the map would get — or `{ ok: false, problem: null }` when the spec is not a screen's
|
|
28
|
+
* (nothing to add: the feature pipeline says what is missing), or a refusal when the front app cannot be told.
|
|
29
|
+
*/
|
|
30
|
+
export declare function screenMapEntryOf(project: GenerateProject, featureId: string, markdown: string, specPath: string): ScreenEntryOutcome;
|
|
31
|
+
/** Adds the entry to the map on disk when the spec is a screen off the map; says what it did. Null entry: nothing to add. */
|
|
32
|
+
export declare function ensureScreenOnMap(project: GenerateProject, featureId: string, log: (line: string) => void): ScreenEntryOutcome | null;
|
package/dist/cli/run.js
CHANGED
|
@@ -71,7 +71,10 @@ function printReport(specPath, report, out) {
|
|
|
71
71
|
}
|
|
72
72
|
async function runCheckCommand(project, featureId, out) {
|
|
73
73
|
const specPaths = [];
|
|
74
|
-
|
|
74
|
+
const offMapSpec = featureId === void 0 ? void 0 : `${project.specsFolder.replace(/\/+$/u, "")}/${featureId}.spec.md`;
|
|
75
|
+
if (featureId !== void 0 && offMapSpec !== void 0 && !project.manifest.features.some((feature) => feature.name === featureId) && existsSync(join(project.root, offMapSpec))) {
|
|
76
|
+
specPaths.push(offMapSpec);
|
|
77
|
+
} else if (featureId !== void 0) {
|
|
75
78
|
const target = targetKindOf(project, featureId) === "screen" ? screenTargetOf(project, featureId) : featureTargetOf(project, featureId);
|
|
76
79
|
if (!target.ok) {
|
|
77
80
|
out.error(formatProblem(target.problem));
|
|
@@ -101,8 +104,8 @@ async function runCheckCommand(project, featureId, out) {
|
|
|
101
104
|
}
|
|
102
105
|
|
|
103
106
|
// src/cli/generate-command.ts
|
|
104
|
-
import { existsSync as
|
|
105
|
-
import { dirname, join as
|
|
107
|
+
import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync3, readdirSync as readdirSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
108
|
+
import { dirname, join as join3 } from "node:path";
|
|
106
109
|
import { performance } from "node:perf_hooks";
|
|
107
110
|
|
|
108
111
|
// ../ogygie-core/src/spec.schema.ts
|
|
@@ -240,6 +243,69 @@ function compositionOfSpec(content) {
|
|
|
240
243
|
return COMPOSITION_LINE.exec(section)?.[1] ?? null;
|
|
241
244
|
}
|
|
242
245
|
|
|
246
|
+
// src/cli/map-entry.ts
|
|
247
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "node:fs";
|
|
248
|
+
import { join as join2 } from "node:path";
|
|
249
|
+
function featureFolderOfApp(project, appName, featureId) {
|
|
250
|
+
const parents = project.manifest.features.filter((feature) => feature.app === appName).map((feature) => feature.folder.split("/").slice(0, -1).join("/")).filter((parent) => parent !== "");
|
|
251
|
+
const counts = /* @__PURE__ */ new Map();
|
|
252
|
+
for (const parent of parents) counts.set(parent, (counts.get(parent) ?? 0) + 1);
|
|
253
|
+
const common = [...counts.entries()].sort((left, right) => right[1] - left[1])[0]?.[0] ?? "src/features";
|
|
254
|
+
return `${common}/${featureId}`;
|
|
255
|
+
}
|
|
256
|
+
function screenMapEntryOf(project, featureId, markdown, specPath) {
|
|
257
|
+
const first = checkSpec({ markdown, specPath, generation: project.generation });
|
|
258
|
+
if (first.kind !== "screen" || first.screen === null) return { ok: false, problem: null };
|
|
259
|
+
const frontApps = project.manifest.apps.filter((app) => app.type === "front");
|
|
260
|
+
const point = first.screen.mounts === void 0 ? void 0 : (project.generation?.front?.extensionPoints ?? []).find((candidate) => candidate.id === first.screen?.mounts?.point);
|
|
261
|
+
const appName = point?.app ?? (frontApps.length === 1 ? frontApps[0]?.name : void 0);
|
|
262
|
+
if (appName === void 0) {
|
|
263
|
+
return {
|
|
264
|
+
ok: false,
|
|
265
|
+
problem: ogygieError({
|
|
266
|
+
code: "OGYGIE_GENERATE_PROJECT_INVALID",
|
|
267
|
+
message: `The screen "${featureId}" is not on the map and the map has ${frontApps.length} front apps: nothing says which one carries it.`,
|
|
268
|
+
file: MANIFEST_FILE,
|
|
269
|
+
cause: `no features[] entry is named "${featureId}" and the front app cannot be told (${frontApps.map((app) => app.name).join(", ") || "none"})`,
|
|
270
|
+
suggestedFix: "Declare the entry on the map with its `app`, or mount the screen on an extension point that names its app.",
|
|
271
|
+
details: { featureId, frontApps: frontApps.map((app) => app.name) }
|
|
272
|
+
})
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
const frontmatter = parseSpecDocument(markdown).frontmatter;
|
|
276
|
+
const known = new Set(project.manifest.features.map((feature) => feature.name));
|
|
277
|
+
const dependsOn = (frontmatter?.depends_on ?? []).filter((dependency) => known.has(dependency));
|
|
278
|
+
return {
|
|
279
|
+
ok: true,
|
|
280
|
+
entry: {
|
|
281
|
+
name: featureId,
|
|
282
|
+
intent: intentForManifest(intentOfSpec(markdown), specPath),
|
|
283
|
+
app: appName,
|
|
284
|
+
folder: featureFolderOfApp(project, appName, featureId),
|
|
285
|
+
spec: specPath,
|
|
286
|
+
routes: [],
|
|
287
|
+
dbTables: [],
|
|
288
|
+
dependsOn,
|
|
289
|
+
status: entryStatusForSpec(frontmatter?.status)
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
function ensureScreenOnMap(project, featureId, log) {
|
|
294
|
+
if (project.manifest.features.some((feature) => feature.name === featureId)) return null;
|
|
295
|
+
const specPath = `${project.specsFolder.replace(/\/+$/u, "")}/${featureId}.spec.md`;
|
|
296
|
+
const absolute = join2(project.root, specPath);
|
|
297
|
+
if (!existsSync2(absolute)) return null;
|
|
298
|
+
const outcome = screenMapEntryOf(project, featureId, readFileSync2(absolute, "utf8"), specPath);
|
|
299
|
+
if (!outcome.ok) return outcome;
|
|
300
|
+
const raw = project.rawManifest;
|
|
301
|
+
const features = Array.isArray(raw["features"]) ? raw["features"] : [];
|
|
302
|
+
raw["features"] = features;
|
|
303
|
+
features.push(outcome.entry);
|
|
304
|
+
writeFileSync(join2(project.root, MANIFEST_FILE), serializeManifest(raw), "utf8");
|
|
305
|
+
log(` ajout\xE9e ${MANIFEST_FILE} \u203A features[${featureId}] (\xE9cran hors carte : app ${outcome.entry.app}, dossier ${outcome.entry.folder}, status ${outcome.entry.status}${outcome.entry.status === "skeleton" ? " tant que la spec est draft" : ""})`);
|
|
306
|
+
return outcome;
|
|
307
|
+
}
|
|
308
|
+
|
|
243
309
|
// src/cli/generate-command.ts
|
|
244
310
|
function generatedFeatureIdsOf(project) {
|
|
245
311
|
const kinds = [];
|
|
@@ -247,9 +313,9 @@ function generatedFeatureIdsOf(project) {
|
|
|
247
313
|
if (feature.spec === null) continue;
|
|
248
314
|
const kind = targetKindOf(project, feature.name);
|
|
249
315
|
if (kind === null) continue;
|
|
250
|
-
const specPath =
|
|
251
|
-
if (!
|
|
252
|
-
const first = checkSpec({ markdown:
|
|
316
|
+
const specPath = join3(project.root, feature.spec);
|
|
317
|
+
if (!existsSync3(specPath)) continue;
|
|
318
|
+
const first = checkSpec({ markdown: readFileSync3(specPath, "utf8"), specPath: feature.spec, generation: project.generation });
|
|
253
319
|
if (kind === "feature" ? first.block !== null : first.screen !== null) kinds.push({ id: feature.name, kind });
|
|
254
320
|
}
|
|
255
321
|
return [...kinds.filter((entry) => entry.kind === "feature"), ...kinds.filter((entry) => entry.kind === "screen")].map((entry) => entry.id);
|
|
@@ -286,6 +352,21 @@ async function runGenerateFeatureCommand(project, featureId, out) {
|
|
|
286
352
|
out.error("usage: ogygie generate generate <feature-id> | --all");
|
|
287
353
|
return 1;
|
|
288
354
|
}
|
|
355
|
+
const joined = ensureScreenOnMap(project, featureId, (line) => out.log(line));
|
|
356
|
+
if (joined !== null) {
|
|
357
|
+
if (!joined.ok && joined.problem !== null) {
|
|
358
|
+
out.error(formatProblem(joined.problem));
|
|
359
|
+
return 1;
|
|
360
|
+
}
|
|
361
|
+
if (joined.ok) {
|
|
362
|
+
const reloaded = loadGenerateProject(project.root);
|
|
363
|
+
if (!reloaded.ok) {
|
|
364
|
+
for (const problem of reloaded.problems) out.error(formatProblem(problem));
|
|
365
|
+
return 1;
|
|
366
|
+
}
|
|
367
|
+
return runGenerateScreenCommand(reloaded.project, featureId, out);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
289
370
|
if (targetKindOf(project, featureId) === "screen") return runGenerateScreenCommand(project, featureId, out);
|
|
290
371
|
const lookup = featureTargetOf(project, featureId);
|
|
291
372
|
if (!lookup.ok) {
|
|
@@ -293,7 +374,7 @@ async function runGenerateFeatureCommand(project, featureId, out) {
|
|
|
293
374
|
return 1;
|
|
294
375
|
}
|
|
295
376
|
const { target } = lookup;
|
|
296
|
-
const markdown =
|
|
377
|
+
const markdown = readFileSync3(join3(project.root, target.specPath), "utf8");
|
|
297
378
|
const firstPass = checkSpec({ markdown, specPath: target.specPath, generation: target.generation });
|
|
298
379
|
if (firstPass.block === null) {
|
|
299
380
|
for (const problem of firstPass.problems) out.error(formatProblem(problem));
|
|
@@ -313,8 +394,8 @@ async function runGenerateFeatureCommand(project, featureId, out) {
|
|
|
313
394
|
return 1;
|
|
314
395
|
}
|
|
315
396
|
const started = performance.now();
|
|
316
|
-
const migrationsDir =
|
|
317
|
-
const existingMigrations =
|
|
397
|
+
const migrationsDir = join3(project.root, target.appPath, target.generation.target.migrations);
|
|
398
|
+
const existingMigrations = existsSync3(migrationsDir) ? readdirSync2(migrationsDir) : [];
|
|
318
399
|
const plan = buildFeaturePlan({
|
|
319
400
|
featureId,
|
|
320
401
|
folder: target.folder,
|
|
@@ -328,10 +409,10 @@ async function runGenerateFeatureCommand(project, featureId, out) {
|
|
|
328
409
|
generatorVersion: GENERATOR_VERSION,
|
|
329
410
|
behaviors: boundBehaviorsOf(report.behaviors)
|
|
330
411
|
});
|
|
331
|
-
const logicPath =
|
|
332
|
-
const existingLogic =
|
|
333
|
-
const migrationPath =
|
|
334
|
-
const existingMigration =
|
|
412
|
+
const logicPath = join3(project.root, target.folder, "logic.ts");
|
|
413
|
+
const existingLogic = existsSync3(logicPath) ? readFileSync3(logicPath, "utf8") : null;
|
|
414
|
+
const migrationPath = join3(project.root, plan.migrationPath);
|
|
415
|
+
const existingMigration = existsSync3(migrationPath) ? readFileSync3(migrationPath, "utf8") : null;
|
|
335
416
|
const files = generateFeature(plan, { existingLogic, existingMigration, library: loadHolesLibrary() });
|
|
336
417
|
const manifestEntry = applyManifestEntry(project.rawManifest, plan, intentOfSpec(markdown), entryStatusForSpec(parseSpecDocument(markdown).frontmatter?.status));
|
|
337
418
|
const specContracts = applySpecContracts(markdown, plan);
|
|
@@ -339,22 +420,22 @@ async function runGenerateFeatureCommand(project, featureId, out) {
|
|
|
339
420
|
let written = 0;
|
|
340
421
|
let emitted = 0;
|
|
341
422
|
for (const file of files) {
|
|
342
|
-
const absolute =
|
|
343
|
-
const exists =
|
|
423
|
+
const absolute = join3(project.root, file.path);
|
|
424
|
+
const exists = existsSync3(absolute);
|
|
344
425
|
emitted += file.content.split("\n").length - 1;
|
|
345
|
-
if (exists &&
|
|
426
|
+
if (exists && readFileSync3(absolute, "utf8") === file.content) {
|
|
346
427
|
out.log(` identique ${file.path}`);
|
|
347
428
|
continue;
|
|
348
429
|
}
|
|
349
430
|
mkdirSync(dirname(absolute), { recursive: true });
|
|
350
|
-
|
|
431
|
+
writeFileSync2(absolute, file.content, "utf8");
|
|
351
432
|
written += 1;
|
|
352
433
|
out.log(` ${exists ? "r\xE9\xE9crit " : "\xE9crit "} ${file.path}`);
|
|
353
434
|
}
|
|
354
435
|
if (manifestEntry.outcome === "unchanged") {
|
|
355
436
|
out.log(` identique ${MANIFEST_FILE} \u203A features[${featureId}].routes, dbTables`);
|
|
356
437
|
} else {
|
|
357
|
-
|
|
438
|
+
writeFileSync2(join3(project.root, MANIFEST_FILE), serializeManifest(manifestEntry.manifest), "utf8");
|
|
358
439
|
out.log(
|
|
359
440
|
` ${manifestEntry.outcome === "created" ? "ajout\xE9e " : "r\xE9\xE9crite "} ${MANIFEST_FILE} \u203A features[${featureId}] (${plan.operations.length} route(s), ${1 + plan.slots.length + plan.references.filter((reference) => reference.table !== null).length} table(s)${manifestEntry.outcome === "created" ? `, status ${entryStatusForSpec(parseSpecDocument(markdown).frontmatter?.status)} : \xE0 vous de dire delivered` : ""})`
|
|
360
441
|
);
|
|
@@ -367,7 +448,7 @@ async function runGenerateFeatureCommand(project, featureId, out) {
|
|
|
367
448
|
out.log(` identique ${target.specPath} \u203A bloc @generated de # Contrats de donn\xE9es`);
|
|
368
449
|
} else {
|
|
369
450
|
const addedLines = specContracts.markdown.split("\n").length - markdown.split("\n").length;
|
|
370
|
-
|
|
451
|
+
writeFileSync2(join3(project.root, target.specPath), specContracts.markdown, "utf8");
|
|
371
452
|
out.log(
|
|
372
453
|
` ${specContracts.outcome === "inserted" ? "ins\xE9r\xE9 " : "r\xE9\xE9crit "} ${target.specPath} \u203A bloc @generated de # Contrats de donn\xE9es${specContracts.outcome === "inserted" ? ` (+${addedLines} lignes)` : ""}`
|
|
373
454
|
);
|
|
@@ -380,8 +461,8 @@ async function runGenerateFeatureCommand(project, featureId, out) {
|
|
|
380
461
|
|
|
381
462
|
// src/cli/preview-command.ts
|
|
382
463
|
import { spawnSync } from "node:child_process";
|
|
383
|
-
import { existsSync as
|
|
384
|
-
import { join as
|
|
464
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, readdirSync as readdirSync3 } from "node:fs";
|
|
465
|
+
import { join as join4 } from "node:path";
|
|
385
466
|
var PREVIEWS_ROOT = "previews";
|
|
386
467
|
function previewsDirOf(featureId, composition) {
|
|
387
468
|
return composition === null ? `${PREVIEWS_ROOT}/${featureId}` : `${PREVIEWS_ROOT}/${featureId}/${composition}`;
|
|
@@ -412,29 +493,29 @@ async function runPreviewCommand(project, featureId, out, runner = vitestPreview
|
|
|
412
493
|
return 0;
|
|
413
494
|
}
|
|
414
495
|
const testPath = `${target.folder}/${pascalCaseOf(featureId)}Screen${PREVIEWS_FILE_SUFFIX}`;
|
|
415
|
-
if (!
|
|
496
|
+
if (!existsSync4(join4(project.root, testPath))) {
|
|
416
497
|
out.error(`ogygie generate preview \u2014 ${testPath} n'existe pas : lancez \`ogygie generate generate ${featureId}\` d'abord, le test des aper\xE7us est \xE9mis avec l'\xE9cran.`);
|
|
417
498
|
return 1;
|
|
418
499
|
}
|
|
419
|
-
const specAbsolute =
|
|
420
|
-
const composition =
|
|
500
|
+
const specAbsolute = join4(project.root, target.specPath);
|
|
501
|
+
const composition = existsSync4(specAbsolute) ? compositionOfSpec(readFileSync4(specAbsolute, "utf8")) : null;
|
|
421
502
|
const relativeDir = previewsDirOf(featureId, composition);
|
|
422
|
-
const outDir =
|
|
503
|
+
const outDir = join4(project.root, relativeDir);
|
|
423
504
|
const run = runner({ projectRoot: project.root, testPath, outDir });
|
|
424
505
|
for (const line of run.lines) out.log(` ${line}`);
|
|
425
506
|
if (run.code !== 0) {
|
|
426
507
|
out.error(`ogygie generate preview \u2014 le rendu des aper\xE7us de ${featureId} a \xE9chou\xE9 (code ${run.code}) : rien n'est publi\xE9 tant qu'un \xE9tat ne rend pas.`);
|
|
427
508
|
return run.code;
|
|
428
509
|
}
|
|
429
|
-
const pages =
|
|
510
|
+
const pages = existsSync4(outDir) ? readdirSync3(outDir).filter((name) => name.endsWith(".html")).sort() : [];
|
|
430
511
|
out.log(`ogygie generate preview \u2014 ${featureId} : ${pages.length} page(s) dans ${relativeDir}/ (${pages.map((name) => name.replace(/\.html$/u, "")).join(", ")})${composition === null ? "" : ` \u2014 composition ${composition}`}.`);
|
|
431
512
|
return pages.length === 0 ? 1 : 0;
|
|
432
513
|
}
|
|
433
514
|
|
|
434
515
|
// src/cli/schemas-command.ts
|
|
435
516
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
436
|
-
import { appendFileSync, existsSync as
|
|
437
|
-
import { dirname as dirname2, join as
|
|
517
|
+
import { appendFileSync, existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync5, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
518
|
+
import { dirname as dirname2, join as join5 } from "node:path";
|
|
438
519
|
import { pathToFileURL } from "node:url";
|
|
439
520
|
|
|
440
521
|
// src/schemas/draft.ts
|
|
@@ -653,9 +734,9 @@ function featureFolderFor(project, featureId) {
|
|
|
653
734
|
return `${common}/${featureId}`;
|
|
654
735
|
}
|
|
655
736
|
function availableGateScripts(projectRoot) {
|
|
656
|
-
const path =
|
|
657
|
-
if (!
|
|
658
|
-
const scripts = JSON.parse(
|
|
737
|
+
const path = join5(projectRoot, "package.json");
|
|
738
|
+
if (!existsSync5(path)) return [];
|
|
739
|
+
const scripts = JSON.parse(readFileSync5(path, "utf8")).scripts ?? {};
|
|
659
740
|
return SCHEMAS_GATE_SCRIPTS.filter((script) => script in scripts);
|
|
660
741
|
}
|
|
661
742
|
async function runSchemasCommand(project, featureId, argv, out, deps = {}) {
|
|
@@ -665,8 +746,8 @@ async function runSchemasCommand(project, featureId, argv, out, deps = {}) {
|
|
|
665
746
|
}
|
|
666
747
|
const onMap = project.manifest.features.find((feature) => feature.name === featureId);
|
|
667
748
|
const specPath = onMap?.spec ?? `${project.specsFolder.replace(/\/+$/u, "")}/${featureId}.spec.md`;
|
|
668
|
-
const specAbsolute =
|
|
669
|
-
if (!
|
|
749
|
+
const specAbsolute = join5(project.root, specPath);
|
|
750
|
+
if (!existsSync5(specAbsolute)) {
|
|
670
751
|
out.error(formatProblem(ogygieError({ code: "OGYGIE_GENERATE_PROJECT_INVALID", message: `No spec carries the feature "${featureId}".`, file: specPath, cause: `${specPath} does not exist`, suggestedFix: "Write the spec first: a schemas module derives from a spec, never the reverse." })));
|
|
671
752
|
return 1;
|
|
672
753
|
}
|
|
@@ -674,7 +755,7 @@ async function runSchemasCommand(project, featureId, argv, out, deps = {}) {
|
|
|
674
755
|
out.error(formatProblem(ogygieError({ code: "OGYGIE_GENERATE_PROJECT_INVALID", message: `The feature "${featureId}" cannot be generated from this map.`, file: MANIFEST_FILE, cause: project.apiApp === void 0 ? "apps[] has no entry with type=api" : "the manifest declares no generation block", suggestedFix: "Declare the api app and the `generation` block in the manifest." })));
|
|
675
756
|
return 1;
|
|
676
757
|
}
|
|
677
|
-
const markdown =
|
|
758
|
+
const markdown = readFileSync5(specAbsolute, "utf8");
|
|
678
759
|
const first = checkSpec({ markdown, specPath, generation: project.generation });
|
|
679
760
|
if (first.kind !== "feature" || first.block === null) {
|
|
680
761
|
out.log(`ogygie generate schemas \u2014 ${specPath} ${first.kind === "screen" ? "est un \xE9cran : ses contrats sont ceux des features qu'il consomme" : "n'a pas de bloc # G\xE9n\xE9ration de feature"}, rien \xE0 \xE9crire.`);
|
|
@@ -683,8 +764,8 @@ async function runSchemasCommand(project, featureId, argv, out, deps = {}) {
|
|
|
683
764
|
const block = first.block;
|
|
684
765
|
const folder = onMap === void 0 ? `${project.apiApp.path}/${featureFolderFor(project, featureId)}` : `${project.apiApp.path}/${onMap.folder}`;
|
|
685
766
|
const modulePath = `${folder}/${block.contracts.module.replace(/^\.\//u, "")}`;
|
|
686
|
-
const moduleAbsolute =
|
|
687
|
-
if (
|
|
767
|
+
const moduleAbsolute = join5(project.root, modulePath);
|
|
768
|
+
if (existsSync5(moduleAbsolute)) {
|
|
688
769
|
out.log(`ogygie generate schemas \u2014 ${modulePath} existe : il appartient au repo, jamais r\xE9g\xE9n\xE9r\xE9 (D52).`);
|
|
689
770
|
return 0;
|
|
690
771
|
}
|
|
@@ -711,7 +792,7 @@ async function runSchemasCommand(project, featureId, argv, out, deps = {}) {
|
|
|
711
792
|
},
|
|
712
793
|
async judge(source, attempt) {
|
|
713
794
|
mkdirSync2(dirname2(moduleAbsolute), { recursive: true });
|
|
714
|
-
|
|
795
|
+
writeFileSync3(moduleAbsolute, source, "utf8");
|
|
715
796
|
let moduleExports;
|
|
716
797
|
try {
|
|
717
798
|
moduleExports = await importProductModule(`${pathToFileURL(moduleAbsolute).href}?attempt=${attempt}`);
|
|
@@ -772,7 +853,7 @@ ${rows.join("\n")}
|
|
|
772
853
|
raw["features"] = features;
|
|
773
854
|
const status = entryStatusForSpec(parseSpecDocument(markdown).frontmatter?.status);
|
|
774
855
|
features.push({ name: featureId, intent: intentForManifest(intentOfSpec(markdown), specPath), folder: featureFolderFor(project, featureId), spec: specPath, routes: [], dbTables: [], dependsOn: [], status });
|
|
775
|
-
|
|
856
|
+
writeFileSync3(join5(project.root, MANIFEST_FILE), serializeManifest(raw), "utf8");
|
|
776
857
|
out.log(` ajout\xE9e ${MANIFEST_FILE} \u203A features[${featureId}] (routes et tables viendront de \`generate\`, status ${status}${status === "skeleton" ? " tant que la spec est draft" : ""})`);
|
|
777
858
|
}
|
|
778
859
|
out.log(`ogygie generate schemas \u2014 ${featureId} : module \xE9crit en ${result.attempts} tentative(s) par ${result.model ?? "?"}, ${result.usage.length} appel(s) de mod\xE8le (${totalInput} jetons d'entr\xE9e, ${totalOutput} de sortie) ; commit\xE9 une fois, jamais r\xE9g\xE9n\xE9r\xE9 (D52).`);
|
|
@@ -784,7 +865,7 @@ import { performance as performance2 } from "node:perf_hooks";
|
|
|
784
865
|
|
|
785
866
|
// ../ogygie-core/src/repo-adapter.ts
|
|
786
867
|
import { readFile as readFileFromDisk, readdir, realpath, stat } from "node:fs/promises";
|
|
787
|
-
import { isAbsolute, join as
|
|
868
|
+
import { isAbsolute, join as join6, resolve, sep } from "node:path";
|
|
788
869
|
import { z as z2 } from "zod";
|
|
789
870
|
var RepoFileEntrySchema = z2.object({
|
|
790
871
|
/** Path relative to the LISTED directory, "/" separated, never absolute. */
|
|
@@ -846,16 +927,16 @@ async function listFilesRecursively(absoluteDir, prefix) {
|
|
|
846
927
|
const relativePath = prefix === "" ? entry.name : `${prefix}/${entry.name}`;
|
|
847
928
|
if (entry.isDirectory()) {
|
|
848
929
|
if (isSkippedDirectory(entry.name)) continue;
|
|
849
|
-
files.push(...await listFilesRecursively(
|
|
930
|
+
files.push(...await listFilesRecursively(join6(absoluteDir, entry.name), relativePath));
|
|
850
931
|
} else if (entry.isFile()) {
|
|
851
|
-
const stats = await stat(
|
|
932
|
+
const stats = await stat(join6(absoluteDir, entry.name));
|
|
852
933
|
files.push({ path: relativePath, sizeBytes: stats.size });
|
|
853
934
|
}
|
|
854
935
|
}
|
|
855
936
|
return files.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
856
937
|
}
|
|
857
938
|
async function resolveGitDirs(rootDir) {
|
|
858
|
-
const dotGit =
|
|
939
|
+
const dotGit = join6(rootDir, ".git");
|
|
859
940
|
let gitDir;
|
|
860
941
|
try {
|
|
861
942
|
const stats = await stat(dotGit);
|
|
@@ -871,7 +952,7 @@ async function resolveGitDirs(rootDir) {
|
|
|
871
952
|
return null;
|
|
872
953
|
}
|
|
873
954
|
try {
|
|
874
|
-
const commonPointer = (await readFileFromDisk(
|
|
955
|
+
const commonPointer = (await readFileFromDisk(join6(gitDir, "commondir"), "utf8")).trim();
|
|
875
956
|
return { gitDir, commonDir: isAbsolute(commonPointer) ? commonPointer : resolve(gitDir, commonPointer) };
|
|
876
957
|
} catch {
|
|
877
958
|
return { gitDir, commonDir: gitDir };
|
|
@@ -880,13 +961,13 @@ async function resolveGitDirs(rootDir) {
|
|
|
880
961
|
async function readRefSha(dirs, refName) {
|
|
881
962
|
for (const base of [dirs.gitDir, dirs.commonDir]) {
|
|
882
963
|
try {
|
|
883
|
-
const sha = (await readFileFromDisk(
|
|
964
|
+
const sha = (await readFileFromDisk(join6(base, refName), "utf8")).trim();
|
|
884
965
|
if (sha !== "") return sha;
|
|
885
966
|
} catch {
|
|
886
967
|
}
|
|
887
968
|
}
|
|
888
969
|
try {
|
|
889
|
-
const packed = await readFileFromDisk(
|
|
970
|
+
const packed = await readFileFromDisk(join6(dirs.commonDir, "packed-refs"), "utf8");
|
|
890
971
|
for (const line of packed.split("\n")) {
|
|
891
972
|
if (line.startsWith("#") || line.startsWith("^")) continue;
|
|
892
973
|
const [sha, name] = line.trim().split(" ");
|
|
@@ -934,7 +1015,7 @@ function createFilesystemRepoAdapter(rootDir) {
|
|
|
934
1015
|
if (dirs === null) return null;
|
|
935
1016
|
let head;
|
|
936
1017
|
try {
|
|
937
|
-
head = (await readFileFromDisk(
|
|
1018
|
+
head = (await readFileFromDisk(join6(dirs.gitDir, "HEAD"), "utf8")).trim();
|
|
938
1019
|
} catch {
|
|
939
1020
|
return null;
|
|
940
1021
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ogygie/generate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.1",
|
|
4
4
|
"description": "Ogygie generator: reads the `# Génération` block of a spec and its schemas.ts, derives everything deterministic without a model (routes, pipeline, database, factories, tests, screens, previews), fills the typed holes of logic.ts by library, draw and escalation, and draws the first schemas.ts of a new feature — judged by the gates. Sixth package of the lockstep (D55).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
}
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@ogygie/core": "0.
|
|
32
|
+
"@ogygie/core": "0.25.1",
|
|
33
33
|
"tsx": "^4.20.0",
|
|
34
34
|
"yaml": "^2.9.0",
|
|
35
35
|
"zod": "^4.0.0"
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"@lingui/babel-plugin-lingui-macro": "^5.9.5",
|
|
39
39
|
"@lingui/core": "^5.9.5",
|
|
40
40
|
"@lingui/react": "^5.9.5",
|
|
41
|
-
"@ogygie/ui": "0.
|
|
41
|
+
"@ogygie/ui": "0.25.1",
|
|
42
42
|
"@starter/api": "0.1.0",
|
|
43
43
|
"@tanstack/react-query": "^5.102.2",
|
|
44
44
|
"@tanstack/react-router": "^1.170.32",
|