@ggdna/dna-gg 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +76 -0
- package/dna/_dna.json +10 -0
- package/dna/_generated.json +204 -0
- package/dna/_vars.json +9 -0
- package/dna/doc/guides/develop-guide.md +41 -0
- package/dna/doc/guides/develop-without-ai-guide.md +63 -0
- package/dna/doc/guides/for-ai/ai-dev-guide.md +97 -0
- package/dna/doc/guides/install-guide/install-gg-guide.md +75 -0
- package/dna/doc/guides/install-guide/install-gg-workspace-guide.md +28 -0
- package/dna/doc/guides/install-guide.overrides.md +9 -0
- package/dna/dot-claude/skills/cleanup/SKILL.md +49 -0
- package/dna/dot-claude/skills/commit/SKILL.md +37 -0
- package/dna/dot-claude/skills/publish/SKILL.md +39 -0
- package/dna/dot-claude/skills/push/SKILL.md +21 -0
- package/dna/dot-claude/skills/ticket/SKILL.md +47 -0
- package/dna/dot-github/workflows/quick_check.yaml +20 -0
- package/dna/scripts/delete-feature-branch.js +97 -0
- package/dna/scripts/functions/colors.js +27 -0
- package/dna/scripts/functions/current-branch.js +11 -0
- package/dna/scripts/functions/directories.js +22 -0
- package/dna/scripts/functions/get-repo-urls.js +43 -0
- package/dna/scripts/functions/get-version.js +14 -0
- package/dna/scripts/functions/is-clean-repo.js +51 -0
- package/dna/scripts/functions/is-main-up-to-date.js +19 -0
- package/dna/scripts/functions/pull-request-url.js +17 -0
- package/dna/scripts/functions/run-command.js +22 -0
- package/dna/scripts/functions/sync-folders.js +46 -0
- package/dna/scripts/rename-class.js +147 -0
- package/dna/scripts/setup-github-repo.js +221 -0
- package/dna/scripts/wait-for-pr.js +90 -0
- package/package.json +45 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: commit
|
|
3
|
+
description: Commits the ticket's work in every repo with `gg do commit`. Use when the user says "/commit" or asks to commit the current changes.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Commit
|
|
7
|
+
|
|
8
|
+
`gg do commit` runs the checks of each repo and commits only when they are
|
|
9
|
+
green. The message also becomes the `CHANGELOG.md` entry.
|
|
10
|
+
|
|
11
|
+
## 1. Propose a message
|
|
12
|
+
|
|
13
|
+
Look at `git status` and `git diff` in the repos of the ticket. Propose one
|
|
14
|
+
imperative one-liner that says what the step does, not which files changed.
|
|
15
|
+
Let the user confirm it.
|
|
16
|
+
|
|
17
|
+
## 2. Commit
|
|
18
|
+
|
|
19
|
+
From the ticket folder:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
gg do commit -m"<message>"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Add `-l` when the message must not land in `CHANGELOG.md`.
|
|
26
|
+
|
|
27
|
+
## 3. On a failing check
|
|
28
|
+
|
|
29
|
+
`gg do commit` stops at the first red check. Fix the cause, never the check,
|
|
30
|
+
then run the command again. Files generated by the DNA are committed
|
|
31
|
+
separately as `#gg: generated DNA` — never edit an instance by hand, change
|
|
32
|
+
its source under `dna/` instead.
|
|
33
|
+
|
|
34
|
+
## Important
|
|
35
|
+
|
|
36
|
+
- Never commit without the confirmed message.
|
|
37
|
+
- Never push here — that is `/push`.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: publish
|
|
3
|
+
description: Prepares the release of a reviewed ticket and hands `gg do publish` over to the user. Use when the user says "/publish" or asks to release the ticket.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Publish
|
|
7
|
+
|
|
8
|
+
`gg do publish` merges the pull requests, publishes to the registry and
|
|
9
|
+
creates and pushes the version tags. It is interactive — the user runs it.
|
|
10
|
+
|
|
11
|
+
## 1. Check the preconditions
|
|
12
|
+
|
|
13
|
+
- Every repo of the ticket is committed and pushed.
|
|
14
|
+
- The pull requests created by `gg do review` are green.
|
|
15
|
+
|
|
16
|
+
## 2. Configure the publish
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
gg do configure-publish
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Check per repo that the version bump matches what the ticket changed and
|
|
23
|
+
that `CHANGELOG.md` reads well.
|
|
24
|
+
|
|
25
|
+
## 3. Let the user publish
|
|
26
|
+
|
|
27
|
+
Ask the user to run this **manually** from the ticket folder:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
gg do publish
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
It asks whether the ticket and the remote feature branches may be deleted.
|
|
34
|
+
If the user declines, run `/cleanup` afterwards.
|
|
35
|
+
|
|
36
|
+
## Important
|
|
37
|
+
|
|
38
|
+
- Never run `gg do publish` yourself.
|
|
39
|
+
- Never write versions or tags by hand.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: push
|
|
3
|
+
description: Pushes the ticket's commits with `gg do push`. Use when the user says "/push" or asks to push the current ticket.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Push
|
|
7
|
+
|
|
8
|
+
From the ticket folder:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
gg do push
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
`gg do push` pushes the repos of the ticket in dependency order and refuses
|
|
15
|
+
repos that still carry uncommitted work. If it reports such a repo, run
|
|
16
|
+
`/commit` first and push again.
|
|
17
|
+
|
|
18
|
+
## Important
|
|
19
|
+
|
|
20
|
+
- Push only when the user asked for it.
|
|
21
|
+
- Do not create pull requests here — `gg do review` does that.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ticket
|
|
3
|
+
description: Creates a gg ticket and adds the repos it needs. Use when the user says "/ticket", "new ticket", "fix bug X", or "implement feature Y".
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Create a ticket
|
|
7
|
+
|
|
8
|
+
All `gg do` commands run inside the workspace. Ask for the workspace
|
|
9
|
+
directory if none is known.
|
|
10
|
+
|
|
11
|
+
## 1. Ask for the ticket data
|
|
12
|
+
|
|
13
|
+
- Ticket ID, e.g. `DnaJiraPrefix-145`
|
|
14
|
+
- Title, one imperative line, e.g. `Fix issue abc`
|
|
15
|
+
|
|
16
|
+
## 2. Create the ticket
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
cd ~/dev/ # workspace
|
|
20
|
+
gg do create ticket DnaJiraPrefix-145 -m"Fix issue abc"
|
|
21
|
+
cd tickets/DnaJiraPrefix-145
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## 3. Choose the repos
|
|
25
|
+
|
|
26
|
+
Read the `index.md` of the candidate repos in `.ocean`. Tell the user which
|
|
27
|
+
repos the ticket needs and what you roughly want to change in each one. If a
|
|
28
|
+
part belongs to a domain that has no repo yet, say so and ask whether to
|
|
29
|
+
create one.
|
|
30
|
+
|
|
31
|
+
After the confirmation:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
gg do add repo1 repo2
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## 4. Open the workspace
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
gg do code
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Important
|
|
44
|
+
|
|
45
|
+
- Never add repos without confirmation. Rather too few than too many — more
|
|
46
|
+
can be added later.
|
|
47
|
+
- Do not start the implementation unasked.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Runs the gg-js quick check on every push and pull request against main.
|
|
2
|
+
|
|
3
|
+
name: Tests
|
|
4
|
+
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
branches: ['main']
|
|
8
|
+
pull_request:
|
|
9
|
+
branches: ['main']
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
build:
|
|
13
|
+
name: dnaGitQuickCheckPipelineName
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v5
|
|
18
|
+
|
|
19
|
+
# Uses the Node.js preinstalled on the runner image.
|
|
20
|
+
- run: npx @tssuite/gg-js one did commit
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) ggsuite
|
|
4
|
+
*
|
|
5
|
+
* Use of this source code is governed by terms that can be
|
|
6
|
+
* found in the LICENSE file in the root of this package.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { gray, green, red, yellow } from './functions/colors.js';
|
|
10
|
+
import { runCommand } from './functions/run-command.js';
|
|
11
|
+
|
|
12
|
+
// Check for uncommitted changes
|
|
13
|
+
function hasUncommittedChanges() {
|
|
14
|
+
console.log(gray('Check for uncommitted changes'));
|
|
15
|
+
const status = runCommand('git status --porcelain');
|
|
16
|
+
return status.length > 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Check for unpushed commits
|
|
20
|
+
function hasUnpushedCommits(branch) {
|
|
21
|
+
try {
|
|
22
|
+
console.log(gray('Check for unpushed commits'));
|
|
23
|
+
runCommand(`git rev-parse --abbrev-ref ${branch}@{u}`);
|
|
24
|
+
const ahead = runCommand(`git rev-list --count ${branch}@{u}..${branch}`);
|
|
25
|
+
return parseInt(ahead, 10) > 0;
|
|
26
|
+
} catch {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Try a test merge into main and check if it introduces changes
|
|
32
|
+
function isBranchEffectivelyMerged(featureBranch) {
|
|
33
|
+
console.log(gray('Check if feature was fully merged'));
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
runCommand(`git merge --no-commit --no-ff ${featureBranch}`);
|
|
37
|
+
const changed = hasUncommittedChanges();
|
|
38
|
+
runCommand('git merge --abort');
|
|
39
|
+
return !changed;
|
|
40
|
+
} catch {
|
|
41
|
+
try {
|
|
42
|
+
runCommand('git merge --abort');
|
|
43
|
+
} catch {}
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
console.log(gray('Get current branch name'));
|
|
50
|
+
const currentBranch = runCommand('git rev-parse --abbrev-ref HEAD');
|
|
51
|
+
|
|
52
|
+
if (currentBranch === 'main') {
|
|
53
|
+
console.log(yellow('Please call this script from a feature branch.'));
|
|
54
|
+
process.exit(0);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (hasUncommittedChanges()) {
|
|
58
|
+
console.error(red('❌ You have uncommitted changes.'));
|
|
59
|
+
console.error(
|
|
60
|
+
yellow('Please commit or stash your changes before continuing.'),
|
|
61
|
+
);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (hasUnpushedCommits(currentBranch)) {
|
|
66
|
+
console.error(red('❌ You have unpushed commits.'));
|
|
67
|
+
console.error(yellow('Please push your branch before continuing.'));
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
console.log(gray(`Fetching and pulling 'main'...`));
|
|
72
|
+
runCommand('git fetch');
|
|
73
|
+
runCommand('git checkout main');
|
|
74
|
+
runCommand('git pull origin main');
|
|
75
|
+
|
|
76
|
+
const isMerged = isBranchEffectivelyMerged(currentBranch);
|
|
77
|
+
|
|
78
|
+
if (isMerged) {
|
|
79
|
+
runCommand(`git branch -d ${currentBranch}`);
|
|
80
|
+
console.log(green(`✅ Branch '${currentBranch}' has been deleted.`));
|
|
81
|
+
} else {
|
|
82
|
+
console.error(
|
|
83
|
+
red(`❌ Branch '${currentBranch}' is not merged into 'main'.`),
|
|
84
|
+
);
|
|
85
|
+
console.log(
|
|
86
|
+
yellow(`Please (create and) merge a pull request and try again.`),
|
|
87
|
+
);
|
|
88
|
+
runCommand(`git checkout ${currentBranch}`);
|
|
89
|
+
console.log(gray(`Switched back to '${currentBranch}'.`));
|
|
90
|
+
}
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.error(red(`Error: ${error.message}`));
|
|
93
|
+
try {
|
|
94
|
+
runCommand('git merge --abort');
|
|
95
|
+
} catch {}
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) ggsuite
|
|
4
|
+
*
|
|
5
|
+
* Use of this source code is governed by terms that can be
|
|
6
|
+
* found in the LICENSE file in the root of this package.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const red = (str) => `\x1b[31m${str}\x1b[0m`;
|
|
10
|
+
export const green = (str) => `\x1b[32m${str}\x1b[0m`;
|
|
11
|
+
export const yellow = (str) => `\x1b[33m${str}\x1b[0m`;
|
|
12
|
+
export const blue = (str) => `\x1b[34m${str}\x1b[0m`;
|
|
13
|
+
export const magenta = (str) => `\x1b[35m${str}\x1b[0m`;
|
|
14
|
+
export const cyan = (str) => `\x1b[36m${str}\x1b[0m`;
|
|
15
|
+
export const white = (str) => `\x1b[37m${str}\x1b[0m`;
|
|
16
|
+
export const black = (str) => `\x1b[30m${str}\x1b[0m`;
|
|
17
|
+
export const bgRed = (str) => `\x1b[41m${str}\x1b[0m`;
|
|
18
|
+
export const bgGreen = (str) => `\x1b[42m${str}\x1b[0m`;
|
|
19
|
+
export const bgYellow = (str) => `\x1b[43m${str}\x1b[0m`;
|
|
20
|
+
export const bgBlue = (str) => `\x1b[44m${str}\x1b[0m`;
|
|
21
|
+
export const bgMagenta = (str) => `\x1b[45m${str}\x1b[0m`;
|
|
22
|
+
export const bgCyan = (str) => `\x1b[46m${str}\x1b[0m`;
|
|
23
|
+
export const bgWhite = (str) => `\x1b[47m${str}\x1b[0m`;
|
|
24
|
+
export const bgBlack = (str) => `\x1b[40m${str}\x1b[0m`;
|
|
25
|
+
export const gray = (str) => `\x1b[90m${str}\x1b[0m`;
|
|
26
|
+
export const lightGray = (str) => `\x1b[37m${str}\x1b[0m`;
|
|
27
|
+
export const reset = (str) => `\x1b[0m${str}\x1b[0m`;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) ggsuite
|
|
4
|
+
*
|
|
5
|
+
* Use of this source code is governed by terms that can be
|
|
6
|
+
* found in the LICENSE file in the root of this package.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const currentBranch = () => {
|
|
10
|
+
return execSync('git rev-parse --abbrev-ref HEAD').toString().trim();
|
|
11
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) ggsuite
|
|
4
|
+
*
|
|
5
|
+
* Use of this source code is governed by terms that can be
|
|
6
|
+
* found in the LICENSE file in the root of this package.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Get the name of this file
|
|
10
|
+
import { dirname, join } from 'path';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = dirname(__filename);
|
|
15
|
+
|
|
16
|
+
export const workSpaceDir = join(__dirname, '..', '..');
|
|
17
|
+
export const distDir = join(workSpaceDir, 'dist');
|
|
18
|
+
export const srcDir = join(workSpaceDir, 'src');
|
|
19
|
+
export const testDir = join(workSpaceDir, 'test');
|
|
20
|
+
export const scriptsDir = join(workSpaceDir, 'scripts');
|
|
21
|
+
export const goldensDir = join(testDir, 'goldens');
|
|
22
|
+
export const nodeModulesDir = join(workSpaceDir, 'node_modules');
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) ggsuite
|
|
4
|
+
*
|
|
5
|
+
* Use of this source code is governed by terms that can be
|
|
6
|
+
* found in the LICENSE file in the root of this package.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Import Node.js built-in module for working with child processes
|
|
10
|
+
import { exec } from 'child_process';
|
|
11
|
+
import { promisify } from 'util';
|
|
12
|
+
import { green, yellow } from './colors.js';
|
|
13
|
+
|
|
14
|
+
// Convert exec to return a Promise for async/await usage
|
|
15
|
+
const execAsync = promisify(exec);
|
|
16
|
+
|
|
17
|
+
// GitHub organization or user
|
|
18
|
+
const org = 'rljson';
|
|
19
|
+
|
|
20
|
+
export async function getRepoUrls() {
|
|
21
|
+
try {
|
|
22
|
+
// Use the GitHub CLI to list repositories in JSON format
|
|
23
|
+
const { stdout } = await execAsync(
|
|
24
|
+
`gh repo list ${org} --limit 1000 --json url`,
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
// Parse the JSON output
|
|
28
|
+
const repos = JSON.parse(stdout);
|
|
29
|
+
|
|
30
|
+
// Map to name + URL and print to console
|
|
31
|
+
return repos.map((repo) => repo.url);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
if (error.message.match('gh auth login').length == 1) {
|
|
34
|
+
throw Error(
|
|
35
|
+
[yellow('Not yet logged in. Please run:'), green('gh auth login')].join(
|
|
36
|
+
'\n',
|
|
37
|
+
),
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) ggsuite
|
|
4
|
+
*
|
|
5
|
+
* Use of this source code is governed by terms that can be
|
|
6
|
+
* found in the LICENSE file in the root of this package.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { readFile } from 'fs/promises';
|
|
10
|
+
|
|
11
|
+
export const getVersion = async () => {
|
|
12
|
+
const packageJson = JSON.parse(await readFile('package.json', 'utf8'));
|
|
13
|
+
return packageJson.version;
|
|
14
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) ggsuite
|
|
4
|
+
*
|
|
5
|
+
* Use of this source code is governed by terms that can be
|
|
6
|
+
* found in the LICENSE file in the root of this package.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// checkRepo.js
|
|
10
|
+
import { execSync } from 'child_process';
|
|
11
|
+
|
|
12
|
+
export function isCleanRepo(path) {
|
|
13
|
+
const cwd = path ?? '.';
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
// Aktuellen Branch ermitteln
|
|
17
|
+
const branch = execSync('git rev-parse --abbrev-ref HEAD', {
|
|
18
|
+
cwd,
|
|
19
|
+
})
|
|
20
|
+
.toString()
|
|
21
|
+
.trim();
|
|
22
|
+
if (branch !== 'main') {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Git-Fetch ausführen (still)
|
|
27
|
+
execSync('git fetch', { stdio: 'ignore', cwd });
|
|
28
|
+
|
|
29
|
+
// Prüfen, ob Arbeitsverzeichnis sauber ist
|
|
30
|
+
const status = execSync('git status --porcelain', { cwd })
|
|
31
|
+
.toString()
|
|
32
|
+
.trim();
|
|
33
|
+
if (status.length > 0) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Prüfen, ob main auf dem neuesten Stand mit origin/main ist
|
|
38
|
+
const revList = execSync(
|
|
39
|
+
'git rev-list --left-right --count origin/main...main',
|
|
40
|
+
{ cwd },
|
|
41
|
+
)
|
|
42
|
+
.toString()
|
|
43
|
+
.trim();
|
|
44
|
+
|
|
45
|
+
const [behind, ahead] = revList.split('\t').map(Number);
|
|
46
|
+
return behind === 0 && ahead === 0;
|
|
47
|
+
} catch (err) {
|
|
48
|
+
console.error('Git error:', err.message);
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) ggsuite
|
|
4
|
+
*
|
|
5
|
+
* Use of this source code is governed by terms that can be
|
|
6
|
+
* found in the LICENSE file in the root of this package.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const isMainUpToDate = () => {
|
|
10
|
+
try {
|
|
11
|
+
execSync('git fetch origin main');
|
|
12
|
+
const localHash = execSync('git rev-parse main').toString().trim();
|
|
13
|
+
const remoteHash = execSync('git rev-parse origin/main').toString().trim();
|
|
14
|
+
return localHash === remoteHash;
|
|
15
|
+
} catch (error) {
|
|
16
|
+
console.error(red('Error checking branch status:'), error);
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) ggsuite
|
|
4
|
+
*
|
|
5
|
+
* Use of this source code is governed by terms that can be
|
|
6
|
+
* found in the LICENSE file in the root of this package.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export function pullRequestUrl() {
|
|
10
|
+
const json = execSync('gh pr view --json url', {
|
|
11
|
+
encoding: 'utf-8',
|
|
12
|
+
}).trim();
|
|
13
|
+
|
|
14
|
+
const parsed = JSON.parse(json);
|
|
15
|
+
const url = parsed.url;
|
|
16
|
+
return url;
|
|
17
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) ggsuite
|
|
4
|
+
*
|
|
5
|
+
* Use of this source code is governed by terms that can be
|
|
6
|
+
* found in the LICENSE file in the root of this package.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { execSync } from 'child_process';
|
|
10
|
+
import { gray } from './colors.js';
|
|
11
|
+
|
|
12
|
+
// Execute a shell command and return trimmed output
|
|
13
|
+
// Execute a shell command and return trimmed output
|
|
14
|
+
export function runCommand(command, silent = true, logCommand = true) {
|
|
15
|
+
if (logCommand) {
|
|
16
|
+
console.log(gray(`${command}`));
|
|
17
|
+
}
|
|
18
|
+
return execSync(command, {
|
|
19
|
+
encoding: 'utf-8',
|
|
20
|
+
stdio: silent ? ['pipe', 'pipe', 'pipe'] : undefined,
|
|
21
|
+
}).trim();
|
|
22
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright (c) ggsuite
|
|
4
|
+
*
|
|
5
|
+
* Use of this source code is governed by terms that can be
|
|
6
|
+
* found in the LICENSE file in the root of this package.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import * as fs from 'fs';
|
|
10
|
+
import * as path from 'path';
|
|
11
|
+
|
|
12
|
+
// Create a node.js script that recursively copies all files from one folder
|
|
13
|
+
// to another
|
|
14
|
+
|
|
15
|
+
export async function syncFolders(
|
|
16
|
+
source,
|
|
17
|
+
target,
|
|
18
|
+
options = { excludeHidden: true },
|
|
19
|
+
) {
|
|
20
|
+
if (!fs.existsSync(source)) {
|
|
21
|
+
console.error(`Source folder "${source}" does not exist.`);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!fs.existsSync(target)) {
|
|
26
|
+
await fs.promises.mkdir(target, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const items = await fs.promises.readdir(source);
|
|
30
|
+
|
|
31
|
+
for (const item of items) {
|
|
32
|
+
if (options.excludeHidden && item.startsWith('.')) {
|
|
33
|
+
continue; // Skip hidden files and folders
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const sourcePath = path.join(source, item);
|
|
37
|
+
const targetPath = path.join(target, item);
|
|
38
|
+
|
|
39
|
+
const stat = await fs.promises.lstat(sourcePath);
|
|
40
|
+
if (stat.isDirectory()) {
|
|
41
|
+
await syncFolders(sourcePath, targetPath, options.excludeHidden);
|
|
42
|
+
} else {
|
|
43
|
+
await fs.promises.copyFile(sourcePath, targetPath);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|