@ggdna/dna-gg 0.1.0 → 0.1.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.
package/README.md CHANGED
@@ -22,17 +22,6 @@ publish.
22
22
  - `/publish` — releases the ticket
23
23
  - `/cleanup` — removes what the ticket left behind
24
24
 
25
- ## Scripts
26
-
27
- - `dna/scripts/setup-github-repo.js` — applies the branch rules a new repo
28
- needs, called by the create repo guide
29
- - `dna/scripts/rename-class.js` — renames a template project to its real
30
- name
31
- - `dna/scripts/wait-for-pr.js` — waits for the pull request to merge
32
- - `dna/scripts/delete-feature-branch.js` — removes the branch after the
33
- merge
34
- - `dna/scripts/functions/` — the helpers those scripts share
35
-
36
25
  ## Configuration
37
26
 
38
27
  - `dna/dot-github/workflows/quick_check.yaml` — the quick check pipeline
@@ -41,8 +30,11 @@ publish.
41
30
  ## Layers
42
31
 
43
32
  Builds on [dna_install](https://github.com/ggdna/dna_install) for the
44
- install overview and [dna_index](https://github.com/ggdna/dna_index) for
45
- the repo index every gg repo keeps.
33
+ install overview, [dna_index](https://github.com/ggdna/dna_index) for the
34
+ repo index every gg repo keeps, and
35
+ [dna_scripts](https://github.com/ggdna/dna_scripts) for
36
+ `setup-github-repo.js`, `rename-class.js`, `wait-for-pr.js` and
37
+ `delete-feature-branch.js`, which the guides above call.
46
38
 
47
39
  It extends the `@tooling` section of the install overview through
48
40
  `dna/doc/guides/install-guide.overrides.md`: it adds `Install gg` and
package/dna/_dna.json CHANGED
@@ -5,6 +5,7 @@
5
5
 
6
6
  // The parent DNAs. dna_install brings the install overview this layer
7
7
  // extends with the gg install guides, dna_index the index conventions
8
- // every gg repo follows. Parents first, this layer wins.
9
- "layers": ["dna_install", "dna_index"]
8
+ // every gg repo follows, dna_scripts the node scripts this layer's
9
+ // guides call. Parents first, this layer wins.
10
+ "layers": ["dna_install", "dna_index", "dna_scripts"]
10
11
  }
@@ -11,27 +11,35 @@
11
11
  },
12
12
  {
13
13
  "name": "dna-install",
14
- "package": "@ggdna/dna-install",
15
- "ecosystem": "node",
14
+ "package": "dna_install",
15
+ "ecosystem": "pub",
16
16
  "resolvedVersion": "0.1.0",
17
17
  "via": null,
18
18
  "hash": "0xbe88eb4256745ee1"
19
19
  },
20
20
  {
21
21
  "name": "dna-index",
22
- "package": "@ggdna/dna-index",
23
- "ecosystem": "node",
22
+ "package": "dna_index",
23
+ "ecosystem": "pub",
24
24
  "resolvedVersion": "0.1.0",
25
25
  "via": null,
26
26
  "hash": "0x1d92b0add37a1ac7"
27
27
  },
28
+ {
29
+ "name": "dna-scripts",
30
+ "package": "@ggdna/dna-scripts",
31
+ "ecosystem": "node",
32
+ "resolvedVersion": "0.1.0",
33
+ "via": null,
34
+ "hash": "0x9ee08ef13ab4c8d3"
35
+ },
28
36
  {
29
37
  "name": "self",
30
38
  "package": null,
31
39
  "ecosystem": null,
32
40
  "resolvedVersion": null,
33
41
  "via": null,
34
- "hash": "0x2a90594a14522652"
42
+ "hash": "0xd5744033dd70c6a3"
35
43
  }
36
44
  ],
37
45
  "claude": {
@@ -64,13 +72,17 @@
64
72
  "path": ".claude/skills/push/SKILL.md",
65
73
  "hash": "0x76c357ca9933317d"
66
74
  },
75
+ {
76
+ "path": ".claude/skills/scripts/SKILL.md",
77
+ "hash": "0xa12593aa0af31ed2"
78
+ },
67
79
  {
68
80
  "path": ".claude/skills/ticket/SKILL.md",
69
81
  "hash": "0xbc13efe6fa9af962"
70
82
  },
71
83
  {
72
84
  "path": ".github/workflows/quick_check.yaml",
73
- "hash": "0xa61b5062f48e430c"
85
+ "hash": "0xf3320fb8f530a565"
74
86
  },
75
87
  {
76
88
  "path": "doc/guides/develop-guide.md",
@@ -136,6 +148,10 @@
136
148
  "path": "doc/guides/install-guide/install-vscode-guide.md",
137
149
  "hash": "0xa0da6a6877bb94c2"
138
150
  },
151
+ {
152
+ "path": "doc/guides/scripts-guide.md",
153
+ "hash": "0x426857c389049083"
154
+ },
139
155
  {
140
156
  "path": "doc/hello_world.md",
141
157
  "hash": "0x0903f7fc5224f6f1"
@@ -1,4 +1,4 @@
1
- # Runs the gg-js quick check on every push and pull request against main.
1
+ # Runs the gg quick check on every push and pull request against main.
2
2
 
3
3
  name: Tests
4
4
 
@@ -17,4 +17,4 @@ jobs:
17
17
  - uses: actions/checkout@v5
18
18
 
19
19
  # Uses the Node.js preinstalled on the runner image.
20
- - run: npx @tssuite/gg-js one did commit
20
+ - run: npx @tssuite/ggwsm one did commit
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ggdna/dna-gg",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "DNA layer: gg CLI workflow guides, repo setup scripts and CI check",
5
5
  "keywords": [
6
6
  "dna",
@@ -29,7 +29,8 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "@ggdna/dna-index": "^0.1.0",
32
- "@ggdna/dna-install": "^0.1.0"
32
+ "@ggdna/dna-install": "^0.1.0",
33
+ "@ggdna/dna-scripts": "^0.1.0"
33
34
  },
34
35
  "scripts": {
35
36
  "test:dart": "dart test",
@@ -1,97 +0,0 @@
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
- }
@@ -1,27 +0,0 @@
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`;
@@ -1,11 +0,0 @@
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
- };
@@ -1,22 +0,0 @@
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');
@@ -1,43 +0,0 @@
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
- }
@@ -1,14 +0,0 @@
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
- };
@@ -1,51 +0,0 @@
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
- }
@@ -1,19 +0,0 @@
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
- };
@@ -1,17 +0,0 @@
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
- }
@@ -1,22 +0,0 @@
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
- }
@@ -1,46 +0,0 @@
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
- }
@@ -1,147 +0,0 @@
1
- #!/bin/node
2
-
3
- import fs from 'fs';
4
- import path from 'path';
5
- import { blue, gray, green, red, yellow } from './functions/colors.js';
6
-
7
- // .............................................................................
8
- const excludedFiles = ['create-new-repo.md', 'rename-class.js'];
9
-
10
- // .............................................................................
11
-
12
- const isExcluded = (fileName) => {
13
- return excludedFiles.includes(fileName);
14
- };
15
-
16
- const shouldProcessFile = (file) => !isExcluded(file);
17
-
18
- // Binary files must not be rewritten as UTF-8, that would corrupt them.
19
- const readTextFile = (fullPath) => {
20
- const buffer = fs.readFileSync(fullPath);
21
- if (buffer.includes(0)) return null;
22
- return buffer.toString('utf8');
23
- };
24
-
25
- const writeTextFile = (fullPath, oldContent, newContent) => {
26
- if (newContent === oldContent) return;
27
- fs.writeFileSync(fullPath, newContent, 'utf8');
28
- };
29
-
30
- if (process.argv.length < 4) {
31
- const usage = red('Usage:');
32
- const script = yellow(path.basename(process.argv[1]));
33
- const classA = green('ClassA');
34
- const classB = blue('ClassB');
35
- console.error([usage, script, classA, classB].join(' '));
36
- process.exit(1);
37
- }
38
-
39
- const classA = process.argv[2];
40
- const classB = process.argv[3];
41
-
42
- const toSnakeCase = (str) => {
43
- return str.replace(/(?<=[a-z0-9])([A-Z])/g, '-$1').toLowerCase();
44
- };
45
-
46
- const toLowerCamelCase = (str) => {
47
- return str.replace(/([-_]\w)/g, (matches) => matches[1].toUpperCase());
48
- };
49
-
50
- const toUpperCamelCase = (str) => {
51
- return str.replace(/(?:^|[-_])(\w)/g, (_, c) => (c ? c.toUpperCase() : ''));
52
- };
53
-
54
- const classALower = toLowerCamelCase(classA);
55
- const classBLower = toLowerCamelCase(classB);
56
- const classAUpper = toUpperCamelCase(classA);
57
- const classBUpper = toUpperCamelCase(classB);
58
- const classASnake = toSnakeCase(classA);
59
- const classBSnake = toSnakeCase(classB);
60
- const classASnakeUnderscore = classASnake.replace(/-/g, '_');
61
- const classBSnakeUnderscore = classBSnake.replace(/-/g, '_');
62
-
63
- const replaceIncludesFirst = (directory) => {
64
- const files = fs.readdirSync(directory, { withFileTypes: true });
65
-
66
- for (const file of files) {
67
- const fullPath = path.join(directory, file.name);
68
-
69
- if (file.isDirectory()) {
70
- if (file.name.startsWith('.') || file.name === 'node_modules') continue;
71
- replaceIncludesFirst(fullPath);
72
- } else if (shouldProcessFile(file.name)) {
73
- const original = readTextFile(fullPath);
74
- if (original === null) continue;
75
- const content = original
76
- .replace(
77
- new RegExp(`(\\/)${classASnake}(\\.ts)?([\\.\\/\\'\\\"\\\\])`, 'g'),
78
- `$1${classBSnake}$2$3`,
79
- )
80
- .replace(
81
- new RegExp(
82
- `(\\/)${classASnakeUnderscore}(\\.ts)?([\\.\\/\\'\\\"\\\\])`,
83
- 'g',
84
- ),
85
- `$1${classBSnakeUnderscore}$2$3`,
86
- );
87
- writeTextFile(fullPath, original, content);
88
- }
89
- }
90
- };
91
-
92
- const replaceInFiles = (directory) => {
93
- const files = fs.readdirSync(directory, { withFileTypes: true });
94
-
95
- for (const file of files) {
96
- const fullPath = path.join(directory, file.name);
97
-
98
- if (file.isDirectory()) {
99
- if (file.name.startsWith('.') || file.name === 'node_modules') continue;
100
- replaceInFiles(fullPath);
101
- } else if (shouldProcessFile(file.name)) {
102
- const original = readTextFile(fullPath);
103
- if (original === null) continue;
104
- const content = original
105
- .replace(new RegExp(classAUpper, 'g'), classBUpper)
106
- .replace(new RegExp(classALower, 'g'), classBLower)
107
- .replace(new RegExp(classASnake, 'g'), classBSnake)
108
- .replace(
109
- new RegExp(classASnakeUnderscore, 'g'),
110
- classBSnakeUnderscore,
111
- );
112
- writeTextFile(fullPath, original, content);
113
- }
114
- }
115
- };
116
-
117
- const renameFiles = (directory) => {
118
- const files = fs.readdirSync(directory, { withFileTypes: true });
119
-
120
- for (const file of files) {
121
- const fullPath = path.join(directory, file.name);
122
-
123
- if (file.isDirectory()) {
124
- if (file.name.startsWith('.') || file.name === 'node_modules') continue;
125
- renameFiles(fullPath);
126
- } else if (
127
- file.name.includes(classASnake) ||
128
- file.name.includes(classASnakeUnderscore)
129
- ) {
130
- const newFileName = file.name
131
- .replace(classASnake, classBSnake)
132
- .replace(classASnakeUnderscore, classBSnakeUnderscore);
133
- fs.renameSync(fullPath, path.join(directory, newFileName));
134
- }
135
- }
136
- };
137
-
138
- console.log(gray('Replacing includes first...'));
139
- replaceIncludesFirst('.');
140
-
141
- console.log(gray('Renaming files...'));
142
- renameFiles('.');
143
-
144
- console.log(gray('Replacing occurrences in files...'));
145
- replaceInFiles('.');
146
-
147
- console.log(green('Done.'));
@@ -1,221 +0,0 @@
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 { writeFileSync } from 'fs';
11
- import { tmpdir } from 'os';
12
- import { join } from 'path';
13
- import {
14
- blue,
15
- cyan,
16
- gray,
17
- green,
18
- red,
19
- white,
20
- yellow,
21
- } from './functions/colors.js';
22
- import { runCommand } from './functions/run-command.js';
23
-
24
- // The name of the branch ruleset managed by this script
25
- const rulesetName = 'Default';
26
-
27
- // The status check that must pass before merging. It is provided by the
28
- // GitHub Actions app, i.e. not by "Any source".
29
- const requiredStatusCheck = 'dnaGitQuickCheckPipelineName';
30
- const gitHubActionsAppId = 15368;
31
-
32
- // ...........................................................................
33
- // Make sure the GitHub CLI is available and authenticated
34
- function checkGhCli() {
35
- console.log(gray('Check GitHub CLI'));
36
-
37
- try {
38
- runCommand('gh --version');
39
- } catch {
40
- throw new Error(
41
- [
42
- yellow('GitHub CLI is not installed. Please install it:'),
43
- green('brew install gh'),
44
- ].join('\n'),
45
- );
46
- }
47
-
48
- try {
49
- runCommand('gh auth status');
50
- } catch {
51
- throw new Error(
52
- [yellow('Not yet logged in. Please run:'), green('gh auth login')].join(
53
- '\n',
54
- ),
55
- );
56
- }
57
- }
58
-
59
- // ...........................................................................
60
- // Get "org/repo" either from the first argument or from the current directory
61
- function repoSlug() {
62
- const fromArgs = process.argv.slice(2).find((a) => !a.startsWith('--'));
63
- if (fromArgs) {
64
- if (!/^[^/]+\/[^/]+$/.test(fromArgs)) {
65
- throw new Error(`Invalid repository '${fromArgs}'. Expected 'org/repo'.`);
66
- }
67
- return fromArgs;
68
- }
69
-
70
- console.log(gray('Get repository from current directory'));
71
- try {
72
- const json = runCommand('gh repo view --json nameWithOwner');
73
- return JSON.parse(json).nameWithOwner;
74
- } catch {
75
- throw new Error(
76
- [
77
- yellow('Could not determine the repository.'),
78
- yellow('Please call this script from a GitHub repository or run:'),
79
- green('node dna/scripts/setup-github-repo.js org/repo'),
80
- ].join('\n'),
81
- );
82
- }
83
- }
84
-
85
- // ...........................................................................
86
- // Call the GitHub API with a JSON body
87
- function ghApi(method, path, body) {
88
- const file = join(tmpdir(), `setup-github-repo-${process.pid}.json`);
89
- writeFileSync(file, JSON.stringify(body));
90
- console.log(gray(`gh api --method ${method} ${path}`));
91
- return execSync(`gh api --method ${method} ${path} --input ${file}`, {
92
- encoding: 'utf-8',
93
- stdio: ['pipe', 'pipe', 'pipe'],
94
- }).trim();
95
- }
96
-
97
- // ...........................................................................
98
- // Only allow squash merges, auto merge and delete branches after merge
99
- function pullRequestSettings() {
100
- return {
101
- allow_merge_commit: false,
102
- allow_squash_merge: true,
103
- // Default commit message: "Pull request title"
104
- squash_merge_commit_title: 'PR_TITLE',
105
- squash_merge_commit_message: 'BLANK',
106
- allow_rebase_merge: false,
107
- allow_auto_merge: true,
108
- allow_update_branch: true,
109
- delete_branch_on_merge: true,
110
- };
111
- }
112
-
113
- // ...........................................................................
114
- function setupPullRequestSettings(slug) {
115
- console.log(gray('Setup pull request settings'));
116
-
117
- ghApi('PATCH', `repos/${slug}`, pullRequestSettings());
118
-
119
- console.log(green('✅ Pull request settings applied.'));
120
- }
121
-
122
- // ...........................................................................
123
- // The ruleset protecting the default branch
124
- function ruleset(requireReview) {
125
- return {
126
- name: rulesetName,
127
- target: 'branch',
128
- enforcement: 'active',
129
- conditions: {
130
- ref_name: { include: ['~DEFAULT_BRANCH'], exclude: [] },
131
- },
132
- rules: [
133
- { type: 'deletion' },
134
- { type: 'non_fast_forward' },
135
- { type: 'required_linear_history' },
136
- {
137
- type: 'pull_request',
138
- parameters: {
139
- required_approving_review_count: requireReview ? 1 : 0,
140
- dismiss_stale_reviews_on_push: false,
141
- require_code_owner_review: false,
142
- require_last_push_approval: requireReview,
143
- required_review_thread_resolution: requireReview,
144
- allowed_merge_methods: ['squash'],
145
- },
146
- },
147
- {
148
- type: 'required_status_checks',
149
- parameters: {
150
- strict_required_status_checks_policy: true,
151
- required_status_checks: [
152
- {
153
- context: requiredStatusCheck,
154
- integration_id: gitHubActionsAppId,
155
- },
156
- ],
157
- },
158
- },
159
- ],
160
- };
161
- }
162
-
163
- // ...........................................................................
164
- // Find the id of an already existing ruleset with the same name
165
- function existingRulesetId(slug) {
166
- const json = runCommand(`gh api repos/${slug}/rulesets`);
167
- const found = JSON.parse(json).find((r) => r.name === rulesetName);
168
- return found ? found.id : undefined;
169
- }
170
-
171
- // ...........................................................................
172
- // Create the ruleset or update it when it already exists
173
- function setupBranchRules(slug, requireReview) {
174
- console.log(gray('Setup branch rules'));
175
-
176
- const id = existingRulesetId(slug);
177
-
178
- if (id) {
179
- ghApi('PUT', `repos/${slug}/rulesets/${id}`, ruleset(requireReview));
180
- console.log(green(`✅ Ruleset '${rulesetName}' has been updated.`));
181
- } else {
182
- ghApi('POST', `repos/${slug}/rulesets`, ruleset(requireReview));
183
- console.log(green(`✅ Ruleset '${rulesetName}' has been created.`));
184
- }
185
- }
186
-
187
- // ...........................................................................
188
- // Print the repo and the settings without changing anything
189
- function dryRun(slug, requireReview) {
190
- console.log(cyan(slug));
191
- console.log(white(JSON.stringify(pullRequestSettings(), null, 2)));
192
- console.log(white(JSON.stringify(ruleset(requireReview), null, 2)));
193
- }
194
-
195
- // ...........................................................................
196
- function main() {
197
- const requireReview = process.argv.includes('--require-review');
198
- const isDryRun = !process.argv.includes('--apply');
199
-
200
- checkGhCli();
201
- const slug = repoSlug();
202
-
203
- if (isDryRun) {
204
- dryRun(slug, requireReview);
205
- return;
206
- }
207
-
208
- console.log(blue(`https://github.com/${slug}`));
209
-
210
- setupPullRequestSettings(slug);
211
- setupBranchRules(slug, requireReview);
212
-
213
- console.log(green(`✅ Repository '${slug}' has been set up.`));
214
- }
215
-
216
- try {
217
- main();
218
- } catch (error) {
219
- console.error(red(`Error: ${error.message}`));
220
- process.exit(1);
221
- }
@@ -1,90 +0,0 @@
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 { blue, green, red, yellow } from './functions/colors.js';
10
- import { runCommand } from './functions/run-command.js';
11
-
12
- function getPRUrl() {
13
- try {
14
- const json = runCommand('gh pr view --json url').trim();
15
-
16
- const parsed = JSON.parse(json);
17
- const url = parsed.url;
18
- console.log(blue(url));
19
- } catch (error) {
20
- console.error(yellow('No PR available'));
21
- process.exit(1);
22
- }
23
- }
24
-
25
- function getPRStatus() {
26
- try {
27
- const jsonString = runCommand(
28
- 'gh pr view --json state',
29
- true,
30
- false,
31
- ).trim();
32
-
33
- const jsonParsed = JSON.parse(jsonString);
34
- return jsonParsed.state;
35
- } catch (error) {
36
- console.error(red('Error fetching PR status'));
37
- process.exit(1);
38
- }
39
- }
40
-
41
- async function checkIfPipelineHasFailed() {
42
- try {
43
- const json = runCommand(
44
- 'gh run list --repo rljson/template-project --limit 1 --json status,conclusion',
45
- true,
46
- false,
47
- ).trim();
48
-
49
- const jsonParsed = JSON.parse(json);
50
- if (jsonParsed.length) {
51
- if (jsonParsed[0].conclusion === 'failure') {
52
- console.error(red('Pipeline has failed. Please fix.'));
53
- process.exit(1);
54
- }
55
- }
56
- } catch (e) {
57
- console.error(red('Error fetching pipeline status'));
58
- process.exit(1);
59
- }
60
- }
61
-
62
- async function waitForPRClosure() {
63
- console.log(yellow('Wait for pipelines, code review and merge ...'));
64
-
65
- while (true) {
66
- const status = getPRStatus();
67
-
68
- if (status === 'MERGED') {
69
- console.log(green('PR has been merged.'));
70
- break;
71
- } else if (status === 'CLOSED') {
72
- console.log(green('PR has been closed.'));
73
- break;
74
- } else if (status === 'FAILED') {
75
- console.log(red('Error: PR has failed.'));
76
- break;
77
- }
78
-
79
- await checkIfPipelineHasFailed();
80
-
81
- await new Promise((resolve) => setTimeout(resolve, 2000));
82
- }
83
- }
84
-
85
- async function main() {
86
- getPRUrl();
87
- await waitForPRClosure();
88
- }
89
-
90
- main();