@oamm/textor 1.0.13 → 1.0.14

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/README.md CHANGED
@@ -209,6 +209,25 @@ pnpm textor rename component Header SiteHeader
209
209
 
210
210
  Use `--scan` to update imports across the entire project.
211
211
 
212
+ ### add
213
+ Add a new sub-item (api, hook, test, etc.) to an existing feature or component. This command is additive and will not overwrite existing files unless `--force` is used.
214
+
215
+ ```bash
216
+ # Add API to an existing feature
217
+ pnpm textor add api auth/login
218
+
219
+ # Add hooks to an existing component
220
+ pnpm textor add hook Button
221
+
222
+ # Add tests and readme to a feature
223
+ pnpm textor add test auth/login
224
+ pnpm textor add readme auth/login
225
+ ```
226
+
227
+ Textor automatically detects if the target is a feature or a component based on its state.
228
+
229
+ You can also use the original `add-section` or `create-component` commands with the same flags to add items; they now also support additive mode.
230
+
212
231
  ### remove-section / remove-component
213
232
  Safely remove Textor-managed modules.
214
233
 
@@ -716,17 +716,6 @@ async function safeDelete(filePath, options = {}) {
716
716
  return { deleted: true };
717
717
  }
718
718
 
719
- async function ensureNotExists(filePath, force = false) {
720
- if (existsSync(filePath)) {
721
- if (!force) {
722
- throw new Error(
723
- `File already exists: ${filePath}\n` +
724
- `Use --force to overwrite.`
725
- );
726
- }
727
- }
728
- }
729
-
730
719
  async function ensureDir(dirPath) {
731
720
  await mkdir(dirPath, { recursive: true });
732
721
  }
@@ -2082,19 +2071,25 @@ async function addSectionCommand(route, featurePath, options) {
2082
2071
  }
2083
2072
  }
2084
2073
 
2085
- await ensureNotExists(featureFilePath, options.force);
2086
-
2087
- if (shouldCreateIndex) await ensureNotExists(indexFilePath, options.force);
2088
- if (shouldCreateContext) await ensureNotExists(contextFilePath, options.force);
2089
- if (shouldCreateHooks) await ensureNotExists(hookFilePath, options.force);
2090
- if (shouldCreateTests) await ensureNotExists(testFilePath, options.force);
2091
- if (shouldCreateTypes) await ensureNotExists(typesFilePath, options.force);
2092
- if (shouldCreateApi) await ensureNotExists(apiFilePath, options.force);
2093
- if (shouldCreateServices) await ensureNotExists(servicesFilePath, options.force);
2094
- if (shouldCreateSchemas) await ensureNotExists(schemasFilePath, options.force);
2095
- if (shouldCreateReadme) await ensureNotExists(readmeFilePath, options.force);
2096
- if (shouldCreateStories) await ensureNotExists(storiesFilePath, options.force);
2097
- if (shouldCreateScriptsDir) await ensureNotExists(scriptsIndexPath, options.force);
2074
+ const featureExists = existsSync(featureFilePath);
2075
+ if (featureExists && !options.force) {
2076
+ console.log(`ℹ Feature already exists at ${featureFilePath}. Entering additive mode.`);
2077
+ }
2078
+
2079
+ // Check sub-items only if not in force mode
2080
+ if (!options.force) {
2081
+ if (shouldCreateIndex && existsSync(indexFilePath)) console.log(` - Skipping existing index: ${indexFilePath}`);
2082
+ if (shouldCreateContext && existsSync(contextFilePath)) console.log(` - Skipping existing context: ${contextFilePath}`);
2083
+ if (shouldCreateHooks && existsSync(hookFilePath)) console.log(` - Skipping existing hook: ${hookFilePath}`);
2084
+ if (shouldCreateTests && existsSync(testFilePath)) console.log(` - Skipping existing test: ${testFilePath}`);
2085
+ if (shouldCreateTypes && existsSync(typesFilePath)) console.log(` - Skipping existing types: ${typesFilePath}`);
2086
+ if (shouldCreateApi && existsSync(apiFilePath)) console.log(` - Skipping existing api: ${apiFilePath}`);
2087
+ if (shouldCreateServices && existsSync(servicesFilePath)) console.log(` - Skipping existing services: ${servicesFilePath}`);
2088
+ if (shouldCreateSchemas && existsSync(schemasFilePath)) console.log(` - Skipping existing schemas: ${schemasFilePath}`);
2089
+ if (shouldCreateReadme && existsSync(readmeFilePath)) console.log(` - Skipping existing readme: ${readmeFilePath}`);
2090
+ if (shouldCreateStories && existsSync(storiesFilePath)) console.log(` - Skipping existing stories: ${storiesFilePath}`);
2091
+ if (shouldCreateScriptsDir && existsSync(scriptsIndexPath)) console.log(` - Skipping existing scripts: ${scriptsIndexPath}`);
2092
+ }
2098
2093
 
