@oamm/textor 1.0.13 → 1.0.16

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
 
@@ -257,6 +276,8 @@ Adopt untracked files into Textor's state and add the Textor signature to them.
257
276
 
258
277
  ```bash
259
278
  pnpm textor adopt <path-or-identifier> [options]
279
+ pnpm textor adopt component <name> [options]
280
+ pnpm textor adopt feature <path> <name> [options]
260
281
  ```
261
282
 
262
283
  **Options:**
@@ -268,6 +289,12 @@ pnpm textor adopt <path-or-identifier> [options]
268
289
  # Adopt a specific component directory
269
290
  pnpm textor adopt src/components/MyNewButton
270
291
 
292
+ # Adopt a component by its name
293
+ pnpm textor adopt component MyNewButton
294
+
295
+ # Adopt a directory as a new feature (moves files to src/features/my-feat)
296
+ pnpm textor adopt feature path/to/legacy-code my-feat
297
+
271
298
  # Adopt a section by its route
272
299
  pnpm textor adopt /users
273
300
 
@@ -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
  }
@@ -1732,23 +1721,29 @@ function reconstructSections(state, config) {
1732
1721
  // Keep existing sections if their files still exist
1733
1722
  const validSections = (state.sections || []).filter(section => {
1734
1723
  // Check if route file exists in state.files
1735
- const routeFile = Object.keys(files).find(f => {
1724
+ const routeFile = section.route ? Object.keys(files).find(f => {
1736
1725
  const normalizedF = f.replace(/\\/g, '/');
1737
1726
  const routePath = section.route === '/' ? 'index' : section.route.slice(1);
1738
1727
  return normalizedF.startsWith(pagesRoot + '/' + routePath + '.') ||
1739
1728
  normalizedF === pagesRoot + '/' + routePath + '/index.astro'; // nested mode
1740
- });
1729
+ }) : true;
1741
1730
 
1742
1731
  // Check if feature directory has at least one file in state.files
1743
- const hasFeatureFiles = Object.keys(files).some(f =>
1744
- f.replace(/\\/g, '/').startsWith(section.featurePath.replace(/\\/g, '/') + '/')
1745
- );
1732
+ const hasFeatureFiles = section.featurePath ? Object.keys(files).some(f => {
1733
+ const normalizedF = f.replace(/\\/g, '/');
1734
+ const featPath = section.featurePath.replace(/\\/g, '/');
1735
+ return normalizedF.startsWith(featPath + '/') ||
1736
+ normalizedF.startsWith(featuresRoot + '/' + featPath + '/');
1737
+ }) : false;
1746
1738
 
1747
1739
  return routeFile && hasFeatureFiles;
1748
1740
  });
1749
1741
 
1750
1742
  const sections = new Map();
1751
- validSections.forEach(s => sections.set(s.route, s));
1743
+ validSections.forEach(s => {
1744
+ const key = s.route || `feature:${s.featurePath}`;
1745
+ sections.set(key, s);
1746
+ });
1752
1747
 
1753
1748
  // Try to discover new sections
1754
1749
  for (const filePath in files) {
@@ -2082,19 +2077,25 @@ async function addSectionCommand(route, featurePath, options) {
2082
2077
  }
2083
2078
  }
2084
2079
 
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);
2080
+ const featureExists = existsSync(featureFilePath);
2081
+ if (featureExists && !options.force) {
2082
+ console.log(`ℹ Feature already exists at ${featureFilePath}. Entering additive mode.`);
2083
+ }
2084
+
2085
+ // Check sub-items only if not in force mode
2086
+ if (!options.force) {
2087
+ if (shouldCreateIndex && existsSync(indexFilePath)) console.log(` - Skipping existing index: ${indexFilePath}`);
2088
+ if (shouldCreateContext && existsSync(contextFilePath)) console.log(` - Skipping existing context: ${contextFilePath}`);
2089
+ if (shouldCreateHooks && existsSync(hookFilePath)) console.log(` - Skipping existing hook: ${hookFilePath}`);
2090
+ if (shouldCreateTests && existsSync(testFilePath)) console.log(` - Skipping existing test: ${testFilePath}`);
2091
+ if (shouldCreateTypes && existsSync(typesFilePath)) console.log(` - Skipping existing types: ${typesFilePath}`);
2092
+ if (shouldCreateApi && existsSync(apiFilePath)) console.log(` - Skipping existing api: ${apiFilePath}`);
2093
+ if (shouldCreateServices && existsSync(servicesFilePath)) console.log(` - Skipping existing services: ${servicesFilePath}`);
2094
+ if (shouldCreateSchemas && existsSync(schemasFilePath)) console.log(` - Skipping existing schemas: ${schemasFilePath}`);
2095
+ if (shouldCreateReadme && existsSync(readmeFilePath)) console.log(` - Skipping existing readme: ${readmeFilePath}`);
2096
+ if (shouldCreateStories && existsSync(storiesFilePath)) console.log(` - Skipping existing stories: ${storiesFilePath}`);
2097
+ if (shouldCreateScriptsDir && existsSync(scriptsIndexPath)) console.log(` - Skipping existing scripts: ${scriptsIndexPath}`);
2098
+ }
2098
2099
 
