Skip to content

Commit a28b796

Browse files
committed
feat(cli): add gen-models command + rename compo to gen-manifests
1 parent 0ae6f27 commit a28b796

File tree

12 files changed

+875
-486
lines changed

12 files changed

+875
-486
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ The CLI tool can be used to generate composition manifests from source files:
149149
2. Run the CLI tool:
150150

151151
```bash
152-
npx @crossplane-js/cli compo
152+
npx @crossplane-js/cli gen-manifests
153153
```
154154

155155
3. This will generate composition manifests in the `manifests/` directory.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
"@eslint/compat": "^1.2.7",
2929
"@eslint/eslintrc": "^3.3.1",
3030
"@eslint/js": "^9.23.0",
31-
"@kubernetes-models/crd-generate": "patch:@kubernetes-models/crd-generate@npm%3A5.0.2#~/.yarn/patches/@kubernetes-models-crd-generate-npm-5.0.2-d0c40558b7.patch",
3231
"@types/chai": "^5.2.0",
3332
"@types/fs-extra": "^11",
3433
"@types/js-yaml": "^4",
@@ -40,6 +39,7 @@
4039
"commit-and-tag-version": "^12.5.0",
4140
"eslint": "^9.23.0",
4241
"eslint-config-prettier": "^10.1.1",
42+
"eslint-import-resolver-typescript": "^3",
4343
"eslint-plugin-import": "^2.31.0",
4444
"eslint-plugin-prettier": "^5.2.5",
4545
"globals": "^16.0.0",

