@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 +27 -0
- package/dist/bin/textor.js +242 -109
- package/dist/bin/textor.js.map +1 -1
- package/dist/index.cjs +242 -107
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +11 -2
- package/dist/index.js +242 -108
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
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
|
|
package/dist/bin/textor.js
CHANGED
|
@@ -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, '/')
|
|
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 =>
|
|
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
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
if
|
|
2091
|
-
if (
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
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
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
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
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
if
|
|
3446
|
-
if (
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
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
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
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
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
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(
|
|
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 [
|
|
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
|
package/dist/bin/textor.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"textor.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"textor.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|