2099
2100
  let layoutImportPath = null;
2100
2101
  const cliProps = options.prop || {};
@@ -2235,21 +2236,23 @@ async function addSectionCommand(route, featurePath, options) {
2235
2236
 
2236
2237
  const featureSignature = getSignature(config, config.naming.featureExtension === '.astro' ? 'astro' : 'tsx');
2237
2238
 
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);
2239
+ if (!featureExists || options.force) {
2240
+ const featureHash = await writeFileWithSignature(
2241
+ featureFilePath,
2242
+ featureContent,
2243
+ featureSignature,
2244
+ config.hashing?.normalization
2245
+ );
2246
+ await registerFile(featureFilePath, {
2247
+ kind: 'feature',
2248
+ template: 'feature',
2249
+ hash: featureHash,
2250
+ owner: normalizedRoute
2251
+ });
2252
+ writtenFiles.push(featureFilePath);
2253
+ }
2251
2254
 
2252
- if (shouldCreateScriptsDir) {
2255
+ if (shouldCreateScriptsDir && (!existsSync(scriptsIndexPath) || options.force)) {
2253
2256
  const hash = await writeFileWithSignature(
2254
2257
  scriptsIndexPath,
2255
2258
  generateScriptsIndexTemplate(),
@@ -2265,7 +2268,7 @@ async function addSectionCommand(route, featurePath, options) {
2265
2268
  writtenFiles.push(scriptsIndexPath);
2266
2269
  }
2267
2270
 
2268
- if (shouldCreateIndex) {
2271
+ if (shouldCreateIndex && (!existsSync(indexFilePath) || options.force)) {
2269
2272
  const indexContent = generateIndexTemplate(featureComponentName, config.naming.featureExtension);
2270
2273
  const hash = await writeFileWithSignature(
2271
2274
  indexFilePath,
@@ -2282,7 +2285,7 @@ async function addSectionCommand(route, featurePath, options) {
2282
2285
  writtenFiles.push(indexFilePath);
2283
2286
  }
2284
2287
 
2285
- if (shouldCreateApi) {
2288
+ if (shouldCreateApi && (!existsSync(apiFilePath) || options.force)) {
2286
2289
  const apiContent = generateApiTemplate(featureComponentName);
2287
2290
  const hash = await writeFileWithSignature(
2288
2291
  apiFilePath,
@@ -2299,7 +2302,7 @@ async function addSectionCommand(route, featurePath, options) {
2299
2302
  writtenFiles.push(apiFilePath);
2300
2303
  }
2301
2304
 
2302
- if (shouldCreateServices) {
2305
+ if (shouldCreateServices && (!existsSync(servicesFilePath) || options.force)) {
2303
2306
  const servicesContent = generateServiceTemplate(featureComponentName);
2304
2307
  const hash = await writeFileWithSignature(
2305
2308
  servicesFilePath,
@@ -2316,7 +2319,7 @@ async function addSectionCommand(route, featurePath, options) {
2316
2319
  writtenFiles.push(servicesFilePath);
2317
2320
  }
2318
2321
 
2319
- if (shouldCreateSchemas) {
2322
+ if (shouldCreateSchemas && (!existsSync(schemasFilePath) || options.force)) {
2320
2323
  const schemasContent = generateSchemaTemplate(featureComponentName);
2321
2324
  const hash = await writeFileWithSignature(
2322
2325
  schemasFilePath,
@@ -2333,7 +2336,7 @@ async function addSectionCommand(route, featurePath, options) {
2333
2336
  writtenFiles.push(schemasFilePath);
2334
2337
  }
2335
2338
 
2336
- if (shouldCreateHooks) {
2339
+ if (shouldCreateHooks && (!existsSync(hookFilePath) || options.force)) {
2337
2340
  const hookName = getHookFunctionName(featureComponentName);
2338
2341
  const hookContent = generateHookTemplate(featureComponentName, hookName);
2339
2342
  const hash = await writeFileWithSignature(
@@ -2351,7 +2354,7 @@ async function addSectionCommand(route, featurePath, options) {
2351
2354
  writtenFiles.push(hookFilePath);
2352
2355
  }
2353
2356
 
2354
- if (shouldCreateContext) {
2357
+ if (shouldCreateContext && (!existsSync(contextFilePath) || options.force)) {
2355
2358
  const contextContent = generateContextTemplate(featureComponentName);
2356
2359
  const hash = await writeFileWithSignature(
2357
2360
  contextFilePath,
@@ -2368,7 +2371,7 @@ async function addSectionCommand(route, featurePath, options) {
2368
2371
  writtenFiles.push(contextFilePath);
2369
2372
  }
2370
2373
 
2371
- if (shouldCreateTests) {
2374
+ if (shouldCreateTests && (!existsSync(testFilePath) || options.force)) {
2372
2375
  const relativeFeaturePath = `./${path.basename(featureFilePath)}`;
2373
2376
  const testContent = generateTestTemplate(featureComponentName, relativeFeaturePath);
2374
2377
  const hash = await writeFileWithSignature(
@@ -2386,7 +2389,7 @@ async function addSectionCommand(route, featurePath, options) {
2386
2389
  writtenFiles.push(testFilePath);
2387
2390
  }
2388
2391
 
2389
- if (shouldCreateTypes) {
2392
+ if (shouldCreateTypes && (!existsSync(typesFilePath) || options.force)) {
2390
2393
  const typesContent = generateTypesTemplate(featureComponentName);
2391
2394
  const hash = await writeFileWithSignature(
2392
2395
  typesFilePath,
@@ -2403,7 +2406,7 @@ async function addSectionCommand(route, featurePath, options) {
2403
2406
  writtenFiles.push(typesFilePath);
2404
2407
  }
2405
2408
 
2406
- if (shouldCreateReadme) {
2409
+ if (shouldCreateReadme && (!existsSync(readmeFilePath) || options.force)) {
2407
2410
  const readmeContent = generateReadmeTemplate(featureComponentName);
2408
2411
  const hash = await writeFileWithSignature(
2409
2412
  readmeFilePath,
@@ -2420,7 +2423,7 @@ async function addSectionCommand(route, featurePath, options) {
2420
2423
  writtenFiles.push(readmeFilePath);
2421
2424
  }
2422
2425
 
2423
- if (shouldCreateStories) {
2426
+ if (shouldCreateStories && (!existsSync(storiesFilePath) || options.force)) {
2424
2427
  const relativePath = `./${path.basename(featureFilePath)}`;
2425
2428
  const storiesContent = generateStoriesTemplate(featureComponentName, relativePath);
2426
2429
  const hash = await writeFileWithSignature(
@@ -3437,20 +3440,26 @@ async function createComponentCommand(componentName, options) {
3437
3440
  return;
3438
3441
  }
3439
3442
 
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);
3443
+ const componentExists = existsSync(componentFilePath);
3444
+ if (componentExists && !options.force) {
3445
+ console.log(`ℹ Component already exists at ${componentFilePath}. Entering additive mode.`);
3446
+ }
3447
+
3448
+ // Check sub-items only if not in force mode
3449
+ if (!options.force) {
3450
+ if (existsSync(indexFilePath)) console.log(` - Skipping existing index: ${indexFilePath}`);
3451
+ if (shouldCreateContext && existsSync(contextFilePath)) console.log(` - Skipping existing context: ${contextFilePath}`);
3452
+ if (shouldCreateHook && existsSync(hookFilePath)) console.log(` - Skipping existing hook: ${hookFilePath}`);
3453
+ if (shouldCreateTests && existsSync(testFilePath)) console.log(` - Skipping existing test: ${testFilePath}`);
3454
+ if (shouldCreateConfig && existsSync(configFilePath)) console.log(` - Skipping existing config: ${configFilePath}`);
3455
+ if (shouldCreateConstants && existsSync(constantsFilePath)) console.log(` - Skipping existing constants: ${constantsFilePath}`);
3456
+ if (shouldCreateTypes && existsSync(typesFilePath)) console.log(` - Skipping existing types: ${typesFilePath}`);
3457
+ if (shouldCreateApi && existsSync(apiFilePath)) console.log(` - Skipping existing api: ${apiFilePath}`);
3458
+ if (shouldCreateServices && existsSync(servicesFilePath)) console.log(` - Skipping existing services: ${servicesFilePath}`);
3459
+ if (shouldCreateSchemas && existsSync(schemasFilePath)) console.log(` - Skipping existing schemas: ${schemasFilePath}`);
3460
+ if (shouldCreateReadme && existsSync(readmeFilePath)) console.log(` - Skipping existing readme: ${readmeFilePath}`);
3461
+ if (shouldCreateStories && existsSync(storiesFilePath)) console.log(` - Skipping existing stories: ${storiesFilePath}`);
3462
+ }
3454
3463
 
3455
3464
  await ensureDir(componentDir);
3456
3465
 
@@ -3468,37 +3477,42 @@ async function createComponentCommand(componentName, options) {
3468
3477
  const componentContent = generateComponentTemplate(normalizedName, framework, config.naming.componentExtension);
3469
3478
  const signature = getSignature(config, config.naming.componentExtension === '.astro' ? 'astro' : 'tsx');
3470
3479
 
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
- });
3480
+ const writtenFiles = [];
3481
+
3482
+ if (!componentExists || options.force) {
3483
+ const componentHash = await writeFileWithSignature(
3484
+ componentFilePath,
3485
+ componentContent,
3486
+ signature,
3487
+ config.hashing?.normalization
3488
+ );
3489
+ await registerFile(componentFilePath, {
3490
+ kind: 'component',
3491
+ template: 'component',
3492
+ hash: componentHash,
3493
+ owner: normalizedName
3494
+ });
3495
+ writtenFiles.push(componentFilePath);
3496
+ }
3483
3497
 
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);
3498
+ if (!existsSync(indexFilePath) || options.force) {
3499
+ const indexContent = generateIndexTemplate(normalizedName, config.naming.componentExtension);
3500
+ const indexHash = await writeFileWithSignature(
3501
+ indexFilePath,
3502
+ indexContent,
3503
+ getSignature(config, 'typescript'),
3504
+ config.hashing?.normalization
3505
+ );
3506
+ await registerFile(indexFilePath, {
3507
+ kind: 'component-file',
3508
+ template: 'index',
3509
+ hash: indexHash,
3510
+ owner: normalizedName
3511
+ });
3512
+ writtenFiles.push(indexFilePath);
3513
+ }
3500
3514
 
3501
- if (shouldCreateTypes) {
3515
+ if (shouldCreateTypes && (!existsSync(typesFilePath) || options.force)) {
3502
3516
  const typesContent = generateTypesTemplate(normalizedName);
3503
3517
  const hash = await writeFileWithSignature(
3504
3518
  typesFilePath,
@@ -3515,7 +3529,7 @@ async function createComponentCommand(componentName, options) {
3515
3529
  writtenFiles.push(typesFilePath);
3516
3530
  }
3517
3531
 
3518
- if (shouldCreateContext) {
3532
+ if (shouldCreateContext && (!existsSync(contextFilePath) || options.force)) {
3519
3533
  const contextContent = generateContextTemplate(normalizedName);
3520
3534
  const hash = await writeFileWithSignature(
3521
3535
  contextFilePath,
@@ -3532,7 +3546,7 @@ async function createComponentCommand(componentName, options) {
3532
3546
  writtenFiles.push(contextFilePath);
3533
3547
  }
3534
3548
 
3535
- if (shouldCreateHook) {
3549
+ if (shouldCreateHook && (!existsSync(hookFilePath) || options.force)) {
3536
3550
  const hookName = getHookFunctionName(normalizedName);
3537
3551
  const hookContent = generateHookTemplate(normalizedName, hookName);
3538
3552
  const hash = await writeFileWithSignature(
@@ -3550,7 +3564,7 @@ async function createComponentCommand(componentName, options) {
3550
3564
  writtenFiles.push(hookFilePath);
3551
3565
  }
3552
3566
 
3553
- if (shouldCreateTests) {
3567
+ if (shouldCreateTests && (!existsSync(testFilePath) || options.force)) {
3554
3568
  const relativeComponentPath = `../${normalizedName}${config.naming.componentExtension}`;
3555
3569
  const testContent = generateTestTemplate(normalizedName, relativeComponentPath);
3556
3570
  const hash = await writeFileWithSignature(
@@ -3568,7 +3582,7 @@ async function createComponentCommand(componentName, options) {
3568
3582
  writtenFiles.push(testFilePath);
3569
3583
  }
3570
3584
 
3571
- if (shouldCreateConfig) {
3585
+ if (shouldCreateConfig && (!existsSync(configFilePath) || options.force)) {
3572
3586
  const configContent = generateConfigTemplate(normalizedName);
3573
3587
  const hash = await writeFileWithSignature(
3574
3588
  configFilePath,
@@ -3585,7 +3599,7 @@ async function createComponentCommand(componentName, options) {
3585
3599
  writtenFiles.push(configFilePath);
3586
3600
  }
3587
3601
 
3588
- if (shouldCreateConstants) {
3602
+ if (shouldCreateConstants && (!existsSync(constantsFilePath) || options.force)) {
3589
3603
  const constantsContent = generateConstantsTemplate(normalizedName);
3590
3604
  const hash = await writeFileWithSignature(
3591
3605
  constantsFilePath,
@@ -3602,7 +3616,7 @@ async function createComponentCommand(componentName, options) {
3602
3616
  writtenFiles.push(constantsFilePath);
3603
3617
  }
3604
3618
 
3605
- if (shouldCreateApi) {
3619
+ if (shouldCreateApi && (!existsSync(apiFilePath) || options.force)) {
3606
3620
  const apiContent = generateApiTemplate(normalizedName);
3607
3621
  const hash = await writeFileWithSignature(
3608
3622
  apiFilePath,
@@ -3619,7 +3633,7 @@ async function createComponentCommand(componentName, options) {
3619
3633
  writtenFiles.push(apiFilePath);
3620
3634
  }
3621
3635
 
3622
- if (shouldCreateServices) {
3636
+ if (shouldCreateServices && (!existsSync(servicesFilePath) || options.force)) {
3623
3637
  const servicesContent = generateServiceTemplate(normalizedName);
3624
3638
  const hash = await writeFileWithSignature(
3625
3639
  servicesFilePath,
@@ -3636,7 +3650,7 @@ async function createComponentCommand(componentName, options) {
3636
3650
  writtenFiles.push(servicesFilePath);
3637
3651
  }
3638
3652
 
3639
- if (shouldCreateSchemas) {
3653
+ if (shouldCreateSchemas && (!existsSync(schemasFilePath) || options.force)) {
3640
3654
  const schemasContent = generateSchemaTemplate(normalizedName);
3641
3655
  const hash = await writeFileWithSignature(
3642
3656
  schemasFilePath,
@@ -3653,7 +3667,7 @@ async function createComponentCommand(componentName, options) {
3653
3667
  writtenFiles.push(schemasFilePath);
3654
3668
  }
3655
3669
 
3656
- if (shouldCreateReadme) {
3670
+ if (shouldCreateReadme && (!existsSync(readmeFilePath) || options.force)) {
3657
3671
  const readmeContent = generateReadmeTemplate(normalizedName);
3658
3672
  const hash = await writeFileWithSignature(
3659
3673
  readmeFilePath,
@@ -3670,7 +3684,7 @@ async function createComponentCommand(componentName, options) {
3670
3684
  writtenFiles.push(readmeFilePath);
3671
3685
  }
3672
3686
 
3673
- if (shouldCreateStories) {
3687
+ if (shouldCreateStories && (!existsSync(storiesFilePath) || options.force)) {
3674
3688
  const relativePath = `./${normalizedName}${config.naming.componentExtension}`;
3675
3689
  const storiesContent = generateStoriesTemplate(normalizedName, relativePath);
3676
3690
  const hash = await writeFileWithSignature(
@@ -4305,7 +4319,24 @@ async function syncCommand(options) {
4305
4319
  }
4306
4320
  }
4307
4321
 
4308
- async function adoptCommand(identifier, options) {
4322
+ async function adoptCommand(kind, arg2, arg3, options) {
4323
+ // Handle cases where options is in a different position due to variable arguments
4324
+ if (typeof kind === 'object' && kind !== null && !Array.isArray(kind)) {
4325
+ options = kind;
4326
+ kind = undefined;
4327
+ arg2 = undefined;
4328
+ arg3 = undefined;
4329
+ } else if (typeof arg2 === 'object' && arg2 !== null && !Array.isArray(arg2)) {
4330
+ options = arg2;
4331
+ arg2 = undefined;
4332
+ arg3 = undefined;
4333
+ } else if (typeof arg3 === 'object' && arg3 !== null && !Array.isArray(arg3)) {
4334
+ options = arg3;
4335
+ arg3 = undefined;
4336
+ }
4337
+
4338
+ options = options || {};
4339
+
4309
4340
  try {
4310
4341
  const config = await loadConfig();
4311
4342
  const state = await loadState();
@@ -4317,6 +4348,9 @@ async function adoptCommand(identifier, options) {
4317
4348
  };
4318
4349
 
4319
4350
  let filesToAdopt = [];
4351
+ let identifier = kind;
4352
+ let customName = null;
4353
+ let sourcePath = null;
4320
4354
 
4321
4355
  if (!identifier && options.all) {
4322
4356
  // Adopt all untracked files in all roots
@@ -4327,6 +4361,43 @@ async function adoptCommand(identifier, options) {
4327
4361
  }
4328
4362
  }
4329
4363
  filesToAdopt = Array.from(managedFiles).filter(f => !state.files[f]);
4364
+ } else if (kind === 'component' && arg2) {
4365
+ const componentName = arg2;
4366
+ const untrackedFiles = new Set();
4367
+ const compPath = path.join(roots.components, componentName);
4368
+ if (existsSync(compPath)) {
4369
+ await scanDirectoryOrFile(compPath, untrackedFiles, state);
4370
+ } else {
4371
+ throw new Error(`Component directory not found: ${compPath}`);
4372
+ }
4373
+ filesToAdopt = Array.from(untrackedFiles);
4374
+ } else if (kind === 'feature' && arg2 && arg3) {
4375
+ sourcePath = arg2;
4376
+ customName = arg3;
4377
+ const targetPath = path.join(roots.features, customName);
4378
+ const absoluteSource = path.resolve(process.cwd(), sourcePath);
4379
+ const absoluteTarget = path.resolve(targetPath);
4380
+
4381
+ if (existsSync(absoluteSource)) {
4382
+ if (!options.dryRun && absoluteSource !== absoluteTarget) {
4383
+ if (!existsSync(path.dirname(absoluteTarget))) {
4384
+ await mkdir(path.dirname(absoluteTarget), { recursive: true });
4385
+ }
4386
+ console.log(`Moving files from ${sourcePath} to ${path.relative(process.cwd(), targetPath)}...`);
4387
+ await rename(absoluteSource, absoluteTarget);
4388
+ sourcePath = path.relative(process.cwd(), targetPath);
4389
+ } else if (options.dryRun && absoluteSource !== absoluteTarget) {
4390
+ console.log(`Dry run: would move files from ${sourcePath} to ${path.relative(process.cwd(), targetPath)}`);
4391
+ sourcePath = path.relative(process.cwd(), targetPath);
4392
+ }
4393
+
4394
+ const untrackedFiles = new Set();
4395
+ const fullPath = absoluteSource !== absoluteTarget && !options.dryRun ? absoluteTarget : absoluteSource;
4396
+ await scanDirectoryOrFile(fullPath, untrackedFiles, state);
4397
+ filesToAdopt = Array.from(untrackedFiles);
4398
+ } else {
4399
+ throw new Error(`Source path not found: ${absoluteSource}`);
4400
+ }
4330
4401
  } else if (identifier) {
4331
4402
  const untrackedFiles = new Set();
4332
4403
 
@@ -4601,6 +4672,61 @@ async function pruneMissingCommand(options = {}) {
4601
4672
  }
4602
4673
  }
4603
4674
 
4675
+ /**
4676
+ * Add a new item (hook, api, service, etc.) to an existing feature or component.
4677
+ *
4678
+ * @param {string} itemType The type of item to add (e.g., 'api', 'hook', 'service')
4679
+ * @param {string} targetName The name of the feature or component
4680
+ * @param {Object} options Additional options from Commander
4681
+ */
4682
+ async function addItemCommand(itemType, targetName, options) {
4683
+ try {
4684
+ const state = await loadState();
4685
+
4686
+ // Normalize itemType
4687
+ let normalizedItem = itemType.toLowerCase();
4688
+ if (normalizedItem === 'test') normalizedItem = 'tests';
4689
+ if (normalizedItem === 'service') normalizedItem = 'services';
4690
+ if (normalizedItem === 'schema') normalizedItem = 'schemas';
4691
+ if (normalizedItem === 'hook') normalizedItem = 'hooks'; // for add-section
4692
+
4693
+ // Try to find as section (feature) first
4694
+ let section = findSection(state, targetName);
4695
+ let component = findComponent(state, targetName);
4696
+
4697
+ // If not found by exact name, try to find by featurePath or part of it
4698
+ if (!section && !component) {
4699
+ section = state.sections.find(s => s.featurePath === targetName || s.featurePath.endsWith('/' + targetName));
4700
+ }
4701
+
4702
+ if (!section && !component) {
4703
+ throw new Error(`Target not found in state: "${targetName}". Please use "add-section" or "create-component" directly if it's not managed by Textor.`);
4704
+ }
4705
+
4706
+ const flags = { [normalizedItem]: true };
4707
+ // Also set singular for create-component which uses 'hook'
4708
+ if (normalizedItem === 'hooks') flags.hook = true;
4709
+
4710
+ if (section) {
4711
+ console.log(`ℹ Adding ${normalizedItem} to feature: ${section.featurePath}`);
4712
+ return await addSectionCommand(undefined, section.featurePath, { ...options, ...flags });
4713
+ }
4714
+
4715
+ if (component) {
4716
+ console.log(`ℹ Adding ${normalizedItem} to component: ${component.name}`);
4717
+ // For create-component, we might need to be careful with flags that are on by default
4718
+ // but getEffectiveOptions should handle it if we pass them explicitly as true.
4719
+ return await createComponentCommand(component.name, { ...options, ...flags });
4720
+ }
4721
+ } catch (error) {
4722
+ console.error('Error:', error.message);
4723
+ if (typeof process.exit === 'function' && process.env.NODE_ENV !== 'test') {
4724
+ process.exit(1);
4725
+ }
4726
+ throw error;
4727
+ }
4728
+ }
4729
+
4604
4730
  /**
4605
4731
  * Dispatcher for rename commands.
4606
4732
  */
@@ -4825,7 +4951,7 @@ program
4825
4951
  .action(syncCommand);
4826
4952
 
4827
4953
  program
4828
- .command('adopt [path]')
4954
+ .command('adopt [kind] [arg2] [arg3]')
4829
4955
  .description('Adopt untracked files into Textor state, adding signatures')
4830
4956
  .option('--all', 'Adopt all untracked files in managed directories')
4831
4957
  .option('--dry-run', 'Show what would be adopted without applying')
@@ -4860,5 +4986,12 @@ program
4860
4986
  .option('--dry-run', 'Show what would be renamed without applying')
4861
4987
  .action(renameCommand);
4862
4988
 
4989
+ program
4990
+ .command('add <item> <target>')
4991
+ .description('Add a sub-item (api, hook, test, etc.) to an existing feature or component')
4992
+ .option('--force', 'Overwrite existing files')
4993
+ .option('--dry-run', 'Show what would be created without creating')
4994
+ .action(addItemCommand);
4995
+
4863
4996
  program.parse();
4864
4997
  //# 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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}