@xrmforge/cli 0.6.1 → 0.7.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/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 XrmForge Contributors
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 XrmForge Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.js CHANGED
@@ -71,6 +71,25 @@ function mergeWithCliOptions(config, cliOpts) {
71
71
  }
72
72
  return merged;
73
73
  }
74
+ var ENV_VAR_MAP = [
75
+ ["url", "XRMFORGE_URL"],
76
+ ["tenantId", "XRMFORGE_TENANT_ID"],
77
+ ["clientId", "XRMFORGE_CLIENT_ID"],
78
+ ["clientSecret", "XRMFORGE_CLIENT_SECRET"],
79
+ ["token", "XRMFORGE_TOKEN"]
80
+ ];
81
+ function applyEnvDefaults(cliOpts, env = process.env) {
82
+ const merged = { ...cliOpts };
83
+ for (const [optionKey, envVar] of ENV_VAR_MAP) {
84
+ if (merged[optionKey] === void 0 || merged[optionKey] === null) {
85
+ const value = env[envVar];
86
+ if (value !== void 0 && value !== "") {
87
+ merged[optionKey] = value;
88
+ }
89
+ }
90
+ }
91
+ return merged;
92
+ }
74
93
 
75
94
  // src/commands/generate.ts
76
95
  import {
@@ -84,7 +103,7 @@ import {
84
103
  ErrorCode as ErrorCode2
85
104
  } from "@xrmforge/typegen";
86
105
  function registerGenerateCommand(program2) {
87
- 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 .ts files", "./generated").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").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(
106
+ program2.command("generate").description("Generate TypeScript declarations from a Dataverse environment").option("--url <url>", "Dataverse environment URL (e.g. https://myorg.crm4.dynamics.com). Falls back to XRMFORGE_URL.").option("--auth <method>", "Authentication method: client-credentials, interactive, device-code, token").option("--tenant-id <id>", "Azure AD tenant ID. Falls back to XRMFORGE_TENANT_ID.").option("--client-id <id>", "Azure AD application (client) ID. Falls back to XRMFORGE_CLIENT_ID.").option("--client-secret <secret>", "Client secret (for client-credentials auth). Prefer the XRMFORGE_CLIENT_SECRET env var over this flag.").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 .ts files", "./generated").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").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(
88
107
  "--check",
89
108
  "Drift check: generate in-memory and compare against the output directory without writing anything. Exit code: 0 = up to date, 1 = error, 2 = drift detected. Intended as a CI step. Ignores --cache.",
90
109
  false
@@ -108,8 +127,16 @@ Error: ${error.message}
108
127
  }
109
128
  async function runGenerate(cliOpts) {
110
129
  const fileConfig = loadConfig();
111
- const merged = mergeWithCliOptions(fileConfig, cliOpts);
130
+ const cliWithEnv = applyEnvDefaults(cliOpts);
131
+ const merged = mergeWithCliOptions(fileConfig, cliWithEnv);
112
132
  const opts = merged;
133
+ if (cliOpts.clientSecret) {
134
+ console.warn("WARNING: Passing --client-secret via CLI exposes it in shell history and the process list. Use the XRMFORGE_CLIENT_SECRET environment variable instead.");
135
+ }
136
+ if (cliOpts.token) {
137
+ console.warn("WARNING: Using --token on the command line exposes the token in the process list and shell history.");
138
+ console.warn(" Prefer setting the XRMFORGE_TOKEN environment variable instead.\n");
139
+ }
113
140
  configureLogging({
114
141
  sink: new ConsoleLogSink(),
115
142
  minLevel: opts.verbose ? LogLevel.DEBUG : LogLevel.INFO
@@ -257,12 +284,9 @@ function buildAuthConfig(opts) {
257
284
  const method = opts.auth;
258
285
  switch (method) {
259
286
  case "client-credentials":
260
- if (!opts.tenantId) throw new AuthenticationError(ErrorCode2.AUTH_MISSING_CONFIG, "--tenant-id is required for client-credentials auth.", { method: "client-credentials", missing: "tenantId" });
261
- if (!opts.clientId) throw new AuthenticationError(ErrorCode2.AUTH_MISSING_CONFIG, "--client-id is required for client-credentials auth.", { method: "client-credentials", missing: "clientId" });
262
- if (!opts.clientSecret) throw new AuthenticationError(ErrorCode2.AUTH_MISSING_CONFIG, "--client-secret is required for client-credentials auth.", { method: "client-credentials", missing: "clientSecret" });
263
- if (opts.clientSecret) {
264
- console.warn("WARNING: Passing --client-secret via CLI exposes it in shell history. Use XRMFORGE_CLIENT_SECRET environment variable instead.");
265
- }
287
+ if (!opts.tenantId) throw new AuthenticationError(ErrorCode2.AUTH_MISSING_CONFIG, "--tenant-id is required for client-credentials auth (or set XRMFORGE_TENANT_ID).", { method: "client-credentials", missing: "tenantId" });
288
+ if (!opts.clientId) throw new AuthenticationError(ErrorCode2.AUTH_MISSING_CONFIG, "--client-id is required for client-credentials auth (or set XRMFORGE_CLIENT_ID).", { method: "client-credentials", missing: "clientId" });
289
+ if (!opts.clientSecret) throw new AuthenticationError(ErrorCode2.AUTH_MISSING_CONFIG, "--client-secret is required for client-credentials auth (or set XRMFORGE_CLIENT_SECRET).", { method: "client-credentials", missing: "clientSecret" });
266
290
  return {
267
291
  method: "client-credentials",
268
292
  tenantId: opts.tenantId,
@@ -270,35 +294,30 @@ function buildAuthConfig(opts) {
270
294
  clientSecret: opts.clientSecret
271
295
  };
272
296
  case "interactive":
273
- if (!opts.tenantId) throw new AuthenticationError(ErrorCode2.AUTH_MISSING_CONFIG, "--tenant-id is required for interactive auth.", { method: "interactive", missing: "tenantId" });
274
- if (!opts.clientId) throw new AuthenticationError(ErrorCode2.AUTH_MISSING_CONFIG, "--client-id is required for interactive auth.", { method: "interactive", missing: "clientId" });
297
+ if (!opts.tenantId) throw new AuthenticationError(ErrorCode2.AUTH_MISSING_CONFIG, "--tenant-id is required for interactive auth (or set XRMFORGE_TENANT_ID).", { method: "interactive", missing: "tenantId" });
298
+ if (!opts.clientId) throw new AuthenticationError(ErrorCode2.AUTH_MISSING_CONFIG, "--client-id is required for interactive auth (or set XRMFORGE_CLIENT_ID).", { method: "interactive", missing: "clientId" });
275
299
  return {
276
300
  method: "interactive",
277
301
  tenantId: opts.tenantId,
278
302
  clientId: opts.clientId
279
303
  };
280
304
  case "device-code":
281
- if (!opts.tenantId) throw new AuthenticationError(ErrorCode2.AUTH_MISSING_CONFIG, "--tenant-id is required for device-code auth.", { method: "device-code", missing: "tenantId" });
282
- if (!opts.clientId) throw new AuthenticationError(ErrorCode2.AUTH_MISSING_CONFIG, "--client-id is required for device-code auth.", { method: "device-code", missing: "clientId" });
305
+ if (!opts.tenantId) throw new AuthenticationError(ErrorCode2.AUTH_MISSING_CONFIG, "--tenant-id is required for device-code auth (or set XRMFORGE_TENANT_ID).", { method: "device-code", missing: "tenantId" });
306
+ if (!opts.clientId) throw new AuthenticationError(ErrorCode2.AUTH_MISSING_CONFIG, "--client-id is required for device-code auth (or set XRMFORGE_CLIENT_ID).", { method: "device-code", missing: "clientId" });
283
307
  return {
284
308
  method: "device-code",
285
309
  tenantId: opts.tenantId,
286
310
  clientId: opts.clientId
287
311
  };
288
312
  case "token": {
289
- const token = opts.token || process.env["XRMFORGE_TOKEN"];
290
- if (!token) {
313
+ if (!opts.token) {
291
314
  throw new AuthenticationError(
292
315
  ErrorCode2.AUTH_MISSING_CONFIG,
293
- "Token authentication requires a token. Set XRMFORGE_TOKEN environment variable or use --token flag.",
316
+ "Token authentication requires a token. Set the XRMFORGE_TOKEN environment variable or use the --token flag.",
294
317
  { method: "token", missing: "token" }
295
318
  );
296
319
  }
297
- if (opts.token) {
298
- console.warn("WARNING: Using --token on the command line exposes the token in process list and shell history.");
299
- console.warn(" Prefer setting XRMFORGE_TOKEN environment variable instead.\n");
300
- }
301
- return { method: "token", token };
320
+ return { method: "token", token: opts.token };
302
321
  }
303
322
  default:
304
323
  throw new ConfigError2(
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/config.ts","../src/commands/generate.ts","../src/commands/build.ts","../src/commands/init.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\nimport { registerBuildCommand } from './commands/build.js';\r\nimport { registerInitCommand } from './commands/init.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\nregisterBuildCommand(program);\r\nregisterInitCommand(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\nimport type { BuildConfig } from '@xrmforge/devkit';\r\nimport { ConfigError, ErrorCode } from '@xrmforge/typegen';\r\n\r\n/**\r\n * Shape of xrmforge.config.json.\r\n *\r\n * Combines generate options (url, auth, entities) with build configuration.\r\n * All fields are optional since they can be provided via CLI flags.\r\n */\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 /** Generate Custom API Action/Function executors */\r\n actions?: boolean;\r\n /** Only generate Custom APIs whose uniquename starts with this prefix (e.g. \"markant_\") */\r\n actionsFilter?: string;\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 /** Build configuration for WebResource bundling */\r\n build?: BuildConfig;\r\n}\r\n\r\nconst CONFIG_FILENAME = 'xrmforge.config.json';\r\n\r\n/**\r\n * Load configuration from xrmforge.config.json in the given working directory.\r\n *\r\n * Returns an empty object if the file does not exist.\r\n * Throws a {@link ConfigError} if the file exists but contains invalid JSON.\r\n * Emits a warning to stderr if clientSecret is found in the config file.\r\n *\r\n * @param cwd - Working directory to search for xrmforge.config.json (defaults to process.cwd())\r\n * @returns Parsed configuration object, or empty object if no config file exists\r\n * @throws {ConfigError} If the config file contains 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: unknown) {\r\n if (error instanceof SyntaxError) {\r\n throw new ConfigError(ErrorCode.CONFIG_INVALID, `Invalid JSON in ${configPath}: ${error.message}`, { file: configPath });\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 *\r\n * CLI flags always take precedence over config file values. Only fills\r\n * in config values for options not explicitly set via CLI. Handles type\r\n * conversions (e.g. config arrays to CLI comma-separated strings, numeric\r\n * LCIDs to string representations).\r\n *\r\n * @param config - Parsed xrmforge.config.json values\r\n * @param cliOpts - CLI options parsed by Commander.js\r\n * @returns Merged options with CLI taking precedence\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 if (merged['actions'] === undefined && config.actions !== undefined) {\r\n merged['actions'] = config.actions;\r\n }\r\n\r\n // Custom API filter (string): CLI takes precedence over config\r\n if (!merged['actionsFilter'] && config.actionsFilter) {\r\n merged['actionsFilter'] = config.actionsFilter;\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 ./generated\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 ./generated \\\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 AuthenticationError,\r\n ConfigError,\r\n ErrorCode,\r\n} from '@xrmforge/typegen';\r\nimport type { AuthConfig, CheckResult, CheckFinding } from '@xrmforge/typegen';\r\n\r\n/** CLI options for the generate command (parsed by Commander.js). */\r\ninterface GenerateOptions {\r\n /** Dataverse environment URL (e.g. 'https://myorg.crm4.dynamics.com') */\r\n url: string;\r\n /** Authentication method ('client-credentials', 'interactive', 'device-code', 'token') */\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 for client-credentials auth */\r\n clientSecret?: string;\r\n /** Pre-acquired Bearer token for token auth */\r\n token?: string;\r\n /** Comma-separated entity logical names */\r\n entities?: string;\r\n /** Comma-separated solution unique names */\r\n solutions?: string;\r\n /** Output directory for generated .ts files */\r\n output: string;\r\n /** Primary label language LCID as string */\r\n labelLanguage: string;\r\n /** Secondary label language LCID as string */\r\n secondaryLanguage?: string;\r\n /** Whether to generate form interfaces */\r\n forms: boolean;\r\n /** Whether to generate OptionSet enums */\r\n optionsets: boolean;\r\n /** Whether to generate Custom API action executors */\r\n actions: boolean;\r\n /** Prefix filter for Custom API generation */\r\n actionsFilter?: string;\r\n /** Whether to enable metadata caching */\r\n cache: boolean;\r\n /** Directory for metadata cache files */\r\n cacheDir: string;\r\n /** Drift check mode: compare against outputDir without writing (exit 0/1/2) */\r\n check: boolean;\r\n /** Whether to enable verbose (debug) logging */\r\n verbose: boolean;\r\n}\r\n\r\n/**\r\n * Register the 'generate' subcommand on the CLI program.\r\n *\r\n * Adds options for Dataverse connection, authentication, entity scope,\r\n * output directory, label languages, feature toggles, and caching.\r\n *\r\n * @param program - The Commander.js program instance to register on\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 .ts files', './generated')\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 // No Commander default for --actions: it must stay `undefined` when not passed\r\n // so a value from xrmforge.config.json can take effect (mergeWithCliOptions).\r\n // The orchestrator defaults generateActions to false, so CLI-only behavior is unchanged.\r\n .option('--actions', 'Generate Custom API Action/Function executors')\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 // Drift check\r\n .option(\r\n '--check',\r\n 'Drift check: generate in-memory and compare against the output directory without writing anything. ' +\r\n 'Exit code: 0 = up to date, 1 = error, 2 = drift detected. Intended as a CI step. Ignores --cache.',\r\n false,\r\n )\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: unknown) {\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: validate options, authenticate, and run\r\n * the type generation orchestrator.\r\n *\r\n * @param cliOpts - Parsed CLI options merged with config file values\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 ConfigError(ErrorCode.CONFIG_INVALID, '--url is required. Set it via CLI flag or in xrmforge.config.json.', { option: 'url' });\r\n }\r\n if (!opts.auth) {\r\n throw new ConfigError(ErrorCode.CONFIG_INVALID, '--auth is required. Set it via CLI flag or in xrmforge.config.json.', { option: 'auth' });\r\n }\r\n if (!opts.entities && !opts.solutions) {\r\n throw new ConfigError(ErrorCode.CONFIG_INVALID, 'Either --entities or --solutions must be specified (CLI or xrmforge.config.json).', { option: 'entities' });\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 ConfigError(ErrorCode.CONFIG_INVALID, 'No entities specified. Use --entities or --solutions.', { option: 'entities' });\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 ConfigError(ErrorCode.CONFIG_INVALID, `Invalid --label-language: \"${opts.labelLanguage}\". Must be a numeric LCID (e.g. 1033, 1031).`, { option: 'labelLanguage', value: opts.labelLanguage });\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 ConfigError(ErrorCode.CONFIG_INVALID, `Invalid --secondary-language: \"${opts.secondaryLanguage}\". Must be a numeric LCID (e.g. 1033, 1031).`, { option: 'secondaryLanguage', value: opts.secondaryLanguage });\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.check) {\r\n console.log(`Mode: drift check (read-only, nothing will be written)`);\r\n if (opts.cache) {\r\n console.warn(`Note: --cache is ignored in check mode (the check must run against live metadata)`);\r\n }\r\n } else 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 checkOnly: opts.check,\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.warn('\\nWarnings:');\r\n for (const entity of result.entities) {\r\n for (const warning of entity.warnings) {\r\n console.warn(` [${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.error(`\\n${failures.length} entity/entities failed. See warnings above.`);\r\n process.exitCode = 1;\r\n return;\r\n }\r\n\r\n // Drift check report (check mode never writes anything)\r\n if (opts.check) {\r\n if (!result.checkResult) {\r\n // Defensive: without a complete generation there is no reliable comparison\r\n console.error('\\nDrift check could not be completed (generation incomplete).');\r\n process.exitCode = 1;\r\n return;\r\n }\r\n printCheckReport(result.checkResult);\r\n if (result.checkResult.drift) {\r\n process.exitCode = 2;\r\n }\r\n return;\r\n }\r\n\r\n console.log(`\\nTypes written to: ${opts.output}/`);\r\n}\r\n\r\n/** Display labels per file category, in report order */\r\nconst CHECK_CATEGORY_LABELS: ReadonlyArray<readonly [CheckFinding['type'], string]> = [\r\n ['entity', 'Entities'],\r\n ['fields', 'Fields'],\r\n ['form', 'Forms'],\r\n ['optionset', 'OptionSets'],\r\n ['action', 'Actions'],\r\n];\r\n\r\n/**\r\n * Print the drift check report, grouped by file category.\r\n *\r\n * Drift classes per file: \"changed\" (differs from live metadata),\r\n * \"missing\" (not on disk), \"orphaned\" (no longer generated).\r\n */\r\nfunction printCheckReport(check: CheckResult): void {\r\n console.log('');\r\n if (!check.drift) {\r\n console.log(`Drift check passed: ${check.unchanged} files up to date.`);\r\n return;\r\n }\r\n\r\n console.log(`Drift detected: ${check.findings.length} finding(s), ${check.unchanged} files up to date.`);\r\n for (const [type, label] of CHECK_CATEGORY_LABELS) {\r\n const findings = check.findings.filter((f) => f.type === type);\r\n if (findings.length === 0) continue;\r\n console.log(` ${label}:`);\r\n for (const finding of findings) {\r\n console.log(` ${finding.status.padEnd(8)} ${finding.relativePath}`);\r\n }\r\n }\r\n console.log('');\r\n console.log('The checked-in generated files no longer match the live environment.');\r\n console.log('Run \"xrmforge generate\" (same options, without --check) and commit the result.');\r\n console.log('Note: a typegen/cli upgrade also reports drift (newer generator, different output).');\r\n}\r\n\r\n/**\r\n * Build an {@link AuthConfig} from parsed CLI options.\r\n *\r\n * Maps the --auth method to the appropriate credential configuration\r\n * and validates that required fields are present for each method.\r\n *\r\n * @param opts - Parsed CLI options containing auth method and credentials\r\n * @returns Validated authentication configuration\r\n * @throws {AuthenticationError} If required credentials are missing for the chosen method\r\n * @throws {ConfigError} If the auth method is unrecognized\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 AuthenticationError(ErrorCode.AUTH_MISSING_CONFIG, '--tenant-id is required for client-credentials auth.', { method: 'client-credentials', missing: 'tenantId' });\r\n if (!opts.clientId) throw new AuthenticationError(ErrorCode.AUTH_MISSING_CONFIG, '--client-id is required for client-credentials auth.', { method: 'client-credentials', missing: 'clientId' });\r\n if (!opts.clientSecret) throw new AuthenticationError(ErrorCode.AUTH_MISSING_CONFIG, '--client-secret is required for client-credentials auth.', { method: 'client-credentials', missing: 'clientSecret' });\r\n if (opts.clientSecret) {\r\n console.warn('WARNING: Passing --client-secret via CLI exposes it in shell history. Use XRMFORGE_CLIENT_SECRET environment variable instead.');\r\n }\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 AuthenticationError(ErrorCode.AUTH_MISSING_CONFIG, '--tenant-id is required for interactive auth.', { method: 'interactive', missing: 'tenantId' });\r\n if (!opts.clientId) throw new AuthenticationError(ErrorCode.AUTH_MISSING_CONFIG, '--client-id is required for interactive auth.', { method: 'interactive', missing: 'clientId' });\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 AuthenticationError(ErrorCode.AUTH_MISSING_CONFIG, '--tenant-id is required for device-code auth.', { method: 'device-code', missing: 'tenantId' });\r\n if (!opts.clientId) throw new AuthenticationError(ErrorCode.AUTH_MISSING_CONFIG, '--client-id is required for device-code auth.', { method: 'device-code', missing: 'clientId' });\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 AuthenticationError(\r\n ErrorCode.AUTH_MISSING_CONFIG,\r\n 'Token authentication requires a token. ' +\r\n 'Set XRMFORGE_TOKEN environment variable or use --token flag.',\r\n { method: 'token', missing: 'token' },\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 ConfigError(\r\n ErrorCode.CONFIG_INVALID,\r\n `Unknown auth method: \"${opts.auth}\". ` +\r\n `Supported: client-credentials, interactive, device-code, token`,\r\n { option: 'auth', value: opts.auth },\r\n );\r\n }\r\n}\r\n","/**\r\n * @xrmforge/cli - Build Command\r\n *\r\n * Builds WebResources as IIFE bundles for Dynamics 365.\r\n *\r\n * Usage:\r\n * xrmforge build # Build all entries from xrmforge.config.json\r\n * xrmforge build --watch # Watch mode with incremental rebuilds\r\n * xrmforge build --minify # Minify output bundles\r\n * xrmforge build --no-sourcemap # Disable source maps\r\n */\r\n\r\nimport type { Command } from 'commander';\r\nimport { loadConfig } from '../config.js';\r\nimport {\r\n validateBuildConfig,\r\n build,\r\n watch,\r\n} from '@xrmforge/devkit';\r\nimport { ConfigError, ErrorCode } from '@xrmforge/typegen';\r\n\r\n/** CLI options for the build command (parsed by Commander.js). */\r\ninterface BuildOptions {\r\n /** Whether to run in watch mode with incremental rebuilds */\r\n watch: boolean;\r\n /** Whether to minify output bundles */\r\n minify?: boolean;\r\n /** Whether to generate source maps (default: true) */\r\n sourcemap: boolean;\r\n /** Override output directory from config */\r\n outDir?: string;\r\n /** Whether to enable verbose logging */\r\n verbose: boolean;\r\n}\r\n\r\n/**\r\n * Register the 'build' subcommand on the CLI program.\r\n *\r\n * Adds options for watch mode, minification, source maps,\r\n * output directory override, and verbose logging.\r\n *\r\n * @param program - The Commander.js program instance to register on\r\n */\r\nexport function registerBuildCommand(program: Command): void {\r\n program\r\n .command('build')\r\n .description('Build WebResources as IIFE bundles for Dynamics 365')\r\n\r\n .option('--watch', 'Watch mode with incremental rebuilds', false)\r\n .option('--minify', 'Minify output bundles')\r\n .option('--no-sourcemap', 'Disable source maps')\r\n .option('--out-dir <dir>', 'Override output directory')\r\n .option('-v, --verbose', 'Verbose logging', false)\r\n\r\n .action(async (opts: BuildOptions) => {\r\n try {\r\n await runBuild(opts);\r\n } catch (error: unknown) {\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 build command: load config, validate, and invoke esbuild\r\n * for a single build or watch mode.\r\n *\r\n * @param opts - Parsed CLI options for the build command\r\n */\r\nasync function runBuild(opts: BuildOptions): Promise<void> {\r\n const fileConfig = loadConfig();\r\n\r\n if (!fileConfig.build) {\r\n throw new ConfigError(\r\n ErrorCode.CONFIG_INVALID,\r\n 'No \"build\" section found in xrmforge.config.json.\\n' +\r\n 'Add a build configuration with entries to get started:\\n\\n' +\r\n ' {\\n' +\r\n ' \"build\": {\\n' +\r\n ' \"entries\": {\\n' +\r\n ' \"my_script\": {\\n' +\r\n ' \"input\": \"./src/my-script.ts\",\\n' +\r\n ' \"namespace\": \"Contoso.MyScript\"\\n' +\r\n ' }\\n' +\r\n ' }\\n' +\r\n ' }\\n' +\r\n ' }\\n',\r\n { section: 'build' },\r\n );\r\n }\r\n\r\n // Apply CLI overrides\r\n const buildConfig = { ...fileConfig.build };\r\n if (opts.outDir) buildConfig.outDir = opts.outDir;\r\n if (opts.minify !== undefined) buildConfig.minify = opts.minify;\r\n if (!opts.sourcemap) buildConfig.sourcemap = false;\r\n\r\n // Validate\r\n const validated = validateBuildConfig(buildConfig);\r\n\r\n const entryCount = Object.keys(validated.entries).length;\r\n console.log(`\\nXrmForge Build (esbuild)`);\r\n console.log(`Entries: ${entryCount}`);\r\n console.log('');\r\n\r\n if (opts.watch) {\r\n // Watch mode\r\n console.log('Watching for changes...\\n');\r\n\r\n const watcher = await watch(validated, {\r\n onRebuild: (result) => {\r\n if (result.errors.length > 0) {\r\n for (const err of result.errors) {\r\n console.error(` ${err}`);\r\n }\r\n } else {\r\n console.log(` Rebuilt ${result.entries.length} entries in ${result.totalDurationMs}ms`);\r\n }\r\n },\r\n });\r\n\r\n // Support Ctrl+C and SIGTERM\r\n const onSignal = () => {\r\n console.log('\\nStopping watch mode...');\r\n watcher.dispose().then(() => process.exit(0));\r\n };\r\n process.once('SIGINT', onSignal);\r\n process.once('SIGTERM', onSignal);\r\n\r\n // Keep process alive\r\n await new Promise(() => {});\r\n }\r\n\r\n // Single build\r\n const result = await build(validated);\r\n\r\n // Print results table\r\n for (const entry of result.entries) {\r\n const size = formatSize(entry.sizeBytes);\r\n const duration = `${entry.durationMs}ms`;\r\n console.log(` ${entry.name.padEnd(30)} ${size.padStart(10)} ${duration.padStart(8)}`);\r\n }\r\n\r\n console.log('');\r\n\r\n if (result.errors.length > 0) {\r\n console.error('Errors:');\r\n for (const err of result.errors) {\r\n console.error(` ${err}`);\r\n }\r\n console.error('');\r\n process.exitCode = 1;\r\n return;\r\n }\r\n\r\n console.log(`${result.entries.length} entries built in ${result.totalDurationMs}ms\\n`);\r\n}\r\n\r\n/**\r\n * Format a byte count to a human-readable string (e.g. '12.3 kB').\r\n *\r\n * @param bytes - File size in bytes\r\n * @returns Formatted string with appropriate unit\r\n */\r\nfunction formatSize(bytes: number): string {\r\n if (bytes < 1024) return `${bytes} B`;\r\n const kb = bytes / 1024;\r\n return `${kb.toFixed(1)} kB`;\r\n}\r\n","/**\r\n * @xrmforge/cli - Init Command\r\n *\r\n * Scaffolds a new D365 form scripting project.\r\n *\r\n * Usage:\r\n * xrmforge init # Scaffold in current directory\r\n * xrmforge init my-project # Scaffold in ./my-project\r\n * xrmforge init --prefix markant # Use \"markant\" as publisher prefix\r\n * xrmforge init --namespace Markant # Use \"Markant\" as base namespace\r\n * xrmforge init --skip-install # Don't run npm install\r\n */\r\n\r\nimport type { Command } from 'commander';\r\nimport { resolve, basename } from 'node:path';\r\nimport { scaffoldProject } from '@xrmforge/devkit';\r\n\r\n/** CLI options for the init command (parsed by Commander.js). */\r\ninterface InitOptions {\r\n /** Project name for package.json (defaults to directory name) */\r\n name?: string;\r\n /** Publisher prefix for D365 WebResources (e.g. 'contoso') */\r\n prefix: string;\r\n /** Base namespace for form scripts (defaults to PascalCase of prefix) */\r\n namespace?: string;\r\n /** Whether to skip running npm install after scaffolding */\r\n skipInstall: boolean;\r\n /** Whether to allow scaffolding in non-empty directories */\r\n force: boolean;\r\n}\r\n\r\n/**\r\n * Register the 'init' subcommand on the CLI program.\r\n *\r\n * Adds options for project name, publisher prefix, namespace,\r\n * skip-install, and force mode.\r\n *\r\n * @param program - The Commander.js program instance to register on\r\n */\r\nexport function registerInitCommand(program: Command): void {\r\n program\r\n .command('init [dir]')\r\n .description('Scaffold a new D365 form scripting project')\r\n\r\n .option('--name <name>', 'Project name for package.json (default: directory name)')\r\n .option('--prefix <prefix>', 'Publisher prefix for D365 WebResources', 'contoso')\r\n .option('--namespace <ns>', 'Base namespace for form scripts (default: PascalCase of prefix)')\r\n .option('--skip-install', 'Skip running npm install after scaffolding', false)\r\n .option('--force', 'Allow scaffolding in non-empty directories (skip existing files)', false)\r\n\r\n .action(async (dir: string | undefined, opts: InitOptions) => {\r\n try {\r\n await runInit(dir, opts);\r\n } catch (error: unknown) {\r\n if (error instanceof Error) {\r\n console.error(`\\nError: ${error.message}\\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 init command: resolve target directory, derive project\r\n * settings, and scaffold a new D365 form scripting project.\r\n *\r\n * @param dir - Optional target directory (defaults to current directory)\r\n * @param opts - Parsed CLI options for the init command\r\n */\r\nasync function runInit(dir: string | undefined, opts: InitOptions): Promise<void> {\r\n const targetDir = resolve(dir ?? '.');\r\n const dirName = basename(targetDir);\r\n\r\n const projectName = opts.name ?? dirName;\r\n const prefix = opts.prefix;\r\n const namespace = opts.namespace ?? toPascalCase(prefix);\r\n\r\n console.log(`\\nXrmForge Project Scaffolding`);\r\n console.log(`Directory: ${targetDir}`);\r\n console.log(`Name: ${projectName}`);\r\n console.log(`Prefix: ${prefix}`);\r\n console.log(`Namespace: ${namespace}`);\r\n console.log('');\r\n\r\n const result = await scaffoldProject({\r\n targetDir,\r\n projectName,\r\n prefix,\r\n namespace,\r\n force: opts.force,\r\n });\r\n\r\n console.log(`Created ${result.filesCreated.length} files:`);\r\n for (const file of result.filesCreated) {\r\n console.log(` ${file}`);\r\n }\r\n\r\n if (result.warnings.length > 0) {\r\n console.warn('\\nWarnings:');\r\n for (const w of result.warnings) {\r\n console.warn(` ${w}`);\r\n }\r\n }\r\n\r\n console.log('\\nNext steps:');\r\n if (dir) {\r\n console.log(` cd ${dir}`);\r\n }\r\n if (!opts.skipInstall) {\r\n console.log(' npm install');\r\n }\r\n console.log(' xrmforge generate --url https://YOUR-ORG.crm4.dynamics.com --auth interactive --entities account --output ./typings');\r\n console.log(' xrmforge build');\r\n console.log('');\r\n}\r\n\r\n/**\r\n * Convert a string to PascalCase (e.g. 'my-prefix' becomes 'MyPrefix').\r\n *\r\n * Splits on hyphens, underscores, and whitespace, capitalizes each part,\r\n * and joins them together.\r\n *\r\n * @param str - Input string with optional separators\r\n * @returns PascalCase version of the input\r\n */\r\nfunction toPascalCase(str: string): string {\r\n return str\r\n .split(/[-_\\s]+/)\r\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())\r\n .join('');\r\n}\r\n"],"mappings":"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","names":["readFileSync","join","ConfigError","ErrorCode","program","ConfigError","ErrorCode","program","result","program","readFileSync","join"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/config.ts","../src/commands/generate.ts","../src/commands/build.ts","../src/commands/init.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\nimport { registerBuildCommand } from './commands/build.js';\r\nimport { registerInitCommand } from './commands/init.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\nregisterBuildCommand(program);\r\nregisterInitCommand(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\nimport type { BuildConfig } from '@xrmforge/devkit';\r\nimport { ConfigError, ErrorCode } from '@xrmforge/typegen';\r\n\r\n/**\r\n * Shape of xrmforge.config.json.\r\n *\r\n * Combines generate options (url, auth, entities) with build configuration.\r\n * All fields are optional since they can be provided via CLI flags.\r\n */\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 /** Generate Custom API Action/Function executors */\r\n actions?: boolean;\r\n /** Only generate Custom APIs whose uniquename starts with this prefix (e.g. \"markant_\") */\r\n actionsFilter?: string;\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 /** Build configuration for WebResource bundling */\r\n build?: BuildConfig;\r\n}\r\n\r\nconst CONFIG_FILENAME = 'xrmforge.config.json';\r\n\r\n/**\r\n * Load configuration from xrmforge.config.json in the given working directory.\r\n *\r\n * Returns an empty object if the file does not exist.\r\n * Throws a {@link ConfigError} if the file exists but contains invalid JSON.\r\n * Emits a warning to stderr if clientSecret is found in the config file.\r\n *\r\n * @param cwd - Working directory to search for xrmforge.config.json (defaults to process.cwd())\r\n * @returns Parsed configuration object, or empty object if no config file exists\r\n * @throws {ConfigError} If the config file contains 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: unknown) {\r\n if (error instanceof SyntaxError) {\r\n throw new ConfigError(ErrorCode.CONFIG_INVALID, `Invalid JSON in ${configPath}: ${error.message}`, { file: configPath });\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 *\r\n * CLI flags always take precedence over config file values. Only fills\r\n * in config values for options not explicitly set via CLI. Handles type\r\n * conversions (e.g. config arrays to CLI comma-separated strings, numeric\r\n * LCIDs to string representations).\r\n *\r\n * @param config - Parsed xrmforge.config.json values\r\n * @param cliOpts - CLI options parsed by Commander.js\r\n * @returns Merged options with CLI taking precedence\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 if (merged['actions'] === undefined && config.actions !== undefined) {\r\n merged['actions'] = config.actions;\r\n }\r\n\r\n // Custom API filter (string): CLI takes precedence over config\r\n if (!merged['actionsFilter'] && config.actionsFilter) {\r\n merged['actionsFilter'] = config.actionsFilter;\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/**\r\n * Connection/credential options that can be supplied via XRMFORGE_* environment\r\n * variables, mapped from the option name to its environment variable.\r\n *\r\n * Generalizes the long-standing XRMFORGE_TOKEN fallback to the remaining\r\n * connection and credential values, so CI pipelines (and local shells) can supply\r\n * them via the environment instead of as command-line flags. That keeps secrets\r\n * out of the shell history and the process list (a flagged --client-secret is\r\n * visible in both).\r\n */\r\nconst ENV_VAR_MAP: ReadonlyArray<readonly [string, string]> = [\r\n ['url', 'XRMFORGE_URL'],\r\n ['tenantId', 'XRMFORGE_TENANT_ID'],\r\n ['clientId', 'XRMFORGE_CLIENT_ID'],\r\n ['clientSecret', 'XRMFORGE_CLIENT_SECRET'],\r\n ['token', 'XRMFORGE_TOKEN'],\r\n];\r\n\r\n/**\r\n * Fill connection/credential options from XRMFORGE_* environment variables where\r\n * no CLI flag set them.\r\n *\r\n * Resolution precedence is: explicit CLI flag > environment variable >\r\n * xrmforge.config.json. To realize that, apply this BEFORE\r\n * {@link mergeWithCliOptions}: the environment value then out-ranks the config\r\n * file but still yields to an explicit flag. An empty-string environment variable\r\n * is treated as unset (an exported-but-empty XRMFORGE_CLIENT_SECRET= must not mask\r\n * a config-file value).\r\n *\r\n * The input is not mutated; a shallow copy is returned. The `env` parameter is\r\n * injectable for testing and defaults to {@link process.env}.\r\n *\r\n * @param cliOpts - CLI options parsed by Commander.js (an unset flag is `undefined`)\r\n * @param env - Environment lookup (defaults to process.env)\r\n * @returns A copy of cliOpts with environment-variable fallbacks applied\r\n */\r\nexport function applyEnvDefaults(\r\n cliOpts: Record<string, unknown>,\r\n env: Record<string, string | undefined> = process.env,\r\n): Record<string, unknown> {\r\n const merged: Record<string, unknown> = { ...cliOpts };\r\n\r\n for (const [optionKey, envVar] of ENV_VAR_MAP) {\r\n if (merged[optionKey] === undefined || merged[optionKey] === null) {\r\n const value = env[envVar];\r\n if (value !== undefined && value !== '') {\r\n merged[optionKey] = value;\r\n }\r\n }\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 ./generated\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 ./generated \\\r\n * --label-language 1033 --secondary-language 1031\r\n *\r\n * Connection and credentials also resolve from XRMFORGE_* environment variables\r\n * (XRMFORGE_URL, XRMFORGE_TENANT_ID, XRMFORGE_CLIENT_ID, XRMFORGE_CLIENT_SECRET,\r\n * XRMFORGE_TOKEN). Precedence per value: explicit CLI flag > environment variable\r\n * > xrmforge.config.json. In CI, inject the secret as an env var rather than via\r\n * --client-secret (which would expose it in the process list):\r\n *\r\n * XRMFORGE_URL=... XRMFORGE_TENANT_ID=... XRMFORGE_CLIENT_ID=... \\\r\n * XRMFORGE_CLIENT_SECRET=... \\\r\n * xrmforge generate --auth client-credentials # scope/output from xrmforge.config.json\r\n */\r\n\r\nimport type { Command } from 'commander';\r\nimport { loadConfig, mergeWithCliOptions, applyEnvDefaults } from '../config.js';\r\nimport {\r\n TypeGenerationOrchestrator,\r\n createCredential,\r\n configureLogging,\r\n ConsoleLogSink,\r\n LogLevel,\r\n AuthenticationError,\r\n ConfigError,\r\n ErrorCode,\r\n} from '@xrmforge/typegen';\r\nimport type { AuthConfig, CheckResult, CheckFinding } from '@xrmforge/typegen';\r\n\r\n/** CLI options for the generate command (parsed by Commander.js). */\r\ninterface GenerateOptions {\r\n /** Dataverse environment URL (e.g. 'https://myorg.crm4.dynamics.com') */\r\n url: string;\r\n /** Authentication method ('client-credentials', 'interactive', 'device-code', 'token') */\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 for client-credentials auth */\r\n clientSecret?: string;\r\n /** Pre-acquired Bearer token for token auth */\r\n token?: string;\r\n /** Comma-separated entity logical names */\r\n entities?: string;\r\n /** Comma-separated solution unique names */\r\n solutions?: string;\r\n /** Output directory for generated .ts files */\r\n output: string;\r\n /** Primary label language LCID as string */\r\n labelLanguage: string;\r\n /** Secondary label language LCID as string */\r\n secondaryLanguage?: string;\r\n /** Whether to generate form interfaces */\r\n forms: boolean;\r\n /** Whether to generate OptionSet enums */\r\n optionsets: boolean;\r\n /** Whether to generate Custom API action executors */\r\n actions: boolean;\r\n /** Prefix filter for Custom API generation */\r\n actionsFilter?: string;\r\n /** Whether to enable metadata caching */\r\n cache: boolean;\r\n /** Directory for metadata cache files */\r\n cacheDir: string;\r\n /** Drift check mode: compare against outputDir without writing (exit 0/1/2) */\r\n check: boolean;\r\n /** Whether to enable verbose (debug) logging */\r\n verbose: boolean;\r\n}\r\n\r\n/**\r\n * Register the 'generate' subcommand on the CLI program.\r\n *\r\n * Adds options for Dataverse connection, authentication, entity scope,\r\n * output directory, label languages, feature toggles, and caching.\r\n *\r\n * @param program - The Commander.js program instance to register on\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 or XRMFORGE_* env vars)\r\n .option('--url <url>', 'Dataverse environment URL (e.g. https://myorg.crm4.dynamics.com). Falls back to XRMFORGE_URL.')\r\n .option('--auth <method>', 'Authentication method: client-credentials, interactive, device-code, token')\r\n\r\n // Auth credentials (each falls back to an XRMFORGE_* env var; precedence: flag > env > config file)\r\n .option('--tenant-id <id>', 'Azure AD tenant ID. Falls back to XRMFORGE_TENANT_ID.')\r\n .option('--client-id <id>', 'Azure AD application (client) ID. Falls back to XRMFORGE_CLIENT_ID.')\r\n .option('--client-secret <secret>', 'Client secret (for client-credentials auth). Prefer the XRMFORGE_CLIENT_SECRET env var over this flag.')\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 .ts files', './generated')\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 // No Commander default for --actions: it must stay `undefined` when not passed\r\n // so a value from xrmforge.config.json can take effect (mergeWithCliOptions).\r\n // The orchestrator defaults generateActions to false, so CLI-only behavior is unchanged.\r\n .option('--actions', 'Generate Custom API Action/Function executors')\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 // Drift check\r\n .option(\r\n '--check',\r\n 'Drift check: generate in-memory and compare against the output directory without writing anything. ' +\r\n 'Exit code: 0 = up to date, 1 = error, 2 = drift detected. Intended as a CI step. Ignores --cache.',\r\n false,\r\n )\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: unknown) {\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: validate options, authenticate, and run\r\n * the type generation orchestrator.\r\n *\r\n * @param cliOpts - Parsed CLI options merged with config file values\r\n */\r\nasync function runGenerate(cliOpts: GenerateOptions): Promise<void> {\r\n // Resolve options in three layers. Precedence: CLI flag > environment variable >\r\n // xrmforge.config.json. applyEnvDefaults runs BEFORE the config merge so an\r\n // XRMFORGE_* value out-ranks the config file but still yields to an explicit flag.\r\n const fileConfig = loadConfig();\r\n const cliWithEnv = applyEnvDefaults(cliOpts as unknown as Record<string, unknown>);\r\n const merged = mergeWithCliOptions(fileConfig, cliWithEnv);\r\n const opts = merged as unknown as GenerateOptions;\r\n\r\n // Warn when a secret/token was passed as a CLI flag: it is then exposed in the\r\n // shell history and the process list. Values resolved from the environment are\r\n // the preferred path and intentionally do not trigger this warning, so we key it\r\n // on the original cliOpts (before env resolution).\r\n if (cliOpts.clientSecret) {\r\n console.warn('WARNING: Passing --client-secret via CLI exposes it in shell history and the process list. Use the XRMFORGE_CLIENT_SECRET environment variable instead.');\r\n }\r\n if (cliOpts.token) {\r\n console.warn('WARNING: Using --token on the command line exposes the token in the process list and shell history.');\r\n console.warn(' Prefer setting the XRMFORGE_TOKEN environment variable instead.\\n');\r\n }\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 ConfigError(ErrorCode.CONFIG_INVALID, '--url is required. Set it via CLI flag or in xrmforge.config.json.', { option: 'url' });\r\n }\r\n if (!opts.auth) {\r\n throw new ConfigError(ErrorCode.CONFIG_INVALID, '--auth is required. Set it via CLI flag or in xrmforge.config.json.', { option: 'auth' });\r\n }\r\n if (!opts.entities && !opts.solutions) {\r\n throw new ConfigError(ErrorCode.CONFIG_INVALID, 'Either --entities or --solutions must be specified (CLI or xrmforge.config.json).', { option: 'entities' });\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 ConfigError(ErrorCode.CONFIG_INVALID, 'No entities specified. Use --entities or --solutions.', { option: 'entities' });\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 ConfigError(ErrorCode.CONFIG_INVALID, `Invalid --label-language: \"${opts.labelLanguage}\". Must be a numeric LCID (e.g. 1033, 1031).`, { option: 'labelLanguage', value: opts.labelLanguage });\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 ConfigError(ErrorCode.CONFIG_INVALID, `Invalid --secondary-language: \"${opts.secondaryLanguage}\". Must be a numeric LCID (e.g. 1033, 1031).`, { option: 'secondaryLanguage', value: opts.secondaryLanguage });\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.check) {\r\n console.log(`Mode: drift check (read-only, nothing will be written)`);\r\n if (opts.cache) {\r\n console.warn(`Note: --cache is ignored in check mode (the check must run against live metadata)`);\r\n }\r\n } else 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 checkOnly: opts.check,\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.warn('\\nWarnings:');\r\n for (const entity of result.entities) {\r\n for (const warning of entity.warnings) {\r\n console.warn(` [${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.error(`\\n${failures.length} entity/entities failed. See warnings above.`);\r\n process.exitCode = 1;\r\n return;\r\n }\r\n\r\n // Drift check report (check mode never writes anything)\r\n if (opts.check) {\r\n if (!result.checkResult) {\r\n // Defensive: without a complete generation there is no reliable comparison\r\n console.error('\\nDrift check could not be completed (generation incomplete).');\r\n process.exitCode = 1;\r\n return;\r\n }\r\n printCheckReport(result.checkResult);\r\n if (result.checkResult.drift) {\r\n process.exitCode = 2;\r\n }\r\n return;\r\n }\r\n\r\n console.log(`\\nTypes written to: ${opts.output}/`);\r\n}\r\n\r\n/** Display labels per file category, in report order */\r\nconst CHECK_CATEGORY_LABELS: ReadonlyArray<readonly [CheckFinding['type'], string]> = [\r\n ['entity', 'Entities'],\r\n ['fields', 'Fields'],\r\n ['form', 'Forms'],\r\n ['optionset', 'OptionSets'],\r\n ['action', 'Actions'],\r\n];\r\n\r\n/**\r\n * Print the drift check report, grouped by file category.\r\n *\r\n * Drift classes per file: \"changed\" (differs from live metadata),\r\n * \"missing\" (not on disk), \"orphaned\" (no longer generated).\r\n */\r\nfunction printCheckReport(check: CheckResult): void {\r\n console.log('');\r\n if (!check.drift) {\r\n console.log(`Drift check passed: ${check.unchanged} files up to date.`);\r\n return;\r\n }\r\n\r\n console.log(`Drift detected: ${check.findings.length} finding(s), ${check.unchanged} files up to date.`);\r\n for (const [type, label] of CHECK_CATEGORY_LABELS) {\r\n const findings = check.findings.filter((f) => f.type === type);\r\n if (findings.length === 0) continue;\r\n console.log(` ${label}:`);\r\n for (const finding of findings) {\r\n console.log(` ${finding.status.padEnd(8)} ${finding.relativePath}`);\r\n }\r\n }\r\n console.log('');\r\n console.log('The checked-in generated files no longer match the live environment.');\r\n console.log('Run \"xrmforge generate\" (same options, without --check) and commit the result.');\r\n console.log('Note: a typegen/cli upgrade also reports drift (newer generator, different output).');\r\n}\r\n\r\n/**\r\n * Build an {@link AuthConfig} from already-resolved options.\r\n *\r\n * Maps the --auth method to the appropriate credential configuration and\r\n * validates that the required fields are present for each method. Pure: it does\r\n * not read environment variables or emit warnings - connection/credential values\r\n * (including XRMFORGE_* fallbacks) are resolved upstream by\r\n * {@link applyEnvDefaults}, and the insecure-flag warnings are emitted in\r\n * runGenerate against the original CLI options.\r\n *\r\n * @param opts - Resolved options containing auth method and credentials\r\n * @returns Validated authentication configuration\r\n * @throws {AuthenticationError} If required credentials are missing for the chosen method\r\n * @throws {ConfigError} If the auth method is unrecognized\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 AuthenticationError(ErrorCode.AUTH_MISSING_CONFIG, '--tenant-id is required for client-credentials auth (or set XRMFORGE_TENANT_ID).', { method: 'client-credentials', missing: 'tenantId' });\r\n if (!opts.clientId) throw new AuthenticationError(ErrorCode.AUTH_MISSING_CONFIG, '--client-id is required for client-credentials auth (or set XRMFORGE_CLIENT_ID).', { method: 'client-credentials', missing: 'clientId' });\r\n if (!opts.clientSecret) throw new AuthenticationError(ErrorCode.AUTH_MISSING_CONFIG, '--client-secret is required for client-credentials auth (or set XRMFORGE_CLIENT_SECRET).', { method: 'client-credentials', missing: 'clientSecret' });\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 AuthenticationError(ErrorCode.AUTH_MISSING_CONFIG, '--tenant-id is required for interactive auth (or set XRMFORGE_TENANT_ID).', { method: 'interactive', missing: 'tenantId' });\r\n if (!opts.clientId) throw new AuthenticationError(ErrorCode.AUTH_MISSING_CONFIG, '--client-id is required for interactive auth (or set XRMFORGE_CLIENT_ID).', { method: 'interactive', missing: 'clientId' });\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 AuthenticationError(ErrorCode.AUTH_MISSING_CONFIG, '--tenant-id is required for device-code auth (or set XRMFORGE_TENANT_ID).', { method: 'device-code', missing: 'tenantId' });\r\n if (!opts.clientId) throw new AuthenticationError(ErrorCode.AUTH_MISSING_CONFIG, '--client-id is required for device-code auth (or set XRMFORGE_CLIENT_ID).', { method: 'device-code', missing: 'clientId' });\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 comes from --token or XRMFORGE_TOKEN; both are resolved upstream by\r\n // applyEnvDefaults, so buildAuthConfig only validates the resolved value.\r\n if (!opts.token) {\r\n throw new AuthenticationError(\r\n ErrorCode.AUTH_MISSING_CONFIG,\r\n 'Token authentication requires a token. ' +\r\n 'Set the XRMFORGE_TOKEN environment variable or use the --token flag.',\r\n { method: 'token', missing: 'token' },\r\n );\r\n }\r\n return { method: 'token', token: opts.token };\r\n }\r\n\r\n default:\r\n throw new ConfigError(\r\n ErrorCode.CONFIG_INVALID,\r\n `Unknown auth method: \"${opts.auth}\". ` +\r\n `Supported: client-credentials, interactive, device-code, token`,\r\n { option: 'auth', value: opts.auth },\r\n );\r\n }\r\n}\r\n","/**\r\n * @xrmforge/cli - Build Command\r\n *\r\n * Builds WebResources as IIFE bundles for Dynamics 365.\r\n *\r\n * Usage:\r\n * xrmforge build # Build all entries from xrmforge.config.json\r\n * xrmforge build --watch # Watch mode with incremental rebuilds\r\n * xrmforge build --minify # Minify output bundles\r\n * xrmforge build --no-sourcemap # Disable source maps\r\n */\r\n\r\nimport type { Command } from 'commander';\r\nimport { loadConfig } from '../config.js';\r\nimport {\r\n validateBuildConfig,\r\n build,\r\n watch,\r\n} from '@xrmforge/devkit';\r\nimport { ConfigError, ErrorCode } from '@xrmforge/typegen';\r\n\r\n/** CLI options for the build command (parsed by Commander.js). */\r\ninterface BuildOptions {\r\n /** Whether to run in watch mode with incremental rebuilds */\r\n watch: boolean;\r\n /** Whether to minify output bundles */\r\n minify?: boolean;\r\n /** Whether to generate source maps (default: true) */\r\n sourcemap: boolean;\r\n /** Override output directory from config */\r\n outDir?: string;\r\n /** Whether to enable verbose logging */\r\n verbose: boolean;\r\n}\r\n\r\n/**\r\n * Register the 'build' subcommand on the CLI program.\r\n *\r\n * Adds options for watch mode, minification, source maps,\r\n * output directory override, and verbose logging.\r\n *\r\n * @param program - The Commander.js program instance to register on\r\n */\r\nexport function registerBuildCommand(program: Command): void {\r\n program\r\n .command('build')\r\n .description('Build WebResources as IIFE bundles for Dynamics 365')\r\n\r\n .option('--watch', 'Watch mode with incremental rebuilds', false)\r\n .option('--minify', 'Minify output bundles')\r\n .option('--no-sourcemap', 'Disable source maps')\r\n .option('--out-dir <dir>', 'Override output directory')\r\n .option('-v, --verbose', 'Verbose logging', false)\r\n\r\n .action(async (opts: BuildOptions) => {\r\n try {\r\n await runBuild(opts);\r\n } catch (error: unknown) {\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 build command: load config, validate, and invoke esbuild\r\n * for a single build or watch mode.\r\n *\r\n * @param opts - Parsed CLI options for the build command\r\n */\r\nasync function runBuild(opts: BuildOptions): Promise<void> {\r\n const fileConfig = loadConfig();\r\n\r\n if (!fileConfig.build) {\r\n throw new ConfigError(\r\n ErrorCode.CONFIG_INVALID,\r\n 'No \"build\" section found in xrmforge.config.json.\\n' +\r\n 'Add a build configuration with entries to get started:\\n\\n' +\r\n ' {\\n' +\r\n ' \"build\": {\\n' +\r\n ' \"entries\": {\\n' +\r\n ' \"my_script\": {\\n' +\r\n ' \"input\": \"./src/my-script.ts\",\\n' +\r\n ' \"namespace\": \"Contoso.MyScript\"\\n' +\r\n ' }\\n' +\r\n ' }\\n' +\r\n ' }\\n' +\r\n ' }\\n',\r\n { section: 'build' },\r\n );\r\n }\r\n\r\n // Apply CLI overrides\r\n const buildConfig = { ...fileConfig.build };\r\n if (opts.outDir) buildConfig.outDir = opts.outDir;\r\n if (opts.minify !== undefined) buildConfig.minify = opts.minify;\r\n if (!opts.sourcemap) buildConfig.sourcemap = false;\r\n\r\n // Validate\r\n const validated = validateBuildConfig(buildConfig);\r\n\r\n const entryCount = Object.keys(validated.entries).length;\r\n console.log(`\\nXrmForge Build (esbuild)`);\r\n console.log(`Entries: ${entryCount}`);\r\n console.log('');\r\n\r\n if (opts.watch) {\r\n // Watch mode\r\n console.log('Watching for changes...\\n');\r\n\r\n const watcher = await watch(validated, {\r\n onRebuild: (result) => {\r\n if (result.errors.length > 0) {\r\n for (const err of result.errors) {\r\n console.error(` ${err}`);\r\n }\r\n } else {\r\n console.log(` Rebuilt ${result.entries.length} entries in ${result.totalDurationMs}ms`);\r\n }\r\n },\r\n });\r\n\r\n // Support Ctrl+C and SIGTERM\r\n const onSignal = () => {\r\n console.log('\\nStopping watch mode...');\r\n watcher.dispose().then(() => process.exit(0));\r\n };\r\n process.once('SIGINT', onSignal);\r\n process.once('SIGTERM', onSignal);\r\n\r\n // Keep process alive\r\n await new Promise(() => {});\r\n }\r\n\r\n // Single build\r\n const result = await build(validated);\r\n\r\n // Print results table\r\n for (const entry of result.entries) {\r\n const size = formatSize(entry.sizeBytes);\r\n const duration = `${entry.durationMs}ms`;\r\n console.log(` ${entry.name.padEnd(30)} ${size.padStart(10)} ${duration.padStart(8)}`);\r\n }\r\n\r\n console.log('');\r\n\r\n if (result.errors.length > 0) {\r\n console.error('Errors:');\r\n for (const err of result.errors) {\r\n console.error(` ${err}`);\r\n }\r\n console.error('');\r\n process.exitCode = 1;\r\n return;\r\n }\r\n\r\n console.log(`${result.entries.length} entries built in ${result.totalDurationMs}ms\\n`);\r\n}\r\n\r\n/**\r\n * Format a byte count to a human-readable string (e.g. '12.3 kB').\r\n *\r\n * @param bytes - File size in bytes\r\n * @returns Formatted string with appropriate unit\r\n */\r\nfunction formatSize(bytes: number): string {\r\n if (bytes < 1024) return `${bytes} B`;\r\n const kb = bytes / 1024;\r\n return `${kb.toFixed(1)} kB`;\r\n}\r\n","/**\r\n * @xrmforge/cli - Init Command\r\n *\r\n * Scaffolds a new D365 form scripting project.\r\n *\r\n * Usage:\r\n * xrmforge init # Scaffold in current directory\r\n * xrmforge init my-project # Scaffold in ./my-project\r\n * xrmforge init --prefix markant # Use \"markant\" as publisher prefix\r\n * xrmforge init --namespace Markant # Use \"Markant\" as base namespace\r\n * xrmforge init --skip-install # Don't run npm install\r\n */\r\n\r\nimport type { Command } from 'commander';\r\nimport { resolve, basename } from 'node:path';\r\nimport { scaffoldProject } from '@xrmforge/devkit';\r\n\r\n/** CLI options for the init command (parsed by Commander.js). */\r\ninterface InitOptions {\r\n /** Project name for package.json (defaults to directory name) */\r\n name?: string;\r\n /** Publisher prefix for D365 WebResources (e.g. 'contoso') */\r\n prefix: string;\r\n /** Base namespace for form scripts (defaults to PascalCase of prefix) */\r\n namespace?: string;\r\n /** Whether to skip running npm install after scaffolding */\r\n skipInstall: boolean;\r\n /** Whether to allow scaffolding in non-empty directories */\r\n force: boolean;\r\n}\r\n\r\n/**\r\n * Register the 'init' subcommand on the CLI program.\r\n *\r\n * Adds options for project name, publisher prefix, namespace,\r\n * skip-install, and force mode.\r\n *\r\n * @param program - The Commander.js program instance to register on\r\n */\r\nexport function registerInitCommand(program: Command): void {\r\n program\r\n .command('init [dir]')\r\n .description('Scaffold a new D365 form scripting project')\r\n\r\n .option('--name <name>', 'Project name for package.json (default: directory name)')\r\n .option('--prefix <prefix>', 'Publisher prefix for D365 WebResources', 'contoso')\r\n .option('--namespace <ns>', 'Base namespace for form scripts (default: PascalCase of prefix)')\r\n .option('--skip-install', 'Skip running npm install after scaffolding', false)\r\n .option('--force', 'Allow scaffolding in non-empty directories (skip existing files)', false)\r\n\r\n .action(async (dir: string | undefined, opts: InitOptions) => {\r\n try {\r\n await runInit(dir, opts);\r\n } catch (error: unknown) {\r\n if (error instanceof Error) {\r\n console.error(`\\nError: ${error.message}\\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 init command: resolve target directory, derive project\r\n * settings, and scaffold a new D365 form scripting project.\r\n *\r\n * @param dir - Optional target directory (defaults to current directory)\r\n * @param opts - Parsed CLI options for the init command\r\n */\r\nasync function runInit(dir: string | undefined, opts: InitOptions): Promise<void> {\r\n const targetDir = resolve(dir ?? '.');\r\n const dirName = basename(targetDir);\r\n\r\n const projectName = opts.name ?? dirName;\r\n const prefix = opts.prefix;\r\n const namespace = opts.namespace ?? toPascalCase(prefix);\r\n\r\n console.log(`\\nXrmForge Project Scaffolding`);\r\n console.log(`Directory: ${targetDir}`);\r\n console.log(`Name: ${projectName}`);\r\n console.log(`Prefix: ${prefix}`);\r\n console.log(`Namespace: ${namespace}`);\r\n console.log('');\r\n\r\n const result = await scaffoldProject({\r\n targetDir,\r\n projectName,\r\n prefix,\r\n namespace,\r\n force: opts.force,\r\n });\r\n\r\n console.log(`Created ${result.filesCreated.length} files:`);\r\n for (const file of result.filesCreated) {\r\n console.log(` ${file}`);\r\n }\r\n\r\n if (result.warnings.length > 0) {\r\n console.warn('\\nWarnings:');\r\n for (const w of result.warnings) {\r\n console.warn(` ${w}`);\r\n }\r\n }\r\n\r\n console.log('\\nNext steps:');\r\n if (dir) {\r\n console.log(` cd ${dir}`);\r\n }\r\n if (!opts.skipInstall) {\r\n console.log(' npm install');\r\n }\r\n console.log(' xrmforge generate --url https://YOUR-ORG.crm4.dynamics.com --auth interactive --entities account --output ./typings');\r\n console.log(' xrmforge build');\r\n console.log('');\r\n}\r\n\r\n/**\r\n * Convert a string to PascalCase (e.g. 'my-prefix' becomes 'MyPrefix').\r\n *\r\n * Splits on hyphens, underscores, and whitespace, capitalizes each part,\r\n * and joins them together.\r\n *\r\n * @param str - Input string with optional separators\r\n * @returns PascalCase version of the input\r\n */\r\nfunction toPascalCase(str: string): string {\r\n return str\r\n .split(/[-_\\s]+/)\r\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())\r\n .join('');\r\n}\r\n"],"mappings":"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","names":["readFileSync","join","ConfigError","ErrorCode","program","ConfigError","ErrorCode","program","result","program","readFileSync","join"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xrmforge/cli",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "description": "CLI for XrmForge - TypeScript type generator for Dynamics 365",
5
5
  "keywords": [
6
6
  "dynamics-365",
@@ -31,7 +31,7 @@
31
31
  "dependencies": {
32
32
  "commander": "^13.0.0",
33
33
  "@xrmforge/typegen": "0.12.0",
34
- "@xrmforge/devkit": "0.7.12"
34
+ "@xrmforge/devkit": "0.7.14"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@types/node": "^22.0.0",