packages/cli/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
},
1111
"dependencies": {
1212
"@crossplane-js/libs": "workspace:^",
13+
"@kubernetes-models/crd-generate": "patch:@kubernetes-models/crd-generate@npm%3A5.0.2#~/.yarn/patches/@kubernetes-models-crd-generate-npm-5.0.2-d0c40558b7.patch",
1314
"commander": "^13.1.0",
1415
"esbuild": "^0.25.4",
1516
"fs-extra": "^11.3.0",

packages/cli/src/commands/compo/index.ts renamed to packages/cli/src/commands/gen-manifests/index.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { v4 as uuidv4 } from "uuid"
1111
import YAML from "yaml"
1212

1313
// Create a logger for this module
14-
const moduleLogger = createLogger("compo")
14+
const moduleLogger = createLogger("gen-manifests")
1515

1616
// Define interfaces for the manifest structure
1717
interface ManifestMetadata {
@@ -344,12 +344,12 @@ async function bundleTypeScript(
344344
}
345345

346346
/**
347-
* Main function for the compo command
347+
* Main function for the gen-manifests command
348348
* Processes function directories and generates composition manifests
349349
* @param options Command options
350350
* @returns Promise<void>
351351
*/
352-
async function compoAction(
352+
async function genManifestsAction(
353353
options: { bundle?: boolean; bundleConfig?: string; embedDeps?: boolean } = {}
354354
): Promise<void> {
355355
// Default to bundling enabled
@@ -663,21 +663,21 @@ async function compoAction(
663663
}
664664

665665
/**
666-
* Register the compo command with the CLI
666+
* Register the gen-manifests command with the CLI
667667
* @param program The Commander program instance
668668
*/
669669
export default function (program: Command): void {
670670
program
671-
.command("compo")
671+
.command("gen-manifests")
672672
.description("Generate composition manifests from function directories")
673673
.option("--no-bundle", "Disable TypeScript bundling")
674674
.option("--bundle-config <json>", "Custom esbuild configuration (JSON string)")
675675
.option("--embed-deps", "Embed dependencies in the bundle (default: false)")
676676
.action(async options => {
677677
try {
678-
await compoAction(options)
678+
await genManifestsAction(options)
679679
} catch (err) {
680-
moduleLogger.error(`Error running compo command: ${err}`)
680+
moduleLogger.error(`Error running gen-manifests command: ${err}`)
681681
process.exit(1)
682682
}
683683
})
File renamed without changes.
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import { spawn } from "child_process"
2+
import os from "os"
3+
import path from "path"
4+
import { fileURLToPath } from "url"
5+
6+
import { createLogger } from "@crossplane-js/libs"
7+
import { Command } from "commander"
8+
import fs from "fs-extra"
9+
import YAML from "yaml"
10+
11+
import { convertXRDtoCRD, parseAndValidateXRD } from "../xrd2crd/converter.js"
12+
13+
// Create a logger for this module
14+
const moduleLogger = createLogger("gen-models")
15+
16+
// Resolve CLI package root to run yarn in the right workspace
17+
const __filename = fileURLToPath(import.meta.url)
18+
const __dirname = path.dirname(__filename)
19+
// packages/cli/src/commands/gen-models -> packages/cli
20+
const cliRoot = path.resolve(__dirname, "../../..")
21+
22+
/**
23+
* Find all XRD files in the functions directory
24+
* @returns Promise<string[]> Array of XRD file paths
25+
*/
26+
async function findXRDFiles(): Promise<string[]> {
27+
const functionsDir = "functions"
28+
29+
// Check if functions directory exists
30+
if (!(await fs.pathExists(functionsDir))) {
31+
throw new Error(`Functions directory '${functionsDir}' does not exist`)
32+
}
33+
34+
const dirs = await fs.readdir(functionsDir, { withFileTypes: true })
35+
const xrdFiles: string[] = []
36+
37+
for (const dir of dirs) {
38+
if (dir.isDirectory()) {
39+
const xrdPath = path.join(functionsDir, dir.name, "xrd.yaml")
40+
if (await fs.pathExists(xrdPath)) {
41+
xrdFiles.push(xrdPath)
42+
}
43+
}
44+
}
45+
46+
if (xrdFiles.length === 0) {
47+
throw new Error("No XRD files found in functions/*/xrd.yaml")
48+
}
49+
50+
return xrdFiles
51+
}
52+
53+
async function runCrdGenerate(crdYaml: string, outputPath: string): Promise<void> {
54+
const tmpBase = path.join(os.tmpdir(), "xfuncjs-")
55+
const tmpDir = await fs.mkdtemp(tmpBase)
56+
const inputFile = path.join(tmpDir, "crd.yaml")
57+
try {
58+
await fs.writeFile(inputFile, crdYaml, { encoding: "utf8" })
59+
const outputAbs = path.resolve(process.cwd(), outputPath)
60+
await new Promise<void>((resolve, reject) => {
61+
// we use spawning instead of importing lib, because of this https://github.com/tommy351/kubernetes-models-ts/issues/241
62+
const args = [
63+
"crd-generate",
64+
"--customBaseClassImportPath",
65+
"@crossplane-js/sdk",
66+
"--modelDecorator",
67+
"@registerXrdModel",
68+
"--modelDecoratorPath",
69+
"@crossplane-js/sdk",
70+
"--input",
71+
inputFile,
72+
"--output",
73+
outputAbs,
74+
]
75+
const child = spawn("yarn", args, { cwd: cliRoot, stdio: ["ignore", "inherit", "inherit"] })
76+
child.on("error", err => reject(err))
77+
child.on("exit", code => {
78+
if (code === 0) resolve()
79+
else reject(new Error(`crd-generate exited with code ${code}`))
80+
})
81+
})
82+
} finally {
83+
// Cleanup temp files
84+
await fs.remove(tmpDir)
85+
}
86+
}
87+
88+
/**
89+
* Main function for the gen-models command
90+
* Generates TypeScript models from all XRD files in functions/
91+
* @returns Promise<void>
92+
*/
93+
async function genModelsAction(): Promise<void> {
94+
try {
95+
moduleLogger.info("Starting model generation...")
96+
97+
// Find all XRD files
98+
const xrdFiles = await findXRDFiles()
99+
moduleLogger.info(`Found ${xrdFiles.length} XRD file(s): ${xrdFiles.join(", ")}`)
100+
101+
// Ensure models directory exists
102+
const modelsDir = "models"
103+
await fs.ensureDir(modelsDir)
104+
105+
// Process each XRD file
106+
for (const xrdPath of xrdFiles) {
107+
moduleLogger.info(`Processing ${xrdPath}...`)
108+
109+
try {
110+
// Read and parse the XRD file
111+
const xrdContent = await fs.readFile(xrdPath, { encoding: "utf8" })
112+
const xrd = parseAndValidateXRD(xrdContent)
113+
114+
// Convert XRD to CRD
115+
const crd = convertXRDtoCRD(xrd)
116+
const crdYaml = YAML.stringify(crd)
117+
118+
// Generate models using crd-generate (via child process)
119+
await runCrdGenerate(crdYaml, modelsDir)
120+
121+
moduleLogger.info(`✓ Generated models for ${xrdPath}`)
122+
} catch (error) {
123+
moduleLogger.error(`✗ Failed to process ${xrdPath}: ${error}`)
124+
throw error
125+
}
126+
}
127+
128+
moduleLogger.info(`✓ Model generation completed. Models saved to '${modelsDir}/' directory.`)
129+
} catch (error) {
130+
moduleLogger.error(`Error generating models: ${error}`)
131+
process.exit(1)
132+
}
133+
}
134+
135+
/**
136+
* Register the gen-models command with the CLI
137+
* @param program The Commander program instance
138+
*/
139+
export default function (program: Command): void {
140+
program
141+
.command("gen-models")
142+
.description("Generate TypeScript models from all XRD files in functions/")
143+
.action(async () => {
144+
try {
145+
await genModelsAction()
146+
} catch (err) {
147+
moduleLogger.error(`Error running gen-models command: ${err}`)
148+
process.exit(1)
149+
}
150+
})
151+
}

0 commit comments

Comments
 (0)