archsync 1.0.0 β 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -0
- package/dist/archsync.cjs +2 -0
- package/package.json +8 -4
- package/bin/cli.js +0 -91
- package/src/__tests__/e2e-workflow.test.js +0 -66
- package/src/__tests__/hashEngine.test.js +0 -109
- package/src/__tests__/impact.test.js +0 -137
- package/src/__tests__/parsers.test.js +0 -496
- package/src/__tests__/scan-pipeline.test.js +0 -332
- package/src/__tests__/schemaBuilder.test.js +0 -145
- package/src/__tests__/workspace.test.js +0 -178
- package/src/commands/backup.js +0 -54
- package/src/commands/connect.js +0 -129
- package/src/commands/diff.js +0 -228
- package/src/commands/export.js +0 -125
- package/src/commands/impactReport.js +0 -50
- package/src/commands/import.js +0 -126
- package/src/commands/init.js +0 -80
- package/src/commands/login.js +0 -116
- package/src/commands/plugin.js +0 -28
- package/src/commands/push.js +0 -194
- package/src/commands/register.js +0 -127
- package/src/commands/scan.js +0 -498
- package/src/commands/serve.js +0 -133
- package/src/commands/setup.js +0 -233
- package/src/commands/status.js +0 -56
- package/src/commands/validate.js +0 -245
- package/src/commands/watch.js +0 -70
- package/src/core/credentialStore.js +0 -76
- package/src/core/hashEngine.js +0 -34
- package/src/core/impactEngine.js +0 -192
- package/src/core/monorepoDetector.js +0 -41
- package/src/core/pluginManager.js +0 -40
- package/src/core/relationshipEngine.js +0 -917
- package/src/core/requestSigning.js +0 -16
- package/src/core/schemaBuilder.js +0 -230
- package/src/core/schemaDeduplicator.js +0 -54
- package/src/core/supabaseClient.js +0 -68
- package/src/core/workspaceDetector.js +0 -113
- package/src/parsers/astParser.js +0 -274
- package/src/parsers/configParser.js +0 -49
- package/src/parsers/dependencyGraph.js +0 -31
- package/src/parsers/flutterParser.js +0 -98
- package/src/parsers/goParser.js +0 -99
- package/src/parsers/index.js +0 -211
- package/src/parsers/javaParser.js +0 -89
- package/src/parsers/nodeParser.js +0 -429
- package/src/parsers/pythonParser.js +0 -109
- package/src/parsers/reactParser.js +0 -368
- package/src/parsers/smartComment.js +0 -144
package/src/commands/import.js
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import ora from 'ora';
|
|
3
|
-
import fs from 'fs';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import { readConfig, getSupabaseClient } from '../core/supabaseClient.js';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* archsync import --file <filename> [--project <projectId>]
|
|
9
|
-
*
|
|
10
|
-
* Reads a JSON or SQL export file and upserts the data into the target project.
|
|
11
|
-
*/
|
|
12
|
-
export async function importCommand(options) {
|
|
13
|
-
console.log(chalk.blue.bold('\nπ₯ ArchSync CLI β Import\n'));
|
|
14
|
-
|
|
15
|
-
const config = readConfig(options.dir);
|
|
16
|
-
if (!config) {
|
|
17
|
-
console.log(chalk.red('β No .archsync.json found. Run `archsync init` first.'));
|
|
18
|
-
process.exit(1);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const projectId = options.project || config.projectId;
|
|
22
|
-
if (!projectId) {
|
|
23
|
-
console.log(chalk.red('β No project ID. Pass --project <id> or set projectId in .archsync.json.'));
|
|
24
|
-
process.exit(1);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const filePath = options.file;
|
|
28
|
-
if (!filePath) {
|
|
29
|
-
console.log(chalk.red('β No input file. Pass --file <path>'));
|
|
30
|
-
process.exit(1);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const resolvedPath = path.resolve(filePath);
|
|
34
|
-
if (!fs.existsSync(resolvedPath)) {
|
|
35
|
-
console.log(chalk.red(`β File not found: ${resolvedPath}`));
|
|
36
|
-
process.exit(1);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const ext = path.extname(resolvedPath).toLowerCase();
|
|
40
|
-
if (ext !== '.json') {
|
|
41
|
-
console.log(chalk.red('β Only .json exports are supported for import. Export to JSON first.'));
|
|
42
|
-
process.exit(1);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const spinner = ora('Reading fileβ¦').start();
|
|
46
|
-
|
|
47
|
-
let exportData;
|
|
48
|
-
try {
|
|
49
|
-
const raw = fs.readFileSync(resolvedPath, 'utf-8');
|
|
50
|
-
exportData = JSON.parse(raw);
|
|
51
|
-
} catch (err) {
|
|
52
|
-
spinner.fail('Failed to parse file');
|
|
53
|
-
console.log(chalk.red(` ${err.message}`));
|
|
54
|
-
process.exit(1);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// βββ Validate ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
58
|
-
if (!exportData.__archsync_export) {
|
|
59
|
-
spinner.fail('Not a valid ArchSync export (missing __archsync_export flag)');
|
|
60
|
-
process.exit(1);
|
|
61
|
-
}
|
|
62
|
-
if (!Array.isArray(exportData.nodes) || !Array.isArray(exportData.edges)) {
|
|
63
|
-
spinner.fail('Invalid export format: missing nodes or edges arrays');
|
|
64
|
-
process.exit(1);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
spinner.succeed(`File validated: ${exportData.nodes.length} nodes, ${exportData.edges.length} edges`);
|
|
68
|
-
|
|
69
|
-
// βββ Connect βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
70
|
-
const spinner2 = ora('Connecting to databaseβ¦').start();
|
|
71
|
-
let client;
|
|
72
|
-
try {
|
|
73
|
-
client = getSupabaseClient(config);
|
|
74
|
-
spinner2.succeed('Connected');
|
|
75
|
-
} catch (err) {
|
|
76
|
-
spinner2.fail('Failed to connect');
|
|
77
|
-
console.log(chalk.red(` ${err.message}`));
|
|
78
|
-
process.exit(1);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// βββ Upsert nodes ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
82
|
-
const spinner3 = ora('Importing nodesβ¦').start();
|
|
83
|
-
|
|
84
|
-
const nodes = exportData.nodes.map(n => ({ ...n, project_id: projectId }));
|
|
85
|
-
const edges = exportData.edges.map(e => ({ ...e, project_id: projectId }));
|
|
86
|
-
|
|
87
|
-
let importedNodes = 0;
|
|
88
|
-
let importedEdges = 0;
|
|
89
|
-
|
|
90
|
-
if (nodes.length > 0) {
|
|
91
|
-
const { error } = await client
|
|
92
|
-
.from('nodes')
|
|
93
|
-
.upsert(nodes, { onConflict: 'id' });
|
|
94
|
-
if (error) {
|
|
95
|
-
spinner3.fail('Failed to import nodes');
|
|
96
|
-
console.log(chalk.red(` ${error.message}`));
|
|
97
|
-
process.exit(1);
|
|
98
|
-
}
|
|
99
|
-
importedNodes = nodes.length;
|
|
100
|
-
}
|
|
101
|
-
spinner3.succeed(`Nodes imported: ${importedNodes}`);
|
|
102
|
-
|
|
103
|
-
// βββ Upsert edges ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
104
|
-
const spinner4 = ora('Importing edgesβ¦').start();
|
|
105
|
-
|
|
106
|
-
if (edges.length > 0) {
|
|
107
|
-
const { error } = await client
|
|
108
|
-
.from('edges')
|
|
109
|
-
.upsert(edges, { onConflict: 'id' });
|
|
110
|
-
if (error) {
|
|
111
|
-
spinner4.fail('Failed to import edges');
|
|
112
|
-
console.log(chalk.red(` ${error.message}`));
|
|
113
|
-
process.exit(1);
|
|
114
|
-
}
|
|
115
|
-
importedEdges = edges.length;
|
|
116
|
-
}
|
|
117
|
-
spinner4.succeed(`Edges imported: ${importedEdges}`);
|
|
118
|
-
|
|
119
|
-
// βββ Summary βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
120
|
-
console.log(chalk.gray('\nβββ Import Summary βββββββββββββββββ'));
|
|
121
|
-
console.log(chalk.green(` Nodes imported: ${chalk.bold(importedNodes)}`));
|
|
122
|
-
console.log(chalk.blue(` Edges imported: ${chalk.bold(importedEdges)}`));
|
|
123
|
-
console.log(chalk.white(` Target project: ${chalk.bold(projectId)}`));
|
|
124
|
-
console.log('');
|
|
125
|
-
console.log(chalk.blue('Next: ') + chalk.gray('Open the ArchSync app to view your imported diagram'));
|
|
126
|
-
}
|
package/src/commands/init.js
DELETED
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import ora from 'ora';
|
|
3
|
-
import fs from 'fs';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import { writeConfig } from '../core/supabaseClient.js';
|
|
6
|
-
|
|
7
|
-
export async function initCommand(options) {
|
|
8
|
-
console.log(chalk.blue.bold('\nποΈ ArchSync CLI β Project Init\n'));
|
|
9
|
-
|
|
10
|
-
const configPath = path.resolve('.archsync.json');
|
|
11
|
-
|
|
12
|
-
if (fs.existsSync(configPath)) {
|
|
13
|
-
console.log(chalk.yellow('β .archsync.json already exists. Overwriting...'));
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const framework = options.framework || detectFrameworkAuto();
|
|
17
|
-
|
|
18
|
-
// Map framework β the `system` label the canvas merger will use.
|
|
19
|
-
// The cross-system inference engine treats any non-{backend,server,api}
|
|
20
|
-
// system as a frontend, so what matters most is "backend" vs anything
|
|
21
|
-
// else. A user can still override this by editing .archsync.json.
|
|
22
|
-
const FRAMEWORK_TO_SYSTEM = {
|
|
23
|
-
node: 'backend',
|
|
24
|
-
express: 'backend',
|
|
25
|
-
nestjs: 'backend',
|
|
26
|
-
react: 'web',
|
|
27
|
-
nextjs: 'web',
|
|
28
|
-
flutter: 'mobile',
|
|
29
|
-
};
|
|
30
|
-
const defaultSystem = options.system || FRAMEWORK_TO_SYSTEM[framework] || 'backend';
|
|
31
|
-
|
|
32
|
-
const config = {
|
|
33
|
-
version: '1.0',
|
|
34
|
-
projectId: options.project || null,
|
|
35
|
-
framework,
|
|
36
|
-
supabase: {
|
|
37
|
-
url: process.env.ARCHSYNC_SUPABASE_URL || process.env.SUPABASE_URL || '',
|
|
38
|
-
anonKey: process.env.ARCHSYNC_SUPABASE_ANON_KEY || process.env.SUPABASE_ANON_KEY || '',
|
|
39
|
-
},
|
|
40
|
-
scan: {
|
|
41
|
-
include: ['**/*.{js,ts,jsx,tsx,dart}'],
|
|
42
|
-
exclude: ['**/node_modules/**', '**/build/**', '**/dist/**', '**/.dart_tool/**', '**/ios/**', '**/android/**'],
|
|
43
|
-
ast: false,
|
|
44
|
-
},
|
|
45
|
-
defaultSystem,
|
|
46
|
-
createdAt: new Date().toISOString(),
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
writeConfig(config);
|
|
50
|
-
|
|
51
|
-
console.log(chalk.green('β
Created .archsync.json'));
|
|
52
|
-
console.log(chalk.gray(` Framework: ${config.framework}`));
|
|
53
|
-
console.log(chalk.gray(` System label: ${config.defaultSystem}`));
|
|
54
|
-
console.log(chalk.gray(` Project ID: ${config.projectId || 'Not linked (local mode)'}`));
|
|
55
|
-
|
|
56
|
-
if (!config.supabase.url) {
|
|
57
|
-
console.log(chalk.yellow('\nβ Supabase not configured. Set ARCHSYNC_SUPABASE_URL and ARCHSYNC_SUPABASE_ANON_KEY environment variables.'));
|
|
58
|
-
console.log(chalk.gray(' Or edit .archsync.json manually.'));
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
console.log(chalk.blue('\nNext steps:'));
|
|
62
|
-
console.log(chalk.gray(' archsync scan # Scan codebase'));
|
|
63
|
-
console.log(chalk.gray(' archsync push # Push schema to cloud'));
|
|
64
|
-
console.log(chalk.gray(' archsync watch # Auto-sync on changes'));
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function detectFrameworkAuto() {
|
|
68
|
-
if (fs.existsSync('pubspec.yaml')) return 'flutter';
|
|
69
|
-
if (fs.existsSync('next.config.js') || fs.existsSync('next.config.mjs')) return 'nextjs';
|
|
70
|
-
if (fs.existsSync('package.json')) {
|
|
71
|
-
try {
|
|
72
|
-
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf-8'));
|
|
73
|
-
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
74
|
-
if (deps['react'] || deps['react-dom']) return 'react';
|
|
75
|
-
if (deps['@nestjs/core']) return 'nestjs';
|
|
76
|
-
if (deps['express']) return 'express';
|
|
77
|
-
} catch { }
|
|
78
|
-
}
|
|
79
|
-
return 'node';
|
|
80
|
-
}
|
package/src/commands/login.js
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import ora from 'ora';
|
|
3
|
-
import { createInterface } from 'readline';
|
|
4
|
-
import { saveCredentials, loadCredentials } from '../core/credentialStore.js';
|
|
5
|
-
import { readConfig, getSupabaseClient } from '../core/supabaseClient.js';
|
|
6
|
-
|
|
7
|
-
function prompt(rl, question) {
|
|
8
|
-
return new Promise(resolve => rl.question(question, resolve));
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function promptPassword(question) {
|
|
12
|
-
return new Promise(resolve => {
|
|
13
|
-
if (process.stdin.isTTY) {
|
|
14
|
-
process.stdout.write(question);
|
|
15
|
-
process.stdin.setRawMode(true);
|
|
16
|
-
process.stdin.resume();
|
|
17
|
-
process.stdin.setEncoding('utf8');
|
|
18
|
-
let password = '';
|
|
19
|
-
const onData = (ch) => {
|
|
20
|
-
if (ch === '\n' || ch === '\r' || ch === '\u0003') {
|
|
21
|
-
process.stdin.setRawMode(false);
|
|
22
|
-
process.stdin.pause();
|
|
23
|
-
process.stdin.removeListener('data', onData);
|
|
24
|
-
process.stdout.write('\n');
|
|
25
|
-
resolve(password);
|
|
26
|
-
} else if (ch === '\u007F') {
|
|
27
|
-
password = password.slice(0, -1);
|
|
28
|
-
} else {
|
|
29
|
-
password += ch;
|
|
30
|
-
process.stdout.write('*');
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
process.stdin.on('data', onData);
|
|
34
|
-
} else {
|
|
35
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
36
|
-
rl.question(question, (ans) => { rl.close(); resolve(ans); });
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export async function loginCommand(options) {
|
|
42
|
-
console.log(chalk.blue.bold('\nπ ArchSync CLI β Login\n'));
|
|
43
|
-
|
|
44
|
-
// Check if already authenticated
|
|
45
|
-
const existing = loadCredentials();
|
|
46
|
-
if (existing && existing.apiKey) {
|
|
47
|
-
console.log(chalk.yellow(`β Already logged in (userId: ${existing.userId || 'unknown'}).`));
|
|
48
|
-
console.log(chalk.gray(' Run with --force to re-authenticate or `archsync logout` to clear credentials.'));
|
|
49
|
-
if (!options.force) return;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const config = readConfig();
|
|
53
|
-
if (!config?.supabase?.url || !config?.supabase?.anonKey) {
|
|
54
|
-
console.log(chalk.red('β No Supabase config found. Run `archsync init` or `archsync connect` first.'));
|
|
55
|
-
process.exit(1);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
let email = options.email;
|
|
59
|
-
let password = options.password;
|
|
60
|
-
|
|
61
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
62
|
-
|
|
63
|
-
try {
|
|
64
|
-
if (!email) {
|
|
65
|
-
email = await prompt(rl, chalk.white('Email: '));
|
|
66
|
-
}
|
|
67
|
-
if (!password) {
|
|
68
|
-
rl.close();
|
|
69
|
-
password = await promptPassword(chalk.white('Password: '));
|
|
70
|
-
} else {
|
|
71
|
-
rl.close();
|
|
72
|
-
}
|
|
73
|
-
} catch (err) {
|
|
74
|
-
rl.close();
|
|
75
|
-
throw err;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
if (!email || !password) {
|
|
79
|
-
console.log(chalk.red('β Email and password are required.'));
|
|
80
|
-
process.exit(1);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const spinner = ora('Authenticating...').start();
|
|
84
|
-
|
|
85
|
-
try {
|
|
86
|
-
const client = getSupabaseClient(config);
|
|
87
|
-
const { data, error } = await client.auth.signInWithPassword({ email, password });
|
|
88
|
-
|
|
89
|
-
if (error) {
|
|
90
|
-
spinner.fail('Authentication failed');
|
|
91
|
-
console.log(chalk.red(`\n Error: ${error.message}\n`));
|
|
92
|
-
process.exit(1);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const session = data.session;
|
|
96
|
-
const user = data.user;
|
|
97
|
-
|
|
98
|
-
if (!session?.access_token) {
|
|
99
|
-
spinner.fail('No session token received');
|
|
100
|
-
process.exit(1);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
saveCredentials(session.access_token, user.id, email);
|
|
104
|
-
|
|
105
|
-
spinner.succeed('Logged in successfully!');
|
|
106
|
-
console.log(chalk.gray(`\n User ID: ${user.id}`));
|
|
107
|
-
console.log(chalk.gray(` Email: ${email}`));
|
|
108
|
-
console.log(chalk.gray(` Token stored in ~/.archsync/credentials.json`));
|
|
109
|
-
console.log(chalk.blue('\nNext: ') + chalk.gray('archsync push -b main'));
|
|
110
|
-
|
|
111
|
-
} catch (err) {
|
|
112
|
-
spinner.fail('Login failed');
|
|
113
|
-
console.log(chalk.red(`\n Error: ${err.message}\n`));
|
|
114
|
-
process.exit(1);
|
|
115
|
-
}
|
|
116
|
-
}
|
package/src/commands/plugin.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
// Plugin management commands - Task 24
|
|
2
|
-
const { program } = require('commander');
|
|
3
|
-
const { listPlugins, installPlugin, uninstallPlugin } = require('../core/pluginManager');
|
|
4
|
-
|
|
5
|
-
function registerPluginCommands(cli) {
|
|
6
|
-
const plugin = cli.command('plugin').description('Manage archsync plugins');
|
|
7
|
-
|
|
8
|
-
plugin.command('add <name>').description('Install a parser plugin').action((name) => {
|
|
9
|
-
console.log(`Installing plugin: ${name}...`);
|
|
10
|
-
try { installPlugin(name); console.log(`β Plugin ${name} installed`); }
|
|
11
|
-
catch (e) { console.error(`β Failed to install: ${e.message}`); process.exit(1); }
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
plugin.command('list').description('List installed plugins').action(() => {
|
|
15
|
-
const plugins = listPlugins();
|
|
16
|
-
if (!plugins.length) { console.log('No plugins installed.'); return; }
|
|
17
|
-
console.log('Installed plugins:');
|
|
18
|
-
plugins.forEach(p => console.log(` - ${p}`));
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
plugin.command('remove <name>').description('Remove a plugin').action((name) => {
|
|
22
|
-
console.log(`Removing plugin: ${name}...`);
|
|
23
|
-
try { uninstallPlugin(name); console.log(`β Plugin ${name} removed`); }
|
|
24
|
-
catch (e) { console.error(`β Failed to remove: ${e.message}`); process.exit(1); }
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
module.exports = { registerPluginCommands };
|
package/src/commands/push.js
DELETED
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import ora from 'ora';
|
|
3
|
-
import fs from 'fs';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import { readConfig, getSupabaseClient, pushSchemaCommit, fetchLatestSchema } from '../core/supabaseClient.js';
|
|
6
|
-
import { schemasEqual } from '../core/hashEngine.js';
|
|
7
|
-
import { diffSchemas } from '../core/schemaBuilder.js';
|
|
8
|
-
import { getApiKey } from '../core/credentialStore.js';
|
|
9
|
-
import { analyzeImpact, embedWarnings, summarizeWarnings } from '../core/impactEngine.js';
|
|
10
|
-
import { printImpactWarnings } from './impactReport.js';
|
|
11
|
-
|
|
12
|
-
const LOCAL_SCHEMA_FILE = '.archsync-schema.json';
|
|
13
|
-
|
|
14
|
-
export async function pushCommand(options) {
|
|
15
|
-
const isCI = options.ci || process.env.CI === 'true';
|
|
16
|
-
|
|
17
|
-
if (!isCI) {
|
|
18
|
-
console.log(chalk.blue.bold('\nπ ArchSync CLI β Push Schema\n'));
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const config = readConfig();
|
|
22
|
-
if (!config) {
|
|
23
|
-
if (isCI) {
|
|
24
|
-
console.log(JSON.stringify({ success: false, error: 'No .archsync.json found' }));
|
|
25
|
-
} else {
|
|
26
|
-
console.log(chalk.red('β No .archsync.json found. Run `archsync init` first.'));
|
|
27
|
-
}
|
|
28
|
-
process.exit(1);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (!config.projectId) {
|
|
32
|
-
if (isCI) {
|
|
33
|
-
console.log(JSON.stringify({ success: false, error: 'No project linked' }));
|
|
34
|
-
} else {
|
|
35
|
-
console.log(chalk.red('β No project linked. Set projectId in .archsync.json or run `archsync init -p <id>`.'));
|
|
36
|
-
}
|
|
37
|
-
process.exit(1);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const schemaPath = path.resolve(LOCAL_SCHEMA_FILE);
|
|
41
|
-
if (!fs.existsSync(schemaPath)) {
|
|
42
|
-
if (isCI) {
|
|
43
|
-
console.log(JSON.stringify({ success: false, error: 'No local schema found. Run archsync scan first.' }));
|
|
44
|
-
} else {
|
|
45
|
-
console.log(chalk.red('β No local schema found. Run `archsync scan` first.'));
|
|
46
|
-
}
|
|
47
|
-
process.exit(1);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Resolve API key: CI env var takes priority, then credentialStore, then config
|
|
51
|
-
const apiKey = getApiKey();
|
|
52
|
-
|
|
53
|
-
if (isCI && !apiKey) {
|
|
54
|
-
console.log(JSON.stringify({ success: false, error: 'ARCHSYNC_API_KEY env var not set' }));
|
|
55
|
-
process.exit(1);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Inject API key into config for Supabase client (use as auth header if provided)
|
|
59
|
-
const resolvedConfig = { ...config };
|
|
60
|
-
if (apiKey && !resolvedConfig.supabase?.anonKey) {
|
|
61
|
-
if (!resolvedConfig.supabase) resolvedConfig.supabase = {};
|
|
62
|
-
resolvedConfig.supabase.anonKey = apiKey;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const localSchema = JSON.parse(fs.readFileSync(schemaPath, 'utf-8'));
|
|
66
|
-
const branch = options.branch || 'main';
|
|
67
|
-
|
|
68
|
-
const spinner = isCI ? null : ora('Connecting to Supabase...').start();
|
|
69
|
-
|
|
70
|
-
try {
|
|
71
|
-
const client = getSupabaseClient(resolvedConfig);
|
|
72
|
-
|
|
73
|
-
// Fetch remote schema (with connectivity check)
|
|
74
|
-
if (!isCI) spinner.text = 'Fetching remote schema...';
|
|
75
|
-
let remoteSchema;
|
|
76
|
-
try {
|
|
77
|
-
remoteSchema = await fetchLatestSchema(client, config.projectId, branch);
|
|
78
|
-
} catch (connErr) {
|
|
79
|
-
const msg = connErr.message || '';
|
|
80
|
-
if (msg.includes('525') || msg.includes('SSL') || msg.includes('ECONNREFUSED') || msg.includes('ENOTFOUND') || msg.includes('fetch failed') || msg.includes('<!DOCTYPE')) {
|
|
81
|
-
if (isCI) {
|
|
82
|
-
console.log(JSON.stringify({ success: false, error: 'Supabase unreachable (SSL/network error)' }));
|
|
83
|
-
} else {
|
|
84
|
-
spinner.fail('Cannot reach Supabase');
|
|
85
|
-
console.log(chalk.red('\nβ Supabase is unreachable (SSL/network error).\n'));
|
|
86
|
-
console.log(chalk.yellow(' This is usually caused by:'));
|
|
87
|
-
console.log(chalk.white(' 1. Your Supabase project is PAUSED (free tier pauses after 1 week of inactivity)'));
|
|
88
|
-
console.log(chalk.white(' 2. Temporary Cloudflare/network issue'));
|
|
89
|
-
console.log(chalk.gray('\n Fix:'));
|
|
90
|
-
console.log(chalk.cyan(' β Go to https://supabase.com/dashboard'));
|
|
91
|
-
console.log(chalk.cyan(' β Click "Restore project" if paused'));
|
|
92
|
-
console.log(chalk.cyan(' β Wait ~30 seconds then run: archsync push\n'));
|
|
93
|
-
}
|
|
94
|
-
process.exit(1);
|
|
95
|
-
}
|
|
96
|
-
throw connErr;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Check if schemas are identical
|
|
100
|
-
if (remoteSchema && schemasEqual(localSchema, remoteSchema)) {
|
|
101
|
-
if (isCI) {
|
|
102
|
-
const result = {
|
|
103
|
-
success: true,
|
|
104
|
-
nodesAdded: 0,
|
|
105
|
-
nodesRemoved: 0,
|
|
106
|
-
schemaHash: localSchema.hash || '',
|
|
107
|
-
timestamp: new Date().toISOString(),
|
|
108
|
-
message: 'Schema already up to date',
|
|
109
|
-
};
|
|
110
|
-
console.log(JSON.stringify(result));
|
|
111
|
-
} else {
|
|
112
|
-
spinner.info('Schema is already up to date. No changes to push.');
|
|
113
|
-
}
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Compute diff stats
|
|
118
|
-
let diffStats = { added: [], modified: [], deleted: [], unchanged: [] };
|
|
119
|
-
let impactWarnings = [];
|
|
120
|
-
if (remoteSchema) {
|
|
121
|
-
diffStats = diffSchemas(remoteSchema, localSchema);
|
|
122
|
-
if (!isCI) {
|
|
123
|
-
console.log(chalk.gray('\nβββ Changes ββββββββββββββββββββββββ'));
|
|
124
|
-
if (diffStats.added.length) console.log(chalk.green(` + ${diffStats.added.length} added`));
|
|
125
|
-
if (diffStats.modified.length) console.log(chalk.yellow(` ~ ${diffStats.modified.length} modified`));
|
|
126
|
-
if (diffStats.deleted.length) console.log(chalk.red(` - ${diffStats.deleted.length} deleted`));
|
|
127
|
-
if (diffStats.unchanged.length) console.log(chalk.gray(` = ${diffStats.unchanged.length} unchanged`));
|
|
128
|
-
console.log('');
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Breaking-change impact vs what the team currently sees on this
|
|
132
|
-
// branch. Embedded into the pushed schema so the canvas badges
|
|
133
|
-
// the affected entities and systems.
|
|
134
|
-
impactWarnings = analyzeImpact(remoteSchema, localSchema);
|
|
135
|
-
embedWarnings(localSchema, impactWarnings);
|
|
136
|
-
if (!isCI) {
|
|
137
|
-
printImpactWarnings(impactWarnings, { heading: `Impact on "${branch}"` });
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
if (options.dryRun) {
|
|
142
|
-
if (isCI) {
|
|
143
|
-
console.log(JSON.stringify({ success: true, dryRun: true, nodesAdded: diffStats.added.length, nodesRemoved: diffStats.deleted.length }));
|
|
144
|
-
} else {
|
|
145
|
-
spinner.info('Dry run β no changes pushed.');
|
|
146
|
-
}
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Push
|
|
151
|
-
if (!isCI) spinner.text = `Pushing to branch "${branch}"...`;
|
|
152
|
-
const message = options.message || `CLI push: ${localSchema.meta?.nodeCount || 0} nodes, ${localSchema.meta?.edgeCount || 0} edges`;
|
|
153
|
-
|
|
154
|
-
const commit = await pushSchemaCommit(client, config.projectId, branch, localSchema, message, isCI ? 'ci' : 'cli');
|
|
155
|
-
|
|
156
|
-
if (isCI) {
|
|
157
|
-
const result = {
|
|
158
|
-
success: true,
|
|
159
|
-
nodesAdded: diffStats.added.length,
|
|
160
|
-
nodesRemoved: diffStats.deleted.length,
|
|
161
|
-
schemaHash: localSchema.hash || '',
|
|
162
|
-
timestamp: new Date().toISOString(),
|
|
163
|
-
commitId: commit.id,
|
|
164
|
-
branch,
|
|
165
|
-
breakingChanges: summarizeWarnings(impactWarnings.filter(w => w.severity === 'breaking')),
|
|
166
|
-
};
|
|
167
|
-
console.log(JSON.stringify(result));
|
|
168
|
-
} else {
|
|
169
|
-
spinner.succeed(`Pushed to ${chalk.bold(branch)}!`);
|
|
170
|
-
console.log(chalk.gray(` Commit: ${commit.id}`));
|
|
171
|
-
console.log(chalk.gray(` Message: ${message}`));
|
|
172
|
-
console.log(chalk.gray(` Nodes: ${localSchema.nodes.length}, Edges: ${localSchema.edges.length}`));
|
|
173
|
-
const breaking = impactWarnings.filter(w => w.severity === 'breaking').length;
|
|
174
|
-
if (breaking > 0) {
|
|
175
|
-
console.log(chalk.red(` β ${breaking} breaking change(s) pushed β affected entities are flagged on the canvas.`));
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
} catch (err) {
|
|
180
|
-
const msg = err.message || '';
|
|
181
|
-
if (isCI) {
|
|
182
|
-
console.log(JSON.stringify({ success: false, error: msg }));
|
|
183
|
-
} else {
|
|
184
|
-
spinner.fail('Push failed');
|
|
185
|
-
if (msg.includes('525') || msg.includes('SSL') || msg.includes('fetch failed') || msg.includes('<!DOCTYPE')) {
|
|
186
|
-
console.log(chalk.red('\nβ Supabase is unreachable. Your project may be paused.'));
|
|
187
|
-
console.log(chalk.cyan(' β https://supabase.com/dashboard\n'));
|
|
188
|
-
} else {
|
|
189
|
-
console.log(chalk.red(`\n Error: ${msg}\n`));
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
process.exit(1);
|
|
193
|
-
}
|
|
194
|
-
}
|
package/src/commands/register.js
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import ora from 'ora';
|
|
3
|
-
import { createInterface } from 'readline';
|
|
4
|
-
import { saveCredentials } from '../core/credentialStore.js';
|
|
5
|
-
import { readConfig, getSupabaseClient } from '../core/supabaseClient.js';
|
|
6
|
-
|
|
7
|
-
function prompt(rl, question) {
|
|
8
|
-
return new Promise(resolve => rl.question(question, resolve));
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function promptPassword(question) {
|
|
12
|
-
return new Promise(resolve => {
|
|
13
|
-
if (process.stdin.isTTY) {
|
|
14
|
-
process.stdout.write(question);
|
|
15
|
-
process.stdin.setRawMode(true);
|
|
16
|
-
process.stdin.resume();
|
|
17
|
-
process.stdin.setEncoding('utf8');
|
|
18
|
-
let password = '';
|
|
19
|
-
const onData = (ch) => {
|
|
20
|
-
if (ch === '\n' || ch === '\r' || ch === '\u0003') {
|
|
21
|
-
process.stdin.setRawMode(false);
|
|
22
|
-
process.stdin.pause();
|
|
23
|
-
process.stdin.removeListener('data', onData);
|
|
24
|
-
process.stdout.write('\n');
|
|
25
|
-
resolve(password);
|
|
26
|
-
} else if (ch === '\u007F') {
|
|
27
|
-
password = password.slice(0, -1);
|
|
28
|
-
} else {
|
|
29
|
-
password += ch;
|
|
30
|
-
process.stdout.write('*');
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
process.stdin.on('data', onData);
|
|
34
|
-
} else {
|
|
35
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
36
|
-
rl.question(question, (ans) => { rl.close(); resolve(ans); });
|
|
37
|
-
}
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export async function registerCommand(options) {
|
|
42
|
-
console.log(chalk.blue.bold('\nπ ArchSync CLI β Register New Account\n'));
|
|
43
|
-
|
|
44
|
-
const config = readConfig();
|
|
45
|
-
if (!config?.supabase?.url || !config?.supabase?.anonKey) {
|
|
46
|
-
console.log(chalk.red('β No Supabase config found. Run `archsync init` or `archsync connect` first.'));
|
|
47
|
-
process.exit(1);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
let email = options.email;
|
|
51
|
-
let password = options.password;
|
|
52
|
-
|
|
53
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
54
|
-
|
|
55
|
-
try {
|
|
56
|
-
if (!email) {
|
|
57
|
-
email = await prompt(rl, chalk.white('Email: '));
|
|
58
|
-
}
|
|
59
|
-
if (!password) {
|
|
60
|
-
rl.close();
|
|
61
|
-
password = await promptPassword(chalk.white('Password (min 6 chars): '));
|
|
62
|
-
} else {
|
|
63
|
-
rl.close();
|
|
64
|
-
}
|
|
65
|
-
} catch (err) {
|
|
66
|
-
rl.close();
|
|
67
|
-
throw err;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
if (!email || !password) {
|
|
71
|
-
console.log(chalk.red('β Email and password are required.'));
|
|
72
|
-
process.exit(1);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (password.length < 6) {
|
|
76
|
-
console.log(chalk.red('β Password must be at least 6 characters.'));
|
|
77
|
-
process.exit(1);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Basic email validation
|
|
81
|
-
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
82
|
-
console.log(chalk.red('β Invalid email address.'));
|
|
83
|
-
process.exit(1);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const spinner = ora('Creating account...').start();
|
|
87
|
-
|
|
88
|
-
try {
|
|
89
|
-
const client = getSupabaseClient(config);
|
|
90
|
-
const { data, error } = await client.auth.signUp({
|
|
91
|
-
email,
|
|
92
|
-
password,
|
|
93
|
-
options: {
|
|
94
|
-
data: { registered_via: 'archsync-cli' }
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
if (error) {
|
|
99
|
-
spinner.fail('Registration failed');
|
|
100
|
-
console.log(chalk.red(`\n Error: ${error.message}\n`));
|
|
101
|
-
process.exit(1);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const user = data.user;
|
|
105
|
-
const session = data.session;
|
|
106
|
-
|
|
107
|
-
spinner.succeed('Account created!');
|
|
108
|
-
console.log(chalk.gray(`\n User ID: ${user.id}`));
|
|
109
|
-
console.log(chalk.gray(` Email: ${email}`));
|
|
110
|
-
|
|
111
|
-
if (session?.access_token) {
|
|
112
|
-
saveCredentials(session.access_token, user.id, email);
|
|
113
|
-
console.log(chalk.gray(` Token stored in ~/.archsync/credentials.json`));
|
|
114
|
-
console.log(chalk.green('\n You are now logged in.'));
|
|
115
|
-
} else {
|
|
116
|
-
console.log(chalk.yellow('\n Check your email for a confirmation link before logging in.'));
|
|
117
|
-
console.log(chalk.gray(' Then run: archsync login'));
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
console.log(chalk.blue('\nNext: ') + chalk.gray('archsync push -b main'));
|
|
121
|
-
|
|
122
|
-
} catch (err) {
|
|
123
|
-
spinner.fail('Registration failed');
|
|
124
|
-
console.log(chalk.red(`\n Error: ${err.message}\n`));
|
|
125
|
-
process.exit(1);
|
|
126
|
-
}
|
|
127
|
-
}
|