@arcadialdev/arcality 2.2.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/.agents/skills/e2e-testing-expert/SKILL.md +28 -0
- package/.agents/skills/frontend-design/LICENSE.txt +177 -0
- package/.agents/skills/frontend-design/SKILL.md +42 -0
- package/.agents/skills/nodejs-backend-patterns/SKILL.md +639 -0
- package/.agents/skills/nodejs-backend-patterns/references/advanced-patterns.md +430 -0
- package/.agents/skills/playwright-best-practices/LICENSE.md +7 -0
- package/.agents/skills/playwright-best-practices/README.md +147 -0
- package/.agents/skills/playwright-best-practices/SKILL.md +303 -0
- package/.agents/skills/playwright-best-practices/advanced/authentication-flows.md +360 -0
- package/.agents/skills/playwright-best-practices/advanced/authentication.md +871 -0
- package/.agents/skills/playwright-best-practices/advanced/clock-mocking.md +364 -0
- package/.agents/skills/playwright-best-practices/advanced/mobile-testing.md +409 -0
- package/.agents/skills/playwright-best-practices/advanced/multi-context.md +288 -0
- package/.agents/skills/playwright-best-practices/advanced/multi-user.md +393 -0
- package/.agents/skills/playwright-best-practices/advanced/network-advanced.md +452 -0
- package/.agents/skills/playwright-best-practices/advanced/third-party.md +464 -0
- package/.agents/skills/playwright-best-practices/architecture/pom-vs-fixtures.md +363 -0
- package/.agents/skills/playwright-best-practices/architecture/test-architecture.md +369 -0
- package/.agents/skills/playwright-best-practices/architecture/when-to-mock.md +383 -0
- package/.agents/skills/playwright-best-practices/browser-apis/browser-apis.md +391 -0
- package/.agents/skills/playwright-best-practices/browser-apis/iframes.md +403 -0
- package/.agents/skills/playwright-best-practices/browser-apis/service-workers.md +504 -0
- package/.agents/skills/playwright-best-practices/browser-apis/websockets.md +403 -0
- package/.agents/skills/playwright-best-practices/core/annotations.md +424 -0
- package/.agents/skills/playwright-best-practices/core/assertions-waiting.md +361 -0
- package/.agents/skills/playwright-best-practices/core/configuration.md +452 -0
- package/.agents/skills/playwright-best-practices/core/fixtures-hooks.md +417 -0
- package/.agents/skills/playwright-best-practices/core/global-setup.md +434 -0
- package/.agents/skills/playwright-best-practices/core/locators.md +242 -0
- package/.agents/skills/playwright-best-practices/core/page-object-model.md +315 -0
- package/.agents/skills/playwright-best-practices/core/projects-dependencies.md +453 -0
- package/.agents/skills/playwright-best-practices/core/test-data.md +492 -0
- package/.agents/skills/playwright-best-practices/core/test-suite-structure.md +361 -0
- package/.agents/skills/playwright-best-practices/core/test-tags.md +298 -0
- package/.agents/skills/playwright-best-practices/debugging/console-errors.md +420 -0
- package/.agents/skills/playwright-best-practices/debugging/debugging.md +504 -0
- package/.agents/skills/playwright-best-practices/debugging/error-testing.md +360 -0
- package/.agents/skills/playwright-best-practices/debugging/flaky-tests.md +496 -0
- package/.agents/skills/playwright-best-practices/frameworks/angular.md +530 -0
- package/.agents/skills/playwright-best-practices/frameworks/nextjs.md +469 -0
- package/.agents/skills/playwright-best-practices/frameworks/react.md +531 -0
- package/.agents/skills/playwright-best-practices/frameworks/vue.md +574 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/ci-cd.md +468 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/docker.md +283 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/github-actions.md +546 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/gitlab.md +397 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/other-providers.md +521 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/parallel-sharding.md +371 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/performance.md +453 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/reporting.md +424 -0
- package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/test-coverage.md +497 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/accessibility.md +359 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/api-testing.md +719 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/browser-extensions.md +506 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/canvas-webgl.md +493 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/component-testing.md +500 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/drag-drop.md +576 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/electron.md +509 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/file-operations.md +377 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/file-upload-download.md +562 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/forms-validation.md +561 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/graphql-testing.md +331 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/i18n.md +508 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/performance-testing.md +476 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/security-testing.md +430 -0
- package/.agents/skills/playwright-best-practices/testing-patterns/visual-regression.md +634 -0
- package/.env.example +21 -0
- package/README.md +30 -0
- package/bin/arcality.mjs +86 -0
- package/package.json +66 -0
- package/playwright.config.ts +12 -0
- package/scripts/cleanup-qmsdev.mjs +63 -0
- package/scripts/discover-view.mjs +52 -0
- package/scripts/extract-view.mjs +64 -0
- package/scripts/gen-and-run.mjs +838 -0
- package/scripts/init.mjs +290 -0
- package/scripts/migrate-to-central-out.mjs +157 -0
- package/scripts/postinstall.mjs +63 -0
- package/scripts/rebrand-report.mjs +241 -0
- package/scripts/setup.mjs +166 -0
- package/src/KnowledgeService.ts +239 -0
- package/src/arcalityClient.mjs +266 -0
- package/src/configLoader.mjs +179 -0
- package/src/configManager.mjs +172 -0
- package/src/consoleBanner.ts +32 -0
- package/src/envSetup.ts +205 -0
- package/src/index.ts +25 -0
- package/src/projectInspector.ts +42 -0
- package/src/services/collectiveMemoryService.ts +178 -0
- package/src/testRunner.ts +201 -0
- package/tests/_helpers/ArcalityReporter.ts +490 -0
- package/tests/_helpers/agentic-runner.spec.ts +741 -0
- package/tests/_helpers/ai-agent-helper.ts +1573 -0
- package/tests/_helpers/discover-view.spec.ts +238 -0
- package/tests/_helpers/extract-view.spec.ts +118 -0
- package/tests/_helpers/qa-tools.ts +333 -0
- package/tests/_helpers/smart-action.spec.ts +1458 -0
package/scripts/init.mjs
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// scripts/init.mjs β Command: arcality init
|
|
3
|
+
// Single-configuration setup flow:
|
|
4
|
+
// 1. Prompt API key β validate with backend
|
|
5
|
+
// 2. Prompt project name, baseUrl, username, password
|
|
6
|
+
// 3. Detect framework (package.json only, NO .git)
|
|
7
|
+
// 4. Create project via POST /api/v1/projects
|
|
8
|
+
// 5. Write arcality.config
|
|
9
|
+
|
|
10
|
+
import 'dotenv/config';
|
|
11
|
+
import fs from 'node:fs';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import { fileURLToPath } from 'url';
|
|
14
|
+
import { intro, outro, text, password as passwordPrompt, spinner, note, isCancel, cancel, confirm } from '@clack/prompts';
|
|
15
|
+
import chalk from 'chalk';
|
|
16
|
+
import figlet from 'figlet';
|
|
17
|
+
import {
|
|
18
|
+
configExists,
|
|
19
|
+
loadProjectConfig,
|
|
20
|
+
saveProjectConfig,
|
|
21
|
+
createConfig,
|
|
22
|
+
} from '../src/configManager.mjs';
|
|
23
|
+
import { getApiUrl } from '../src/configLoader.mjs';
|
|
24
|
+
|
|
25
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
26
|
+
const __dirname = path.dirname(__filename);
|
|
27
|
+
const PACKAGE_ROOT = process.env.ARCALITY_ROOT || path.resolve(__dirname, '..');
|
|
28
|
+
|
|
29
|
+
// ββ Helpers ββ
|
|
30
|
+
|
|
31
|
+
function getVersion() {
|
|
32
|
+
try {
|
|
33
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(PACKAGE_ROOT, 'package.json'), 'utf8'));
|
|
34
|
+
return pkg.version || 'unknown';
|
|
35
|
+
} catch {
|
|
36
|
+
return 'unknown';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function detectFramework(projectRoot) {
|
|
41
|
+
try {
|
|
42
|
+
const pkgPath = path.join(projectRoot, 'package.json');
|
|
43
|
+
if (!fs.existsSync(pkgPath)) return null;
|
|
44
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
45
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
46
|
+
if (deps['next']) return 'Next.js';
|
|
47
|
+
if (deps['nuxt'] || deps['nuxt3']) return 'Nuxt';
|
|
48
|
+
if (deps['vite']) return 'Vite';
|
|
49
|
+
if (deps['react-scripts']) return 'Create React App';
|
|
50
|
+
if (deps['@angular/core']) return 'Angular';
|
|
51
|
+
if (deps['vue']) return 'Vue.js';
|
|
52
|
+
if (deps['svelte'] || deps['@sveltejs/kit']) return 'Svelte';
|
|
53
|
+
return null;
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function isValidUrl(url) {
|
|
60
|
+
try {
|
|
61
|
+
const u = new URL(url);
|
|
62
|
+
return u.protocol === 'http:' || u.protocol === 'https:';
|
|
63
|
+
} catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ββ Main ββ
|
|
69
|
+
|
|
70
|
+
async function main() {
|
|
71
|
+
const version = getVersion();
|
|
72
|
+
const projectRoot = process.cwd();
|
|
73
|
+
|
|
74
|
+
console.clear();
|
|
75
|
+
const logo = figlet.textSync('Arcality', { font: 'Standard' });
|
|
76
|
+
intro(chalk.cyanBright(logo));
|
|
77
|
+
|
|
78
|
+
note(
|
|
79
|
+
chalk.magentaBright('Project Initialization') + '\n' +
|
|
80
|
+
chalk.gray('β'.repeat(50)) + '\n' +
|
|
81
|
+
chalk.bold.white('π¦ Version: ') + chalk.yellow(version) + '\n' +
|
|
82
|
+
chalk.bold.white('π Project: ') + chalk.yellow(projectRoot),
|
|
83
|
+
'arcality init'
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
// ββ Check for existing config ββ
|
|
87
|
+
if (configExists(projectRoot)) {
|
|
88
|
+
const existing = loadProjectConfig(projectRoot);
|
|
89
|
+
if (existing) {
|
|
90
|
+
note(
|
|
91
|
+
chalk.yellow('β οΈ An arcality.config already exists in this project.') + '\n\n' +
|
|
92
|
+
chalk.white(' Project: ') + chalk.cyan(existing.project?.name || 'unknown') + '\n' +
|
|
93
|
+
chalk.white(' Base URL: ') + chalk.cyan(existing.project?.baseUrl || 'unknown') + '\n' +
|
|
94
|
+
chalk.white(' Project ID: ') + chalk.gray(existing.projectId || 'unknown'),
|
|
95
|
+
'Existing Configuration'
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const overwrite = await confirm({
|
|
99
|
+
message: 'Do you want to reconfigure? This will create a NEW project in backend.',
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
if (isCancel(overwrite) || !overwrite) {
|
|
103
|
+
outro(chalk.cyan('Configuration unchanged. Use `arcality run` to start.'));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ββ Step 1: API Key ββ
|
|
110
|
+
const apiKey = await text({
|
|
111
|
+
message: chalk.cyan('π Enter your Arcality API Key:'),
|
|
112
|
+
placeholder: 'arc_k_live_xxxxx',
|
|
113
|
+
validate: (v) => {
|
|
114
|
+
if (!v || !v.trim()) return 'API Key is required.';
|
|
115
|
+
if (!v.startsWith('arc_k_')) return 'API Key must start with "arc_k_"';
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
if (isCancel(apiKey)) {
|
|
120
|
+
cancel('Setup cancelled.');
|
|
121
|
+
process.exit(0);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ββ Step 2: Validate API Key (uses internal API URL) ββ
|
|
125
|
+
const apiUrl = getApiUrl();
|
|
126
|
+
const s = spinner();
|
|
127
|
+
s.start('Validating API Key...');
|
|
128
|
+
|
|
129
|
+
let organizationId = null;
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
const res = await fetch(`${apiUrl}/api/v1/auth/validate`, {
|
|
133
|
+
method: 'POST',
|
|
134
|
+
headers: {
|
|
135
|
+
'Content-Type': 'application/json',
|
|
136
|
+
'x-api-key': apiKey.trim(),
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
if (!res.ok) {
|
|
141
|
+
const errorMap = {
|
|
142
|
+
401: 'β Invalid API Key. Please check your key and try again.',
|
|
143
|
+
403: 'β Your plan has expired. Contact support.',
|
|
144
|
+
};
|
|
145
|
+
s.stop(chalk.red(errorMap[res.status] || `β Server error (HTTP ${res.status})`));
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const data = await res.json();
|
|
150
|
+
organizationId = data.organizationId || data.organization_id || null;
|
|
151
|
+
|
|
152
|
+
s.stop(chalk.green('β
API Key validated successfully'));
|
|
153
|
+
|
|
154
|
+
if (data.plan) {
|
|
155
|
+
note(
|
|
156
|
+
chalk.bold.white('π Plan: ') + chalk.cyan(data.plan) + '\n' +
|
|
157
|
+
chalk.bold.white('π’ Org: ') + chalk.cyan(organizationId || 'N/A'),
|
|
158
|
+
'Account Info'
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
} catch (err) {
|
|
162
|
+
s.stop(chalk.red(`β Could not connect to Arcality backend: ${err.message}`));
|
|
163
|
+
console.log(chalk.yellow(' Make sure the backend is running and accessible.'));
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ββ Step 3: Project details ββ
|
|
168
|
+
const projectName = await text({
|
|
169
|
+
message: chalk.cyan('π Project name:'),
|
|
170
|
+
placeholder: 'My Portal QA',
|
|
171
|
+
validate: (v) => {
|
|
172
|
+
if (!v || v.trim().length < 2) return 'Project name is required (min 2 chars).';
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
if (isCancel(projectName)) { cancel('Setup cancelled.'); process.exit(0); }
|
|
176
|
+
|
|
177
|
+
const baseUrl = await text({
|
|
178
|
+
message: chalk.cyan('π Base URL of the application to test:'),
|
|
179
|
+
placeholder: 'https://staging.example.com',
|
|
180
|
+
validate: (v) => {
|
|
181
|
+
if (!v || !v.trim()) return 'Base URL is required.';
|
|
182
|
+
if (!isValidUrl(v)) return 'Invalid URL. Use http:// or https://';
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
if (isCancel(baseUrl)) { cancel('Setup cancelled.'); process.exit(0); }
|
|
186
|
+
|
|
187
|
+
const username = await text({
|
|
188
|
+
message: chalk.cyan('π€ Login username / email:'),
|
|
189
|
+
validate: (v) => {
|
|
190
|
+
if (!v || !v.trim()) return 'Username is required.';
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
if (isCancel(username)) { cancel('Setup cancelled.'); process.exit(0); }
|
|
194
|
+
|
|
195
|
+
const password = await passwordPrompt({
|
|
196
|
+
message: chalk.cyan('π Login password:'),
|
|
197
|
+
validate: (v) => {
|
|
198
|
+
if (!v || !v.length) return 'Password is required.';
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
if (isCancel(password)) { cancel('Setup cancelled.'); process.exit(0); }
|
|
202
|
+
|
|
203
|
+
// ββ Step 4: Detect framework ββ
|
|
204
|
+
const frameworkDetected = detectFramework(projectRoot);
|
|
205
|
+
if (frameworkDetected) {
|
|
206
|
+
console.log(chalk.gray(` π οΈ Framework detected: ${chalk.magenta(frameworkDetected)}`));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ββ Step 5: Create project in backend ββ
|
|
210
|
+
const sCreate = spinner();
|
|
211
|
+
sCreate.start('Creating project in Arcality backend...');
|
|
212
|
+
|
|
213
|
+
let projectId = null;
|
|
214
|
+
try {
|
|
215
|
+
const payload = {
|
|
216
|
+
name: projectName.trim(),
|
|
217
|
+
base_url: baseUrl.trim(),
|
|
218
|
+
framework_detected: frameworkDetected || undefined,
|
|
219
|
+
arcality_version: version,
|
|
220
|
+
config: {
|
|
221
|
+
source: 'npm-cli',
|
|
222
|
+
single_configuration_mode: true,
|
|
223
|
+
yaml_reuse_enabled: true,
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const res = await fetch(`${apiUrl}/api/v1/projects`, {
|
|
228
|
+
method: 'POST',
|
|
229
|
+
headers: {
|
|
230
|
+
'Content-Type': 'application/json',
|
|
231
|
+
'x-api-key': apiKey.trim(),
|
|
232
|
+
},
|
|
233
|
+
body: JSON.stringify(payload),
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
if (!res.ok) {
|
|
237
|
+
const errText = await res.text().catch(() => '');
|
|
238
|
+
sCreate.stop(chalk.red(`β Failed to create project (HTTP ${res.status}): ${errText}`));
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const created = await res.json();
|
|
243
|
+
projectId = created.id || created.Id;
|
|
244
|
+
organizationId = organizationId || created.organizationId || created.organization_id || null;
|
|
245
|
+
|
|
246
|
+
sCreate.stop(chalk.green(`β
Project created: "${projectName.trim()}" (${projectId})`));
|
|
247
|
+
} catch (err) {
|
|
248
|
+
sCreate.stop(chalk.red(`β Error creating project: ${err.message}`));
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ββ Step 6: Write arcality.config ββ
|
|
253
|
+
const config = createConfig({
|
|
254
|
+
apiKey: apiKey.trim(),
|
|
255
|
+
organizationId,
|
|
256
|
+
projectId,
|
|
257
|
+
projectName: projectName.trim(),
|
|
258
|
+
baseUrl: baseUrl.trim(),
|
|
259
|
+
frameworkDetected,
|
|
260
|
+
username: username.trim(),
|
|
261
|
+
password,
|
|
262
|
+
arcalityVersion: version,
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
saveProjectConfig(config, projectRoot);
|
|
266
|
+
|
|
267
|
+
// ββ Summary ββ
|
|
268
|
+
note(
|
|
269
|
+
chalk.green('β
arcality.config created successfully!') + '\n\n' +
|
|
270
|
+
chalk.bold.white(' Project: ') + chalk.cyan(config.project.name) + '\n' +
|
|
271
|
+
chalk.bold.white(' Base URL: ') + chalk.cyan(config.project.baseUrl) + '\n' +
|
|
272
|
+
chalk.bold.white(' Framework: ') + chalk.magenta(config.project.frameworkDetected || 'N/A') + '\n' +
|
|
273
|
+
chalk.bold.white(' Project ID: ') + chalk.gray(config.projectId) + '\n' +
|
|
274
|
+
chalk.bold.white(' YAML Dir: ') + chalk.yellow(config.runtime.yamlOutputDir),
|
|
275
|
+
'Configuration Saved'
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
outro(
|
|
279
|
+
chalk.cyanBright('π Ready! Run ') +
|
|
280
|
+
chalk.bold.white('arcality run') +
|
|
281
|
+
chalk.cyanBright(' or ') +
|
|
282
|
+
chalk.bold.white('arcality') +
|
|
283
|
+
chalk.cyanBright(' to start testing.')
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
main().catch(err => {
|
|
288
|
+
console.error(chalk.red(' β Error during init:'), err.message);
|
|
289
|
+
process.exit(1);
|
|
290
|
+
});
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = path.dirname(__filename);
|
|
7
|
+
const PROJECT_ROOT = path.join(__dirname, "..");
|
|
8
|
+
|
|
9
|
+
function migrate() {
|
|
10
|
+
const outDirs = fs.readdirSync(PROJECT_ROOT).filter(f => f.startsWith('out_') && fs.statSync(path.join(PROJECT_ROOT, f)).isDirectory());
|
|
11
|
+
const centralOut = path.join(PROJECT_ROOT, 'out');
|
|
12
|
+
|
|
13
|
+
if (!fs.existsSync(centralOut)) fs.mkdirSync(centralOut);
|
|
14
|
+
|
|
15
|
+
console.log(`π Starting migration of ${outDirs.length} legacy output directories...`);
|
|
16
|
+
|
|
17
|
+
for (const oldDirName of outDirs) {
|
|
18
|
+
const oldDirPath = path.join(PROJECT_ROOT, oldDirName);
|
|
19
|
+
const children = fs.readdirSync(oldDirPath);
|
|
20
|
+
|
|
21
|
+
// Try to find a config name
|
|
22
|
+
let configName = oldDirName.replace('out_', '');
|
|
23
|
+
const configSubDir = children.find(c => fs.statSync(path.join(oldDirPath, c)).isDirectory());
|
|
24
|
+
|
|
25
|
+
if (configSubDir) {
|
|
26
|
+
configName = configSubDir;
|
|
27
|
+
console.log(`π¦ Found configuration folder "${configName}" inside ${oldDirName}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const newConfigPath = path.join(centralOut, configName);
|
|
31
|
+
const contextPath = path.join(newConfigPath, 'context');
|
|
32
|
+
const missionsPath = path.join(newConfigPath, 'missions');
|
|
33
|
+
const reportsPath = path.join(newConfigPath, 'reports');
|
|
34
|
+
|
|
35
|
+
[contextPath, missionsPath, reportsPath].forEach(p => fs.mkdirSync(p, { recursive: true }));
|
|
36
|
+
|
|
37
|
+
// Move files
|
|
38
|
+
children.forEach(child => {
|
|
39
|
+
const oldChildPath = path.join(oldDirPath, child);
|
|
40
|
+
const isDir = fs.statSync(oldChildPath).isDirectory();
|
|
41
|
+
|
|
42
|
+
if (child === configSubDir) {
|
|
43
|
+
// Move internals of config subDir to context/projects or similar?
|
|
44
|
+
// For now, let's just move the whole folder to context
|
|
45
|
+
moveFolder(oldChildPath, path.join(contextPath, child));
|
|
46
|
+
} else if (child.startsWith('agent-log') || child.includes('memory') || child.includes('memoria') || child.includes('components-')) {
|
|
47
|
+
moveFile(oldChildPath, path.join(contextPath, child));
|
|
48
|
+
} else if (child.endsWith('.yaml') || child.endsWith('.yml')) {
|
|
49
|
+
moveFile(oldChildPath, path.join(missionsPath, child));
|
|
50
|
+
} else if (child === 'saved_missions') {
|
|
51
|
+
const subMissions = fs.readdirSync(oldChildPath);
|
|
52
|
+
subMissions.forEach(sm => moveFile(path.join(oldChildPath, sm), path.join(missionsPath, sm)));
|
|
53
|
+
fs.rmdirSync(oldChildPath, { recursive: true });
|
|
54
|
+
} else {
|
|
55
|
+
// Default to context
|
|
56
|
+
if (isDir) moveFolder(oldChildPath, path.join(contextPath, child));
|
|
57
|
+
else moveFile(oldChildPath, path.join(contextPath, child));
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Remove the old out_ directory if empty
|
|
62
|
+
if (fs.readdirSync(oldDirPath).length === 0) {
|
|
63
|
+
fs.rmdirSync(oldDirPath);
|
|
64
|
+
console.log(`β
Cleaned up ${oldDirName}`);
|
|
65
|
+
} else {
|
|
66
|
+
console.warn(`β οΈ ${oldDirName} not empty after migration.`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Move tests/projects folders
|
|
71
|
+
const projectsDir = path.join(PROJECT_ROOT, 'tests', 'projects');
|
|
72
|
+
if (fs.existsSync(projectsDir)) {
|
|
73
|
+
const projects = fs.readdirSync(projectsDir).filter(f => fs.statSync(path.join(projectsDir, f)).isDirectory());
|
|
74
|
+
console.log(`π Migrating ${projects.length} project models from tests/projects...`);
|
|
75
|
+
for (const pName of projects) {
|
|
76
|
+
const oldPPath = path.join(projectsDir, pName);
|
|
77
|
+
// We assume project name matches or is related to a config
|
|
78
|
+
// If out/[pName] exists, move there
|
|
79
|
+
const targetConfigPath = path.join(centralOut, pName);
|
|
80
|
+
const targetContextPath = path.join(targetConfigPath, 'context');
|
|
81
|
+
if (!fs.existsSync(targetContextPath)) fs.mkdirSync(targetContextPath, { recursive: true });
|
|
82
|
+
|
|
83
|
+
moveFolder(oldPPath, path.join(targetContextPath, pName));
|
|
84
|
+
console.log(`β
Moved project model "${pName}" to out/${pName}/context/`);
|
|
85
|
+
}
|
|
86
|
+
// If empty, remove tests/projects
|
|
87
|
+
if (fs.readdirSync(projectsDir).length === 0) {
|
|
88
|
+
fs.rmdirSync(projectsDir);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Move loose files in central out/ to out/Default/context/
|
|
93
|
+
const looseFiles = fs.readdirSync(centralOut).filter(f => fs.statSync(path.join(centralOut, f)).isFile());
|
|
94
|
+
if (looseFiles.length > 0) {
|
|
95
|
+
console.log(`π Migrating ${looseFiles.length} loose files from out/ to out/Default/context/...`);
|
|
96
|
+
const defaultContext = path.join(centralOut, 'Default', 'context');
|
|
97
|
+
if (!fs.existsSync(defaultContext)) fs.mkdirSync(defaultContext, { recursive: true });
|
|
98
|
+
looseFiles.forEach(f => moveFile(path.join(centralOut, f), path.join(defaultContext, f)));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Move global reports
|
|
102
|
+
const globalReports = ['tests-report', 'playwright-internal-report', 'test-results'];
|
|
103
|
+
for (const gr of globalReports) {
|
|
104
|
+
const grPath = path.join(PROJECT_ROOT, gr);
|
|
105
|
+
if (fs.existsSync(grPath)) {
|
|
106
|
+
const activeConfig = process.env.ACTIVE_CONFIG || 'Default';
|
|
107
|
+
const targetDir = path.join(centralOut, activeConfig, 'reports', gr);
|
|
108
|
+
fs.mkdirSync(path.dirname(targetDir), { recursive: true });
|
|
109
|
+
moveFolder(grPath, targetDir);
|
|
110
|
+
console.log(`π Moved global report "${gr}" to out/${activeConfig}/reports/`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
console.log('β¨ Migration completed successfully.');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function moveFile(src, dest) {
|
|
118
|
+
if (!fs.existsSync(src)) return;
|
|
119
|
+
try {
|
|
120
|
+
fs.renameSync(src, dest);
|
|
121
|
+
} catch (e) {
|
|
122
|
+
if (e.code === 'EXDEV') {
|
|
123
|
+
fs.copyFileSync(src, dest);
|
|
124
|
+
fs.unlinkSync(src);
|
|
125
|
+
} else {
|
|
126
|
+
console.error(`Error moving ${src}:`, e.message);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function moveFolder(src, dest) {
|
|
132
|
+
if (!fs.existsSync(src)) return;
|
|
133
|
+
try {
|
|
134
|
+
if (fs.existsSync(dest)) {
|
|
135
|
+
// Merge content if exists
|
|
136
|
+
const files = fs.readdirSync(src);
|
|
137
|
+
files.forEach(f => {
|
|
138
|
+
const s = path.join(src, f);
|
|
139
|
+
const d = path.join(dest, f);
|
|
140
|
+
if (fs.statSync(s).isDirectory()) moveFolder(s, d);
|
|
141
|
+
else moveFile(s, d);
|
|
142
|
+
});
|
|
143
|
+
fs.rmdirSync(src, { recursive: true });
|
|
144
|
+
} else {
|
|
145
|
+
fs.renameSync(src, dest);
|
|
146
|
+
}
|
|
147
|
+
} catch (e) {
|
|
148
|
+
if (e.code === 'EXDEV') {
|
|
149
|
+
fs.cpSync(src, dest, { recursive: true });
|
|
150
|
+
fs.rmSync(src, { recursive: true });
|
|
151
|
+
} else {
|
|
152
|
+
console.error(`Error moving folder ${src}:`, e.message);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
migrate();
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// scripts/postinstall.mjs β Post-install hook
|
|
3
|
+
// Se ejecuta automΓ‘ticamente despuΓ©s de 'npm install -g @arcadial/arcality'
|
|
4
|
+
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import os from 'node:os';
|
|
8
|
+
|
|
9
|
+
const CONFIG_DIR = path.join(os.homedir(), '.arcality');
|
|
10
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
|
|
11
|
+
|
|
12
|
+
// Leer versiΓ³n del package.json
|
|
13
|
+
let version = 'unknown';
|
|
14
|
+
try {
|
|
15
|
+
const pkgPath = path.resolve(path.dirname(new URL(import.meta.url).pathname), '..', 'package.json');
|
|
16
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
17
|
+
version = pkg.version || 'unknown';
|
|
18
|
+
} catch { }
|
|
19
|
+
|
|
20
|
+
console.log('');
|
|
21
|
+
console.log(' ββββββββββββββββββββββββββββββββββββββββββββ');
|
|
22
|
+
console.log(` β ARCALITY v${version.padEnd(10)} β Instalado β
β`);
|
|
23
|
+
console.log(' ββββββββββββββββββββββββββββββββββββββββββββ');
|
|
24
|
+
console.log('');
|
|
25
|
+
|
|
26
|
+
// Crear directorio de config si no existe
|
|
27
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
28
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
29
|
+
console.log(` π Directorio creado: ${CONFIG_DIR}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Si no hay config, crear una por defecto (sin key)
|
|
33
|
+
if (!fs.existsSync(CONFIG_FILE)) {
|
|
34
|
+
const defaultConfig = {
|
|
35
|
+
api_key: '',
|
|
36
|
+
version: version,
|
|
37
|
+
installed_at: new Date().toISOString()
|
|
38
|
+
};
|
|
39
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(defaultConfig, null, 2), 'utf8');
|
|
40
|
+
console.log(` π Config creada: ${CONFIG_FILE}`);
|
|
41
|
+
console.log('');
|
|
42
|
+
console.log(' π Para configurar tu API Key, ejecuta:');
|
|
43
|
+
console.log('');
|
|
44
|
+
console.log(' arcality setup');
|
|
45
|
+
console.log('');
|
|
46
|
+
} else {
|
|
47
|
+
// Actualizar versiΓ³n en config existente
|
|
48
|
+
try {
|
|
49
|
+
let raw = fs.readFileSync(CONFIG_FILE, 'utf8');
|
|
50
|
+
if (raw.charCodeAt(0) === 0xFEFF) raw = raw.slice(1);
|
|
51
|
+
const config = JSON.parse(raw);
|
|
52
|
+
config.version = version;
|
|
53
|
+
config.updated_at = new Date().toISOString();
|
|
54
|
+
fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf8');
|
|
55
|
+
console.log(` β
Config actualizada (v${version})`);
|
|
56
|
+
} catch { }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
console.log(' π PrΓ³ximos pasos:');
|
|
60
|
+
console.log(' 1. arcality setup β Configura tu API Key y navegador');
|
|
61
|
+
console.log(' 2. arcality β Abre el menΓΊ principal');
|
|
62
|
+
console.log(' 3. arcality --agent "misiΓ³n" β Ejecuta directamente');
|
|
63
|
+
console.log('');
|