2099
2094
  let layoutImportPath = null;
2100
2095
  const cliProps = options.prop || {};
@@ -2235,21 +2230,23 @@ async function addSectionCommand(route, featurePath, options) {
2235
2230
 
2236
2231
  const featureSignature = getSignature(config, config.naming.featureExtension === '.astro' ? 'astro' : 'tsx');
2237
2232
 
2238
- const featureHash = await writeFileWithSignature(
2239
- featureFilePath,
2240
- featureContent,
2241
- featureSignature,
2242
- config.hashing?.normalization
2243
- );
2244
- await registerFile(featureFilePath, {
2245
- kind: 'feature',
2246
- template: 'feature',
2247
- hash: featureHash,
2248
- owner: normalizedRoute
2249
- });
2250
- writtenFiles.push(featureFilePath);
2233
+ if (!featureExists || options.force) {
2234
+ const featureHash = await writeFileWithSignature(
2235
+ featureFilePath,
2236
+ featureContent,
2237
+ featureSignature,
2238
+ config.hashing?.normalization
2239
+ );
2240
+ await registerFile(featureFilePath, {
2241
+ kind: 'feature',
2242
+ template: 'feature',
2243
+ hash: featureHash,
2244
+ owner: normalizedRoute
2245
+ });
2246
+ writtenFiles.push(featureFilePath);
2247
+ }
2251
2248
 
2252
- if (shouldCreateScriptsDir) {
2249
+ if (shouldCreateScriptsDir && (!existsSync(scriptsIndexPath) || options.force)) {
2253
2250
  const hash = await writeFileWithSignature(
2254
2251
  scriptsIndexPath,
2255
2252
  generateScriptsIndexTemplate(),
@@ -2265,7 +2262,7 @@ async function addSectionCommand(route, featurePath, options) {
2265
2262
  writtenFiles.push(scriptsIndexPath);
2266
2263
  }
2267
2264
 
2268
- if (shouldCreateIndex) {
2265
+ if (shouldCreateIndex && (!existsSync(indexFilePath) || options.force)) {
2269
2266
  const indexContent = generateIndexTemplate(featureComponentName, config.naming.featureExtension);
2270
2267
  const hash = await writeFileWithSignature(
2271
2268
  indexFilePath,
@@ -2282,7 +2279,7 @@ async function addSectionCommand(route, featurePath, options) {
2282
2279
  writtenFiles.push(indexFilePath);
2283
2280
  }
2284
2281
 
2285
- if (shouldCreateApi) {
2282
+ if (shouldCreateApi && (!existsSync(apiFilePath) || options.force)) {
2286
2283
  const apiContent = generateApiTemplate(featureComponentName);
2287
2284
  const hash = await writeFileWithSignature(
2288
2285
  apiFilePath,
@@ -2299,7 +2296,7 @@ async function addSectionCommand(route, featurePath, options) {
2299
2296
  writtenFiles.push(apiFilePath);
2300
2297
  }
2301
2298
 
2302
- if (shouldCreateServices) {
2299
+ if (shouldCreateServices && (!existsSync(servicesFilePath) || options.force)) {
2303
2300
  const servicesContent = generateServiceTemplate(featureComponentName);
2304
2301
  const hash = await writeFileWithSignature(
2305
2302
  servicesFilePath,
@@ -2316,7 +2313,7 @@ async function addSectionCommand(route, featurePath, options) {
2316
2313
  writtenFiles.push(servicesFilePath);
2317
2314
  }
2318
2315
 
2319
- if (shouldCreateSchemas) {
2316
+ if (shouldCreateSchemas && (!existsSync(schemasFilePath) || options.force)) {
2320
2317
  const schemasContent = generateSchemaTemplate(featureComponentName);
2321
2318
  const hash = await writeFileWithSignature(
2322
2319
  schemasFilePath,
@@ -2333,7 +2330,7 @@ async function addSectionCommand(route, featurePath, options) {
2333
2330
  writtenFiles.push(schemasFilePath);
2334
2331
  }
2335
2332
 
2336
- if (shouldCreateHooks) {
2333
+ if (shouldCreateHooks && (!existsSync(hookFilePath) || options.force)) {
2337
2334
  const hookName = getHookFunctionName(featureComponentName);
2338
2335
  const hookContent = generateHookTemplate(featureComponentName, hookName);
2339
2336
  const hash = await writeFileWithSignature(
@@ -2351,7 +2348,7 @@ async function addSectionCommand(route, featurePath, options) {
2351
2348
  writtenFiles.push(hookFilePath);
2352
2349
  }
2353
2350
 
2354
- if (shouldCreateContext) {
2351
+ if (shouldCreateContext && (!existsSync(contextFilePath) || options.force)) {
2355
2352
  const contextContent = generateContextTemplate(featureComponentName);
2356
2353
  const hash = await writeFileWithSignature(
2357
2354
  contextFilePath,
@@ -2368,7 +2365,7 @@ async function addSectionCommand(route, featurePath, options) {
2368
2365
  writtenFiles.push(contextFilePath);
2369
2366
  }
2370
2367
 
2371
- if (shouldCreateTests) {
2368
+ if (shouldCreateTests && (!existsSync(testFilePath) || options.force)) {
2372
2369
  const relativeFeaturePath = `./${path.basename(featureFilePath)}`;
2373
2370
  const testContent = generateTestTemplate(featureComponentName, relativeFeaturePath);
2374
2371
  const hash = await writeFileWithSignature(
@@ -2386,7 +2383,7 @@ async function addSectionCommand(route, featurePath, options) {
2386
2383
  writtenFiles.push(testFilePath);
2387
2384
  }
2388
2385
 
2389
- if (shouldCreateTypes) {
2386
+ if (shouldCreateTypes && (!existsSync(typesFilePath) || options.force)) {
2390
2387
  const typesContent = generateTypesTemplate(featureComponentName);
2391
2388
  const hash = await writeFileWithSignature(
2392
2389
  typesFilePath,
@@ -2403,7 +2400,7 @@ async function addSectionCommand(route, featurePath, options) {
2403
2400
  writtenFiles.push(typesFilePath);
2404
2401
  }
2405
2402
 
2406
- if (shouldCreateReadme) {
2403
+ if (shouldCreateReadme && (!existsSync(readmeFilePath) || options.force)) {
2407
2404
  const readmeContent = generateReadmeTemplate(featureComponentName);
2408
2405
  const hash = await writeFileWithSignature(
2409
2406
  readmeFilePath,
@@ -2420,7 +2417,7 @@ async function addSectionCommand(route, featurePath, options) {
2420
2417
  writtenFiles.push(readmeFilePath);
2421
2418
  }
2422
2419
 
2423
- if (shouldCreateStories) {
2420
+ if (shouldCreateStories && (!existsSync(storiesFilePath) || options.force)) {
2424
2421
  const relativePath = `./${path.basename(featureFilePath)}`;
2425
2422
  const storiesContent = generateStoriesTemplate(featureComponentName, relativePath);
2426
2423
  const hash = await writeFileWithSignature(
@@ -3437,20 +3434,26 @@ async function createComponentCommand(componentName, options) {
3437
3434
  return;
3438
3435
  }
3439
3436
 
3440
- await ensureNotExists(componentFilePath, options.force);
3441
- await ensureNotExists(indexFilePath, options.force);
3442
-
3443
- if (shouldCreateContext) await ensureNotExists(contextFilePath, options.force);
3444
- if (shouldCreateHook) await ensureNotExists(hookFilePath, options.force);
3445
- if (shouldCreateTests) await ensureNotExists(testFilePath, options.force);
3446
- if (shouldCreateConfig) await ensureNotExists(configFilePath, options.force);
3447
- if (shouldCreateConstants) await ensureNotExists(constantsFilePath, options.force);
3448
- if (shouldCreateTypes) await ensureNotExists(typesFilePath, options.force);
3449
- if (shouldCreateApi) await ensureNotExists(apiFilePath, options.force);
3450
- if (shouldCreateServices) await ensureNotExists(servicesFilePath, options.force);
3451
- if (shouldCreateSchemas) await ensureNotExists(schemasFilePath, options.force);
3452
- if (shouldCreateReadme) await ensureNotExists(readmeFilePath, options.force);
3453
- if (shouldCreateStories) await ensureNotExists(storiesFilePath, options.force);
3437
+ const componentExists = existsSync(componentFilePath);
3438
+ if (componentExists && !options.force) {
3439
+ console.log(`ℹ Component already exists at ${componentFilePath}. Entering additive mode.`);
3440
+ }
3441
+
3442
+ // Check sub-items only if not in force mode
3443
+ if (!options.force) {
3444
+ if (existsSync(indexFilePath)) console.log(` - Skipping existing index: ${indexFilePath}`);
3445
+ if (shouldCreateContext && existsSync(contextFilePath)) console.log(` - Skipping existing context: ${contextFilePath}`);
3446
+ if (shouldCreateHook && existsSync(hookFilePath)) console.log(` - Skipping existing hook: ${hookFilePath}`);
3447
+ if (shouldCreateTests && existsSync(testFilePath)) console.log(` - Skipping existing test: ${testFilePath}`);
3448
+ if (shouldCreateConfig && existsSync(configFilePath)) console.log(` - Skipping existing config: ${configFilePath}`);
3449
+ if (shouldCreateConstants && existsSync(constantsFilePath)) console.log(` - Skipping existing constants: ${constantsFilePath}`);
3450
+ if (shouldCreateTypes && existsSync(typesFilePath)) console.log(` - Skipping existing types: ${typesFilePath}`);
3451
+ if (shouldCreateApi && existsSync(apiFilePath)) console.log(` - Skipping existing api: ${apiFilePath}`);
3452
+ if (shouldCreateServices && existsSync(servicesFilePath)) console.log(` - Skipping existing services: ${servicesFilePath}`);
3453
+ if (shouldCreateSchemas && existsSync(schemasFilePath)) console.log(` - Skipping existing schemas: ${schemasFilePath}`);
3454
+ if (shouldCreateReadme && existsSync(readmeFilePath)) console.log(` - Skipping existing readme: ${readmeFilePath}`);
3455
+ if (shouldCreateStories && existsSync(storiesFilePath)) console.log(` - Skipping existing stories: ${storiesFilePath}`);
3456
+ }
3454
3457
 
3455
3458
  await ensureDir(componentDir);
3456
3459
 
@@ -3468,37 +3471,42 @@ async function createComponentCommand(componentName, options) {
3468
3471
  const componentContent = generateComponentTemplate(normalizedName, framework, config.naming.componentExtension);
3469
3472
  const signature = getSignature(config, config.naming.componentExtension === '.astro' ? 'astro' : 'tsx');
3470
3473
 
3471
- const componentHash = await writeFileWithSignature(
3472
- componentFilePath,
3473
- componentContent,
3474
- signature,
3475
- config.hashing?.normalization
3476
- );
3477
- await registerFile(componentFilePath, {
3478
- kind: 'component',
3479
- template: 'component',
3480
- hash: componentHash,
3481
- owner: normalizedName
3482
- });
3474
+ const writtenFiles = [];
3475
+
3476
+ if (!componentExists || options.force) {
3477
+ const componentHash = await writeFileWithSignature(
3478
+ componentFilePath,
3479
+ componentContent,
3480
+ signature,
3481
+ config.hashing?.normalization
3482
+ );
3483
+ await registerFile(componentFilePath, {
3484
+ kind: 'component',
3485
+ template: 'component',
3486
+ hash: componentHash,
3487
+ owner: normalizedName
3488
+ });
3489
+ writtenFiles.push(componentFilePath);
3490
+ }
3483
3491
 
3484
- const writtenFiles = [componentFilePath];
3485
-
3486
- const indexContent = generateIndexTemplate(normalizedName, config.naming.componentExtension);
3487
- const indexHash = await writeFileWithSignature(
3488
- indexFilePath,
3489
- indexContent,
3490
- getSignature(config, 'typescript'),
3491
- config.hashing?.normalization
3492
- );
3493
- await registerFile(indexFilePath, {
3494
- kind: 'component-file',
3495
- template: 'index',
3496
- hash: indexHash,
3497
- owner: normalizedName
3498
- });
3499
- writtenFiles.push(indexFilePath);
3492
+ if (!existsSync(indexFilePath) || options.force) {
3493
+ const indexContent = generateIndexTemplate(normalizedName, config.naming.componentExtension);
3494
+ const indexHash = await writeFileWithSignature(
3495
+ indexFilePath,
3496
+ indexContent,
3497
+ getSignature(config, 'typescript'),
3498
+ config.hashing?.normalization
3499
+ );
3500
+ await registerFile(indexFilePath, {
3501
+ kind: 'component-file',
3502
+ template: 'index',
3503
+ hash: indexHash,
3504
+ owner: normalizedName
3505
+ });
3506
+ writtenFiles.push(indexFilePath);
3507
+ }
3500
3508
 
3501
- if (shouldCreateTypes) {
3509
+ if (shouldCreateTypes && (!existsSync(typesFilePath) || options.force)) {
3502
3510
  const typesContent = generateTypesTemplate(normalizedName);
3503
3511
  const hash = await writeFileWithSignature(
3504
3512
  typesFilePath,
@@ -3515,7 +3523,7 @@ async function createComponentCommand(componentName, options) {
3515
3523
  writtenFiles.push(typesFilePath);
3516
3524
  }
3517
3525
 
3518
- if (shouldCreateContext) {
3526
+ if (shouldCreateContext && (!existsSync(contextFilePath) || options.force)) {
3519
3527
  const contextContent = generateContextTemplate(normalizedName);
3520
3528
  const hash = await writeFileWithSignature(
3521
3529
  contextFilePath,
@@ -3532,7 +3540,7 @@ async function createComponentCommand(componentName, options) {
3532
3540
  writtenFiles.push(contextFilePath);
3533
3541
  }
3534
3542
 
3535
- if (shouldCreateHook) {
3543
+ if (shouldCreateHook && (!existsSync(hookFilePath) || options.force)) {
3536
3544
  const hookName = getHookFunctionName(normalizedName);
3537
3545
  const hookContent = generateHookTemplate(normalizedName, hookName);
3538
3546
  const hash = await writeFileWithSignature(
@@ -3550,7 +3558,7 @@ async function createComponentCommand(componentName, options) {
3550
3558
  writtenFiles.push(hookFilePath);
3551
3559
  }
3552
3560
 
3553
- if (shouldCreateTests) {
3561
+ if (shouldCreateTests && (!existsSync(testFilePath) || options.force)) {
3554
3562
  const relativeComponentPath = `../${normalizedName}${config.naming.componentExtension}`;
3555
3563
  const testContent = generateTestTemplate(normalizedName, relativeComponentPath);
3556
3564
  const hash = await writeFileWithSignature(
@@ -3568,7 +3576,7 @@ async function createComponentCommand(componentName, options) {
3568
3576
  writtenFiles.push(testFilePath);
3569
3577
  }
3570
3578
 
3571
- if (shouldCreateConfig) {
3579
+ if (shouldCreateConfig && (!existsSync(configFilePath) || options.force)) {
3572
3580
  const configContent = generateConfigTemplate(normalizedName);
3573
3581
  const hash = await writeFileWithSignature(
3574
3582
  configFilePath,
@@ -3585,7 +3593,7 @@ async function createComponentCommand(componentName, options) {
3585
3593
  writtenFiles.push(configFilePath);
3586
3594
  }
3587
3595
 
3588
- if (shouldCreateConstants) {
3596
+ if (shouldCreateConstants && (!existsSync(constantsFilePath) || options.force)) {
3589
3597
  const constantsContent = generateConstantsTemplate(normalizedName);
3590
3598
  const hash = await writeFileWithSignature(
3591
3599
  constantsFilePath,
@@ -3602,7 +3610,7 @@ async function createComponentCommand(componentName, options) {
3602
3610
  writtenFiles.push(constantsFilePath);
3603
3611
  }
3604
3612
 
3605
- if (shouldCreateApi) {
3613
+ if (shouldCreateApi && (!existsSync(apiFilePath) || options.force)) {
3606
3614
  const apiContent = generateApiTemplate(normalizedName);
3607
3615
  const hash = await writeFileWithSignature(
3608
3616
  apiFilePath,
@@ -3619,7 +3627,7 @@ async function createComponentCommand(componentName, options) {
3619
3627
  writtenFiles.push(apiFilePath);
3620
3628
  }
3621
3629
 
3622
- if (shouldCreateServices) {
3630
+ if (shouldCreateServices && (!existsSync(servicesFilePath) || options.force)) {
3623
3631
  const servicesContent = generateServiceTemplate(normalizedName);
3624
3632
  const hash = await writeFileWithSignature(
3625
3633
  servicesFilePath,
@@ -3636,7 +3644,7 @@ async function createComponentCommand(componentName, options) {
3636
3644
  writtenFiles.push(servicesFilePath);
3637
3645
  }
3638
3646
 
3639
- if (shouldCreateSchemas) {
3647
+ if (shouldCreateSchemas && (!existsSync(schemasFilePath) || options.force)) {
3640
3648
  const schemasContent = generateSchemaTemplate(normalizedName);
3641
3649
  const hash = await writeFileWithSignature(
3642
3650
  schemasFilePath,
@@ -3653,7 +3661,7 @@ async function createComponentCommand(componentName, options) {
3653
3661
  writtenFiles.push(schemasFilePath);
3654
3662
  }
3655
3663
 
3656
- if (shouldCreateReadme) {
3664
+ if (shouldCreateReadme && (!existsSync(readmeFilePath) || options.force)) {
3657
3665
  const readmeContent = generateReadmeTemplate(normalizedName);
3658
3666
  const hash = await writeFileWithSignature(
3659
3667
  readmeFilePath,
@@ -3670,7 +3678,7 @@ async function createComponentCommand(componentName, options) {
3670
3678
  writtenFiles.push(readmeFilePath);
3671
3679
  }
3672
3680
 
3673
- if (shouldCreateStories) {
3681
+ if (shouldCreateStories && (!existsSync(storiesFilePath) || options.force)) {
3674
3682
  const relativePath = `./${normalizedName}${config.naming.componentExtension}`;
3675
3683
  const storiesContent = generateStoriesTemplate(normalizedName, relativePath);
3676
3684
  const hash = await writeFileWithSignature(
@@ -4601,6 +4609,61 @@ async function pruneMissingCommand(options = {}) {
4601
4609
  }
4602
4610
  }
4603
4611
 
4612
+ /**
4613
+ * Add a new item (hook, api, service, etc.) to an existing feature or component.
4614
+ *
4615
+ * @param {string} itemType The type of item to add (e.g., 'api', 'hook', 'service')
4616
+ * @param {string} targetName The name of the feature or component
4617
+ * @param {Object} options Additional options from Commander
4618
+ */
4619
+ async function addItemCommand(itemType, targetName, options) {
4620
+ try {
4621
+ const state = await loadState();
4622
+
4623
+ // Normalize itemType
4624
+ let normalizedItem = itemType.toLowerCase();
4625
+ if (normalizedItem === 'test') normalizedItem = 'tests';
4626
+ if (normalizedItem === 'service') normalizedItem = 'services';
4627
+ if (normalizedItem === 'schema') normalizedItem = 'schemas';
4628
+ if (normalizedItem === 'hook') normalizedItem = 'hooks'; // for add-section
4629
+
4630
+ // Try to find as section (feature) first
4631
+ let section = findSection(state, targetName);
4632
+ let component = findComponent(state, targetName);
4633
+
4634
+ // If not found by exact name, try to find by featurePath or part of it
4635
+ if (!section && !component) {
4636
+ section = state.sections.find(s => s.featurePath === targetName || s.featurePath.endsWith('/' + targetName));
4637
+ }
4638
+
4639
+ if (!section && !component) {
4640
+ throw new Error(`Target not found in state: "${targetName}". Please use "add-section" or "create-component" directly if it's not managed by Textor.`);
4641
+ }
4642
+
4643
+ const flags = { [normalizedItem]: true };
4644
+ // Also set singular for create-component which uses 'hook'
4645
+ if (normalizedItem === 'hooks') flags.hook = true;
4646
+
4647
+ if (section) {
4648
+ console.log(`ℹ Adding ${normalizedItem} to feature: ${section.featurePath}`);
4649
+ return await addSectionCommand(undefined, section.featurePath, { ...options, ...flags });
4650
+ }
4651
+
4652
+ if (component) {
4653
+ console.log(`ℹ Adding ${normalizedItem} to component: ${component.name}`);
4654
+ // For create-component, we might need to be careful with flags that are on by default
4655
+ // but getEffectiveOptions should handle it if we pass them explicitly as true.
4656
+ return await createComponentCommand(component.name, { ...options, ...flags });
4657
+ }
4658
+ } catch (error) {
4659
+ console.error('Error:', error.message);
4660
+ if (typeof process.exit === 'function' && process.env.NODE_ENV !== 'test') {
4661
+ process.exit(1);
4662
+ }
4663
+ throw error;
4664
+ }
4665
+ }
4666
+
4604
4667
  /**
4605
4668
  * Dispatcher for rename commands.
4606
4669
  */
@@ -4860,5 +4923,12 @@ program
4860
4923
  .option('--dry-run', 'Show what would be renamed without applying')
4861
4924
  .action(renameCommand);
4862
4925
 
4926
+ program
4927
+ .command('add <item> <target>')
4928
+ .description('Add a sub-item (api, hook, test, etc.) to an existing feature or component')
4929
+ .option('--force', 'Overwrite existing files')
4930
+ .option('--dry-run', 'Show what would be created without creating')
4931
+ .action(addItemCommand);
4932
+
4863
4933
  program.parse();
4864
4934
  //# sourceMappingURL=textor.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"textor.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"textor.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}