czon 0.8.0 → 0.8.2

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.
@@ -35,44 +35,328 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.ConfigGithubCommand = void 0;
37
37
  const clipanion_1 = require("clipanion");
38
+ const prompts_1 = require("@inquirer/prompts");
38
39
  const fs = __importStar(require("fs/promises"));
39
40
  const path = __importStar(require("path"));
40
41
  const writeFile_1 = require("../utils/writeFile");
42
+ const git = __importStar(require("../utils/git"));
43
+ const github = __importStar(require("../utils/github"));
41
44
  class ConfigGithubCommand extends clipanion_1.Command {
42
45
  async execute() {
46
+ const stdout = this.context.stdout;
47
+ const stderr = this.context.stderr;
48
+ // ========== Step 0: Prerequisites check ==========
49
+ stdout.write('Checking prerequisites...\n');
50
+ // Check gh CLI installation
51
+ if (!github.isGhInstalled()) {
52
+ stderr.write('\nGitHub CLI (gh) is not installed.\n' +
53
+ 'Please install it from: https://cli.github.com/\n');
54
+ return 1;
55
+ }
56
+ // Check gh authentication
57
+ if (!github.isGhAuthenticated()) {
58
+ stderr.write('\nGitHub CLI is not authenticated.\n' + 'Please run: gh auth login\n');
59
+ return 1;
60
+ }
61
+ // Check required token scopes
62
+ const requiredScopes = ['repo'];
63
+ const { hasScopes, missingScopes, currentScopes } = github.checkTokenScopes(requiredScopes);
64
+ if (!hasScopes) {
65
+ stderr.write('\nGitHub CLI token is missing required permissions.\n' +
66
+ ` Current scopes: ${currentScopes.length > 0 ? currentScopes.join(', ') : '(none detected)'}\n` +
67
+ ` Missing scopes: ${missingScopes.join(', ')}\n\n` +
68
+ 'Please re-authenticate with the required scopes:\n' +
69
+ ' gh auth login --scopes repo\n\n' +
70
+ 'Or refresh your existing token:\n' +
71
+ ' gh auth refresh --scopes repo\n');
72
+ return 1;
73
+ }
74
+ stdout.write('GitHub CLI detected and authenticated.\n\n');
75
+ // ========== Step 1: Git repository check ==========
76
+ if (!git.isGitRepository()) {
77
+ const shouldInit = await (0, prompts_1.confirm)({
78
+ message: 'Current directory is not a Git repository. Initialize one?',
79
+ default: true,
80
+ });
81
+ if (!shouldInit) {
82
+ stdout.write('Aborted.\n');
83
+ return 0;
84
+ }
85
+ git.initGitRepository();
86
+ stdout.write('Git repository initialized.\n\n');
87
+ }
88
+ // ========== Step 2: Remote repository configuration ==========
89
+ const remotes = git.getRemotes();
90
+ const githubRemotes = remotes.filter(r => github.isGitHubUrl(r.fetchUrl));
91
+ let selectedRemote;
92
+ if (githubRemotes.length === 0) {
93
+ // No GitHub remote found
94
+ stdout.write('No GitHub remote repository found.\n');
95
+ const action = await (0, prompts_1.select)({
96
+ message: 'What would you like to do?',
97
+ choices: [
98
+ { name: 'Create a new GitHub repository', value: 'create' },
99
+ {
100
+ name: 'Add an existing GitHub repository URL',
101
+ value: 'add',
102
+ },
103
+ ],
104
+ });
105
+ if (action === 'create') {
106
+ // Create new repository
107
+ const repoName = await (0, prompts_1.input)({
108
+ message: 'Repository name:',
109
+ default: path.basename(process.cwd()),
110
+ });
111
+ const isPrivate = await (0, prompts_1.confirm)({
112
+ message: 'Make repository private?',
113
+ default: false,
114
+ });
115
+ stdout.write('Creating GitHub repository...\n');
116
+ const { owner, repo, url } = github.createRepository({
117
+ name: repoName,
118
+ private: isPrivate,
119
+ });
120
+ // Determine remote name
121
+ let remoteName = 'origin';
122
+ if (git.remoteExists('origin')) {
123
+ remoteName = await (0, prompts_1.input)({
124
+ message: 'Remote "origin" already exists. Enter a name for the GitHub remote:',
125
+ default: 'github',
126
+ });
127
+ }
128
+ git.addRemote(remoteName, url);
129
+ selectedRemote = { name: remoteName, owner, repo };
130
+ stdout.write(`Repository created: https://github.com/${owner}/${repo}\n`);
131
+ stdout.write(`Added as remote "${remoteName}".\n\n`);
132
+ }
133
+ else {
134
+ // Add existing repository
135
+ const repoUrl = await (0, prompts_1.input)({
136
+ message: 'GitHub repository URL:',
137
+ validate: value => {
138
+ if (!github.isGitHubUrl(value)) {
139
+ return 'Please enter a valid GitHub repository URL';
140
+ }
141
+ return true;
142
+ },
143
+ });
144
+ const parsed = github.parseGitHubUrl(repoUrl);
145
+ // Determine remote name
146
+ let remoteName = 'origin';
147
+ if (git.remoteExists('origin')) {
148
+ remoteName = await (0, prompts_1.input)({
149
+ message: 'Remote "origin" already exists. Enter a name for the GitHub remote:',
150
+ default: 'github',
151
+ });
152
+ }
153
+ git.addRemote(remoteName, repoUrl);
154
+ selectedRemote = {
155
+ name: remoteName,
156
+ owner: parsed.owner,
157
+ repo: parsed.repo,
158
+ };
159
+ stdout.write(`Remote "${remoteName}" added.\n\n`);
160
+ }
161
+ }
162
+ else if (githubRemotes.length === 1) {
163
+ // Single GitHub remote
164
+ const remote = githubRemotes[0];
165
+ const parsed = github.parseGitHubUrl(remote.fetchUrl);
166
+ selectedRemote = {
167
+ name: remote.name,
168
+ owner: parsed.owner,
169
+ repo: parsed.repo,
170
+ };
171
+ stdout.write(`Using GitHub remote "${remote.name}" (${parsed.owner}/${parsed.repo}).\n\n`);
172
+ }
173
+ else {
174
+ // Multiple GitHub remotes - ask user to choose
175
+ const choices = githubRemotes.map(r => {
176
+ const parsed = github.parseGitHubUrl(r.fetchUrl);
177
+ return {
178
+ name: `${r.name} (${parsed.owner}/${parsed.repo})`,
179
+ value: r.name,
180
+ };
181
+ });
182
+ const selectedName = await (0, prompts_1.select)({
183
+ message: 'Multiple GitHub remotes found. Which one to configure?',
184
+ choices,
185
+ });
186
+ const remote = githubRemotes.find(r => r.name === selectedName);
187
+ const parsed = github.parseGitHubUrl(remote.fetchUrl);
188
+ selectedRemote = {
189
+ name: remote.name,
190
+ owner: parsed.owner,
191
+ repo: parsed.repo,
192
+ };
193
+ stdout.write('\n');
194
+ }
195
+ stdout.write(`Configuring GitHub Pages for ${selectedRemote.owner}/${selectedRemote.repo}...\n`);
196
+ // ========== Step 3: GitHub Pages configuration ==========
197
+ let pagesInfo;
43
198
  try {
44
- const targetDir = process.cwd();
45
- const templatePath = path.join(__dirname, '..', '..', 'templates', 'pages.yml');
46
- const targetPath = path.join(targetDir, '.github', 'workflows', 'pages.yml');
47
- // 检查模板文件是否存在
199
+ pagesInfo = github.getPagesInfo(selectedRemote.owner, selectedRemote.repo);
200
+ }
201
+ catch (error) {
202
+ stderr.write(`\nFailed to get GitHub Pages info: ${error}\n` +
203
+ 'You may need to enable Pages manually in repository settings.\n');
204
+ pagesInfo = { enabled: false };
205
+ }
206
+ if (!pagesInfo.enabled || pagesInfo.buildType !== 'workflow') {
207
+ stdout.write('Enabling GitHub Pages with workflow deployment...\n');
48
208
  try {
49
- await fs.access(templatePath);
209
+ github.enablePages(selectedRemote.owner, selectedRemote.repo);
210
+ stdout.write('GitHub Pages enabled.\n');
50
211
  }
51
- catch {
52
- this.context.stderr.write(`Template file not found: ${templatePath}\n`);
53
- return 1;
212
+ catch (error) {
213
+ stderr.write(`\nFailed to enable GitHub Pages: ${error}\n` +
214
+ 'You may need to enable Pages manually:\n' +
215
+ ` https://github.com/${selectedRemote.owner}/${selectedRemote.repo}/settings/pages\n` +
216
+ ' Set "Build and deployment" source to "GitHub Actions"\n\n');
54
217
  }
55
- // 读取模板文件
56
- const content = await fs.readFile(templatePath, 'utf-8');
57
- // 确保目标目录存在并写入文件
58
- await (0, writeFile_1.writeFile)(targetPath, content);
59
- this.context.stdout.write(`GitHub Actions workflow copied to ${targetPath}\n`);
60
- return 0;
61
218
  }
62
- catch (error) {
63
- this.context.stderr.write(`Failed to copy workflow template: ${error}\n`);
219
+ else {
220
+ stdout.write('GitHub Pages is already configured for workflow deployment.\n');
221
+ }
222
+ // CNAME configuration
223
+ const configureCname = await (0, prompts_1.confirm)({
224
+ message: 'Do you want to configure a custom domain (CNAME)?',
225
+ default: false,
226
+ });
227
+ if (configureCname) {
228
+ const cname = await (0, prompts_1.input)({
229
+ message: 'Enter your custom domain (e.g., docs.example.com):',
230
+ validate: value => {
231
+ if (!value || !/^[a-zA-Z0-9][a-zA-Z0-9-_.]*[a-zA-Z0-9]$/.test(value)) {
232
+ return 'Please enter a valid domain name';
233
+ }
234
+ return true;
235
+ },
236
+ });
237
+ try {
238
+ github.setPagesCname(selectedRemote.owner, selectedRemote.repo, cname);
239
+ stdout.write(`Custom domain set to: ${cname}\n`);
240
+ }
241
+ catch (error) {
242
+ stderr.write(`\nFailed to set custom domain: ${error}\n` +
243
+ 'You can set it manually in repository settings.\n');
244
+ }
245
+ }
246
+ stdout.write('\n');
247
+ // ========== Step 4: Workflow file management ==========
248
+ const workflowRelativePath = '.github/workflows/pages.yml';
249
+ const workflowPath = path.join(process.cwd(), workflowRelativePath);
250
+ const templatePath = path.join(__dirname, '..', '..', 'templates', 'pages.yml');
251
+ let templateContent;
252
+ try {
253
+ templateContent = await fs.readFile(templatePath, 'utf-8');
254
+ }
255
+ catch {
256
+ stderr.write(`Template file not found: ${templatePath}\n`);
64
257
  return 1;
65
258
  }
259
+ let workflowNeedsWrite = false;
260
+ let workflowExists = false;
261
+ try {
262
+ const existingContent = await fs.readFile(workflowPath, 'utf-8');
263
+ workflowExists = true;
264
+ if (existingContent !== templateContent) {
265
+ const shouldUpdate = await (0, prompts_1.confirm)({
266
+ message: 'Workflow file exists but differs from template. Update it?',
267
+ default: true,
268
+ });
269
+ if (shouldUpdate) {
270
+ workflowNeedsWrite = true;
271
+ }
272
+ else {
273
+ stdout.write('Keeping existing workflow file.\n');
274
+ }
275
+ }
276
+ else {
277
+ stdout.write('Workflow file is up to date.\n');
278
+ }
279
+ }
280
+ catch {
281
+ // File doesn't exist
282
+ workflowNeedsWrite = true;
283
+ }
284
+ if (workflowNeedsWrite) {
285
+ await (0, writeFile_1.writeFile)(workflowPath, templateContent);
286
+ stdout.write(workflowExists ? 'Workflow file updated.\n' : 'Workflow file created.\n');
287
+ }
288
+ // ========== Step 5: Commit and push ==========
289
+ const hasWorkflowChanges = git.hasChanges(workflowRelativePath) || git.isUntracked(workflowRelativePath);
290
+ if (hasWorkflowChanges) {
291
+ const shouldCommit = await (0, prompts_1.confirm)({
292
+ message: 'Commit and push the workflow file?',
293
+ default: true,
294
+ });
295
+ if (shouldCommit) {
296
+ try {
297
+ git.stageFiles([workflowRelativePath]);
298
+ git.commit('chore: add GitHub Pages deployment workflow');
299
+ const branch = git.getCurrentBranch() || git.getDefaultBranch();
300
+ stdout.write(`Pushing to ${selectedRemote.name}/${branch}...\n`);
301
+ git.push(selectedRemote.name, branch, { setUpstream: true });
302
+ stdout.write('Changes committed and pushed.\n');
303
+ }
304
+ catch (error) {
305
+ stderr.write(`\nFailed to commit/push: ${error}\n`);
306
+ stderr.write('You can manually commit and push the workflow file:\n' +
307
+ ` git add ${workflowRelativePath}\n` +
308
+ ' git commit -m "chore: add GitHub Pages deployment workflow"\n' +
309
+ ` git push -u ${selectedRemote.name} <branch>\n`);
310
+ }
311
+ }
312
+ }
313
+ else {
314
+ stdout.write('No changes to commit.\n');
315
+ }
316
+ // ========== Step 6: Show result ==========
317
+ let pagesUrl = null;
318
+ try {
319
+ pagesUrl = github.getPagesUrl(selectedRemote.owner, selectedRemote.repo);
320
+ }
321
+ catch {
322
+ // Ignore error
323
+ }
324
+ stdout.write('\n');
325
+ stdout.write('========================================\n');
326
+ stdout.write('GitHub Pages configuration complete!\n');
327
+ stdout.write('========================================\n');
328
+ if (pagesUrl) {
329
+ stdout.write(`\nYour site will be available at:\n ${pagesUrl}\n`);
330
+ }
331
+ else {
332
+ stdout.write('\nYour site URL will be available after the first deployment.\n' +
333
+ `Check: https://github.com/${selectedRemote.owner}/${selectedRemote.repo}/settings/pages\n`);
334
+ }
335
+ stdout.write('\nThe workflow will automatically deploy on:\n' +
336
+ ' - Push to main branch\n' +
337
+ ' - Every hour (scheduled)\n' +
338
+ ' - Manual trigger from Actions tab\n');
339
+ return 0;
66
340
  }
67
341
  }
68
342
  exports.ConfigGithubCommand = ConfigGithubCommand;
69
343
  ConfigGithubCommand.paths = [['config', 'github']];
70
344
  ConfigGithubCommand.usage = clipanion_1.Command.Usage({
71
- description: 'Copy GitHub Pages deployment workflow template to .github/workflows/pages.yml',
345
+ description: 'Configure GitHub Pages deployment for your CZON site',
72
346
  details: `
73
- This command copies the GitHub Pages deployment workflow template (templates/pages.yml)
74
- to the current directory's .github/workflows/pages.yml location.
75
-
347
+ This command guides you through setting up GitHub Pages deployment:
348
+
349
+ 1. Initialize Git repository (if needed)
350
+ 2. Configure GitHub remote repository
351
+ 3. Enable GitHub Pages with workflow deployment
352
+ 4. Create/update the deployment workflow file
353
+ 5. Commit and push changes
354
+
355
+ Prerequisites:
356
+ - GitHub CLI (gh) must be installed and authenticated
357
+ - Install: https://cli.github.com/
358
+ - Login: gh auth login
359
+
76
360
  Examples:
77
361
  $ czon config github
78
362
  `,
@@ -4,6 +4,7 @@ exports.extractMetadataByAI = extractMetadataByAI;
4
4
  const promises_1 = require("fs/promises");
5
5
  const extractMetadataFromMarkdown_1 = require("../ai/extractMetadataFromMarkdown");
6
6
  const metadata_1 = require("../metadata");
7
+ const sha256_1 = require("../utils/sha256");
7
8
  /**
8
9
  * 运行 AI 元数据提取
9
10
  */
@@ -16,12 +17,19 @@ async function extractMetadataByAI() {
16
17
  console.info(`ℹ️ Skipping ${file.path}, not a Markdown file`);
17
18
  return;
18
19
  }
19
- if (file.metadata && file.metadata.slug && file.metadata.short_summary) {
20
- console.info(`ℹ️ Skipping ${file.path}, already has metadata`);
20
+ const content = await (0, promises_1.readFile)(file.path, 'utf-8');
21
+ const hash = (0, sha256_1.sha256)(content);
22
+ // 检查是否已有 metadata 且源文件未变化
23
+ if (file.metadata &&
24
+ file.metadata.slug &&
25
+ file.metadata.short_summary &&
26
+ hash === file.hash) {
27
+ console.info(`ℹ️ Skipping ${file.path}, already has metadata and content unchanged`);
21
28
  return;
22
29
  }
23
- const content = await (0, promises_1.readFile)(file.path, 'utf-8');
24
30
  file.metadata = await (0, extractMetadataFromMarkdown_1.extractMetadataFromMarkdown)(file.path, content);
31
+ // 记录提取 metadata 时的源文件 hash
32
+ file.hash = hash;
25
33
  console.log(`✅ Extracted AI metadata for ${file.path}`, file.metadata.tokens_used);
26
34
  }));
27
35
  const errors = results.filter(r => r.status === 'rejected');
@@ -9,7 +9,6 @@ const path_1 = __importDefault(require("path"));
9
9
  const findEntries_1 = require("../findEntries");
10
10
  const metadata_1 = require("../metadata");
11
11
  const paths_1 = require("../paths");
12
- const sha256_1 = require("../utils/sha256");
13
12
  const extractLinksFromMarkdown = (content) => {
14
13
  const linkRegex = /\[.*?\]\((.*?)\)/g;
15
14
  const links = [];
@@ -50,21 +49,17 @@ async function scanSourceFiles() {
50
49
  continue;
51
50
  }
52
51
  const contentBuffer = await (0, promises_1.readFile)(fullPath);
53
- const hash = (0, sha256_1.sha256)(contentBuffer);
54
52
  paths.add(relativePath);
55
53
  let meta = metadata_1.MetaData.files.find(f => f.path === relativePath);
56
54
  if (!meta) {
57
- meta = { hash, path: relativePath, links: [] };
55
+ meta = { path: relativePath, links: [] };
58
56
  metadata_1.MetaData.files.push(meta);
59
57
  }
60
- else {
61
- meta.hash = hash;
62
- }
63
58
  // 处理 Markdown 文件
64
59
  if (fullPath.endsWith('.md')) {
65
60
  const content = contentBuffer.toString('utf-8');
66
61
  const links = extractLinksFromMarkdown(content);
67
- console.info(` - Found file: ${relativePath} (hash: ${hash})`);
62
+ console.info(` - Found file: ${relativePath}`);
68
63
  console.info(` Links: ${links.join(', ') || 'None'}`);
69
64
  meta.links = links;
70
65
  for (const link of links) {
@@ -0,0 +1,188 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isGitRepository = isGitRepository;
4
+ exports.initGitRepository = initGitRepository;
5
+ exports.getRemotes = getRemotes;
6
+ exports.addRemote = addRemote;
7
+ exports.remoteExists = remoteExists;
8
+ exports.getCurrentBranch = getCurrentBranch;
9
+ exports.getDefaultBranch = getDefaultBranch;
10
+ exports.stageFiles = stageFiles;
11
+ exports.commit = commit;
12
+ exports.push = push;
13
+ exports.hasChanges = hasChanges;
14
+ exports.isUntracked = isUntracked;
15
+ const child_process_1 = require("child_process");
16
+ /**
17
+ * Execute a git command and return the output
18
+ * @throws Error if the command fails
19
+ */
20
+ function execGit(args, options) {
21
+ const command = ['git', ...args].join(' ');
22
+ try {
23
+ return (0, child_process_1.execSync)(command, {
24
+ cwd: options?.cwd ?? process.cwd(),
25
+ encoding: 'utf-8',
26
+ stdio: ['pipe', 'pipe', 'pipe'],
27
+ }).trim();
28
+ }
29
+ catch (error) {
30
+ const err = error;
31
+ throw new Error(err.stderr || err.message || 'Git command failed');
32
+ }
33
+ }
34
+ /**
35
+ * Check if the current directory is a Git repository
36
+ */
37
+ function isGitRepository() {
38
+ try {
39
+ execGit(['rev-parse', '--is-inside-work-tree']);
40
+ return true;
41
+ }
42
+ catch {
43
+ return false;
44
+ }
45
+ }
46
+ /**
47
+ * Initialize a new Git repository
48
+ */
49
+ function initGitRepository() {
50
+ execGit(['init']);
51
+ }
52
+ /**
53
+ * Get all remote repositories
54
+ */
55
+ function getRemotes() {
56
+ try {
57
+ const output = execGit(['remote', '-v']);
58
+ if (!output)
59
+ return [];
60
+ const lines = output.split('\n');
61
+ const remoteMap = new Map();
62
+ for (const line of lines) {
63
+ const match = line.match(/^(\S+)\s+(\S+)\s+\((fetch|push)\)$/);
64
+ if (match) {
65
+ const [, name, url, type] = match;
66
+ if (!remoteMap.has(name)) {
67
+ remoteMap.set(name, { name, fetchUrl: '', pushUrl: '' });
68
+ }
69
+ const remote = remoteMap.get(name);
70
+ if (type === 'fetch') {
71
+ remote.fetchUrl = url;
72
+ }
73
+ else {
74
+ remote.pushUrl = url;
75
+ }
76
+ }
77
+ }
78
+ return Array.from(remoteMap.values());
79
+ }
80
+ catch {
81
+ return [];
82
+ }
83
+ }
84
+ /**
85
+ * Add a remote repository
86
+ */
87
+ function addRemote(name, url) {
88
+ execGit(['remote', 'add', name, url]);
89
+ }
90
+ /**
91
+ * Check if a remote exists
92
+ */
93
+ function remoteExists(name) {
94
+ const remotes = getRemotes();
95
+ return remotes.some(r => r.name === name);
96
+ }
97
+ /**
98
+ * Get the current branch name
99
+ */
100
+ function getCurrentBranch() {
101
+ try {
102
+ return execGit(['rev-parse', '--abbrev-ref', 'HEAD']);
103
+ }
104
+ catch {
105
+ return null;
106
+ }
107
+ }
108
+ /**
109
+ * Get the default branch name (main or master)
110
+ */
111
+ function getDefaultBranch() {
112
+ try {
113
+ // Try to get the default branch from git config
114
+ const defaultBranch = execGit(['config', '--get', 'init.defaultBranch']);
115
+ if (defaultBranch)
116
+ return defaultBranch;
117
+ }
118
+ catch {
119
+ // Ignore error
120
+ }
121
+ // Check if main branch exists
122
+ try {
123
+ execGit(['rev-parse', '--verify', 'main']);
124
+ return 'main';
125
+ }
126
+ catch {
127
+ // Ignore error
128
+ }
129
+ // Check if master branch exists
130
+ try {
131
+ execGit(['rev-parse', '--verify', 'master']);
132
+ return 'master';
133
+ }
134
+ catch {
135
+ // Ignore error
136
+ }
137
+ // Default to main
138
+ return 'main';
139
+ }
140
+ /**
141
+ * Stage files for commit
142
+ */
143
+ function stageFiles(files) {
144
+ execGit(['add', ...files]);
145
+ }
146
+ /**
147
+ * Commit staged changes
148
+ */
149
+ function commit(message) {
150
+ execGit(['commit', '-m', message]);
151
+ }
152
+ /**
153
+ * Push to remote repository
154
+ */
155
+ function push(remote, branch, options) {
156
+ const args = ['push'];
157
+ if (options?.setUpstream) {
158
+ args.push('-u');
159
+ }
160
+ args.push(remote, branch);
161
+ execGit(args);
162
+ }
163
+ /**
164
+ * Check if a file has uncommitted changes (staged or unstaged)
165
+ */
166
+ function hasChanges(file) {
167
+ try {
168
+ // Check for both staged and unstaged changes
169
+ const status = execGit(['status', '--porcelain', file]);
170
+ return status.length > 0;
171
+ }
172
+ catch {
173
+ return false;
174
+ }
175
+ }
176
+ /**
177
+ * Check if a file is untracked
178
+ */
179
+ function isUntracked(file) {
180
+ try {
181
+ const status = execGit(['status', '--porcelain', file]);
182
+ return status.startsWith('??');
183
+ }
184
+ catch {
185
+ return false;
186
+ }
187
+ }
188
+ //# sourceMappingURL=git.js.map
@@ -0,0 +1,239 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isGhInstalled = isGhInstalled;
4
+ exports.isGhAuthenticated = isGhAuthenticated;
5
+ exports.getTokenScopes = getTokenScopes;
6
+ exports.checkTokenScopes = checkTokenScopes;
7
+ exports.parseGitHubUrl = parseGitHubUrl;
8
+ exports.isGitHubUrl = isGitHubUrl;
9
+ exports.createRepository = createRepository;
10
+ exports.getPagesInfo = getPagesInfo;
11
+ exports.enablePages = enablePages;
12
+ exports.setPagesCname = setPagesCname;
13
+ exports.getPagesUrl = getPagesUrl;
14
+ exports.getAuthenticatedUser = getAuthenticatedUser;
15
+ const child_process_1 = require("child_process");
16
+ /**
17
+ * Execute a gh CLI command and return the output
18
+ * Uses spawnSync to avoid shell parsing issues with special characters
19
+ * @throws Error if the command fails
20
+ */
21
+ function execGh(args) {
22
+ const result = (0, child_process_1.spawnSync)('gh', args, {
23
+ cwd: process.cwd(),
24
+ encoding: 'utf-8',
25
+ stdio: ['pipe', 'pipe', 'pipe'],
26
+ });
27
+ if (result.error) {
28
+ throw result.error;
29
+ }
30
+ if (result.status !== 0) {
31
+ throw new Error(result.stderr || `gh command failed with exit code ${result.status}`);
32
+ }
33
+ return (result.stdout || '').trim();
34
+ }
35
+ /**
36
+ * Check if GitHub CLI (gh) is installed
37
+ */
38
+ function isGhInstalled() {
39
+ try {
40
+ (0, child_process_1.execSync)('gh --version', {
41
+ encoding: 'utf-8',
42
+ stdio: ['pipe', 'pipe', 'pipe'],
43
+ });
44
+ return true;
45
+ }
46
+ catch {
47
+ return false;
48
+ }
49
+ }
50
+ /**
51
+ * Check if GitHub CLI is authenticated
52
+ */
53
+ function isGhAuthenticated() {
54
+ try {
55
+ execGh(['auth', 'status']);
56
+ return true;
57
+ }
58
+ catch {
59
+ return false;
60
+ }
61
+ }
62
+ /**
63
+ * Get the token scopes for the current GitHub CLI authentication
64
+ * @returns Array of scope strings, or empty array if unable to determine
65
+ */
66
+ function getTokenScopes() {
67
+ try {
68
+ const result = (0, child_process_1.spawnSync)('gh', ['auth', 'status'], {
69
+ encoding: 'utf-8',
70
+ stdio: ['pipe', 'pipe', 'pipe'],
71
+ });
72
+ // gh auth status outputs to stderr
73
+ const output = result.stderr || result.stdout || '';
74
+ // Parse token scopes from output
75
+ // Format: "- Token scopes: 'scope1', 'scope2', 'scope3'"
76
+ const scopesMatch = output.match(/Token scopes:\s*(.+)/);
77
+ if (scopesMatch) {
78
+ const scopesStr = scopesMatch[1];
79
+ // Extract scopes from quoted strings like 'repo', 'workflow'
80
+ const scopes = scopesStr.match(/'([^']+)'/g);
81
+ if (scopes) {
82
+ return scopes.map(s => s.replace(/'/g, ''));
83
+ }
84
+ }
85
+ return [];
86
+ }
87
+ catch {
88
+ return [];
89
+ }
90
+ }
91
+ /**
92
+ * Check if the current authentication has the required scopes
93
+ * @param requiredScopes Array of required scope names
94
+ * @returns Object with hasScopes boolean and missingScopes array
95
+ */
96
+ function checkTokenScopes(requiredScopes) {
97
+ const currentScopes = getTokenScopes();
98
+ const missingScopes = requiredScopes.filter(required => !currentScopes.includes(required));
99
+ return {
100
+ hasScopes: missingScopes.length === 0,
101
+ missingScopes,
102
+ currentScopes,
103
+ };
104
+ }
105
+ /**
106
+ * Parse a GitHub URL and extract owner and repo
107
+ * Supports formats:
108
+ * - https://github.com/owner/repo.git
109
+ * - https://github.com/owner/repo
110
+ * - git@github.com:owner/repo.git
111
+ * - git@github.com:owner/repo
112
+ */
113
+ function parseGitHubUrl(url) {
114
+ // HTTPS format
115
+ const httpsMatch = url.match(/^https?:\/\/github\.com\/([^/]+)\/([^/.]+)(?:\.git)?$/);
116
+ if (httpsMatch) {
117
+ return { owner: httpsMatch[1], repo: httpsMatch[2] };
118
+ }
119
+ // SSH format
120
+ const sshMatch = url.match(/^git@github\.com:([^/]+)\/([^/.]+)(?:\.git)?$/);
121
+ if (sshMatch) {
122
+ return { owner: sshMatch[1], repo: sshMatch[2] };
123
+ }
124
+ return null;
125
+ }
126
+ /**
127
+ * Check if a URL is a GitHub repository URL
128
+ */
129
+ function isGitHubUrl(url) {
130
+ return parseGitHubUrl(url) !== null;
131
+ }
132
+ /**
133
+ * Create a new GitHub repository
134
+ */
135
+ function createRepository(options) {
136
+ const args = ['repo', 'create', options.name, '--confirm'];
137
+ if (options.private) {
138
+ args.push('--private');
139
+ }
140
+ else {
141
+ args.push('--public');
142
+ }
143
+ if (options.description) {
144
+ args.push('--description', options.description);
145
+ }
146
+ const output = execGh(args);
147
+ // Parse the output to get the repo URL
148
+ // Output format: https://github.com/owner/repo
149
+ const urlMatch = output.match(/https:\/\/github\.com\/([^/]+)\/([^\s]+)/);
150
+ if (!urlMatch) {
151
+ throw new Error('Failed to parse repository URL from gh output');
152
+ }
153
+ const owner = urlMatch[1];
154
+ const repo = urlMatch[2];
155
+ return {
156
+ owner,
157
+ repo,
158
+ url: `https://github.com/${owner}/${repo}.git`,
159
+ isPrivate: options.private ?? false,
160
+ };
161
+ }
162
+ /**
163
+ * Get GitHub Pages configuration for a repository
164
+ */
165
+ function getPagesInfo(owner, repo) {
166
+ try {
167
+ const output = execGh([
168
+ 'api',
169
+ `repos/${owner}/${repo}/pages`,
170
+ '--jq',
171
+ '{ url: .html_url, build_type: .build_type, cname: .cname }',
172
+ ]);
173
+ const data = JSON.parse(output);
174
+ return {
175
+ enabled: true,
176
+ url: data.url,
177
+ buildType: data.build_type,
178
+ cname: data.cname,
179
+ };
180
+ }
181
+ catch (error) {
182
+ const err = error;
183
+ // 404 means Pages is not enabled
184
+ if (err.message?.includes('404')) {
185
+ return { enabled: false };
186
+ }
187
+ throw error;
188
+ }
189
+ }
190
+ /**
191
+ * Enable GitHub Pages with workflow deployment
192
+ */
193
+ function enablePages(owner, repo) {
194
+ try {
195
+ // Try to create/enable Pages with workflow build type
196
+ execGh(['api', `repos/${owner}/${repo}/pages`, '-X', 'POST', '-f', 'build_type=workflow']);
197
+ }
198
+ catch (error) {
199
+ const err = error;
200
+ // If Pages already exists, update it
201
+ if (err.message?.includes('409') || err.message?.includes('already')) {
202
+ execGh(['api', `repos/${owner}/${repo}/pages`, '-X', 'PUT', '-f', 'build_type=workflow']);
203
+ }
204
+ else {
205
+ throw error;
206
+ }
207
+ }
208
+ }
209
+ /**
210
+ * Set custom domain (CNAME) for GitHub Pages
211
+ */
212
+ function setPagesCname(owner, repo, cname) {
213
+ if (cname) {
214
+ execGh(['api', `repos/${owner}/${repo}/pages`, '-X', 'PUT', '-f', `cname=${cname}`]);
215
+ }
216
+ else {
217
+ // Remove CNAME by setting it to empty
218
+ execGh(['api', `repos/${owner}/${repo}/pages`, '-X', 'PUT', '-f', 'cname=']);
219
+ }
220
+ }
221
+ /**
222
+ * Get the GitHub Pages URL for a repository
223
+ */
224
+ function getPagesUrl(owner, repo) {
225
+ try {
226
+ const output = execGh(['api', `repos/${owner}/${repo}/pages`, '--jq', '.html_url']);
227
+ return output || null;
228
+ }
229
+ catch {
230
+ return null;
231
+ }
232
+ }
233
+ /**
234
+ * Get the authenticated user's login name
235
+ */
236
+ function getAuthenticatedUser() {
237
+ return execGh(['api', 'user', '--jq', '.login']);
238
+ }
239
+ //# sourceMappingURL=github.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "czon",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "description": "CZON - AI enhanced Markdown content engine",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -50,6 +50,7 @@
50
50
  "typescript": "^5.9.3"
51
51
  },
52
52
  "dependencies": {
53
+ "@inquirer/prompts": "^8.2.0",
53
54
  "@opencode-ai/sdk": "^1.1.34",
54
55
  "@types/react": "^19.2.7",
55
56
  "@types/react-dom": "^19.2.3",