@nexical/cli 0.1.7 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/deploy.yml +3 -3
- package/GEMINI.md +193 -0
- package/README.md +317 -104
- package/dist/chunk-JYASTIIW.js +42 -0
- package/dist/chunk-JYASTIIW.js.map +1 -0
- package/dist/chunk-LZ3YQWAR.js +2204 -0
- package/dist/chunk-LZ3YQWAR.js.map +1 -0
- package/dist/chunk-OKXOCNXP.js +105 -0
- package/dist/chunk-OKXOCNXP.js.map +1 -0
- package/dist/chunk-OYFWMYPG.js +52 -0
- package/dist/chunk-OYFWMYPG.js.map +1 -0
- package/dist/chunk-WKERTCM6.js +74 -0
- package/dist/chunk-WKERTCM6.js.map +1 -0
- package/dist/index.js +32 -5
- package/dist/index.js.map +1 -1
- package/dist/src/commands/init.d.ts +11 -0
- package/dist/src/commands/init.js +89 -0
- package/dist/src/commands/init.js.map +1 -0
- package/dist/src/commands/module/add.d.ts +14 -0
- package/dist/src/commands/module/add.js +136 -0
- package/dist/src/commands/module/add.js.map +1 -0
- package/dist/src/commands/module/list.d.ts +10 -0
- package/dist/src/commands/module/list.js +73 -0
- package/dist/src/commands/module/list.js.map +1 -0
- package/dist/src/commands/module/remove.d.ts +12 -0
- package/dist/src/commands/module/remove.js +71 -0
- package/dist/src/commands/module/remove.js.map +1 -0
- package/dist/src/commands/module/update.d.ts +11 -0
- package/dist/src/commands/module/update.js +52 -0
- package/dist/src/commands/module/update.js.map +1 -0
- package/dist/src/commands/run.d.ts +11 -0
- package/dist/src/commands/run.js +93 -0
- package/dist/src/commands/run.js.map +1 -0
- package/dist/src/commands/{login.d.ts → setup.d.ts} +2 -2
- package/dist/src/commands/setup.js +62 -0
- package/dist/src/commands/setup.js.map +1 -0
- package/dist/src/utils/discovery.d.ts +13 -0
- package/dist/src/utils/discovery.js +9 -0
- package/dist/src/utils/git.d.ts +16 -0
- package/dist/src/utils/git.js +29 -0
- package/dist/src/utils/git.js.map +1 -0
- package/dist/src/utils/url-resolver.d.ts +15 -0
- package/dist/src/utils/url-resolver.js +9 -0
- package/dist/src/utils/url-resolver.js.map +1 -0
- package/index.ts +29 -5
- package/package.json +32 -30
- package/src/commands/init.ts +86 -0
- package/src/commands/module/add.ts +169 -0
- package/src/commands/module/list.ts +69 -0
- package/src/commands/module/remove.ts +74 -0
- package/src/commands/module/update.ts +50 -0
- package/src/commands/run.ts +98 -0
- package/src/commands/setup.ts +74 -0
- package/src/utils/discovery.ts +134 -0
- package/src/utils/git.ts +65 -0
- package/src/utils/url-resolver.ts +57 -0
- package/test/e2e/lifecycle.e2e.test.ts +153 -0
- package/test/integration/commands/init.integration.test.ts +85 -0
- package/test/integration/commands/module.integration.test.ts +144 -0
- package/test/integration/commands/run.integration.test.ts +90 -0
- package/test/integration/utils/command-loading.integration.test.ts +80 -0
- package/test/unit/commands/init.test.ts +153 -0
- package/test/unit/commands/module/add.test.ts +262 -0
- package/test/unit/commands/module/list.test.ts +115 -0
- package/test/unit/commands/module/remove.test.ts +89 -0
- package/test/unit/commands/module/update.test.ts +91 -0
- package/test/unit/commands/run.test.ts +252 -0
- package/test/unit/commands/setup.test.ts +169 -0
- package/test/unit/utils/command-discovery.test.ts +176 -0
- package/test/unit/utils/git.test.ts +152 -0
- package/test/unit/utils/integration-helpers.test.ts +72 -0
- package/test/unit/utils/url-resolver.test.ts +39 -0
- package/test/utils/integration-helpers.ts +66 -0
- package/vitest.e2e.config.ts +0 -1
- package/dist/chunk-JDRAVUKK.js +0 -48
- package/dist/chunk-JDRAVUKK.js.map +0 -1
- package/dist/src/commands/admin/create-user.d.ts +0 -15
- package/dist/src/commands/admin/create-user.js +0 -49
- package/dist/src/commands/admin/create-user.js.map +0 -1
- package/dist/src/commands/branch/create.d.ts +0 -19
- package/dist/src/commands/branch/create.js +0 -59
- package/dist/src/commands/branch/create.js.map +0 -1
- package/dist/src/commands/branch/delete.d.ts +0 -15
- package/dist/src/commands/branch/delete.js +0 -50
- package/dist/src/commands/branch/delete.js.map +0 -1
- package/dist/src/commands/branch/get.d.ts +0 -15
- package/dist/src/commands/branch/get.js +0 -53
- package/dist/src/commands/branch/get.js.map +0 -1
- package/dist/src/commands/branch/list.d.ts +0 -15
- package/dist/src/commands/branch/list.js +0 -51
- package/dist/src/commands/branch/list.js.map +0 -1
- package/dist/src/commands/job/get.d.ts +0 -15
- package/dist/src/commands/job/get.js +0 -62
- package/dist/src/commands/job/get.js.map +0 -1
- package/dist/src/commands/job/list.d.ts +0 -15
- package/dist/src/commands/job/list.js +0 -57
- package/dist/src/commands/job/list.js.map +0 -1
- package/dist/src/commands/job/logs.d.ts +0 -15
- package/dist/src/commands/job/logs.js +0 -67
- package/dist/src/commands/job/logs.js.map +0 -1
- package/dist/src/commands/job/trigger.d.ts +0 -19
- package/dist/src/commands/job/trigger.js +0 -74
- package/dist/src/commands/job/trigger.js.map +0 -1
- package/dist/src/commands/login.js +0 -31
- package/dist/src/commands/login.js.map +0 -1
- package/dist/src/commands/project/create.d.ts +0 -24
- package/dist/src/commands/project/create.js +0 -63
- package/dist/src/commands/project/create.js.map +0 -1
- package/dist/src/commands/project/delete.d.ts +0 -20
- package/dist/src/commands/project/delete.js +0 -58
- package/dist/src/commands/project/delete.js.map +0 -1
- package/dist/src/commands/project/get.d.ts +0 -15
- package/dist/src/commands/project/get.js +0 -49
- package/dist/src/commands/project/get.js.map +0 -1
- package/dist/src/commands/project/list.d.ts +0 -15
- package/dist/src/commands/project/list.js +0 -45
- package/dist/src/commands/project/list.js.map +0 -1
- package/dist/src/commands/project/update.d.ts +0 -19
- package/dist/src/commands/project/update.js +0 -66
- package/dist/src/commands/project/update.js.map +0 -1
- package/dist/src/commands/team/create.d.ts +0 -19
- package/dist/src/commands/team/create.js +0 -45
- package/dist/src/commands/team/create.js.map +0 -1
- package/dist/src/commands/team/delete.d.ts +0 -20
- package/dist/src/commands/team/delete.js +0 -52
- package/dist/src/commands/team/delete.js.map +0 -1
- package/dist/src/commands/team/get.d.ts +0 -15
- package/dist/src/commands/team/get.js +0 -42
- package/dist/src/commands/team/get.js.map +0 -1
- package/dist/src/commands/team/list.d.ts +0 -8
- package/dist/src/commands/team/list.js +0 -30
- package/dist/src/commands/team/list.js.map +0 -1
- package/dist/src/commands/team/member/invite.d.ts +0 -20
- package/dist/src/commands/team/member/invite.js +0 -54
- package/dist/src/commands/team/member/invite.js.map +0 -1
- package/dist/src/commands/team/member/remove.d.ts +0 -15
- package/dist/src/commands/team/member/remove.js +0 -43
- package/dist/src/commands/team/member/remove.js.map +0 -1
- package/dist/src/commands/team/update.d.ts +0 -19
- package/dist/src/commands/team/update.js +0 -55
- package/dist/src/commands/team/update.js.map +0 -1
- package/dist/src/commands/token/generate.d.ts +0 -19
- package/dist/src/commands/token/generate.js +0 -48
- package/dist/src/commands/token/generate.js.map +0 -1
- package/dist/src/commands/token/list.d.ts +0 -8
- package/dist/src/commands/token/list.js +0 -31
- package/dist/src/commands/token/list.js.map +0 -1
- package/dist/src/commands/token/revoke.d.ts +0 -15
- package/dist/src/commands/token/revoke.js +0 -38
- package/dist/src/commands/token/revoke.js.map +0 -1
- package/dist/src/commands/whoami.d.ts +0 -8
- package/dist/src/commands/whoami.js +0 -26
- package/dist/src/commands/whoami.js.map +0 -1
- package/dist/src/utils/nexical-client.d.ts +0 -10
- package/dist/src/utils/nexical-client.js +0 -12
- package/src/commands/admin/create-user.ts +0 -46
- package/src/commands/branch/create.ts +0 -57
- package/src/commands/branch/delete.ts +0 -47
- package/src/commands/branch/get.ts +0 -50
- package/src/commands/branch/list.ts +0 -50
- package/src/commands/job/get.ts +0 -59
- package/src/commands/job/list.ts +0 -56
- package/src/commands/job/logs.ts +0 -67
- package/src/commands/job/trigger.ts +0 -73
- package/src/commands/login.ts +0 -31
- package/src/commands/project/create.ts +0 -61
- package/src/commands/project/delete.ts +0 -56
- package/src/commands/project/get.ts +0 -46
- package/src/commands/project/list.ts +0 -44
- package/src/commands/project/update.ts +0 -63
- package/src/commands/team/create.ts +0 -43
- package/src/commands/team/delete.ts +0 -50
- package/src/commands/team/get.ts +0 -39
- package/src/commands/team/list.ts +0 -26
- package/src/commands/team/member/invite.ts +0 -56
- package/src/commands/team/member/remove.ts +0 -40
- package/src/commands/team/update.ts +0 -53
- package/src/commands/token/generate.ts +0 -45
- package/src/commands/token/list.ts +0 -27
- package/src/commands/token/revoke.ts +0 -35
- package/src/commands/whoami.ts +0 -21
- package/src/utils/nexical-client.ts +0 -47
- package/test/e2e/auth.e2e.test.ts +0 -46
- package/test/e2e/job-workflow.e2e.test.ts +0 -33
- package/test/e2e/project-lifecycle.e2e.test.ts +0 -48
- package/test/e2e/setup.ts +0 -237
- package/test/e2e/utils.ts +0 -33
- package/test/integration/commands/admin/create-user.test.ts +0 -51
- package/test/integration/commands/branch/create.test.ts +0 -51
- package/test/integration/commands/branch/delete.test.ts +0 -43
- package/test/integration/commands/branch/get.test.ts +0 -49
- package/test/integration/commands/branch/list.test.ts +0 -47
- package/test/integration/commands/job/get.test.ts +0 -54
- package/test/integration/commands/job/list.test.ts +0 -47
- package/test/integration/commands/job/logs.test.ts +0 -47
- package/test/integration/commands/job/trigger.test.ts +0 -57
- package/test/integration/commands/login.test.ts +0 -62
- package/test/integration/commands/project/create.test.ts +0 -53
- package/test/integration/commands/project/delete.test.ts +0 -43
- package/test/integration/commands/project/get.test.ts +0 -51
- package/test/integration/commands/project/list.test.ts +0 -47
- package/test/integration/commands/project/update.test.ts +0 -53
- package/test/integration/commands/team/create.test.ts +0 -53
- package/test/integration/commands/team/delete.test.ts +0 -43
- package/test/integration/commands/team/get.test.ts +0 -50
- package/test/integration/commands/team/list.test.ts +0 -47
- package/test/integration/commands/team/member/invite.test.ts +0 -46
- package/test/integration/commands/team/member/remove.test.ts +0 -43
- package/test/integration/commands/team/update.test.ts +0 -50
- package/test/integration/commands/token/generate.test.ts +0 -51
- package/test/integration/commands/token/list.test.ts +0 -47
- package/test/integration/commands/token/revoke.test.ts +0 -43
- package/test/integration/commands/whoami.test.ts +0 -49
- package/test/unit/commands/admin/create-user.test.ts +0 -51
- package/test/unit/commands/branch/create.test.ts +0 -57
- package/test/unit/commands/branch/delete.test.ts +0 -49
- package/test/unit/commands/branch/get.test.ts +0 -67
- package/test/unit/commands/branch/list.test.ts +0 -62
- package/test/unit/commands/job/get.test.ts +0 -76
- package/test/unit/commands/job/list.test.ts +0 -62
- package/test/unit/commands/job/logs.test.ts +0 -60
- package/test/unit/commands/job/trigger.test.ts +0 -75
- package/test/unit/commands/login.test.ts +0 -64
- package/test/unit/commands/project/create.test.ts +0 -64
- package/test/unit/commands/project/delete.test.ts +0 -72
- package/test/unit/commands/project/get.test.ts +0 -73
- package/test/unit/commands/project/list.test.ts +0 -62
- package/test/unit/commands/project/update.test.ts +0 -58
- package/test/unit/commands/team/create.test.ts +0 -68
- package/test/unit/commands/team/delete.test.ts +0 -71
- package/test/unit/commands/team/get.test.ts +0 -70
- package/test/unit/commands/team/list.test.ts +0 -56
- package/test/unit/commands/team/member/invite.test.ts +0 -52
- package/test/unit/commands/team/member/remove.test.ts +0 -49
- package/test/unit/commands/team/update.test.ts +0 -63
- package/test/unit/commands/token/generate.test.ts +0 -65
- package/test/unit/commands/token/list.test.ts +0 -58
- package/test/unit/commands/token/revoke.test.ts +0 -49
- package/test/unit/commands/whoami.test.ts +0 -49
- package/test/unit/utils/nexical-client.test.ts +0 -113
- /package/dist/src/utils/{nexical-client.js.map → discovery.js.map} +0 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { type CommandDefinition, BaseCommand, logger, runCommand } from '@nexical/cli-core';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { clone, getRemoteUrl } from '../../utils/git.js';
|
|
5
|
+
import { resolveGitUrl } from '../../utils/url-resolver.js';
|
|
6
|
+
import YAML from 'yaml';
|
|
7
|
+
|
|
8
|
+
export default class ModuleAddCommand extends BaseCommand {
|
|
9
|
+
static usage = 'module add <url>';
|
|
10
|
+
static description = 'Add a module and its dependencies as git submodules.';
|
|
11
|
+
static requiresProject = true;
|
|
12
|
+
|
|
13
|
+
static args: CommandDefinition = {
|
|
14
|
+
args: [
|
|
15
|
+
{ name: 'url', required: true, description: 'Git repository URL or gh@org/repo' }
|
|
16
|
+
]
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
private visited = new Set<string>();
|
|
20
|
+
|
|
21
|
+
async run(options: any) {
|
|
22
|
+
const projectRoot = this.projectRoot as string;
|
|
23
|
+
let { url } = options;
|
|
24
|
+
|
|
25
|
+
if (!url) {
|
|
26
|
+
this.error('Please specify a repository URL.');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
await this.installModule(url);
|
|
32
|
+
|
|
33
|
+
this.info('Syncing workspace dependencies...');
|
|
34
|
+
await runCommand('npm install', projectRoot);
|
|
35
|
+
|
|
36
|
+
this.success('All modules installed successfully.');
|
|
37
|
+
} catch (e: any) {
|
|
38
|
+
this.error(`Failed to add module: ${e.message}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private async installModule(url: string) {
|
|
43
|
+
const projectRoot = this.projectRoot as string;
|
|
44
|
+
|
|
45
|
+
// Resolve URL using utility
|
|
46
|
+
url = resolveGitUrl(url);
|
|
47
|
+
|
|
48
|
+
const [repoUrl, subPath] = url.split('.git//');
|
|
49
|
+
const cleanUrl = subPath ? repoUrl + '.git' : url;
|
|
50
|
+
|
|
51
|
+
if (this.visited.has(cleanUrl)) {
|
|
52
|
+
logger.debug(`Already visited ${cleanUrl}, skipping.`);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
this.visited.add(cleanUrl);
|
|
56
|
+
|
|
57
|
+
this.info(`Inspecting ${cleanUrl}...`);
|
|
58
|
+
|
|
59
|
+
// Stage 1: Inspect (Temp Clone)
|
|
60
|
+
const stagingDir = path.resolve(projectRoot!, '.nexical', 'cache', `staging-${Date.now()}-${Math.random().toString(36).substring(7)}`);
|
|
61
|
+
let moduleName = '';
|
|
62
|
+
let dependencies: string[] = [];
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
await fs.ensureDir(stagingDir);
|
|
66
|
+
|
|
67
|
+
// Shallow clone to inspect
|
|
68
|
+
await clone(cleanUrl, stagingDir, { depth: 1 });
|
|
69
|
+
|
|
70
|
+
// Read module.yaml
|
|
71
|
+
const searchPath = subPath ? path.join(stagingDir, subPath) : stagingDir;
|
|
72
|
+
const moduleYamlPath = path.join(searchPath, 'module.yaml');
|
|
73
|
+
const moduleYmlPath = path.join(searchPath, 'module.yml');
|
|
74
|
+
|
|
75
|
+
let configPath = '';
|
|
76
|
+
if (await fs.pathExists(moduleYamlPath)) configPath = moduleYamlPath;
|
|
77
|
+
else if (await fs.pathExists(moduleYmlPath)) configPath = moduleYmlPath;
|
|
78
|
+
else {
|
|
79
|
+
throw new Error(`No module.yaml found in ${cleanUrl}${subPath ? '//' + subPath : ''}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const configContent = await fs.readFile(configPath, 'utf8');
|
|
83
|
+
const config = YAML.parse(configContent);
|
|
84
|
+
|
|
85
|
+
if (!config.name) {
|
|
86
|
+
throw new Error(`Module at ${url} is missing 'name' in module.yaml`);
|
|
87
|
+
}
|
|
88
|
+
moduleName = config.name;
|
|
89
|
+
dependencies = config.dependencies || [];
|
|
90
|
+
|
|
91
|
+
// Normalize dependencies to array if object (though spec says list of strings, defensiveness is good)
|
|
92
|
+
if (dependencies && !Array.isArray(dependencies)) {
|
|
93
|
+
dependencies = Object.keys(dependencies);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
} catch (e: any) { // Catching as 'any' for error message access
|
|
97
|
+
throw e;
|
|
98
|
+
} finally {
|
|
99
|
+
// Cleanup staging always
|
|
100
|
+
await fs.remove(stagingDir);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Stage 2: Conflict Detection
|
|
104
|
+
const targetDir = path.join(projectRoot!, 'modules', moduleName);
|
|
105
|
+
const relativeTargetDir = path.relative(projectRoot!, targetDir);
|
|
106
|
+
|
|
107
|
+
if (await fs.pathExists(targetDir)) {
|
|
108
|
+
// Check origin
|
|
109
|
+
const existingRemote = await getRemoteUrl(targetDir);
|
|
110
|
+
// We compare cleanUrl (the repo root).
|
|
111
|
+
// normalize both
|
|
112
|
+
const normExisting = existingRemote.replace(/\.git$/, '');
|
|
113
|
+
const normNew = cleanUrl.replace(/\.git$/, '');
|
|
114
|
+
|
|
115
|
+
if (normExisting !== normNew && existingRemote !== '') {
|
|
116
|
+
throw new Error(`Dependency Conflict! Module '${moduleName}' exists but remote '${existingRemote}' does not match '${cleanUrl}'.`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
this.info(`Module ${moduleName} already installed.`);
|
|
120
|
+
// Proceed to recurse, but skip add
|
|
121
|
+
} else {
|
|
122
|
+
// Stage 3: Submodule Add
|
|
123
|
+
this.info(`Installing ${moduleName} to ${relativeTargetDir}...`);
|
|
124
|
+
// We install the ROOT repo.
|
|
125
|
+
// IMPORTANT: If subPath exists, "Identity is Internal" means we name the folder `moduleName`.
|
|
126
|
+
// But the CONTENT will be the whole repo.
|
|
127
|
+
// If the user meant to only have the subdir, we can't do that with submodule add easily without manual git plumbing.
|
|
128
|
+
// Given instructions, I will proceed with submodule add of root repo to target dir.
|
|
129
|
+
await runCommand(`git submodule add ${cleanUrl} ${relativeTargetDir}`, projectRoot!);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Update nexical.yaml
|
|
133
|
+
await this.addToConfig(moduleName);
|
|
134
|
+
|
|
135
|
+
// Stage 4: Recurse
|
|
136
|
+
if (dependencies.length > 0) {
|
|
137
|
+
this.info(`Resolving ${dependencies.length} dependencies for ${moduleName}...`);
|
|
138
|
+
for (const depUrl of dependencies) {
|
|
139
|
+
await this.installModule(depUrl);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
private async addToConfig(moduleName: string) {
|
|
145
|
+
const projectRoot = this.projectRoot as string;
|
|
146
|
+
const configPath = path.join(projectRoot, 'nexical.yaml');
|
|
147
|
+
|
|
148
|
+
if (!await fs.pathExists(configPath)) {
|
|
149
|
+
// Not strictly required to exist for all operations, but good to have if we are tracking modules.
|
|
150
|
+
logger.warn('nexical.yaml not found, skipping module list update.');
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
const content = await fs.readFile(configPath, 'utf8');
|
|
156
|
+
let config = YAML.parse(content) || {};
|
|
157
|
+
|
|
158
|
+
if (!config.modules) config.modules = [];
|
|
159
|
+
|
|
160
|
+
if (!config.modules.includes(moduleName)) {
|
|
161
|
+
config.modules.push(moduleName);
|
|
162
|
+
await fs.writeFile(configPath, YAML.stringify(config));
|
|
163
|
+
logger.debug(`Added ${moduleName} to nexical.yaml modules list.`);
|
|
164
|
+
}
|
|
165
|
+
} catch (e: any) {
|
|
166
|
+
logger.warn(`Failed to update nexical.yaml: ${e.message}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { BaseCommand, logger } from '@nexical/cli-core';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import YAML from 'yaml';
|
|
5
|
+
|
|
6
|
+
export default class ModuleListCommand extends BaseCommand {
|
|
7
|
+
static usage = 'module list';
|
|
8
|
+
static description = 'List installed modules.';
|
|
9
|
+
static requiresProject = true;
|
|
10
|
+
|
|
11
|
+
async run() {
|
|
12
|
+
const projectRoot = this.projectRoot as string;
|
|
13
|
+
const modulesDir = path.resolve(projectRoot, 'modules');
|
|
14
|
+
logger.debug(`Scanning for modules in: ${modulesDir}`);
|
|
15
|
+
|
|
16
|
+
if (!(await fs.pathExists(modulesDir))) {
|
|
17
|
+
this.info('No modules installed (modules directory missing).');
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const modules = await fs.readdir(modulesDir);
|
|
23
|
+
const validModules: { name: string; version: string; description: string }[] = [];
|
|
24
|
+
|
|
25
|
+
for (const moduleName of modules) {
|
|
26
|
+
const modulePath = path.join(modulesDir, moduleName);
|
|
27
|
+
if ((await fs.stat(modulePath)).isDirectory()) {
|
|
28
|
+
let version = 'unknown';
|
|
29
|
+
let description = '';
|
|
30
|
+
|
|
31
|
+
const pkgJsonPath = path.join(modulePath, 'package.json');
|
|
32
|
+
const moduleYamlPath = path.join(modulePath, 'module.yaml');
|
|
33
|
+
const moduleYmlPath = path.join(modulePath, 'module.yml');
|
|
34
|
+
|
|
35
|
+
let pkg: any = {};
|
|
36
|
+
let modConfig: any = {};
|
|
37
|
+
|
|
38
|
+
if (await fs.pathExists(pkgJsonPath)) {
|
|
39
|
+
try {
|
|
40
|
+
pkg = await fs.readJson(pkgJsonPath);
|
|
41
|
+
} catch (e) { /* ignore */ }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (await fs.pathExists(moduleYamlPath) || await fs.pathExists(moduleYmlPath)) {
|
|
45
|
+
try {
|
|
46
|
+
const configPath = await fs.pathExists(moduleYamlPath) ? moduleYamlPath : moduleYmlPath;
|
|
47
|
+
const content = await fs.readFile(configPath, 'utf8');
|
|
48
|
+
modConfig = YAML.parse(content) || {};
|
|
49
|
+
} catch (e) { /* ignore */ }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
version = pkg.version || 'unknown';
|
|
53
|
+
description = modConfig.description || pkg.description || '';
|
|
54
|
+
// Optionally use display name from module.yaml if present, but strictly list is usually dir name.
|
|
55
|
+
// Let's stick to dir name for "name" column, but description from module.yaml is good.
|
|
56
|
+
validModules.push({ name: moduleName, version, description });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (validModules.length === 0) {
|
|
61
|
+
this.info('No modules installed.');
|
|
62
|
+
} else {
|
|
63
|
+
console.table(validModules);
|
|
64
|
+
}
|
|
65
|
+
} catch (error: any) {
|
|
66
|
+
this.error(`Failed to list modules: ${error.message}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { type CommandDefinition, BaseCommand, logger, runCommand } from '@nexical/cli-core';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import YAML from 'yaml';
|
|
5
|
+
|
|
6
|
+
export default class ModuleRemoveCommand extends BaseCommand {
|
|
7
|
+
static usage = 'module remove <name>';
|
|
8
|
+
static description = 'Remove an installed module.';
|
|
9
|
+
static requiresProject = true;
|
|
10
|
+
|
|
11
|
+
static args: CommandDefinition = {
|
|
12
|
+
args: [
|
|
13
|
+
{ name: 'name', required: true, description: 'Name of the module to remove' }
|
|
14
|
+
]
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
async run(options: any) {
|
|
18
|
+
const projectRoot = this.projectRoot as string;
|
|
19
|
+
let { name } = options;
|
|
20
|
+
|
|
21
|
+
const relativePath = `modules/${name}`;
|
|
22
|
+
const fullPath = path.resolve(projectRoot, relativePath);
|
|
23
|
+
|
|
24
|
+
logger.debug('Removing module at:', fullPath);
|
|
25
|
+
|
|
26
|
+
if (!(await fs.pathExists(fullPath))) {
|
|
27
|
+
this.error(`Module ${name} not found at ${relativePath}.`);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
this.info(`Removing module ${name}...`);
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
await runCommand(`git submodule deinit -f ${relativePath}`, projectRoot);
|
|
35
|
+
await runCommand(`git rm -f ${relativePath}`, projectRoot);
|
|
36
|
+
|
|
37
|
+
// Clean up .git/modules
|
|
38
|
+
const gitModulesDir = path.resolve(projectRoot, '.git', 'modules', 'modules', name);
|
|
39
|
+
if (await fs.pathExists(gitModulesDir)) {
|
|
40
|
+
await fs.remove(gitModulesDir);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
this.info('Syncing workspace dependencies...');
|
|
44
|
+
await runCommand('npm install', projectRoot);
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
await this.removeFromConfig(name);
|
|
48
|
+
|
|
49
|
+
this.success(`Module ${name} removed successfully.`);
|
|
50
|
+
} catch (e: any) {
|
|
51
|
+
this.error(`Failed to remove module: ${e.message}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
private async removeFromConfig(moduleName: string) {
|
|
56
|
+
const projectRoot = this.projectRoot as string;
|
|
57
|
+
const configPath = path.join(projectRoot, 'nexical.yaml');
|
|
58
|
+
|
|
59
|
+
if (!await fs.pathExists(configPath)) return;
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const content = await fs.readFile(configPath, 'utf8');
|
|
63
|
+
let config = YAML.parse(content) || {};
|
|
64
|
+
|
|
65
|
+
if (config.modules && config.modules.includes(moduleName)) {
|
|
66
|
+
config.modules = config.modules.filter((m: string) => m !== moduleName);
|
|
67
|
+
await fs.writeFile(configPath, YAML.stringify(config));
|
|
68
|
+
logger.debug(`Removed ${moduleName} from nexical.yaml modules list.`);
|
|
69
|
+
}
|
|
70
|
+
} catch (e: any) {
|
|
71
|
+
logger.warn(`Failed to update nexical.yaml: ${e.message}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { type CommandDefinition, BaseCommand, logger, runCommand } from '@nexical/cli-core';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
export default class ModuleUpdateCommand extends BaseCommand {
|
|
6
|
+
static usage = 'module update [name]';
|
|
7
|
+
static description = 'Update a specific module or all modules.';
|
|
8
|
+
static requiresProject = true;
|
|
9
|
+
|
|
10
|
+
static args: CommandDefinition = {
|
|
11
|
+
args: [
|
|
12
|
+
{ name: 'name', required: false, description: 'Name of the module to update' }
|
|
13
|
+
]
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
async run(options: any) {
|
|
17
|
+
const projectRoot = this.projectRoot as string;
|
|
18
|
+
let { name } = options;
|
|
19
|
+
|
|
20
|
+
this.info(name ? `Updating module ${name}...` : 'Updating all modules...');
|
|
21
|
+
logger.debug('Update context:', { name, projectRoot: projectRoot });
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
if (name) {
|
|
25
|
+
const relativePath = `modules/${name}`;
|
|
26
|
+
const fullPath = path.resolve(projectRoot, relativePath);
|
|
27
|
+
|
|
28
|
+
if (!(await fs.pathExists(fullPath))) {
|
|
29
|
+
this.error(`Module ${name} not found.`);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Update specific module
|
|
34
|
+
// We enter the directory and pull? Or generic submodule update?
|
|
35
|
+
// Generic submodule update --remote src/modules/name
|
|
36
|
+
await runCommand(`git submodule update --remote --merge ${relativePath}`, projectRoot);
|
|
37
|
+
} else {
|
|
38
|
+
// Update all
|
|
39
|
+
await runCommand('git submodule update --remote --merge', projectRoot);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
this.info('Syncing workspace dependencies...');
|
|
43
|
+
await runCommand('npm install', projectRoot);
|
|
44
|
+
|
|
45
|
+
this.success('Modules updated successfully.');
|
|
46
|
+
} catch (e: any) {
|
|
47
|
+
this.error(`Failed to update modules: ${e.message}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { type CommandDefinition, BaseCommand, logger } from '@nexical/cli-core';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { spawn } from 'child_process';
|
|
5
|
+
import process from 'node:process';
|
|
6
|
+
|
|
7
|
+
export default class RunCommand extends BaseCommand {
|
|
8
|
+
static usage = 'run <script> [args...]';
|
|
9
|
+
static description = 'Run a script inside the Nexical environment.';
|
|
10
|
+
static requiresProject = true;
|
|
11
|
+
|
|
12
|
+
static args: CommandDefinition = {
|
|
13
|
+
args: [
|
|
14
|
+
{ name: 'script', required: true, description: 'The script to run (script-name OR module:script-name)' },
|
|
15
|
+
{ name: 'args...', required: false, description: 'Arguments for the script' }
|
|
16
|
+
]
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
async run(options: any) {
|
|
20
|
+
const projectRoot = this.projectRoot as string;
|
|
21
|
+
const script = options.script;
|
|
22
|
+
const scriptArgs = options.args || [];
|
|
23
|
+
|
|
24
|
+
if (!script) {
|
|
25
|
+
this.error('Please specify a script to run.');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
logger.debug('Run command context:', { script, args: scriptArgs, projectRoot });
|
|
30
|
+
|
|
31
|
+
let execPath = projectRoot;
|
|
32
|
+
let scriptName = script;
|
|
33
|
+
|
|
34
|
+
// Handle module:script syntax
|
|
35
|
+
if (script.includes(':')) {
|
|
36
|
+
const [moduleName, name] = script.split(':');
|
|
37
|
+
execPath = path.resolve(projectRoot, 'modules', moduleName);
|
|
38
|
+
scriptName = name;
|
|
39
|
+
|
|
40
|
+
logger.debug(`Resolving module script: ${moduleName}:${scriptName} at ${execPath}`);
|
|
41
|
+
} else {
|
|
42
|
+
logger.debug(`Resolving core script: ${scriptName} at ${execPath}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Validate script existence
|
|
46
|
+
const pkgJsonPath = path.join(execPath, 'package.json');
|
|
47
|
+
if (!(await fs.pathExists(pkgJsonPath))) {
|
|
48
|
+
this.error(`Failed to find package.json at ${execPath}`);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const pkg = await fs.readJson(pkgJsonPath);
|
|
54
|
+
if (!pkg.scripts || !pkg.scripts[scriptName]) {
|
|
55
|
+
const type = script.includes(':') ? `module ${script.split(':')[0]}` : 'Nexical core';
|
|
56
|
+
this.error(`Script "${scriptName}" does not exist in ${type}`);
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
} catch (e: any) {
|
|
60
|
+
this.error(`Failed to read package.json at ${execPath}: ${e.message}`);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const finalArgs = ['run', scriptName, '--', ...scriptArgs];
|
|
65
|
+
logger.debug(`Executing: npm ${finalArgs.join(' ')} in ${execPath}`);
|
|
66
|
+
|
|
67
|
+
const child = spawn('npm', finalArgs, {
|
|
68
|
+
cwd: execPath,
|
|
69
|
+
stdio: 'inherit',
|
|
70
|
+
env: {
|
|
71
|
+
...process.env,
|
|
72
|
+
FORCE_COLOR: '1'
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Handle process termination to kill child
|
|
77
|
+
const cleanup = () => {
|
|
78
|
+
child.kill();
|
|
79
|
+
process.exit();
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
process.on('SIGINT', cleanup);
|
|
83
|
+
process.on('SIGTERM', cleanup);
|
|
84
|
+
|
|
85
|
+
await new Promise<void>((resolve) => {
|
|
86
|
+
child.on('close', (code) => {
|
|
87
|
+
// Remove listeners to prevent memory leaks if this command is run multiple times in-process (e.g. tests)
|
|
88
|
+
process.off('SIGINT', cleanup);
|
|
89
|
+
process.off('SIGTERM', cleanup);
|
|
90
|
+
|
|
91
|
+
if (code !== 0) {
|
|
92
|
+
process.exit(code || 1);
|
|
93
|
+
}
|
|
94
|
+
resolve();
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { BaseCommand, logger } from '@nexical/cli-core';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
export default class SetupCommand extends BaseCommand {
|
|
6
|
+
static description = 'Setup the application environment by symlinking core assets.';
|
|
7
|
+
|
|
8
|
+
async run() {
|
|
9
|
+
// We assume we are in the project root
|
|
10
|
+
// But the CLI might be run from anywhere?
|
|
11
|
+
// findProjectRoot in index.ts handles finding the root.
|
|
12
|
+
// BaseCommand has this.projectRoot?
|
|
13
|
+
|
|
14
|
+
// BaseCommand doesn't expose projectRoot directly in current implementation seen in memory, checking source if possible?
|
|
15
|
+
// InitCommand used process.cwd().
|
|
16
|
+
|
|
17
|
+
// Let's assume process.cwd() is project root if run via `npm run setup` from root.
|
|
18
|
+
const rootDir = process.cwd();
|
|
19
|
+
|
|
20
|
+
// Verify we are in the right place
|
|
21
|
+
if (!fs.existsSync(path.join(rootDir, 'core'))) {
|
|
22
|
+
this.error('Could not find "core" directory. Are you in the project root?');
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const apps = ['frontend', 'backend'];
|
|
27
|
+
const sharedAssets = ['prisma', 'src', 'public', 'locales', 'scripts', 'astro.config.mjs', 'tsconfig.json']; // tsconfig might be needed if extended
|
|
28
|
+
|
|
29
|
+
for (const app of apps) {
|
|
30
|
+
const appDir = path.join(rootDir, 'apps', app);
|
|
31
|
+
if (!fs.existsSync(appDir)) {
|
|
32
|
+
this.warn(`App directory ${app} not found. Skipping.`);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
this.info(`Setting up ${app}...`);
|
|
37
|
+
|
|
38
|
+
for (const asset of sharedAssets) {
|
|
39
|
+
const source = path.join(rootDir, 'core', asset);
|
|
40
|
+
const dest = path.join(appDir, asset);
|
|
41
|
+
|
|
42
|
+
if (!fs.existsSync(source)) {
|
|
43
|
+
this.warn(`Source asset ${asset} not found in core.`);
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
// Remove existing destination if it exists (to ensure clean symlink)
|
|
49
|
+
// Be careful not to delete real files if they aren't symlinks?
|
|
50
|
+
// For now, we assume setup controls these.
|
|
51
|
+
|
|
52
|
+
const destDir = path.dirname(dest);
|
|
53
|
+
await fs.ensureDir(destDir);
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const stats = fs.lstatSync(dest);
|
|
57
|
+
fs.removeSync(dest);
|
|
58
|
+
} catch (e: any) {
|
|
59
|
+
if (e.code !== 'ENOENT') throw e;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const relSource = path.relative(destDir, source);
|
|
63
|
+
await fs.symlink(relSource, dest);
|
|
64
|
+
|
|
65
|
+
logger.debug(`Symlinked ${asset} to ${app}`);
|
|
66
|
+
} catch (e: any) {
|
|
67
|
+
this.error(`Failed to symlink ${asset} to ${app}: ${e.message}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this.success('Application setup complete.');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { logger } from '@nexical/cli-core';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Discovers command directories to load into the CLI.
|
|
7
|
+
*
|
|
8
|
+
* Scans for:
|
|
9
|
+
* 1. Core commands (projectRoot/src/commands)
|
|
10
|
+
* 2. Module commands (projectRoot/src/modules/ * /src/commands)
|
|
11
|
+
*
|
|
12
|
+
* @param projectRoot - The root directory of the project
|
|
13
|
+
* @returns Array of absolute paths to command directories
|
|
14
|
+
*/
|
|
15
|
+
export function discoverCommandDirectories(projectRoot: string): string[] {
|
|
16
|
+
const directories: string[] = [];
|
|
17
|
+
const visited = new Set<string>();
|
|
18
|
+
|
|
19
|
+
const addDir = (dir: string) => {
|
|
20
|
+
const resolved = path.resolve(dir);
|
|
21
|
+
if (visited.has(resolved)) return;
|
|
22
|
+
|
|
23
|
+
if (fs.existsSync(resolved)) {
|
|
24
|
+
// Check if we already have a similar path (e.g. dist/src/commands vs src/commands)
|
|
25
|
+
// If we are adding src/commands and dist/src/commands already exists in visited, skip it
|
|
26
|
+
// and vice versa.
|
|
27
|
+
const isSrc = resolved.endsWith(path.join('src', 'commands'));
|
|
28
|
+
const isDist = resolved.includes(path.join('dist', 'src', 'commands')) ||
|
|
29
|
+
resolved.endsWith(path.join('dist', 'commands'));
|
|
30
|
+
|
|
31
|
+
if (isSrc) {
|
|
32
|
+
const distEquivalent1 = resolved.replace(path.sep + 'src' + path.sep, path.sep + 'dist' + path.sep + 'src' + path.sep);
|
|
33
|
+
const distEquivalent2 = resolved.replace(path.sep + 'src' + path.sep, path.sep + 'dist' + path.sep);
|
|
34
|
+
if (visited.has(distEquivalent1) || visited.has(distEquivalent2)) {
|
|
35
|
+
logger.debug(`Skipping ${resolved} because a dist version is already registered`);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (isDist) {
|
|
41
|
+
const srcEquivalent1 = resolved.replace(path.sep + 'dist' + path.sep, path.sep);
|
|
42
|
+
const srcEquivalent2 = resolved.replace(path.sep + 'dist' + path.sep + 'src' + path.sep, path.sep + 'src' + path.sep);
|
|
43
|
+
if (visited.has(srcEquivalent1) || visited.has(srcEquivalent2)) {
|
|
44
|
+
// If we just added src, and now we find dist, we should actually REPLACE src with dist
|
|
45
|
+
// but for now, the loop order prefers dist, so this case shouldn't happen much.
|
|
46
|
+
// However, let's keep it simple.
|
|
47
|
+
logger.debug(`Skipping ${resolved} because a src version is already registered`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
logger.debug(`Found command directory: ${resolved}`);
|
|
53
|
+
directories.push(resolved);
|
|
54
|
+
visited.add(resolved);
|
|
55
|
+
} else {
|
|
56
|
+
logger.debug(`Command directory not found (skipping): ${resolved}`);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// 1. Core commands
|
|
61
|
+
// Search in projectRoot
|
|
62
|
+
const possibleCorePaths = [
|
|
63
|
+
path.join(projectRoot, 'src/commands'),
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
possibleCorePaths.forEach(addDir);
|
|
67
|
+
|
|
68
|
+
// 2. Module commands
|
|
69
|
+
const possibleModuleDirs = [
|
|
70
|
+
path.join(projectRoot, 'modules'),
|
|
71
|
+
path.join(projectRoot, 'src', 'modules') // Support both flat and src-nested
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
possibleModuleDirs.forEach(modulesDir => {
|
|
75
|
+
if (fs.existsSync(modulesDir)) {
|
|
76
|
+
try {
|
|
77
|
+
const modules = fs.readdirSync(modulesDir);
|
|
78
|
+
for (const mod of modules) {
|
|
79
|
+
// exclude system files/dirs like .keep
|
|
80
|
+
if (mod.startsWith('.')) continue;
|
|
81
|
+
|
|
82
|
+
const modPath = path.join(modulesDir, mod);
|
|
83
|
+
if (!fs.statSync(modPath).isDirectory()) continue;
|
|
84
|
+
|
|
85
|
+
// Check for commands inside the module/package
|
|
86
|
+
// Order matters: prefer dist if it exists
|
|
87
|
+
const possibleCmdPaths = [
|
|
88
|
+
path.join(modPath, 'dist/src/commands'),
|
|
89
|
+
path.join(modPath, 'dist/commands'),
|
|
90
|
+
path.join(modPath, 'src/commands')
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
for (const cmdPath of possibleCmdPaths) {
|
|
94
|
+
if (fs.existsSync(cmdPath) && fs.statSync(cmdPath).isDirectory()) {
|
|
95
|
+
addDir(cmdPath);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
} catch (e: any) {
|
|
100
|
+
logger.debug(`Error scanning modules directory ${modulesDir}: ${e.message}`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// 3. Package commands (e.g. packages/*)
|
|
106
|
+
const packagesDir = path.join(projectRoot, 'packages');
|
|
107
|
+
if (fs.existsSync(packagesDir)) {
|
|
108
|
+
try {
|
|
109
|
+
const packages = fs.readdirSync(packagesDir);
|
|
110
|
+
for (const pkg of packages) {
|
|
111
|
+
if (pkg.startsWith('.')) continue;
|
|
112
|
+
|
|
113
|
+
const pkgPath = path.join(packagesDir, pkg);
|
|
114
|
+
if (!fs.statSync(pkgPath).isDirectory()) continue;
|
|
115
|
+
|
|
116
|
+
const possibleCmdPaths = [
|
|
117
|
+
path.join(pkgPath, 'dist/src/commands'),
|
|
118
|
+
path.join(pkgPath, 'dist/commands'),
|
|
119
|
+
path.join(pkgPath, 'src/commands')
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
for (const cmdPath of possibleCmdPaths) {
|
|
123
|
+
if (fs.existsSync(cmdPath) && fs.statSync(cmdPath).isDirectory()) {
|
|
124
|
+
addDir(cmdPath);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
} catch (e: any) {
|
|
129
|
+
logger.debug(`Error scanning packages directory: ${e.message}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return directories;
|
|
134
|
+
}
|