@xrmforge/cli 0.2.0 → 0.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/index.js CHANGED
@@ -56,6 +56,12 @@ function mergeWithCliOptions(config, cliOpts) {
56
56
  if (merged["optionsets"] === void 0 && config.optionsets !== void 0) {
57
57
  merged["optionsets"] = config.optionsets;
58
58
  }
59
+ if (merged["cache"] === void 0 && config.cache !== void 0) {
60
+ merged["cache"] = config.cache;
61
+ }
62
+ if (!merged["cacheDir"] && config.cacheDir) {
63
+ merged["cacheDir"] = config.cacheDir;
64
+ }
59
65
  return merged;
60
66
  }
61
67
 
@@ -68,7 +74,7 @@ import {
68
74
  LogLevel
69
75
  } from "@xrmforge/typegen";
70
76
  function registerGenerateCommand(program2) {
71
- program2.command("generate").description("Generate TypeScript declarations from a Dataverse environment").option("--url <url>", "Dataverse environment URL (e.g. https://myorg.crm4.dynamics.com)").option("--auth <method>", "Authentication method: client-credentials, interactive, device-code, token").option("--tenant-id <id>", "Azure AD tenant ID").option("--client-id <id>", "Azure AD application (client) ID").option("--client-secret <secret>", "Client secret (for client-credentials auth)").option("--token <token>", "Pre-acquired Bearer token (for --auth token). Prefer XRMFORGE_TOKEN env var for security.").option("--entities <list>", "Comma-separated list of entity logical names (e.g. account,contact)").option("--solutions <list>", "Comma-separated solution unique names to discover entities").option("--output <dir>", "Output directory for generated .d.ts files", "./typings").option("--label-language <code>", "Primary label language code", "1033").option("--secondary-language <code>", "Secondary label language code (for dual-language JSDoc)").option("--no-forms", "Skip form interface generation").option("--no-optionsets", "Skip OptionSet enum generation").option("--actions", "Generate Custom API Action/Function executors", false).option("-v, --verbose", "Enable verbose logging", false).action(async (opts) => {
77
+ program2.command("generate").description("Generate TypeScript declarations from a Dataverse environment").option("--url <url>", "Dataverse environment URL (e.g. https://myorg.crm4.dynamics.com)").option("--auth <method>", "Authentication method: client-credentials, interactive, device-code, token").option("--tenant-id <id>", "Azure AD tenant ID").option("--client-id <id>", "Azure AD application (client) ID").option("--client-secret <secret>", "Client secret (for client-credentials auth)").option("--token <token>", "Pre-acquired Bearer token (for --auth token). Prefer XRMFORGE_TOKEN env var for security.").option("--entities <list>", "Comma-separated list of entity logical names (e.g. account,contact)").option("--solutions <list>", "Comma-separated solution unique names to discover entities").option("--output <dir>", "Output directory for generated .d.ts files", "./typings").option("--label-language <code>", "Primary label language code", "1033").option("--secondary-language <code>", "Secondary label language code (for dual-language JSDoc)").option("--no-forms", "Skip form interface generation").option("--no-optionsets", "Skip OptionSet enum generation").option("--actions", "Generate Custom API Action/Function executors", false).option("--actions-filter <prefix>", 'Only generate Custom APIs whose uniquename starts with this prefix (e.g. "markant_")').option("--cache", "Enable metadata cache for incremental generation", false).option("--no-cache", "Force full metadata refresh (disables cache)").option("--cache-dir <dir>", "Directory for metadata cache files", ".xrmforge/cache").option("-v, --verbose", "Enable verbose logging", false).action(async (opts) => {
72
78
  try {
73
79
  await runGenerate(opts);
74
80
  } catch (error) {
@@ -131,6 +137,9 @@ XrmForge Type Generator`);
131
137
  }
132
138
  console.log(`Output: ${opts.output}`);
133
139
  console.log(`Languages: ${primaryLanguage}${secondaryLanguage ? ` + ${secondaryLanguage}` : ""}`);
140
+ if (opts.cache) {
141
+ console.log(`Cache: enabled (${opts.cacheDir})`);
142
+ }
134
143
  console.log("");
135
144
  const orchestrator = new TypeGenerationOrchestrator(credential, {
136
145
  environmentUrl: opts.url,
@@ -140,7 +149,10 @@ XrmForge Type Generator`);
140
149
  labelConfig: { primaryLanguage, secondaryLanguage },
141
150
  generateForms: opts.forms,
142
151
  generateOptionSets: opts.optionsets,
143
- generateActions: opts.actions
152
+ generateActions: opts.actions,
153
+ actionsFilter: opts.actionsFilter,
154
+ useCache: opts.cache,
155
+ cacheDir: opts.cacheDir
144
156
  });
145
157
  const controller = new AbortController();
146
158
  const onSignal = () => {
@@ -156,6 +168,14 @@ XrmForge Type Generator`);
156
168
  console.log(` Files: ${result.totalFiles}`);
157
169
  console.log(` Warnings: ${result.totalWarnings}`);
158
170
  console.log(` Duration: ${result.durationMs}ms`);
171
+ if (result.cacheStats) {
172
+ const cs = result.cacheStats;
173
+ if (cs.fullRefresh) {
174
+ console.log(` Cache: full refresh (${cs.entitiesFetched} entities fetched)`);
175
+ } else {
176
+ console.log(` Cache: ${cs.entitiesFromCache} from cache, ${cs.entitiesFetched} fetched, ${cs.entitiesDeleted} deleted`);
177
+ }
178
+ }
159
179
  if (result.totalWarnings > 0) {
160
180
  console.log("\nWarnings:");
161
181
  for (const entity of result.entities) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/config.ts","../src/commands/generate.ts"],"sourcesContent":["/**\r\n * @xrmforge/cli - Command-line interface for XrmForge\r\n *\r\n * Usage:\r\n * xrmforge generate --url https://myorg.crm4.dynamics.com \\\r\n * --auth client-credentials \\\r\n * --tenant <tenant-id> --client-id <app-id> --client-secret <secret> \\\r\n * --entities account,contact \\\r\n * --output ./typings\r\n */\r\n\r\nimport { readFileSync } from 'node:fs';\r\nimport { dirname, join } from 'node:path';\r\nimport { fileURLToPath } from 'node:url';\r\nimport { Command } from 'commander';\r\nimport { registerGenerateCommand } from './commands/generate.js';\r\n\r\n// Read version from package.json (single source of truth)\r\nconst __dirname = dirname(fileURLToPath(import.meta.url));\r\nconst pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));\r\n\r\nconst program = new Command();\r\n\r\nprogram\r\n .name('xrmforge')\r\n .description('TypeScript type generator for Dynamics 365 / Dataverse')\r\n .version(pkg.version);\r\n\r\nregisterGenerateCommand(program);\r\n\r\nprogram.parse();\r\n","/**\r\n * @xrmforge/cli - Configuration File Support\r\n *\r\n * Reads xrmforge.config.json from the current working directory.\r\n * CLI flags override config file values.\r\n *\r\n * Example xrmforge.config.json:\r\n * ```json\r\n * {\r\n * \"url\": \"https://myorg.crm4.dynamics.com\",\r\n * \"auth\": \"interactive\",\r\n * \"tenantId\": \"your-tenant-id\",\r\n * \"solutions\": [\"MySolution\", \"MyOtherSolution\"],\r\n * \"entities\": [\"systemuser\", \"task\"],\r\n * \"output\": \"./typings\",\r\n * \"labelLanguage\": 1033,\r\n * \"secondaryLanguage\": 1031\r\n * }\r\n * ```\r\n */\r\n\r\nimport { readFileSync, existsSync } from 'node:fs';\r\nimport { join } from 'node:path';\r\n\r\n/** Shape of xrmforge.config.json */\r\nexport interface XrmForgeConfig {\r\n /** Dataverse environment URL */\r\n url?: string;\r\n /** Authentication method */\r\n auth?: string;\r\n /** Azure AD tenant ID */\r\n tenantId?: string;\r\n /** Azure AD application (client) ID */\r\n clientId?: string;\r\n /** Client secret (NOT recommended in config file, use env vars) */\r\n clientSecret?: string;\r\n /** Entity logical names */\r\n entities?: string[];\r\n /** Solution unique names (array or comma-separated string) */\r\n solutions?: string[] | string;\r\n /** Output directory */\r\n output?: string;\r\n /** Primary label language LCID */\r\n labelLanguage?: number;\r\n /** Secondary label language LCID */\r\n secondaryLanguage?: number;\r\n /** Generate form interfaces */\r\n forms?: boolean;\r\n /** Generate OptionSet enums */\r\n optionsets?: boolean;\r\n}\r\n\r\nconst CONFIG_FILENAME = 'xrmforge.config.json';\r\n\r\n/**\r\n * Load config from xrmforge.config.json in the current working directory.\r\n * Returns empty object if file doesn't exist.\r\n * Throws with clear message if file exists but is invalid JSON.\r\n */\r\nexport function loadConfig(cwd: string = process.cwd()): XrmForgeConfig {\r\n const configPath = join(cwd, CONFIG_FILENAME);\r\n\r\n if (!existsSync(configPath)) {\r\n return {};\r\n }\r\n\r\n try {\r\n const raw = readFileSync(configPath, 'utf-8');\r\n const config = JSON.parse(raw) as XrmForgeConfig;\r\n\r\n // Warn about secrets in config file\r\n if (config.clientSecret) {\r\n console.warn(`WARNING: clientSecret found in ${CONFIG_FILENAME}. This is a security risk.`);\r\n console.warn(' Use XRMFORGE_CLIENT_SECRET environment variable instead.\\n');\r\n }\r\n\r\n return config;\r\n } catch (error) {\r\n if (error instanceof SyntaxError) {\r\n throw new Error(`Invalid JSON in ${configPath}: ${error.message}`);\r\n }\r\n throw error;\r\n }\r\n}\r\n\r\n/**\r\n * Merge config file values with CLI options.\r\n * CLI flags take precedence over config file.\r\n */\r\nexport function mergeWithCliOptions(\r\n config: XrmForgeConfig,\r\n cliOpts: Record<string, unknown>,\r\n): Record<string, unknown> {\r\n const merged: Record<string, unknown> = { ...cliOpts };\r\n\r\n // Only fill in values from config that weren't set via CLI\r\n if (!merged['url'] && config.url) merged['url'] = config.url;\r\n if (!merged['auth'] && config.auth) merged['auth'] = config.auth;\r\n if (!merged['tenantId'] && config.tenantId) merged['tenantId'] = config.tenantId;\r\n if (!merged['clientId'] && config.clientId) merged['clientId'] = config.clientId;\r\n if (!merged['clientSecret'] && config.clientSecret) merged['clientSecret'] = config.clientSecret;\r\n // Solutions: CLI comma-separated string vs config array\r\n if (!merged['solutions'] && config.solutions) {\r\n merged['solutions'] = Array.isArray(config.solutions)\r\n ? config.solutions.join(',')\r\n : config.solutions;\r\n }\r\n if (!merged['output'] && config.output) merged['output'] = config.output;\r\n\r\n // Entities: CLI comma-separated string vs config array\r\n if (!merged['entities'] && config.entities) {\r\n merged['entities'] = config.entities.join(',');\r\n }\r\n\r\n // Label languages: config uses numbers, CLI uses strings\r\n if (!merged['labelLanguage'] && config.labelLanguage) {\r\n merged['labelLanguage'] = String(config.labelLanguage);\r\n }\r\n if (!merged['secondaryLanguage'] && config.secondaryLanguage) {\r\n merged['secondaryLanguage'] = String(config.secondaryLanguage);\r\n }\r\n\r\n // Booleans: only override if explicitly set in config\r\n if (merged['forms'] === undefined && config.forms !== undefined) {\r\n merged['forms'] = config.forms;\r\n }\r\n if (merged['optionsets'] === undefined && config.optionsets !== undefined) {\r\n merged['optionsets'] = config.optionsets;\r\n }\r\n\r\n return merged;\r\n}\r\n","/**\r\n * @xrmforge/cli - Generate Command\r\n *\r\n * Orchestrates type generation from a Dataverse environment.\r\n *\r\n * Usage:\r\n * xrmforge generate --url https://myorg.crm4.dynamics.com \\\r\n * --auth client-credentials \\\r\n * --tenant <tenant-id> --client-id <app-id> --client-secret <secret> \\\r\n * --entities account,contact \\\r\n * --output ./typings\r\n *\r\n * xrmforge generate --url https://myorg.crm4.dynamics.com \\\r\n * --auth interactive \\\r\n * --tenant <tenant-id> --client-id <app-id> \\\r\n * --entities account,contact,opportunity \\\r\n * --output ./typings \\\r\n * --label-language 1033 --secondary-language 1031\r\n */\r\n\r\nimport type { Command } from 'commander';\r\nimport { loadConfig, mergeWithCliOptions } from '../config.js';\r\nimport {\r\n TypeGenerationOrchestrator,\r\n createCredential,\r\n configureLogging,\r\n ConsoleLogSink,\r\n LogLevel,\r\n} from '@xrmforge/typegen';\r\nimport type { AuthConfig } from '@xrmforge/typegen';\r\n\r\n/** CLI options for the generate command */\r\ninterface GenerateOptions {\r\n url: string;\r\n auth: string;\r\n tenantId?: string;\r\n clientId?: string;\r\n clientSecret?: string;\r\n token?: string;\r\n entities?: string;\r\n solutions?: string;\r\n output: string;\r\n labelLanguage: string;\r\n secondaryLanguage?: string;\r\n forms: boolean;\r\n optionsets: boolean;\r\n actions: boolean;\r\n verbose: boolean;\r\n}\r\n\r\n/**\r\n * Register the 'generate' subcommand on the CLI program.\r\n */\r\nexport function registerGenerateCommand(program: Command): void {\r\n program\r\n .command('generate')\r\n .description('Generate TypeScript declarations from a Dataverse environment')\r\n\r\n // Connection (can come from xrmforge.config.json)\r\n .option('--url <url>', 'Dataverse environment URL (e.g. https://myorg.crm4.dynamics.com)')\r\n .option('--auth <method>', 'Authentication method: client-credentials, interactive, device-code, token')\r\n\r\n // Auth credentials\r\n .option('--tenant-id <id>', 'Azure AD tenant ID')\r\n .option('--client-id <id>', 'Azure AD application (client) ID')\r\n .option('--client-secret <secret>', 'Client secret (for client-credentials auth)')\r\n .option('--token <token>', 'Pre-acquired Bearer token (for --auth token). Prefer XRMFORGE_TOKEN env var for security.')\r\n\r\n // Scope\r\n .option('--entities <list>', 'Comma-separated list of entity logical names (e.g. account,contact)')\r\n .option('--solutions <list>', 'Comma-separated solution unique names to discover entities')\r\n\r\n // Output\r\n .option('--output <dir>', 'Output directory for generated .d.ts files', './typings')\r\n\r\n // Labels\r\n .option('--label-language <code>', 'Primary label language code', '1033')\r\n .option('--secondary-language <code>', 'Secondary label language code (for dual-language JSDoc)')\r\n\r\n // Feature toggles\r\n .option('--no-forms', 'Skip form interface generation')\r\n .option('--no-optionsets', 'Skip OptionSet enum generation')\r\n .option('--actions', 'Generate Custom API Action/Function executors', false)\r\n\r\n // Verbosity\r\n .option('-v, --verbose', 'Enable verbose logging', false)\r\n\r\n .action(async (opts: GenerateOptions) => {\r\n try {\r\n await runGenerate(opts);\r\n } catch (error) {\r\n if (error instanceof Error) {\r\n console.error(`\\nError: ${error.message}\\n`);\r\n if (opts.verbose && error.stack) {\r\n console.error(error.stack);\r\n }\r\n } else {\r\n console.error('\\nAn unexpected error occurred.\\n');\r\n }\r\n process.exitCode = 1;\r\n }\r\n });\r\n}\r\n\r\n/**\r\n * Execute the generate command.\r\n */\r\nasync function runGenerate(cliOpts: GenerateOptions): Promise<void> {\r\n // Load config file and merge with CLI options (CLI takes precedence)\r\n const fileConfig = loadConfig();\r\n const merged = mergeWithCliOptions(fileConfig, cliOpts as unknown as Record<string, unknown>);\r\n const opts = merged as unknown as GenerateOptions;\r\n\r\n // Configure logging\r\n configureLogging({\r\n sink: new ConsoleLogSink(),\r\n minLevel: opts.verbose ? LogLevel.DEBUG : LogLevel.INFO,\r\n });\r\n\r\n // Validate required options (may come from config file)\r\n if (!opts.url) {\r\n throw new Error('--url is required. Set it via CLI flag or in xrmforge.config.json.');\r\n }\r\n if (!opts.auth) {\r\n throw new Error('--auth is required. Set it via CLI flag or in xrmforge.config.json.');\r\n }\r\n if (!opts.entities && !opts.solutions) {\r\n throw new Error('Either --entities or --solutions must be specified (CLI or xrmforge.config.json).');\r\n }\r\n\r\n // Build auth config\r\n const authConfig = buildAuthConfig(opts);\r\n const credential = createCredential(authConfig);\r\n\r\n // Parse entity list\r\n const entities = opts.entities\r\n ? opts.entities.split(',').map((e) => e.trim().toLowerCase())\r\n : [];\r\n\r\n // Parse solutions list\r\n const solutionNames = opts.solutions\r\n ? opts.solutions.split(',').map((s) => s.trim())\r\n : [];\r\n\r\n if (entities.length === 0 && solutionNames.length === 0) {\r\n throw new Error('No entities specified. Use --entities or --solutions.');\r\n }\r\n\r\n // Build label config (R8-05: validate LCID)\r\n const primaryLanguage = parseInt(opts.labelLanguage, 10);\r\n if (isNaN(primaryLanguage)) {\r\n throw new Error(`Invalid --label-language: \"${opts.labelLanguage}\". Must be a numeric LCID (e.g. 1033, 1031).`);\r\n }\r\n let secondaryLanguage: number | undefined;\r\n if (opts.secondaryLanguage) {\r\n secondaryLanguage = parseInt(opts.secondaryLanguage, 10);\r\n if (isNaN(secondaryLanguage)) {\r\n throw new Error(`Invalid --secondary-language: \"${opts.secondaryLanguage}\". Must be a numeric LCID (e.g. 1033, 1031).`);\r\n }\r\n }\r\n\r\n console.log(`\\nXrmForge Type Generator`);\r\n console.log(`Environment: ${opts.url}`);\r\n console.log(`Auth method: ${opts.auth}`);\r\n console.log(`Entities: ${entities.length > 0 ? entities.join(', ') : '(none specified directly)'}`)\r\n if (solutionNames.length > 0) {\r\n console.log(`Solutions: ${solutionNames.join(', ')}`);\r\n }\r\n console.log(`Output: ${opts.output}`);\r\n console.log(`Languages: ${primaryLanguage}${secondaryLanguage ? ` + ${secondaryLanguage}` : ''}`);\r\n console.log('');\r\n\r\n // Create orchestrator and run\r\n const orchestrator = new TypeGenerationOrchestrator(credential, {\r\n environmentUrl: opts.url,\r\n entities,\r\n solutionNames: solutionNames.length > 0 ? solutionNames : undefined,\r\n outputDir: opts.output,\r\n labelConfig: { primaryLanguage, secondaryLanguage },\r\n generateForms: opts.forms,\r\n generateOptionSets: opts.optionsets,\r\n generateActions: opts.actions,\r\n });\r\n\r\n // Support Ctrl+C and SIGTERM (R8-07: Docker/K8s sends SIGTERM)\r\n const controller = new AbortController();\r\n const onSignal = () => {\r\n console.log('\\nAborting generation...');\r\n controller.abort();\r\n };\r\n process.once('SIGINT', onSignal);\r\n process.once('SIGTERM', onSignal);\r\n\r\n const result = await orchestrator.generate({ signal: controller.signal });\r\n\r\n // Summary\r\n console.log('');\r\n console.log('Generation complete:');\r\n console.log(` Entities: ${result.entities.length}`);\r\n console.log(` Files: ${result.totalFiles}`);\r\n console.log(` Warnings: ${result.totalWarnings}`);\r\n console.log(` Duration: ${result.durationMs}ms`);\r\n\r\n // Show warnings\r\n if (result.totalWarnings > 0) {\r\n console.log('\\nWarnings:');\r\n for (const entity of result.entities) {\r\n for (const warning of entity.warnings) {\r\n console.log(` [${entity.entityLogicalName}] ${warning}`);\r\n }\r\n }\r\n }\r\n\r\n // Show failures\r\n const failures = result.entities.filter((e) => e.files.length === 0 && e.warnings.length > 0);\r\n if (failures.length > 0) {\r\n console.log(`\\n${failures.length} entity/entities failed. See warnings above.`);\r\n process.exitCode = 1;\r\n return;\r\n }\r\n\r\n console.log(`\\nTypes written to: ${opts.output}/`);\r\n}\r\n\r\n/**\r\n * Build AuthConfig from CLI options.\r\n */\r\nfunction buildAuthConfig(opts: GenerateOptions): AuthConfig {\r\n const method = opts.auth as AuthConfig['method'];\r\n\r\n switch (method) {\r\n case 'client-credentials':\r\n if (!opts.tenantId) throw new Error('--tenant-id is required for client-credentials auth.');\r\n if (!opts.clientId) throw new Error('--client-id is required for client-credentials auth.');\r\n if (!opts.clientSecret) throw new Error('--client-secret is required for client-credentials auth.');\r\n return {\r\n method: 'client-credentials',\r\n tenantId: opts.tenantId,\r\n clientId: opts.clientId,\r\n clientSecret: opts.clientSecret,\r\n };\r\n\r\n case 'interactive':\r\n if (!opts.tenantId) throw new Error('--tenant-id is required for interactive auth.');\r\n if (!opts.clientId) throw new Error('--client-id is required for interactive auth.');\r\n return {\r\n method: 'interactive',\r\n tenantId: opts.tenantId,\r\n clientId: opts.clientId,\r\n };\r\n\r\n case 'device-code':\r\n if (!opts.tenantId) throw new Error('--tenant-id is required for device-code auth.');\r\n if (!opts.clientId) throw new Error('--client-id is required for device-code auth.');\r\n return {\r\n method: 'device-code',\r\n tenantId: opts.tenantId,\r\n clientId: opts.clientId,\r\n };\r\n\r\n case 'token': {\r\n // Token from --token flag or XRMFORGE_TOKEN environment variable\r\n const token = opts.token || process.env['XRMFORGE_TOKEN'];\r\n if (!token) {\r\n throw new Error(\r\n 'Token authentication requires a token. ' +\r\n 'Set XRMFORGE_TOKEN environment variable or use --token flag.',\r\n );\r\n }\r\n if (opts.token) {\r\n console.warn('WARNING: Using --token on the command line exposes the token in process list and shell history.');\r\n console.warn(' Prefer setting XRMFORGE_TOKEN environment variable instead.\\n');\r\n }\r\n return { method: 'token', token };\r\n }\r\n\r\n default:\r\n throw new Error(\r\n `Unknown auth method: \"${opts.auth}\". ` +\r\n `Supported: client-credentials, interactive, device-code, token`,\r\n );\r\n }\r\n}\r\n"],"mappings":"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","names":["readFileSync","join","program","readFileSync","join"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/config.ts","../src/commands/generate.ts"],"sourcesContent":["/**\r\n * @xrmforge/cli - Command-line interface for XrmForge\r\n *\r\n * Usage:\r\n * xrmforge generate --url https://myorg.crm4.dynamics.com \\\r\n * --auth client-credentials \\\r\n * --tenant <tenant-id> --client-id <app-id> --client-secret <secret> \\\r\n * --entities account,contact \\\r\n * --output ./typings\r\n */\r\n\r\nimport { readFileSync } from 'node:fs';\r\nimport { dirname, join } from 'node:path';\r\nimport { fileURLToPath } from 'node:url';\r\nimport { Command } from 'commander';\r\nimport { registerGenerateCommand } from './commands/generate.js';\r\n\r\n// Read version from package.json (single source of truth)\r\nconst __dirname = dirname(fileURLToPath(import.meta.url));\r\nconst pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));\r\n\r\nconst program = new Command();\r\n\r\nprogram\r\n .name('xrmforge')\r\n .description('TypeScript type generator for Dynamics 365 / Dataverse')\r\n .version(pkg.version);\r\n\r\nregisterGenerateCommand(program);\r\n\r\nprogram.parse();\r\n","/**\r\n * @xrmforge/cli - Configuration File Support\r\n *\r\n * Reads xrmforge.config.json from the current working directory.\r\n * CLI flags override config file values.\r\n *\r\n * Example xrmforge.config.json:\r\n * ```json\r\n * {\r\n * \"url\": \"https://myorg.crm4.dynamics.com\",\r\n * \"auth\": \"interactive\",\r\n * \"tenantId\": \"your-tenant-id\",\r\n * \"solutions\": [\"MySolution\", \"MyOtherSolution\"],\r\n * \"entities\": [\"systemuser\", \"task\"],\r\n * \"output\": \"./typings\",\r\n * \"labelLanguage\": 1033,\r\n * \"secondaryLanguage\": 1031\r\n * }\r\n * ```\r\n */\r\n\r\nimport { readFileSync, existsSync } from 'node:fs';\r\nimport { join } from 'node:path';\r\n\r\n/** Shape of xrmforge.config.json */\r\nexport interface XrmForgeConfig {\r\n /** Dataverse environment URL */\r\n url?: string;\r\n /** Authentication method */\r\n auth?: string;\r\n /** Azure AD tenant ID */\r\n tenantId?: string;\r\n /** Azure AD application (client) ID */\r\n clientId?: string;\r\n /** Client secret (NOT recommended in config file, use env vars) */\r\n clientSecret?: string;\r\n /** Entity logical names */\r\n entities?: string[];\r\n /** Solution unique names (array or comma-separated string) */\r\n solutions?: string[] | string;\r\n /** Output directory */\r\n output?: string;\r\n /** Primary label language LCID */\r\n labelLanguage?: number;\r\n /** Secondary label language LCID */\r\n secondaryLanguage?: number;\r\n /** Generate form interfaces */\r\n forms?: boolean;\r\n /** Generate OptionSet enums */\r\n optionsets?: boolean;\r\n /** Enable metadata cache for incremental generation */\r\n cache?: boolean;\r\n /** Directory for metadata cache files */\r\n cacheDir?: string;\r\n}\r\n\r\nconst CONFIG_FILENAME = 'xrmforge.config.json';\r\n\r\n/**\r\n * Load config from xrmforge.config.json in the current working directory.\r\n * Returns empty object if file doesn't exist.\r\n * Throws with clear message if file exists but is invalid JSON.\r\n */\r\nexport function loadConfig(cwd: string = process.cwd()): XrmForgeConfig {\r\n const configPath = join(cwd, CONFIG_FILENAME);\r\n\r\n if (!existsSync(configPath)) {\r\n return {};\r\n }\r\n\r\n try {\r\n const raw = readFileSync(configPath, 'utf-8');\r\n const config = JSON.parse(raw) as XrmForgeConfig;\r\n\r\n // Warn about secrets in config file\r\n if (config.clientSecret) {\r\n console.warn(`WARNING: clientSecret found in ${CONFIG_FILENAME}. This is a security risk.`);\r\n console.warn(' Use XRMFORGE_CLIENT_SECRET environment variable instead.\\n');\r\n }\r\n\r\n return config;\r\n } catch (error) {\r\n if (error instanceof SyntaxError) {\r\n throw new Error(`Invalid JSON in ${configPath}: ${error.message}`);\r\n }\r\n throw error;\r\n }\r\n}\r\n\r\n/**\r\n * Merge config file values with CLI options.\r\n * CLI flags take precedence over config file.\r\n */\r\nexport function mergeWithCliOptions(\r\n config: XrmForgeConfig,\r\n cliOpts: Record<string, unknown>,\r\n): Record<string, unknown> {\r\n const merged: Record<string, unknown> = { ...cliOpts };\r\n\r\n // Only fill in values from config that weren't set via CLI\r\n if (!merged['url'] && config.url) merged['url'] = config.url;\r\n if (!merged['auth'] && config.auth) merged['auth'] = config.auth;\r\n if (!merged['tenantId'] && config.tenantId) merged['tenantId'] = config.tenantId;\r\n if (!merged['clientId'] && config.clientId) merged['clientId'] = config.clientId;\r\n if (!merged['clientSecret'] && config.clientSecret) merged['clientSecret'] = config.clientSecret;\r\n // Solutions: CLI comma-separated string vs config array\r\n if (!merged['solutions'] && config.solutions) {\r\n merged['solutions'] = Array.isArray(config.solutions)\r\n ? config.solutions.join(',')\r\n : config.solutions;\r\n }\r\n if (!merged['output'] && config.output) merged['output'] = config.output;\r\n\r\n // Entities: CLI comma-separated string vs config array\r\n if (!merged['entities'] && config.entities) {\r\n merged['entities'] = config.entities.join(',');\r\n }\r\n\r\n // Label languages: config uses numbers, CLI uses strings\r\n if (!merged['labelLanguage'] && config.labelLanguage) {\r\n merged['labelLanguage'] = String(config.labelLanguage);\r\n }\r\n if (!merged['secondaryLanguage'] && config.secondaryLanguage) {\r\n merged['secondaryLanguage'] = String(config.secondaryLanguage);\r\n }\r\n\r\n // Booleans: only override if explicitly set in config\r\n if (merged['forms'] === undefined && config.forms !== undefined) {\r\n merged['forms'] = config.forms;\r\n }\r\n if (merged['optionsets'] === undefined && config.optionsets !== undefined) {\r\n merged['optionsets'] = config.optionsets;\r\n }\r\n\r\n // Cache options\r\n if (merged['cache'] === undefined && config.cache !== undefined) {\r\n merged['cache'] = config.cache;\r\n }\r\n if (!merged['cacheDir'] && config.cacheDir) {\r\n merged['cacheDir'] = config.cacheDir;\r\n }\r\n\r\n return merged;\r\n}\r\n","/**\r\n * @xrmforge/cli - Generate Command\r\n *\r\n * Orchestrates type generation from a Dataverse environment.\r\n *\r\n * Usage:\r\n * xrmforge generate --url https://myorg.crm4.dynamics.com \\\r\n * --auth client-credentials \\\r\n * --tenant <tenant-id> --client-id <app-id> --client-secret <secret> \\\r\n * --entities account,contact \\\r\n * --output ./typings\r\n *\r\n * xrmforge generate --url https://myorg.crm4.dynamics.com \\\r\n * --auth interactive \\\r\n * --tenant <tenant-id> --client-id <app-id> \\\r\n * --entities account,contact,opportunity \\\r\n * --output ./typings \\\r\n * --label-language 1033 --secondary-language 1031\r\n */\r\n\r\nimport type { Command } from 'commander';\r\nimport { loadConfig, mergeWithCliOptions } from '../config.js';\r\nimport {\r\n TypeGenerationOrchestrator,\r\n createCredential,\r\n configureLogging,\r\n ConsoleLogSink,\r\n LogLevel,\r\n} from '@xrmforge/typegen';\r\nimport type { AuthConfig } from '@xrmforge/typegen';\r\n\r\n/** CLI options for the generate command */\r\ninterface GenerateOptions {\r\n url: string;\r\n auth: string;\r\n tenantId?: string;\r\n clientId?: string;\r\n clientSecret?: string;\r\n token?: string;\r\n entities?: string;\r\n solutions?: string;\r\n output: string;\r\n labelLanguage: string;\r\n secondaryLanguage?: string;\r\n forms: boolean;\r\n optionsets: boolean;\r\n actions: boolean;\r\n actionsFilter?: string;\r\n cache: boolean;\r\n cacheDir: string;\r\n verbose: boolean;\r\n}\r\n\r\n/**\r\n * Register the 'generate' subcommand on the CLI program.\r\n */\r\nexport function registerGenerateCommand(program: Command): void {\r\n program\r\n .command('generate')\r\n .description('Generate TypeScript declarations from a Dataverse environment')\r\n\r\n // Connection (can come from xrmforge.config.json)\r\n .option('--url <url>', 'Dataverse environment URL (e.g. https://myorg.crm4.dynamics.com)')\r\n .option('--auth <method>', 'Authentication method: client-credentials, interactive, device-code, token')\r\n\r\n // Auth credentials\r\n .option('--tenant-id <id>', 'Azure AD tenant ID')\r\n .option('--client-id <id>', 'Azure AD application (client) ID')\r\n .option('--client-secret <secret>', 'Client secret (for client-credentials auth)')\r\n .option('--token <token>', 'Pre-acquired Bearer token (for --auth token). Prefer XRMFORGE_TOKEN env var for security.')\r\n\r\n // Scope\r\n .option('--entities <list>', 'Comma-separated list of entity logical names (e.g. account,contact)')\r\n .option('--solutions <list>', 'Comma-separated solution unique names to discover entities')\r\n\r\n // Output\r\n .option('--output <dir>', 'Output directory for generated .d.ts files', './typings')\r\n\r\n // Labels\r\n .option('--label-language <code>', 'Primary label language code', '1033')\r\n .option('--secondary-language <code>', 'Secondary label language code (for dual-language JSDoc)')\r\n\r\n // Feature toggles\r\n .option('--no-forms', 'Skip form interface generation')\r\n .option('--no-optionsets', 'Skip OptionSet enum generation')\r\n .option('--actions', 'Generate Custom API Action/Function executors', false)\r\n .option('--actions-filter <prefix>', 'Only generate Custom APIs whose uniquename starts with this prefix (e.g. \"markant_\")')\r\n\r\n // Cache\r\n .option('--cache', 'Enable metadata cache for incremental generation', false)\r\n .option('--no-cache', 'Force full metadata refresh (disables cache)')\r\n .option('--cache-dir <dir>', 'Directory for metadata cache files', '.xrmforge/cache')\r\n\r\n // Verbosity\r\n .option('-v, --verbose', 'Enable verbose logging', false)\r\n\r\n .action(async (opts: GenerateOptions) => {\r\n try {\r\n await runGenerate(opts);\r\n } catch (error) {\r\n if (error instanceof Error) {\r\n console.error(`\\nError: ${error.message}\\n`);\r\n if (opts.verbose && error.stack) {\r\n console.error(error.stack);\r\n }\r\n } else {\r\n console.error('\\nAn unexpected error occurred.\\n');\r\n }\r\n process.exitCode = 1;\r\n }\r\n });\r\n}\r\n\r\n/**\r\n * Execute the generate command.\r\n */\r\nasync function runGenerate(cliOpts: GenerateOptions): Promise<void> {\r\n // Load config file and merge with CLI options (CLI takes precedence)\r\n const fileConfig = loadConfig();\r\n const merged = mergeWithCliOptions(fileConfig, cliOpts as unknown as Record<string, unknown>);\r\n const opts = merged as unknown as GenerateOptions;\r\n\r\n // Configure logging\r\n configureLogging({\r\n sink: new ConsoleLogSink(),\r\n minLevel: opts.verbose ? LogLevel.DEBUG : LogLevel.INFO,\r\n });\r\n\r\n // Validate required options (may come from config file)\r\n if (!opts.url) {\r\n throw new Error('--url is required. Set it via CLI flag or in xrmforge.config.json.');\r\n }\r\n if (!opts.auth) {\r\n throw new Error('--auth is required. Set it via CLI flag or in xrmforge.config.json.');\r\n }\r\n if (!opts.entities && !opts.solutions) {\r\n throw new Error('Either --entities or --solutions must be specified (CLI or xrmforge.config.json).');\r\n }\r\n\r\n // Build auth config\r\n const authConfig = buildAuthConfig(opts);\r\n const credential = createCredential(authConfig);\r\n\r\n // Parse entity list\r\n const entities = opts.entities\r\n ? opts.entities.split(',').map((e) => e.trim().toLowerCase())\r\n : [];\r\n\r\n // Parse solutions list\r\n const solutionNames = opts.solutions\r\n ? opts.solutions.split(',').map((s) => s.trim())\r\n : [];\r\n\r\n if (entities.length === 0 && solutionNames.length === 0) {\r\n throw new Error('No entities specified. Use --entities or --solutions.');\r\n }\r\n\r\n // Build label config (R8-05: validate LCID)\r\n const primaryLanguage = parseInt(opts.labelLanguage, 10);\r\n if (isNaN(primaryLanguage)) {\r\n throw new Error(`Invalid --label-language: \"${opts.labelLanguage}\". Must be a numeric LCID (e.g. 1033, 1031).`);\r\n }\r\n let secondaryLanguage: number | undefined;\r\n if (opts.secondaryLanguage) {\r\n secondaryLanguage = parseInt(opts.secondaryLanguage, 10);\r\n if (isNaN(secondaryLanguage)) {\r\n throw new Error(`Invalid --secondary-language: \"${opts.secondaryLanguage}\". Must be a numeric LCID (e.g. 1033, 1031).`);\r\n }\r\n }\r\n\r\n console.log(`\\nXrmForge Type Generator`);\r\n console.log(`Environment: ${opts.url}`);\r\n console.log(`Auth method: ${opts.auth}`);\r\n console.log(`Entities: ${entities.length > 0 ? entities.join(', ') : '(none specified directly)'}`)\r\n if (solutionNames.length > 0) {\r\n console.log(`Solutions: ${solutionNames.join(', ')}`);\r\n }\r\n console.log(`Output: ${opts.output}`);\r\n console.log(`Languages: ${primaryLanguage}${secondaryLanguage ? ` + ${secondaryLanguage}` : ''}`);\r\n if (opts.cache) {\r\n console.log(`Cache: enabled (${opts.cacheDir})`);\r\n }\r\n console.log('');\r\n\r\n // Create orchestrator and run\r\n const orchestrator = new TypeGenerationOrchestrator(credential, {\r\n environmentUrl: opts.url,\r\n entities,\r\n solutionNames: solutionNames.length > 0 ? solutionNames : undefined,\r\n outputDir: opts.output,\r\n labelConfig: { primaryLanguage, secondaryLanguage },\r\n generateForms: opts.forms,\r\n generateOptionSets: opts.optionsets,\r\n generateActions: opts.actions,\r\n actionsFilter: opts.actionsFilter,\r\n useCache: opts.cache,\r\n cacheDir: opts.cacheDir,\r\n });\r\n\r\n // Support Ctrl+C and SIGTERM (R8-07: Docker/K8s sends SIGTERM)\r\n const controller = new AbortController();\r\n const onSignal = () => {\r\n console.log('\\nAborting generation...');\r\n controller.abort();\r\n };\r\n process.once('SIGINT', onSignal);\r\n process.once('SIGTERM', onSignal);\r\n\r\n const result = await orchestrator.generate({ signal: controller.signal });\r\n\r\n // Summary\r\n console.log('');\r\n console.log('Generation complete:');\r\n console.log(` Entities: ${result.entities.length}`);\r\n console.log(` Files: ${result.totalFiles}`);\r\n console.log(` Warnings: ${result.totalWarnings}`);\r\n console.log(` Duration: ${result.durationMs}ms`);\r\n if (result.cacheStats) {\r\n const cs = result.cacheStats;\r\n if (cs.fullRefresh) {\r\n console.log(` Cache: full refresh (${cs.entitiesFetched} entities fetched)`);\r\n } else {\r\n console.log(` Cache: ${cs.entitiesFromCache} from cache, ${cs.entitiesFetched} fetched, ${cs.entitiesDeleted} deleted`);\r\n }\r\n }\r\n\r\n // Show warnings\r\n if (result.totalWarnings > 0) {\r\n console.log('\\nWarnings:');\r\n for (const entity of result.entities) {\r\n for (const warning of entity.warnings) {\r\n console.log(` [${entity.entityLogicalName}] ${warning}`);\r\n }\r\n }\r\n }\r\n\r\n // Show failures\r\n const failures = result.entities.filter((e) => e.files.length === 0 && e.warnings.length > 0);\r\n if (failures.length > 0) {\r\n console.log(`\\n${failures.length} entity/entities failed. See warnings above.`);\r\n process.exitCode = 1;\r\n return;\r\n }\r\n\r\n console.log(`\\nTypes written to: ${opts.output}/`);\r\n}\r\n\r\n/**\r\n * Build AuthConfig from CLI options.\r\n */\r\nfunction buildAuthConfig(opts: GenerateOptions): AuthConfig {\r\n const method = opts.auth as AuthConfig['method'];\r\n\r\n switch (method) {\r\n case 'client-credentials':\r\n if (!opts.tenantId) throw new Error('--tenant-id is required for client-credentials auth.');\r\n if (!opts.clientId) throw new Error('--client-id is required for client-credentials auth.');\r\n if (!opts.clientSecret) throw new Error('--client-secret is required for client-credentials auth.');\r\n return {\r\n method: 'client-credentials',\r\n tenantId: opts.tenantId,\r\n clientId: opts.clientId,\r\n clientSecret: opts.clientSecret,\r\n };\r\n\r\n case 'interactive':\r\n if (!opts.tenantId) throw new Error('--tenant-id is required for interactive auth.');\r\n if (!opts.clientId) throw new Error('--client-id is required for interactive auth.');\r\n return {\r\n method: 'interactive',\r\n tenantId: opts.tenantId,\r\n clientId: opts.clientId,\r\n };\r\n\r\n case 'device-code':\r\n if (!opts.tenantId) throw new Error('--tenant-id is required for device-code auth.');\r\n if (!opts.clientId) throw new Error('--client-id is required for device-code auth.');\r\n return {\r\n method: 'device-code',\r\n tenantId: opts.tenantId,\r\n clientId: opts.clientId,\r\n };\r\n\r\n case 'token': {\r\n // Token from --token flag or XRMFORGE_TOKEN environment variable\r\n const token = opts.token || process.env['XRMFORGE_TOKEN'];\r\n if (!token) {\r\n throw new Error(\r\n 'Token authentication requires a token. ' +\r\n 'Set XRMFORGE_TOKEN environment variable or use --token flag.',\r\n );\r\n }\r\n if (opts.token) {\r\n console.warn('WARNING: Using --token on the command line exposes the token in process list and shell history.');\r\n console.warn(' Prefer setting XRMFORGE_TOKEN environment variable instead.\\n');\r\n }\r\n return { method: 'token', token };\r\n }\r\n\r\n default:\r\n throw new Error(\r\n `Unknown auth method: \"${opts.auth}\". ` +\r\n `Supported: client-credentials, interactive, device-code, token`,\r\n );\r\n }\r\n}\r\n"],"mappings":"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","names":["readFileSync","join","program","readFileSync","join"]}
package/package.json CHANGED
@@ -1,53 +1,54 @@
1
- {
2
- "name": "@xrmforge/cli",
3
- "version": "0.2.0",
4
- "description": "CLI for XrmForge - TypeScript type generator for Dynamics 365",
5
- "keywords": [
6
- "dynamics-365",
7
- "typescript",
8
- "xrm",
9
- "dataverse",
10
- "cli",
11
- "code-generation"
12
- ],
13
- "license": "MIT",
14
- "author": "XrmForge Contributors",
15
- "homepage": "https://github.com/juergenbeck/XrmForge/tree/main/packages/cli",
16
- "repository": {
17
- "type": "git",
18
- "url": "https://github.com/juergenbeck/XrmForge.git",
19
- "directory": "packages/cli"
20
- },
21
- "bugs": {
22
- "url": "https://github.com/juergenbeck/XrmForge/issues"
23
- },
24
- "type": "module",
25
- "bin": {
26
- "xrmforge": "./dist/index.js"
27
- },
28
- "files": [
29
- "dist"
30
- ],
31
- "scripts": {
32
- "build": "tsup",
33
- "dev": "tsup --watch",
34
- "test": "vitest run",
35
- "test:watch": "vitest",
36
- "typecheck": "tsc --noEmit",
37
- "lint": "eslint src/",
38
- "clean": "rm -rf dist"
39
- },
40
- "dependencies": {
41
- "@xrmforge/typegen": "workspace:*",
42
- "commander": "^13.0.0"
43
- },
44
- "devDependencies": {
45
- "@types/node": "^22.0.0",
46
- "tsup": "^8.3.0",
47
- "typescript": "^5.7.0",
48
- "vitest": "^3.0.0"
49
- },
50
- "engines": {
51
- "node": ">=20.0.0"
52
- }
53
- }
1
+ {
2
+ "name": "@xrmforge/cli",
3
+ "version": "0.3.0",
4
+ "description": "CLI for XrmForge - TypeScript type generator for Dynamics 365",
5
+ "keywords": [
6
+ "dynamics-365",
7
+ "typescript",
8
+ "xrm",
9
+ "dataverse",
10
+ "cli",
11
+ "code-generation"
12
+ ],
13
+ "license": "MIT",
14
+ "author": "XrmForge Contributors",
15
+ "homepage": "https://github.com/juergenbeck/XrmForge/tree/main/packages/cli",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/juergenbeck/XrmForge.git",
19
+ "directory": "packages/cli"
20
+ },
21
+ "bugs": {
22
+ "url": "https://github.com/juergenbeck/XrmForge/issues"
23
+ },
24
+ "type": "module",
25
+ "bin": {
26
+ "xrmforge": "./dist/index.js"
27
+ },
28
+ "files": [
29
+ "dist"
30
+ ],
31
+ "scripts": {
32
+ "build": "tsup",
33
+ "dev": "tsup --watch",
34
+ "test": "vitest run",
35
+ "test:watch": "vitest",
36
+ "typecheck": "tsc --noEmit",
37
+ "lint": "eslint src/",
38
+ "clean": "rm -rf dist"
39
+ },
40
+ "dependencies": {
41
+ "@xrmforge/typegen": "workspace:*",
42
+ "commander": "^13.0.0"
43
+ },
44
+ "devDependencies": {
45
+ "@types/node": "^22.0.0",
46
+ "@vitest/coverage-v8": "^3.2.4",
47
+ "tsup": "^8.3.0",
48
+ "typescript": "^5.7.0",
49
+ "vitest": "^3.0.0"
50
+ },
51
+ "engines": {
52
+ "node": ">=20.0.0"
53
+ }
54
+ }