@gitpm/cli 0.1.0 → 0.1.15
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/dist/index.js +2149 -467
- package/package.json +61 -8
- package/src/__tests__/import.test.ts +0 -220
- package/src/__tests__/init.test.ts +0 -112
- package/src/__tests__/pull.test.ts +0 -225
- package/src/__tests__/push.test.ts +0 -242
- package/src/__tests__/sync.test.ts +0 -264
- package/src/__tests__/validate.test.ts +0 -178
- package/src/commands/import.ts +0 -184
- package/src/commands/init.ts +0 -62
- package/src/commands/pull.ts +0 -145
- package/src/commands/push.ts +0 -224
- package/src/commands/sync.ts +0 -238
- package/src/commands/validate.ts +0 -79
- package/src/index.ts +0 -33
- package/src/utils/auth.ts +0 -28
- package/src/utils/config.ts +0 -5
- package/src/utils/conflict-ui.ts +0 -45
- package/src/utils/output.ts +0 -23
- package/tsconfig.json +0 -8
- package/tsup.config.ts +0 -11
package/src/commands/import.ts
DELETED
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
import { importFromGitHub } from '@gitpm/sync-github';
|
|
2
|
-
import type { LinkStrategy } from '@gitpm/sync-github';
|
|
3
|
-
import { importFromGitLab } from '@gitpm/sync-gitlab';
|
|
4
|
-
import type { LinkStrategy as GitLabLinkStrategy } from '@gitpm/sync-gitlab';
|
|
5
|
-
import chalk from 'chalk';
|
|
6
|
-
import { Command } from 'commander';
|
|
7
|
-
import ora from 'ora';
|
|
8
|
-
import { resolveToken } from '../utils/auth.js';
|
|
9
|
-
import { resolveMetaDir } from '../utils/config.js';
|
|
10
|
-
import { printError, printSuccess } from '../utils/output.js';
|
|
11
|
-
|
|
12
|
-
const VALID_LINK_STRATEGIES = [
|
|
13
|
-
'body-refs',
|
|
14
|
-
'sub-issues',
|
|
15
|
-
'milestone',
|
|
16
|
-
'labels',
|
|
17
|
-
'all',
|
|
18
|
-
] as const;
|
|
19
|
-
|
|
20
|
-
const VALID_GITLAB_LINK_STRATEGIES = [
|
|
21
|
-
'body-refs',
|
|
22
|
-
'native-epics',
|
|
23
|
-
'milestone',
|
|
24
|
-
'labels',
|
|
25
|
-
'all',
|
|
26
|
-
] as const;
|
|
27
|
-
|
|
28
|
-
export const importCommand = new Command('import')
|
|
29
|
-
.description('Import project data from GitHub or GitLab into .meta/')
|
|
30
|
-
.option(
|
|
31
|
-
'--source <source>',
|
|
32
|
-
'Source platform: github or gitlab (default: github)',
|
|
33
|
-
'github',
|
|
34
|
-
)
|
|
35
|
-
.option('--repo <owner/repo>', 'GitHub repository (owner/repo)')
|
|
36
|
-
.option(
|
|
37
|
-
'--project <value>',
|
|
38
|
-
'GitHub Project number or GitLab project path (namespace/project)',
|
|
39
|
-
)
|
|
40
|
-
.option('--token <token>', 'Personal access token')
|
|
41
|
-
.option('--base-url <url>', 'GitLab base URL (default: https://gitlab.com)')
|
|
42
|
-
.option(
|
|
43
|
-
'--link-strategy <strategy>',
|
|
44
|
-
'Epic-story linkage strategy (default: all)',
|
|
45
|
-
)
|
|
46
|
-
.action(async (opts, cmd) => {
|
|
47
|
-
const metaDir = resolveMetaDir(cmd.optsWithGlobals().metaDir);
|
|
48
|
-
const source: string = opts.source;
|
|
49
|
-
|
|
50
|
-
if (source === 'gitlab') {
|
|
51
|
-
await importGitLab(opts, metaDir);
|
|
52
|
-
} else if (source === 'github') {
|
|
53
|
-
await importGitHub(opts, metaDir);
|
|
54
|
-
} else {
|
|
55
|
-
printError(`Unknown source "${source}". Use "github" or "gitlab".`);
|
|
56
|
-
process.exit(1);
|
|
57
|
-
}
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
async function importGitHub(
|
|
61
|
-
opts: Record<string, string | undefined>,
|
|
62
|
-
metaDir: string,
|
|
63
|
-
): Promise<void> {
|
|
64
|
-
const repo = opts.repo;
|
|
65
|
-
if (!repo || !repo.includes('/') || repo.split('/').length !== 2) {
|
|
66
|
-
printError('--repo is required for GitHub import. Expected "owner/repo".');
|
|
67
|
-
process.exit(1);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
let token: string;
|
|
71
|
-
try {
|
|
72
|
-
token = await resolveToken(opts.token);
|
|
73
|
-
} catch (err) {
|
|
74
|
-
printError(err instanceof Error ? err.message : 'Failed to resolve token.');
|
|
75
|
-
process.exit(1);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const linkStrategy: LinkStrategy | undefined = opts.linkStrategy as
|
|
79
|
-
| LinkStrategy
|
|
80
|
-
| undefined;
|
|
81
|
-
if (
|
|
82
|
-
linkStrategy &&
|
|
83
|
-
!VALID_LINK_STRATEGIES.includes(
|
|
84
|
-
linkStrategy as (typeof VALID_LINK_STRATEGIES)[number],
|
|
85
|
-
)
|
|
86
|
-
) {
|
|
87
|
-
printError(
|
|
88
|
-
`Invalid link strategy "${linkStrategy}". Must be one of: ${VALID_LINK_STRATEGIES.join(', ')}`,
|
|
89
|
-
);
|
|
90
|
-
process.exit(1);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const spinner = ora('Importing from GitHub...').start();
|
|
94
|
-
|
|
95
|
-
const result = await importFromGitHub({
|
|
96
|
-
token,
|
|
97
|
-
repo,
|
|
98
|
-
projectNumber: opts.project ? Number.parseInt(opts.project, 10) : undefined,
|
|
99
|
-
metaDir,
|
|
100
|
-
linkStrategy,
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
if (!result.ok) {
|
|
104
|
-
spinner.fail('Import failed.');
|
|
105
|
-
printError(result.error.message);
|
|
106
|
-
process.exit(1);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
spinner.succeed('Import complete.');
|
|
110
|
-
printImportSummary(result.value);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
async function importGitLab(
|
|
114
|
-
opts: Record<string, string | undefined>,
|
|
115
|
-
metaDir: string,
|
|
116
|
-
): Promise<void> {
|
|
117
|
-
const project = opts.project ?? opts.repo;
|
|
118
|
-
if (!project) {
|
|
119
|
-
printError('--project (namespace/project) is required for GitLab import.');
|
|
120
|
-
process.exit(1);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const token = opts.token ?? process.env.GITLAB_TOKEN;
|
|
124
|
-
if (!token) {
|
|
125
|
-
printError(
|
|
126
|
-
'No GitLab token found. Provide via --token flag or GITLAB_TOKEN env var.',
|
|
127
|
-
);
|
|
128
|
-
process.exit(1);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const linkStrategy: GitLabLinkStrategy | undefined = opts.linkStrategy as
|
|
132
|
-
| GitLabLinkStrategy
|
|
133
|
-
| undefined;
|
|
134
|
-
if (
|
|
135
|
-
linkStrategy &&
|
|
136
|
-
!VALID_GITLAB_LINK_STRATEGIES.includes(
|
|
137
|
-
linkStrategy as (typeof VALID_GITLAB_LINK_STRATEGIES)[number],
|
|
138
|
-
)
|
|
139
|
-
) {
|
|
140
|
-
printError(
|
|
141
|
-
`Invalid link strategy "${linkStrategy}". Must be one of: ${VALID_GITLAB_LINK_STRATEGIES.join(', ')}`,
|
|
142
|
-
);
|
|
143
|
-
process.exit(1);
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const spinner = ora('Importing from GitLab...').start();
|
|
147
|
-
|
|
148
|
-
const result = await importFromGitLab({
|
|
149
|
-
token,
|
|
150
|
-
project,
|
|
151
|
-
baseUrl: opts.baseUrl,
|
|
152
|
-
metaDir,
|
|
153
|
-
linkStrategy,
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
if (!result.ok) {
|
|
157
|
-
spinner.fail('Import failed.');
|
|
158
|
-
printError(result.error.message);
|
|
159
|
-
process.exit(1);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
spinner.succeed('Import complete.');
|
|
163
|
-
printImportSummary(result.value);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function printImportSummary(value: {
|
|
167
|
-
milestones: number;
|
|
168
|
-
epics: number;
|
|
169
|
-
stories: number;
|
|
170
|
-
totalFiles: number;
|
|
171
|
-
}): void {
|
|
172
|
-
const { milestones, epics, stories, totalFiles } = value;
|
|
173
|
-
console.log();
|
|
174
|
-
console.log(chalk.bold('Summary:'));
|
|
175
|
-
console.log(` Milestones: ${milestones}`);
|
|
176
|
-
console.log(` Epics: ${epics}`);
|
|
177
|
-
console.log(` Stories: ${stories}`);
|
|
178
|
-
console.log(` Files: ${totalFiles}`);
|
|
179
|
-
console.log();
|
|
180
|
-
printSuccess(
|
|
181
|
-
`Imported ${milestones + epics + stories} entities (${totalFiles} files).`,
|
|
182
|
-
);
|
|
183
|
-
console.log(chalk.dim('Run `gitpm validate` to verify the imported tree.'));
|
|
184
|
-
}
|
package/src/commands/init.ts
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { readdir } from 'node:fs/promises';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { scaffoldMeta } from '@gitpm/core';
|
|
4
|
-
import { input } from '@inquirer/prompts';
|
|
5
|
-
import chalk from 'chalk';
|
|
6
|
-
import { Command } from 'commander';
|
|
7
|
-
import { resolveMetaDir } from '../utils/config.js';
|
|
8
|
-
import { printError, printSuccess } from '../utils/output.js';
|
|
9
|
-
|
|
10
|
-
async function collectFiles(dir: string, prefix = ''): Promise<string[]> {
|
|
11
|
-
const entries = await readdir(dir, { withFileTypes: true });
|
|
12
|
-
const results: string[] = [];
|
|
13
|
-
for (const entry of entries) {
|
|
14
|
-
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
15
|
-
if (entry.isDirectory()) {
|
|
16
|
-
results.push(rel);
|
|
17
|
-
const children = await collectFiles(join(dir, entry.name), rel);
|
|
18
|
-
results.push(...children);
|
|
19
|
-
} else {
|
|
20
|
-
results.push(rel);
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return results;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export const initCommand = new Command('init')
|
|
27
|
-
.description('Initialize a new .meta/ project structure')
|
|
28
|
-
.argument('[project-name]', 'Name of the project')
|
|
29
|
-
.action(async (nameArg: string | undefined, _opts, cmd) => {
|
|
30
|
-
const metaDir = resolveMetaDir(cmd.optsWithGlobals().metaDir);
|
|
31
|
-
|
|
32
|
-
let projectName = nameArg;
|
|
33
|
-
if (!projectName) {
|
|
34
|
-
projectName = await input({
|
|
35
|
-
message: 'Project name:',
|
|
36
|
-
required: true,
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const result = await scaffoldMeta(metaDir, projectName);
|
|
41
|
-
|
|
42
|
-
if (!result.ok) {
|
|
43
|
-
printError(result.error.message);
|
|
44
|
-
process.exit(1);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// Print created file tree
|
|
48
|
-
const files = await collectFiles(metaDir);
|
|
49
|
-
console.log();
|
|
50
|
-
console.log(chalk.bold('.meta/'));
|
|
51
|
-
for (const file of files) {
|
|
52
|
-
const parts = file.split('/');
|
|
53
|
-
const indent = ' '.repeat(parts.length);
|
|
54
|
-
const name = parts[parts.length - 1];
|
|
55
|
-
const isDir = !name.includes('.');
|
|
56
|
-
console.log(`${indent}${isDir ? chalk.blue(`${name}/`) : name}`);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const fileCount = files.filter((f) => f.includes('.')).length;
|
|
60
|
-
console.log();
|
|
61
|
-
printSuccess(`Created ${fileCount} files in .meta/`);
|
|
62
|
-
});
|
package/src/commands/pull.ts
DELETED
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
loadConfig as loadGitHubConfig,
|
|
3
|
-
syncWithGitHub,
|
|
4
|
-
} from '@gitpm/sync-github';
|
|
5
|
-
import type { ConflictStrategy } from '@gitpm/sync-github';
|
|
6
|
-
import {
|
|
7
|
-
loadConfig as loadGitLabConfig,
|
|
8
|
-
syncWithGitLab,
|
|
9
|
-
} from '@gitpm/sync-gitlab';
|
|
10
|
-
import chalk from 'chalk';
|
|
11
|
-
import { Command } from 'commander';
|
|
12
|
-
import ora from 'ora';
|
|
13
|
-
import { resolveToken } from '../utils/auth.js';
|
|
14
|
-
import { resolveMetaDir } from '../utils/config.js';
|
|
15
|
-
import { promptConflictResolution } from '../utils/conflict-ui.js';
|
|
16
|
-
import { printError, printSuccess } from '../utils/output.js';
|
|
17
|
-
|
|
18
|
-
export const pullCommand = new Command('pull')
|
|
19
|
-
.description('Pull changes from GitHub or GitLab into local .meta/')
|
|
20
|
-
.option('--token <token>', 'Personal access token')
|
|
21
|
-
.option(
|
|
22
|
-
'--strategy <strategy>',
|
|
23
|
-
'Conflict resolution strategy (local-wins, remote-wins, ask)',
|
|
24
|
-
'remote-wins',
|
|
25
|
-
)
|
|
26
|
-
.action(async (opts, cmd) => {
|
|
27
|
-
const metaDir = resolveMetaDir(cmd.optsWithGlobals().metaDir);
|
|
28
|
-
const strategy = opts.strategy as ConflictStrategy;
|
|
29
|
-
|
|
30
|
-
// Detect platform
|
|
31
|
-
const ghConfig = await loadGitHubConfig(metaDir);
|
|
32
|
-
const glConfig = await loadGitLabConfig(metaDir);
|
|
33
|
-
|
|
34
|
-
if (glConfig.ok) {
|
|
35
|
-
await pullFromGitLab(opts, metaDir, glConfig.value, strategy);
|
|
36
|
-
} else if (ghConfig.ok) {
|
|
37
|
-
await pullFromGitHub(opts, metaDir, ghConfig.value.repo, strategy);
|
|
38
|
-
} else {
|
|
39
|
-
printError(
|
|
40
|
-
'No sync config found. Run `gitpm import` first to set up sync.',
|
|
41
|
-
);
|
|
42
|
-
process.exit(1);
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
async function pullFromGitHub(
|
|
47
|
-
opts: Record<string, string | undefined>,
|
|
48
|
-
metaDir: string,
|
|
49
|
-
repo: string,
|
|
50
|
-
strategy: ConflictStrategy,
|
|
51
|
-
): Promise<void> {
|
|
52
|
-
let token: string;
|
|
53
|
-
try {
|
|
54
|
-
token = await resolveToken(opts.token);
|
|
55
|
-
} catch (err) {
|
|
56
|
-
printError(err instanceof Error ? err.message : 'Failed to resolve token.');
|
|
57
|
-
process.exit(1);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const spinner = ora('Pulling from GitHub...').start();
|
|
61
|
-
const result = await syncWithGitHub({
|
|
62
|
-
token,
|
|
63
|
-
repo,
|
|
64
|
-
metaDir,
|
|
65
|
-
strategy: strategy === 'ask' ? 'ask' : strategy,
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
if (!result.ok) {
|
|
69
|
-
spinner.fail('Pull failed.');
|
|
70
|
-
printError(result.error.message);
|
|
71
|
-
process.exit(1);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
spinner.succeed('Pull complete.');
|
|
75
|
-
await handleConflictsAndPrint(result.value, strategy);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
async function pullFromGitLab(
|
|
79
|
-
opts: Record<string, string | undefined>,
|
|
80
|
-
metaDir: string,
|
|
81
|
-
config: { project: string; project_id: number; base_url: string },
|
|
82
|
-
strategy: ConflictStrategy,
|
|
83
|
-
): Promise<void> {
|
|
84
|
-
const token = opts.token ?? process.env.GITLAB_TOKEN;
|
|
85
|
-
if (!token) {
|
|
86
|
-
printError(
|
|
87
|
-
'No GitLab token found. Provide via --token flag or GITLAB_TOKEN env var.',
|
|
88
|
-
);
|
|
89
|
-
process.exit(1);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const spinner = ora('Pulling from GitLab...').start();
|
|
93
|
-
const result = await syncWithGitLab({
|
|
94
|
-
token,
|
|
95
|
-
project: config.project,
|
|
96
|
-
projectId: config.project_id,
|
|
97
|
-
baseUrl: config.base_url,
|
|
98
|
-
metaDir,
|
|
99
|
-
strategy: strategy === 'ask' ? 'ask' : strategy,
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
if (!result.ok) {
|
|
103
|
-
spinner.fail('Pull failed.');
|
|
104
|
-
printError(result.error.message);
|
|
105
|
-
process.exit(1);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
spinner.succeed('Pull complete.');
|
|
109
|
-
await handleConflictsAndPrint(result.value, strategy);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
async function handleConflictsAndPrint(
|
|
113
|
-
result: {
|
|
114
|
-
pulled: { milestones: number; issues: number };
|
|
115
|
-
conflicts: unknown[];
|
|
116
|
-
resolved: number;
|
|
117
|
-
skipped: number;
|
|
118
|
-
},
|
|
119
|
-
strategy: ConflictStrategy,
|
|
120
|
-
): Promise<void> {
|
|
121
|
-
const { pulled, conflicts, resolved, skipped } = result;
|
|
122
|
-
|
|
123
|
-
if (strategy === 'ask' && conflicts.length > 0) {
|
|
124
|
-
console.log();
|
|
125
|
-
console.log(
|
|
126
|
-
chalk.yellow(`${conflicts.length} conflict(s) require resolution:`),
|
|
127
|
-
);
|
|
128
|
-
const resolutions = await promptConflictResolution(
|
|
129
|
-
conflicts as Parameters<typeof promptConflictResolution>[0],
|
|
130
|
-
);
|
|
131
|
-
console.log(
|
|
132
|
-
chalk.dim(
|
|
133
|
-
`Resolved ${resolutions.length}, skipped ${conflicts.length - resolutions.length}`,
|
|
134
|
-
),
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
console.log();
|
|
139
|
-
console.log(chalk.bold('Pull Summary:'));
|
|
140
|
-
console.log(` Pulled: ${pulled.milestones + pulled.issues} changes`);
|
|
141
|
-
console.log(` Conflicts resolved: ${resolved}`);
|
|
142
|
-
console.log(` Skipped: ${skipped}`);
|
|
143
|
-
console.log();
|
|
144
|
-
printSuccess('Pull complete.');
|
|
145
|
-
}
|
package/src/commands/push.ts
DELETED
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
exportToGitHub,
|
|
3
|
-
loadConfig as loadGitHubConfig,
|
|
4
|
-
} from '@gitpm/sync-github';
|
|
5
|
-
import {
|
|
6
|
-
exportToGitLab,
|
|
7
|
-
loadConfig as loadGitLabConfig,
|
|
8
|
-
} from '@gitpm/sync-gitlab';
|
|
9
|
-
import { confirm } from '@inquirer/prompts';
|
|
10
|
-
import chalk from 'chalk';
|
|
11
|
-
import { Command } from 'commander';
|
|
12
|
-
import ora from 'ora';
|
|
13
|
-
import { resolveToken } from '../utils/auth.js';
|
|
14
|
-
import { resolveMetaDir } from '../utils/config.js';
|
|
15
|
-
import { printError, printSuccess } from '../utils/output.js';
|
|
16
|
-
|
|
17
|
-
export const pushCommand = new Command('push')
|
|
18
|
-
.description('Push local .meta/ changes to GitHub or GitLab')
|
|
19
|
-
.option('--token <token>', 'Personal access token')
|
|
20
|
-
.option('--dry-run', 'Preview changes without pushing')
|
|
21
|
-
.option('--yes', 'Skip confirmation prompt')
|
|
22
|
-
.action(async (opts, cmd) => {
|
|
23
|
-
const metaDir = resolveMetaDir(cmd.optsWithGlobals().metaDir);
|
|
24
|
-
|
|
25
|
-
// Detect which platform config exists
|
|
26
|
-
const ghConfig = await loadGitHubConfig(metaDir);
|
|
27
|
-
const glConfig = await loadGitLabConfig(metaDir);
|
|
28
|
-
|
|
29
|
-
if (glConfig.ok) {
|
|
30
|
-
await pushToGitLab(opts, metaDir, glConfig.value);
|
|
31
|
-
} else if (ghConfig.ok) {
|
|
32
|
-
await pushToGitHub(opts, metaDir, ghConfig.value.repo);
|
|
33
|
-
} else {
|
|
34
|
-
printError(
|
|
35
|
-
'No sync config found. Run `gitpm import` first to set up sync.',
|
|
36
|
-
);
|
|
37
|
-
process.exit(1);
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
async function pushToGitHub(
|
|
42
|
-
opts: Record<string, string | boolean | undefined>,
|
|
43
|
-
metaDir: string,
|
|
44
|
-
repo: string,
|
|
45
|
-
): Promise<void> {
|
|
46
|
-
let token: string;
|
|
47
|
-
try {
|
|
48
|
-
token = await resolveToken(opts.token as string | undefined);
|
|
49
|
-
} catch (err) {
|
|
50
|
-
printError(err instanceof Error ? err.message : 'Failed to resolve token.');
|
|
51
|
-
process.exit(1);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (opts.dryRun) {
|
|
55
|
-
const spinner = ora('Calculating changes...').start();
|
|
56
|
-
const result = await exportToGitHub({ token, repo, metaDir, dryRun: true });
|
|
57
|
-
|
|
58
|
-
if (!result.ok) {
|
|
59
|
-
spinner.fail('Dry run failed.');
|
|
60
|
-
printError(result.error.message);
|
|
61
|
-
process.exit(1);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
spinner.succeed('Dry run complete.');
|
|
65
|
-
printPreview(result.value);
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const previewSpinner = ora('Calculating changes...').start();
|
|
70
|
-
const preview = await exportToGitHub({
|
|
71
|
-
token,
|
|
72
|
-
repo,
|
|
73
|
-
metaDir,
|
|
74
|
-
dryRun: true,
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
if (!preview.ok) {
|
|
78
|
-
previewSpinner.fail('Failed to calculate changes.');
|
|
79
|
-
printError(preview.error.message);
|
|
80
|
-
process.exit(1);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
previewSpinner.succeed('Changes calculated.');
|
|
84
|
-
printPreview(preview.value);
|
|
85
|
-
|
|
86
|
-
if (preview.value.totalChanges === 0) {
|
|
87
|
-
printSuccess('Nothing to push — already in sync.');
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (!opts.yes) {
|
|
92
|
-
const confirmed = await confirm({
|
|
93
|
-
message: 'Push these changes to GitHub?',
|
|
94
|
-
default: false,
|
|
95
|
-
});
|
|
96
|
-
if (!confirmed) {
|
|
97
|
-
console.log(chalk.dim('Push cancelled.'));
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const pushSpinner = ora('Pushing to GitHub...').start();
|
|
103
|
-
const result = await exportToGitHub({ token, repo, metaDir, dryRun: false });
|
|
104
|
-
|
|
105
|
-
if (!result.ok) {
|
|
106
|
-
pushSpinner.fail('Push failed.');
|
|
107
|
-
printError(result.error.message);
|
|
108
|
-
process.exit(1);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
pushSpinner.succeed('Push complete.');
|
|
112
|
-
const { created, updated } = result.value;
|
|
113
|
-
console.log(
|
|
114
|
-
` Created ${created.milestones + created.issues}, Updated ${updated.milestones + updated.issues}`,
|
|
115
|
-
);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
async function pushToGitLab(
|
|
119
|
-
opts: Record<string, string | boolean | undefined>,
|
|
120
|
-
metaDir: string,
|
|
121
|
-
config: { project: string; project_id: number; base_url: string },
|
|
122
|
-
): Promise<void> {
|
|
123
|
-
const token = (opts.token as string | undefined) ?? process.env.GITLAB_TOKEN;
|
|
124
|
-
if (!token) {
|
|
125
|
-
printError(
|
|
126
|
-
'No GitLab token found. Provide via --token flag or GITLAB_TOKEN env var.',
|
|
127
|
-
);
|
|
128
|
-
process.exit(1);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (opts.dryRun) {
|
|
132
|
-
const spinner = ora('Calculating changes...').start();
|
|
133
|
-
const result = await exportToGitLab({
|
|
134
|
-
token,
|
|
135
|
-
project: config.project,
|
|
136
|
-
projectId: config.project_id,
|
|
137
|
-
baseUrl: config.base_url,
|
|
138
|
-
metaDir,
|
|
139
|
-
dryRun: true,
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
if (!result.ok) {
|
|
143
|
-
spinner.fail('Dry run failed.');
|
|
144
|
-
printError(result.error.message);
|
|
145
|
-
process.exit(1);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
spinner.succeed('Dry run complete.');
|
|
149
|
-
printPreview(result.value);
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
const previewSpinner = ora('Calculating changes...').start();
|
|
154
|
-
const preview = await exportToGitLab({
|
|
155
|
-
token,
|
|
156
|
-
project: config.project,
|
|
157
|
-
projectId: config.project_id,
|
|
158
|
-
baseUrl: config.base_url,
|
|
159
|
-
metaDir,
|
|
160
|
-
dryRun: true,
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
if (!preview.ok) {
|
|
164
|
-
previewSpinner.fail('Failed to calculate changes.');
|
|
165
|
-
printError(preview.error.message);
|
|
166
|
-
process.exit(1);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
previewSpinner.succeed('Changes calculated.');
|
|
170
|
-
printPreview(preview.value);
|
|
171
|
-
|
|
172
|
-
if (preview.value.totalChanges === 0) {
|
|
173
|
-
printSuccess('Nothing to push — already in sync.');
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (!opts.yes) {
|
|
178
|
-
const confirmed = await confirm({
|
|
179
|
-
message: 'Push these changes to GitLab?',
|
|
180
|
-
default: false,
|
|
181
|
-
});
|
|
182
|
-
if (!confirmed) {
|
|
183
|
-
console.log(chalk.dim('Push cancelled.'));
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
const pushSpinner = ora('Pushing to GitLab...').start();
|
|
189
|
-
const result = await exportToGitLab({
|
|
190
|
-
token,
|
|
191
|
-
project: config.project,
|
|
192
|
-
projectId: config.project_id,
|
|
193
|
-
baseUrl: config.base_url,
|
|
194
|
-
metaDir,
|
|
195
|
-
dryRun: false,
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
if (!result.ok) {
|
|
199
|
-
pushSpinner.fail('Push failed.');
|
|
200
|
-
printError(result.error.message);
|
|
201
|
-
process.exit(1);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
pushSpinner.succeed('Push complete.');
|
|
205
|
-
const { created, updated } = result.value;
|
|
206
|
-
console.log(
|
|
207
|
-
` Created ${created.milestones + created.issues}, Updated ${updated.milestones + updated.issues}`,
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function printPreview(result: {
|
|
212
|
-
created: { milestones: number; issues: number };
|
|
213
|
-
updated: { milestones: number; issues: number };
|
|
214
|
-
totalChanges: number;
|
|
215
|
-
}): void {
|
|
216
|
-
console.log();
|
|
217
|
-
console.log(chalk.bold('Changes to push:'));
|
|
218
|
-
console.log(` New milestones: ${result.created.milestones}`);
|
|
219
|
-
console.log(` New issues: ${result.created.issues}`);
|
|
220
|
-
console.log(` Updated milestones: ${result.updated.milestones}`);
|
|
221
|
-
console.log(` Updated issues: ${result.updated.issues}`);
|
|
222
|
-
console.log(` Total changes: ${result.totalChanges}`);
|
|
223
|
-
console.log();
|
|
224
|
-
}
|