@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.
Files changed (97) hide show
  1. package/.agents/skills/e2e-testing-expert/SKILL.md +28 -0
  2. package/.agents/skills/frontend-design/LICENSE.txt +177 -0
  3. package/.agents/skills/frontend-design/SKILL.md +42 -0
  4. package/.agents/skills/nodejs-backend-patterns/SKILL.md +639 -0
  5. package/.agents/skills/nodejs-backend-patterns/references/advanced-patterns.md +430 -0
  6. package/.agents/skills/playwright-best-practices/LICENSE.md +7 -0
  7. package/.agents/skills/playwright-best-practices/README.md +147 -0
  8. package/.agents/skills/playwright-best-practices/SKILL.md +303 -0
  9. package/.agents/skills/playwright-best-practices/advanced/authentication-flows.md +360 -0
  10. package/.agents/skills/playwright-best-practices/advanced/authentication.md +871 -0
  11. package/.agents/skills/playwright-best-practices/advanced/clock-mocking.md +364 -0
  12. package/.agents/skills/playwright-best-practices/advanced/mobile-testing.md +409 -0
  13. package/.agents/skills/playwright-best-practices/advanced/multi-context.md +288 -0
  14. package/.agents/skills/playwright-best-practices/advanced/multi-user.md +393 -0
  15. package/.agents/skills/playwright-best-practices/advanced/network-advanced.md +452 -0
  16. package/.agents/skills/playwright-best-practices/advanced/third-party.md +464 -0
  17. package/.agents/skills/playwright-best-practices/architecture/pom-vs-fixtures.md +363 -0
  18. package/.agents/skills/playwright-best-practices/architecture/test-architecture.md +369 -0
  19. package/.agents/skills/playwright-best-practices/architecture/when-to-mock.md +383 -0
  20. package/.agents/skills/playwright-best-practices/browser-apis/browser-apis.md +391 -0
  21. package/.agents/skills/playwright-best-practices/browser-apis/iframes.md +403 -0
  22. package/.agents/skills/playwright-best-practices/browser-apis/service-workers.md +504 -0
  23. package/.agents/skills/playwright-best-practices/browser-apis/websockets.md +403 -0
  24. package/.agents/skills/playwright-best-practices/core/annotations.md +424 -0
  25. package/.agents/skills/playwright-best-practices/core/assertions-waiting.md +361 -0
  26. package/.agents/skills/playwright-best-practices/core/configuration.md +452 -0
  27. package/.agents/skills/playwright-best-practices/core/fixtures-hooks.md +417 -0
  28. package/.agents/skills/playwright-best-practices/core/global-setup.md +434 -0
  29. package/.agents/skills/playwright-best-practices/core/locators.md +242 -0
  30. package/.agents/skills/playwright-best-practices/core/page-object-model.md +315 -0
  31. package/.agents/skills/playwright-best-practices/core/projects-dependencies.md +453 -0
  32. package/.agents/skills/playwright-best-practices/core/test-data.md +492 -0
  33. package/.agents/skills/playwright-best-practices/core/test-suite-structure.md +361 -0
  34. package/.agents/skills/playwright-best-practices/core/test-tags.md +298 -0
  35. package/.agents/skills/playwright-best-practices/debugging/console-errors.md +420 -0
  36. package/.agents/skills/playwright-best-practices/debugging/debugging.md +504 -0
  37. package/.agents/skills/playwright-best-practices/debugging/error-testing.md +360 -0
  38. package/.agents/skills/playwright-best-practices/debugging/flaky-tests.md +496 -0
  39. package/.agents/skills/playwright-best-practices/frameworks/angular.md +530 -0
  40. package/.agents/skills/playwright-best-practices/frameworks/nextjs.md +469 -0
  41. package/.agents/skills/playwright-best-practices/frameworks/react.md +531 -0
  42. package/.agents/skills/playwright-best-practices/frameworks/vue.md +574 -0
  43. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/ci-cd.md +468 -0
  44. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/docker.md +283 -0
  45. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/github-actions.md +546 -0
  46. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/gitlab.md +397 -0
  47. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/other-providers.md +521 -0
  48. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/parallel-sharding.md +371 -0
  49. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/performance.md +453 -0
  50. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/reporting.md +424 -0
  51. package/.agents/skills/playwright-best-practices/infrastructure-ci-cd/test-coverage.md +497 -0
  52. package/.agents/skills/playwright-best-practices/testing-patterns/accessibility.md +359 -0
  53. package/.agents/skills/playwright-best-practices/testing-patterns/api-testing.md +719 -0
  54. package/.agents/skills/playwright-best-practices/testing-patterns/browser-extensions.md +506 -0
  55. package/.agents/skills/playwright-best-practices/testing-patterns/canvas-webgl.md +493 -0
  56. package/.agents/skills/playwright-best-practices/testing-patterns/component-testing.md +500 -0
  57. package/.agents/skills/playwright-best-practices/testing-patterns/drag-drop.md +576 -0
  58. package/.agents/skills/playwright-best-practices/testing-patterns/electron.md +509 -0
  59. package/.agents/skills/playwright-best-practices/testing-patterns/file-operations.md +377 -0
  60. package/.agents/skills/playwright-best-practices/testing-patterns/file-upload-download.md +562 -0
  61. package/.agents/skills/playwright-best-practices/testing-patterns/forms-validation.md +561 -0
  62. package/.agents/skills/playwright-best-practices/testing-patterns/graphql-testing.md +331 -0
  63. package/.agents/skills/playwright-best-practices/testing-patterns/i18n.md +508 -0
  64. package/.agents/skills/playwright-best-practices/testing-patterns/performance-testing.md +476 -0
  65. package/.agents/skills/playwright-best-practices/testing-patterns/security-testing.md +430 -0
  66. package/.agents/skills/playwright-best-practices/testing-patterns/visual-regression.md +634 -0
  67. package/.env.example +21 -0
  68. package/README.md +30 -0
  69. package/bin/arcality.mjs +86 -0
  70. package/package.json +66 -0
  71. package/playwright.config.ts +12 -0
  72. package/scripts/cleanup-qmsdev.mjs +63 -0
  73. package/scripts/discover-view.mjs +52 -0
  74. package/scripts/extract-view.mjs +64 -0
  75. package/scripts/gen-and-run.mjs +838 -0
  76. package/scripts/init.mjs +290 -0
  77. package/scripts/migrate-to-central-out.mjs +157 -0
  78. package/scripts/postinstall.mjs +63 -0
  79. package/scripts/rebrand-report.mjs +241 -0
  80. package/scripts/setup.mjs +166 -0
  81. package/src/KnowledgeService.ts +239 -0
  82. package/src/arcalityClient.mjs +266 -0
  83. package/src/configLoader.mjs +179 -0
  84. package/src/configManager.mjs +172 -0
  85. package/src/consoleBanner.ts +32 -0
  86. package/src/envSetup.ts +205 -0
  87. package/src/index.ts +25 -0
  88. package/src/projectInspector.ts +42 -0
  89. package/src/services/collectiveMemoryService.ts +178 -0
  90. package/src/testRunner.ts +201 -0
  91. package/tests/_helpers/ArcalityReporter.ts +490 -0
  92. package/tests/_helpers/agentic-runner.spec.ts +741 -0
  93. package/tests/_helpers/ai-agent-helper.ts +1573 -0
  94. package/tests/_helpers/discover-view.spec.ts +238 -0
  95. package/tests/_helpers/extract-view.spec.ts +118 -0
  96. package/tests/_helpers/qa-tools.ts +333 -0
  97. package/tests/_helpers/smart-action.spec.ts +1458 -0
@@ -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('');