Curated, product-agnostic snapshot of the post-story-04 tree: demo content deleted, auth-only reference feature, web-next shell, all gates green. Product-specific docs, ADRs 027-029, PRDs/epics/archive, editor library traces, and product naming are curated out; generic template repairs (coverage provider devDeps, root test:coverage script, live lint fixes, root-only release-please) are kept. See TEMPLATE.md for provenance, curation list, and usage. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016j8z4VHjedXDTjEDNg7qHK
102 lines
3.5 KiB
TypeScript
102 lines
3.5 KiB
TypeScript
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
|
|
const MANIFEST_FILE = ".release-please-manifest.json";
|
|
const CONFIG_FILE = "release-please-config.json";
|
|
const INITIAL_VERSION = "0.1.0";
|
|
|
|
/**
|
|
* Register a new feature package in release-please tracking (ADR-021).
|
|
*
|
|
* Mutates two files at the repo root:
|
|
* - `.release-please-manifest.json` — adds `"packages/<name>": "0.1.0"`
|
|
* - `release-please-config.json` — adds the per-package config block
|
|
*
|
|
* Idempotent: if the package is already tracked, both writes are no-ops.
|
|
*
|
|
* Throws if either file is missing — release-please MUST be set up before
|
|
* a feature generator can register against it (ADR-021 land-down sequence).
|
|
*/
|
|
export function registerFeatureInReleasePlease(
|
|
repoRoot: string,
|
|
featureKebab: string,
|
|
): { manifestChanged: boolean; configChanged: boolean } {
|
|
const manifestPath = join(repoRoot, MANIFEST_FILE);
|
|
const configPath = join(repoRoot, CONFIG_FILE);
|
|
|
|
if (!existsSync(manifestPath)) {
|
|
throw new Error(
|
|
`Cannot register feature in release-please: ${MANIFEST_FILE} is missing. ` +
|
|
`release-please must be set up before scaffolding features that participate in tracking.`,
|
|
);
|
|
}
|
|
if (!existsSync(configPath)) {
|
|
throw new Error(
|
|
`Cannot register feature in release-please: ${CONFIG_FILE} is missing.`,
|
|
);
|
|
}
|
|
|
|
const packagePath = `packages/${featureKebab}`;
|
|
const manifestChanged = addToManifest(manifestPath, packagePath);
|
|
const configChanged = addToConfig(configPath, packagePath, featureKebab);
|
|
|
|
return { manifestChanged, configChanged };
|
|
}
|
|
|
|
function addToManifest(manifestPath: string, packagePath: string): boolean {
|
|
const text = readFileSync(manifestPath, "utf8");
|
|
const manifest = JSON.parse(text) as Record<string, string>;
|
|
if (manifest[packagePath]) return false; // already tracked
|
|
manifest[packagePath] = INITIAL_VERSION;
|
|
// Sort keys: root (".") stays first if present; the rest alphabetically by
|
|
// path so insertions are deterministic + future diffs stay minimal.
|
|
const sorted: Record<string, string> = {};
|
|
if (manifest["."] !== undefined) sorted["."] = manifest["."];
|
|
for (const key of Object.keys(manifest).sort()) {
|
|
if (key === ".") continue;
|
|
sorted[key] = manifest[key];
|
|
}
|
|
writeFileSync(manifestPath, JSON.stringify(sorted, null, 2) + "\n");
|
|
return true;
|
|
}
|
|
|
|
function addToConfig(
|
|
configPath: string,
|
|
packagePath: string,
|
|
featureKebab: string,
|
|
): boolean {
|
|
const text = readFileSync(configPath, "utf8");
|
|
const config = JSON.parse(text) as {
|
|
packages?: Record<
|
|
string,
|
|
{
|
|
"package-name": string;
|
|
component: string;
|
|
"changelog-path"?: string;
|
|
}
|
|
>;
|
|
[k: string]: unknown;
|
|
};
|
|
if (!config.packages) {
|
|
throw new Error(
|
|
`release-please-config.json has no "packages" key — config is malformed.`,
|
|
);
|
|
}
|
|
if (config.packages[packagePath]) return false; // already tracked
|
|
config.packages[packagePath] = {
|
|
"package-name": `@repo/${featureKebab}`,
|
|
component: featureKebab,
|
|
"changelog-path": "CHANGELOG.md",
|
|
};
|
|
// Sort packages the same way as the manifest: root first, rest alphabetical.
|
|
const sortedPackages: typeof config.packages = {};
|
|
if (config.packages["."]) sortedPackages["."] = config.packages["."];
|
|
for (const key of Object.keys(config.packages).sort()) {
|
|
if (key === ".") continue;
|
|
sortedPackages[key] = config.packages[key];
|
|
}
|
|
config.packages = sortedPackages;
|
|
writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
return true;
|
|
}
|