github-settings-as-code 3.0.1 → 3.0.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.
Files changed (32) hide show
  1. package/dist/cmd/repo-settings.mjs +5 -5
  2. package/dist/github/actions-settings/api/get-actions-settings.mjs +7 -7
  3. package/dist/github/actions-settings/api/set-actions-settings.mjs +7 -7
  4. package/dist/github/actions-settings/apply.mjs +5 -5
  5. package/dist/github/actions-settings/backup.mjs +2 -2
  6. package/dist/github/actions-settings/constants.mjs +24 -24
  7. package/dist/github/constants.mjs +2 -2
  8. package/dist/github/octokit.mjs +12 -12
  9. package/dist/github/pages/api/get-pages-settings.mjs +6 -6
  10. package/dist/github/pages/api/set-pages-settings.mjs +8 -8
  11. package/dist/github/pages/apply.mjs +10 -10
  12. package/dist/github/pages/backup.mjs +2 -2
  13. package/dist/github/pages/constants.mjs +6 -6
  14. package/dist/github/repository/api/get-repository.mjs +1 -1
  15. package/dist/github/repository/api/update-repository.mjs +3 -3
  16. package/dist/github/repository/apply.mjs +14 -14
  17. package/dist/github/repository/backup.mjs +1 -1
  18. package/dist/github/repository/constants.mjs +3 -3
  19. package/dist/github/resolve-target-repo.mjs +24 -24
  20. package/dist/github/ruleset/api/create-ruleset.mjs +7 -7
  21. package/dist/github/ruleset/api/get-all-rulesets.mjs +3 -3
  22. package/dist/github/ruleset/api/get-ruleset.mjs +1 -1
  23. package/dist/github/ruleset/api/update-ruleset.mjs +7 -7
  24. package/dist/github/ruleset/apply.mjs +3 -3
  25. package/dist/github/ruleset/backup.mjs +1 -1
  26. package/dist/github/ruleset/constants.mjs +3 -3
  27. package/dist/github/ruleset/read-rule-set-contents.mjs +2 -2
  28. package/dist/github/variables/api/create-repo-variable.mjs +1 -1
  29. package/dist/github/variables/api/list-repo-variables.mjs +1 -1
  30. package/dist/github/variables/api/update-repo-variable.mjs +1 -1
  31. package/dist/github/variables/apply.mjs +7 -7
  32. package/package.json +9 -9
@@ -52,8 +52,8 @@ const COMMAND_SET = new Set(COMMANDS);
52
52
  const isCommand = (value) => COMMAND_SET.has(value);
53
53
  const TARGET_SET = new Set(TARGETS);
54
54
  const isTarget = (value) => TARGET_SET.has(value);
55
- // NOTE: 明示的な型注釈を変数側に付けることで、呼び出し後に制御フローが到達
56
- // しないことを TypeScript に認識させる( narrowing のために必要 )。
55
+
56
+
57
57
  const fail = (message) => {
58
58
  console.error(`❌ ${message}`);
59
59
  console.error('');
@@ -72,7 +72,7 @@ const { values, positionals } = util.parseArgs({
72
72
  });
73
73
  if (values.help === true || Arr.isEmpty(positionals)) {
74
74
  console.info(HELP);
75
- // Asking for help is not an error, even though being given no command is.
75
+
76
76
  process.exit(values.help === true ? 0 : 1);
77
77
  }
78
78
  const [commandArg, targetArg = 'all'] = positionals;
@@ -82,8 +82,8 @@ if (commandArg === undefined || !isCommand(commandArg)) {
82
82
  if (!isTarget(targetArg)) {
83
83
  fail(`Unknown target: "${targetArg}"`);
84
84
  }
85
- // NOTE: ここで初めて設定モジュールを読み込む。 owner / repo の解決は import 時に
86
- // 走るため、 --help や引数エラーの時点では評価させたくない。
85
+
86
+
87
87
  const github = await import('../github/index.mjs');
88
88
  const runners = {
89
89
  apply: {
@@ -1,15 +1,15 @@
1
1
  import { octokitHeaders, OWNER, REPO } from '../../constants.mjs';
2
2
  import { octokit } from '../../octokit.mjs';
3
3
  import { ActionsSettings } from '../constants.mjs';
4
- /**
5
- * Settings > Actions > General の現在値を取得する。
6
- *
7
- * 2 つのエンドポイントの結果を 1 つのオブジェクトにまとめる。
8
- */
4
+
5
+
6
+
7
+
8
+
9
9
  export const getActionsSettings = async () => {
10
- // https://docs.github.com/rest/actions/permissions#get-github-actions-permissions-for-a-repository
10
+
11
11
  const permissions = await octokit.request('GET /repos/{owner}/{repo}/actions/permissions', { owner: OWNER, repo: REPO, headers: octokitHeaders });
12
- // https://docs.github.com/rest/actions/permissions#get-fork-pr-contributor-approval-permissions-for-a-repository
12
+
13
13
  const forkPrApproval = await octokit.request('GET /repos/{owner}/{repo}/actions/permissions/fork-pr-contributor-approval', { owner: OWNER, repo: REPO, headers: octokitHeaders });
14
14
  return ActionsSettings.fill({
15
15
  enabled: permissions.data.enabled,
@@ -1,12 +1,12 @@
1
1
  import { octokitHeaders, OWNER, REPO } from '../../constants.mjs';
2
2
  import { octokit } from '../../octokit.mjs';
3
- /** Settings > Actions > General を JSON の内容に合わせる。 */
3
+
4
4
  export const setActionsSettings = async (settings) => {
5
- // https://docs.github.com/rest/actions/permissions#set-github-actions-permissions-for-a-repository
6
- //
7
- // NOTE: sha_pinning_required は @octokit/types v16 の型定義にまだ無い。 API は
8
- // 受け付けるので、いったん変数に束縛して渡している(オブジェクトリテラルを直接
9
- // 渡すと excess property check に引っかかるため)。型が追随したら不要になる。
5
+
6
+
7
+
8
+
9
+
10
10
  const permissionsParams = {
11
11
  owner: OWNER,
12
12
  repo: REPO,
@@ -16,7 +16,7 @@ export const setActionsSettings = async (settings) => {
16
16
  sha_pinning_required: settings.sha_pinning_required,
17
17
  };
18
18
  await octokit.request('PUT /repos/{owner}/{repo}/actions/permissions', permissionsParams);
19
- // https://docs.github.com/rest/actions/permissions#set-fork-pr-contributor-approval-permissions-for-a-repository
19
+
20
20
  await octokit.request('PUT /repos/{owner}/{repo}/actions/permissions/fork-pr-contributor-approval', {
21
21
  owner: OWNER,
22
22
  repo: REPO,
@@ -8,24 +8,24 @@ import { actionsSettingsDir, settingsJsonName } from '../constants.mjs';
8
8
  import { getActionsSettings, setActionsSettings } from './api/index.mjs';
9
9
  import { backupActionsSettings } from './backup.mjs';
10
10
  import { ActionsSettings } from './constants.mjs';
11
- /** `repo-settings/actions-settings/settings.json` を Settings > Actions > General に反映する。 */
11
+
12
12
  export const applyActionsSettings = async () => {
13
13
  await backupActionsSettings(false);
14
14
  const settings = await readSettings();
15
15
  await setActionsSettings(settings);
16
- // 反映後の実際の値でローカルのファイルを更新する
16
+
17
17
  {
18
18
  const applied = await getActionsSettings();
19
19
  const str = JSON.stringify(applied, undefined, 2);
20
- // eslint-disable-next-line security/detect-non-literal-fs-filename
20
+
21
21
  await fs.writeFile(path.resolve(actionsSettingsDir, settingsJsonName), str);
22
- // eslint-disable-next-line security/detect-non-literal-fs-filename
22
+
23
23
  await fs.writeFile(path.resolve(actionsSettingsDir, 'bk', settingsJsonName), str);
24
24
  await formatUncommittedFiles();
25
25
  }
26
26
  };
27
27
  const readSettings = async () => {
28
- // eslint-disable-next-line security/detect-non-literal-fs-filename
28
+
29
29
  const settingsText = await fs.readFile(path.resolve(actionsSettingsDir, settingsJsonName), { encoding: 'utf8' });
30
30
  const validationResult = ActionsSettings.validate(JSON.parse(settingsText));
31
31
  if (Result.isErr(validationResult)) {
@@ -6,11 +6,11 @@ import { formatUncommittedFiles, isDirectlyExecuted, makeEmptyDir, } from 'ts-re
6
6
  import { actionsSettingsDir, settingsJsonName } from '../constants.mjs';
7
7
  import { getActionsSettings } from './api/index.mjs';
8
8
  const backupDir = path.resolve(actionsSettingsDir, './bk');
9
- /** Settings > Actions > General の現在値を `bk/` に保存する。 */
9
+
10
10
  export const backupActionsSettings = async (fmt = true) => {
11
11
  await makeEmptyDir(backupDir);
12
12
  const settings = await getActionsSettings();
13
- // eslint-disable-next-line security/detect-non-literal-fs-filename
13
+
14
14
  await fs.writeFile(path.resolve(backupDir, settingsJsonName), JSON.stringify(settings, undefined, 2));
15
15
  if (fmt) {
16
16
  await formatUncommittedFiles();
@@ -1,35 +1,35 @@
1
1
  import * as t from 'ts-fortress';
2
- /**
3
- * `repo-settings/actions-settings/settings.json` の型。
4
- *
5
- * Settings > Actions > General に対応する。 2 つの API をまとめて 1 ファイルで
6
- * 管理する:
7
- *
8
- * - `GET|PUT /repos/{owner}/{repo}/actions/permissions`
9
- * - `GET|PUT /repos/{owner}/{repo}/actions/permissions/fork-pr-contributor-approval`
10
- */
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
11
  export const ActionsSettings = t.record({
12
- /** Actions permissions: この repository で Actions を有効にするか。 */
12
+
13
13
  enabled: t.boolean(true),
14
- /** Actions permissions: 実行を許可する action の範囲。 */
14
+
15
15
  allowed_actions: t.enumType(['all', 'local_only', 'selected'], {
16
16
  typeName: 'AllowedActions',
17
17
  defaultValue: 'all',
18
18
  }),
19
- /**
20
- * 「 Require actions to be pinned to a full-length commit SHA 」。
21
- *
22
- * NOTE: @octokit/types v16 時点でこのフィールドは型定義に入っていないため、
23
- * リクエスト時にのみ手当てしている( set-actions-permissions.mts 参照)。
24
- */
19
+
20
+
21
+
22
+
23
+
24
+
25
25
  sha_pinning_required: t.boolean(false),
26
- /**
27
- * 「 Approval for running fork pull request workflows from contributors 」。
28
- *
29
- * - `first_time_contributors_new_to_github`: GitHub 自体に不慣れな初回貢献者のみ承認必須
30
- * - `first_time_contributors`: この repository への初回貢献者のみ承認必須
31
- * - `all_external_contributors`: 外部貢献者すべてに承認必須
32
- */
26
+
27
+
28
+
29
+
30
+
31
+
32
+
33
33
  fork_pr_contributor_approval_policy: t.enumType([
34
34
  'first_time_contributors_new_to_github',
35
35
  'first_time_contributors',
@@ -3,8 +3,8 @@ import { resolveTargetRepo } from './resolve-target-repo.mjs';
3
3
  const targetRepo = await resolveTargetRepo();
4
4
  export const OWNER = targetRepo.owner;
5
5
  export const REPO = targetRepo.repo;
6
- // Named `repo-settings` rather than `github`, which read as a sibling of
7
- // `.github` and invited the guess that workflows lived here too.
6
+
7
+
8
8
  const settingsDir = path.resolve(process.cwd(), './repo-settings');
9
9
  export const settingsJsonName = 'settings.json';
10
10
  export const repositorySettingsDir = path.resolve(settingsDir, './repository-settings');
@@ -1,15 +1,15 @@
1
1
  import { Octokit } from '@octokit/core';
2
2
  import { execFileSync } from 'node:child_process';
3
- /**
4
- * GitHub API の認証トークンを解決する。
5
- *
6
- * 1. 明示的な環境変数 … CI では GitHub App の installation token を渡す。
7
- * 2. `gh auth token` … ローカルでは gh CLI のログイン情報をそのまま使うため、 PAT
8
- * を .env などに平文で置く必要がない。
9
- *
10
- * `GITHUB_TOKEN` は候補に含めない。 Actions では既定の( administration 権限を
11
- * 持たない)トークンが同名で入っていることがあり、黙って弱い方を掴むため。
12
- */
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
13
  const resolveAuthToken = () => {
14
14
  const fromEnv = process.env['GITHUB_APP_TOKEN'] ??
15
15
  process.env['GH_TOKEN'] ??
@@ -18,7 +18,7 @@ const resolveAuthToken = () => {
18
18
  return fromEnv;
19
19
  }
20
20
  try {
21
- // シェルを経由させない。 stderr も捨てて、失敗時にトークンが混ざらないようにする。
21
+
22
22
  const stdout = execFileSync('gh', ['auth', 'token'], {
23
23
  encoding: 'utf8',
24
24
  stdio: ['ignore', 'pipe', 'ignore'],
@@ -29,7 +29,7 @@ const resolveAuthToken = () => {
29
29
  }
30
30
  }
31
31
  catch {
32
- // gh が未インストール、または未ログイン。下のエラーにまとめる。
32
+
33
33
  }
34
34
  throw new Error([
35
35
  'GitHub のトークンが見つかりませんでした。',
@@ -2,14 +2,14 @@ import { hasKey, isRecord } from 'ts-data-forge';
2
2
  import { octokitHeaders, OWNER, REPO } from '../../constants.mjs';
3
3
  import { octokit } from '../../octokit.mjs';
4
4
  import { PagesSettings } from '../constants.mjs';
5
- /**
6
- * GitHub Pages の現在の設定を取得する。
7
- *
8
- * Pages が未設定の repository では 404 が返るため、その場合は `undefined` を返す。
9
- */
5
+
6
+
7
+
8
+
9
+
10
10
  export const getPagesSettings = async () => {
11
11
  try {
12
- // https://docs.github.com/rest/pages/pages#get-a-apiname-pages-site
12
+
13
13
  const response = await octokit.request('GET /repos/{owner}/{repo}/pages', { owner: OWNER, repo: REPO, headers: octokitHeaders });
14
14
  return PagesSettings.fill({ build_type: response.data.build_type });
15
15
  }
@@ -1,16 +1,16 @@
1
1
  import { octokitHeaders, OWNER, REPO } from '../../constants.mjs';
2
2
  import { octokit } from '../../octokit.mjs';
3
3
  import { getPagesSettings } from './get-pages-settings.mjs';
4
- /**
5
- * GitHub Pages の Source を JSON の内容に合わせる。
6
- *
7
- * Pages 未設定なら POST で作成し、設定済みなら PUT で更新する。 これにより
8
- * 「 Settings > Pages で Source を GitHub Actions に変更する 」という手作業が不要になる。
9
- */
4
+
5
+
6
+
7
+
8
+
9
+
10
10
  export const setPagesSettings = async (settings) => {
11
11
  const current = await getPagesSettings();
12
12
  if (current === undefined) {
13
- // https://docs.github.com/rest/pages/pages#create-a-apiname-pages-site
13
+
14
14
  await octokit.request('POST /repos/{owner}/{repo}/pages', {
15
15
  owner: OWNER,
16
16
  repo: REPO,
@@ -22,7 +22,7 @@ export const setPagesSettings = async (settings) => {
22
22
  if (current.build_type === settings.build_type) {
23
23
  return;
24
24
  }
25
- // https://docs.github.com/rest/pages/pages#update-information-about-a-apiname-pages-site
25
+
26
26
  await octokit.request('PUT /repos/{owner}/{repo}/pages', {
27
27
  owner: OWNER,
28
28
  repo: REPO,
@@ -7,12 +7,12 @@ import { formatUncommittedFiles, isDirectlyExecuted, pathExists, Result, } from
7
7
  import { pagesSettingsDir, settingsJsonName } from '../constants.mjs';
8
8
  import { getPagesSettings, setPagesSettings } from './api/index.mjs';
9
9
  import { PagesSettings } from './constants.mjs';
10
- /**
11
- * `repo-settings/pages/settings.json` を Settings > Pages に反映する。
12
- *
13
- * 設定ファイルを置いていない repository では何もしない( Pages を使わない
14
- * repository で誤って Pages を有効化しないため)。
15
- */
10
+
11
+
12
+
13
+
14
+
15
+
16
16
  export const applyPagesSettings = async () => {
17
17
  const settingsPath = path.resolve(pagesSettingsDir, settingsJsonName);
18
18
  if (!(await pathExists(settingsPath))) {
@@ -20,22 +20,22 @@ export const applyPagesSettings = async () => {
20
20
  }
21
21
  const settings = await readSettings(settingsPath);
22
22
  await setPagesSettings(settings);
23
- // 反映後の実際の値でローカルのファイルを更新する
23
+
24
24
  {
25
25
  const applied = await getPagesSettings();
26
26
  if (applied === undefined) {
27
27
  return;
28
28
  }
29
29
  const str = JSON.stringify(applied, undefined, 2);
30
- // eslint-disable-next-line security/detect-non-literal-fs-filename
30
+
31
31
  await fs.writeFile(settingsPath, str);
32
- // eslint-disable-next-line security/detect-non-literal-fs-filename
32
+
33
33
  await fs.writeFile(path.resolve(pagesSettingsDir, 'bk', settingsJsonName), str);
34
34
  await formatUncommittedFiles();
35
35
  }
36
36
  };
37
37
  const readSettings = async (settingsPath) => {
38
- // eslint-disable-next-line security/detect-non-literal-fs-filename
38
+
39
39
  const settingsText = await fs.readFile(settingsPath, { encoding: 'utf8' });
40
40
  const validationResult = PagesSettings.validate(JSON.parse(settingsText));
41
41
  if (Result.isErr(validationResult)) {
@@ -6,7 +6,7 @@ import { formatUncommittedFiles, isDirectlyExecuted, makeEmptyDir, } from 'ts-re
6
6
  import { pagesSettingsDir, settingsJsonName } from '../constants.mjs';
7
7
  import { getPagesSettings } from './api/index.mjs';
8
8
  const backupDir = path.resolve(pagesSettingsDir, './bk');
9
- /** Settings > Pages の現在値を `bk/` に保存する。 */
9
+
10
10
  export const backupPagesSettings = async (fmt = true) => {
11
11
  await makeEmptyDir(backupDir);
12
12
  const settings = await getPagesSettings();
@@ -14,7 +14,7 @@ export const backupPagesSettings = async (fmt = true) => {
14
14
  console.warn('GitHub Pages is not enabled for this repository; skipping backup.');
15
15
  return;
16
16
  }
17
- // eslint-disable-next-line security/detect-non-literal-fs-filename
17
+
18
18
  await fs.writeFile(path.resolve(backupDir, settingsJsonName), JSON.stringify(settings, undefined, 2));
19
19
  if (fmt) {
20
20
  await formatUncommittedFiles();
@@ -1,10 +1,10 @@
1
1
  import * as t from 'ts-fortress';
2
- /**
3
- * `repo-settings/pages/settings.json` の型。
4
- *
5
- * Settings > Pages > Build and deployment > Source に対応する。
6
- * `workflow` が GUI 上の「 GitHub Actions 」、 `legacy` が「 Deploy from a branch 」。
7
- */
2
+
3
+
4
+
5
+
6
+
7
+
8
8
  export const PagesSettings = t.record({
9
9
  build_type: t.enumType(['workflow', 'legacy'], {
10
10
  typeName: 'PagesBuildType',
@@ -4,7 +4,7 @@ import { Result } from 'ts-repo-utils';
4
4
  import { octokitHeaders, OWNER, REPO } from '../../constants.mjs';
5
5
  import { octokit } from '../../octokit.mjs';
6
6
  export const getRepositorySettings = async () => {
7
- // https://docs.github.com/ja/rest/repos/repos?apiVersion=2022-11-28#get-a-repository
7
+
8
8
  const getRepositoryResult = await octokit.request('GET /repos/{owner}/{repo}', {
9
9
  owner: OWNER,
10
10
  repo: REPO,
@@ -1,8 +1,8 @@
1
- import { castDeepMutable } from 'ts-data-forge';
1
+
2
2
  import { octokitHeaders, OWNER, REPO } from '../../constants.mjs';
3
3
  import { octokit } from '../../octokit.mjs';
4
4
  export const updateRepository = async ({ payload: { name, description, homepage, private: private_, visibility, security_and_analysis, has_issues, has_projects, has_wiki, has_pull_requests, pull_request_creation_policy, is_template, default_branch, allow_squash_merge, allow_merge_commit, allow_rebase_merge, allow_auto_merge, delete_branch_on_merge, allow_update_branch, use_squash_pr_title_as_default, squash_merge_commit_title, squash_merge_commit_message, merge_commit_title, merge_commit_message, archived, allow_forking, web_commit_signoff_required, }, }) => {
5
- // https://docs.github.com/ja/rest/repos/repos?apiVersion=2022-11-28#update-a-repository
5
+
6
6
  await octokit.request('PATCH /repos/{owner}/{repo}', {
7
7
  owner: OWNER,
8
8
  repo: REPO,
@@ -12,7 +12,7 @@ export const updateRepository = async ({ payload: { name, description, homepage,
12
12
  homepage,
13
13
  private: private_,
14
14
  visibility,
15
- security_and_analysis: castDeepMutable(security_and_analysis),
15
+ security_and_analysis: security_and_analysis,
16
16
  has_issues,
17
17
  has_projects,
18
18
  has_wiki,
@@ -14,17 +14,17 @@ export const applyRepositorySettings = async () => {
14
14
  const settings = await readSettings();
15
15
  await updateRepository({
16
16
  payload: {
17
- // name: settings.name,
18
- // description: settings.description ?? undefined,
19
- // homepage: settings.homepage ?? undefined,
20
- // private: settings.private,
21
- // visibility: settings.visibility === 'public' ? 'public' : 'private',
17
+
18
+
19
+
20
+
21
+
22
22
  security_and_analysis: settings.security_and_analysis,
23
23
  has_issues: settings.has_issues,
24
24
  has_projects: settings.has_projects,
25
25
  has_wiki: settings.has_wiki,
26
- // is_template: settings.is_template,
27
- // default_branch: settings.default_branch,
26
+
27
+
28
28
  allow_squash_merge: settings.allow_squash_merge,
29
29
  allow_merge_commit: settings.allow_merge_commit,
30
30
  allow_rebase_merge: settings.allow_rebase_merge,
@@ -36,25 +36,25 @@ export const applyRepositorySettings = async () => {
36
36
  squash_merge_commit_message: settings.squash_merge_commit_message,
37
37
  merge_commit_title: settings.merge_commit_title,
38
38
  merge_commit_message: settings.merge_commit_message,
39
- // archived: settings.archived,
40
- // NOTE: "RequestError [HttpError]: Allow forks can only be changed on org-owned repositories"
41
- // allow_forking: settings.allow_forking,
39
+
40
+
41
+
42
42
  web_commit_signoff_required: settings.web_commit_signoff_required,
43
43
  },
44
44
  });
45
- // update local setting file
45
+
46
46
  {
47
47
  const repositorySettings = await getRepositorySettings();
48
48
  const str = JSON.stringify(Obj.pick(repositorySettings, repositoryKeysToPick), undefined, 2);
49
- // eslint-disable-next-line security/detect-non-literal-fs-filename
49
+
50
50
  await fs.writeFile(path.resolve(repositorySettingsDir, settingsJsonName), str);
51
- // eslint-disable-next-line security/detect-non-literal-fs-filename
51
+
52
52
  await fs.writeFile(path.resolve(repositorySettingsDir, 'bk', settingsJsonName), str);
53
53
  await formatUncommittedFiles();
54
54
  }
55
55
  };
56
56
  const readSettings = async () => {
57
- // eslint-disable-next-line security/detect-non-literal-fs-filename
57
+
58
58
  const settingsText = await fs.readFile(path.resolve(repositorySettingsDir, settingsJsonName), {
59
59
  encoding: 'utf8',
60
60
  });
@@ -11,7 +11,7 @@ const backupDir = path.resolve(repositorySettingsDir, './bk');
11
11
  export const backupRepositorySettings = async (fmt = true) => {
12
12
  await makeEmptyDir(backupDir);
13
13
  const repositorySettings = await getRepositorySettings();
14
- // eslint-disable-next-line security/detect-non-literal-fs-filename
14
+
15
15
  await fs.writeFile(path.resolve(backupDir, settingsJsonName), JSON.stringify(Obj.pick(repositorySettings, repositoryKeysToPick), undefined, 2));
16
16
  if (fmt) {
17
17
  await formatUncommittedFiles();
@@ -1,5 +1,5 @@
1
1
  import { FullRepository, } from 'octokit-safe-types';
2
- import { expectType } from 'ts-data-forge';
2
+
3
3
  import * as t from 'ts-fortress';
4
4
  const keysToPickBase = [
5
5
  'allow_auto_merge',
@@ -37,11 +37,11 @@ const keysToDropList = [
37
37
  'is_template',
38
38
  'archived',
39
39
  'visibility',
40
- // NOTE: "RequestError [HttpError]: Allow forks can only be changed on org-owned repositories"
40
+
41
41
  'allow_forking',
42
42
  ];
43
43
  const keysToDrop = new Set(keysToDropList);
44
44
  export const repositoryKeysToPick = keysToPickBase.filter((k) => !keysToDrop.has(k));
45
- expectType('>=');
45
+
46
46
  export const RepositoryPicked = t.pick(FullRepository, repositoryKeysToPick);
47
47
  //# sourceMappingURL=constants.mjs.map
@@ -4,13 +4,13 @@ import * as path from 'node:path';
4
4
  import * as util from 'node:util';
5
5
  import { Arr, hasKey, isRecord, isString, Json } from 'ts-data-forge';
6
6
  import { Result } from 'ts-repo-utils';
7
- /** `strict: false` の解析結果は string | boolean | 配列 になりうるため絞り込む。 */
7
+
8
8
  const pickString = (value) => value !== '' && isString(value) ? value : undefined;
9
- /**
10
- * コマンドライン引数から `--owner` / `--repo` を読む。
11
- *
12
- * サブコマンドや他のフラグと共存できるよう `strict: false` で解析する。
13
- */
9
+
10
+
11
+
12
+
13
+
14
14
  const fromCliArgs = () => {
15
15
  const { values } = util.parseArgs({
16
16
  args: Arr.skip(process.argv, 2),
@@ -30,12 +30,12 @@ const fromEnv = () => ({
30
30
  owner: process.env['OWNER'],
31
31
  repo: process.env['REPO_NAME'],
32
32
  });
33
- /**
34
- * `git remote get-url origin` から owner / repo を推測する。
35
- *
36
- * SSH 形式( `git@github.com:owner/repo.git` )と HTTPS 形式
37
- * ( `https://github.com/owner/repo.git` )の両方に対応する。
38
- */
33
+
34
+
35
+
36
+
37
+
38
+
39
39
  const fromGitRemote = () => {
40
40
  try {
41
41
  const url = execFileSync('git', ['remote', 'get-url', 'origin'], {
@@ -52,11 +52,11 @@ const fromGitRemote = () => {
52
52
  return {};
53
53
  }
54
54
  };
55
- /** `package.json` の `name` を repository 名として使う(最後の手段)。 */
55
+
56
56
  const fromPackageJson = async () => {
57
57
  const packageJsonPath = path.resolve(process.cwd(), './package.json');
58
58
  try {
59
- // eslint-disable-next-line security/detect-non-literal-fs-filename
59
+
60
60
  const packageJsonStr = await fs.readFile(packageJsonPath, {
61
61
  encoding: 'utf8',
62
62
  });
@@ -73,16 +73,16 @@ const fromPackageJson = async () => {
73
73
  return {};
74
74
  }
75
75
  };
76
- /**
77
- * 操作対象の repository を決める。
78
- *
79
- * 優先順位は以下の通り。
80
- *
81
- * 1. コマンドライン引数 `--owner` / `--repo`
82
- * 2. 環境変数 `OWNER` / `REPO_NAME`
83
- * 3. `git remote get-url origin`
84
- * 4. `package.json` の `name` ( repo のみ )
85
- */
76
+
77
+
78
+
79
+
80
+
81
+
82
+
83
+
84
+
85
+
86
86
  export const resolveTargetRepo = async () => {
87
87
  const sources = [
88
88
  fromCliArgs(),
@@ -1,10 +1,10 @@
1
- import { castDeepMutable } from 'ts-data-forge';
1
+
2
2
  import { octokitHeaders, OWNER, REPO } from '../../constants.mjs';
3
3
  import { octokit } from '../../octokit.mjs';
4
4
  export const createRuleset = async ({ payload: { name, target, enforcement, bypass_actors, conditions, rules }, }) => {
5
- // https://docs.github.com/ja/rest/repos/rules?apiVersion=2022-11-28#create-a-repository-ruleset
5
+
6
6
  await octokit.request('POST /repos/{owner}/{repo}/rulesets',
7
- // transformer-ignore-next-line convert-to-readonly
7
+
8
8
  {
9
9
  owner: OWNER,
10
10
  repo: REPO,
@@ -12,10 +12,10 @@ export const createRuleset = async ({ payload: { name, target, enforcement, bypa
12
12
  name,
13
13
  target,
14
14
  enforcement,
15
- bypass_actors: castDeepMutable(bypass_actors),
16
- conditions: castDeepMutable(conditions),
17
- // eslint-disable-next-line total-functions/no-unsafe-type-assertion
18
- rules: castDeepMutable(rules),
15
+ bypass_actors: bypass_actors,
16
+ conditions: conditions,
17
+
18
+ rules: rules,
19
19
  });
20
20
  };
21
21
  //# sourceMappingURL=create-ruleset.mjs.map
@@ -1,17 +1,17 @@
1
1
  import { GetAllRulesetsResponse, } from 'octokit-safe-types';
2
- import { expectType } from 'ts-data-forge';
2
+
3
3
  import * as t from 'ts-fortress';
4
4
  import { Result } from 'ts-repo-utils';
5
5
  import { octokitHeaders, OWNER, REPO } from '../../constants.mjs';
6
6
  import { octokit } from '../../octokit.mjs';
7
7
  export const getAllRulesets = async () => {
8
- // https://docs.github.com/ja/rest/repos/rules?apiVersion=2022-11-28#get-all-repository-rulesets
8
+
9
9
  const getAllRulesetsResponse = (await octokit.request('GET /repos/{owner}/{repo}/rulesets', {
10
10
  owner: OWNER,
11
11
  repo: REPO,
12
12
  headers: octokitHeaders,
13
13
  }));
14
- expectType('=');
14
+
15
15
  {
16
16
  const res = GetAllRulesetsResponse.validate(getAllRulesetsResponse.data);
17
17
  if (Result.isErr(res)) {
@@ -4,7 +4,7 @@ import { Result } from 'ts-repo-utils';
4
4
  import { octokitHeaders, OWNER, REPO } from '../../constants.mjs';
5
5
  import { octokit } from '../../octokit.mjs';
6
6
  export const getRuleset = async (rulesetId) => {
7
- // https://docs.github.com/ja/rest/repos/rules?apiVersion=2022-11-28#get-a-repository-ruleset
7
+
8
8
  const getRulesetResult = await octokit.request('GET /repos/{owner}/{repo}/rulesets/{ruleset_id}', {
9
9
  owner: OWNER,
10
10
  repo: REPO,
@@ -1,10 +1,10 @@
1
- import { castDeepMutable } from 'ts-data-forge';
1
+
2
2
  import { octokitHeaders, OWNER, REPO } from '../../constants.mjs';
3
3
  import { octokit } from '../../octokit.mjs';
4
4
  export const updateRuleset = async ({ rulesetId, payload: { name, target, enforcement, bypass_actors, conditions, rules }, }) => {
5
- // https://docs.github.com/ja/rest/repos/rules?apiVersion=2022-11-28#update-a-repository-ruleset
5
+
6
6
  await octokit.request('PUT /repos/{owner}/{repo}/rulesets/{ruleset_id}',
7
- // transformer-ignore-next-line convert-to-readonly
7
+
8
8
  {
9
9
  owner: OWNER,
10
10
  repo: REPO,
@@ -13,10 +13,10 @@ export const updateRuleset = async ({ rulesetId, payload: { name, target, enforc
13
13
  name,
14
14
  target,
15
15
  enforcement,
16
- bypass_actors: castDeepMutable(bypass_actors),
17
- conditions: castDeepMutable(conditions),
18
- // eslint-disable-next-line total-functions/no-unsafe-type-assertion
19
- rules: castDeepMutable(rules),
16
+ bypass_actors: bypass_actors,
17
+ conditions: conditions,
18
+
19
+ rules: rules,
20
20
  });
21
21
  };
22
22
  //# sourceMappingURL=update-ruleset.mjs.map
@@ -40,15 +40,15 @@ export const applyRulesets = async () => {
40
40
  },
41
41
  });
42
42
  }
43
- // update local ruleset files
43
+
44
44
  {
45
45
  const rulesetsResult = await getAllRulesets();
46
46
  for (const rule of rulesetsResult) {
47
47
  const content = await getRuleset(rule.id);
48
48
  const str = JSON.stringify(Obj.pick(content, rulesetKeysToPick), undefined, 2);
49
- // eslint-disable-next-line security/detect-non-literal-fs-filename
49
+
50
50
  await fs.writeFile(path.resolve(rulesetsDir, `${rule.name}.json`), str);
51
- // eslint-disable-next-line security/detect-non-literal-fs-filename
51
+
52
52
  await fs.writeFile(path.resolve(rulesetsDir, `./bk/${rule.name}.json`), str);
53
53
  }
54
54
  await formatUncommittedFiles();
@@ -13,7 +13,7 @@ export const backupRulesets = async (fmt = true) => {
13
13
  const rulesetsResult = await getAllRulesets();
14
14
  for (const rule of rulesetsResult) {
15
15
  const content = await getRuleset(rule.id);
16
- // eslint-disable-next-line security/detect-non-literal-fs-filename
16
+
17
17
  await fs.writeFile(path.resolve(backupDir, `${rule.name}.json`), JSON.stringify(Obj.pick(content, rulesetKeysToPick), undefined, 2));
18
18
  }
19
19
  if (fmt) {
@@ -1,5 +1,5 @@
1
1
  import { RepositoryRuleset, } from 'octokit-safe-types';
2
- import { expectType } from 'ts-data-forge';
2
+
3
3
  import * as t from 'ts-fortress';
4
4
  export const rulesetKeysToPick = [
5
5
  'id',
@@ -10,7 +10,7 @@ export const rulesetKeysToPick = [
10
10
  'conditions',
11
11
  'rules',
12
12
  ];
13
- expectType('=');
14
- expectType('=');
13
+
14
+
15
15
  export const RulesetPicked = t.pick(RepositoryRuleset, rulesetKeysToPick);
16
16
  //# sourceMappingURL=constants.mjs.map
@@ -7,14 +7,14 @@ import { RulesetPicked } from './constants.mjs';
7
7
  export const readRulesetFiles = async () => readFilesIn(rulesetsDir);
8
8
  export const readRulesetBackupFiles = async () => readFilesIn(path.resolve(rulesetsDir, './bk'));
9
9
  const readFilesIn = async (dir) => {
10
- // eslint-disable-next-line security/detect-non-literal-fs-filename
10
+
11
11
  const files = await fs.readdir(dir, {
12
12
  withFileTypes: true,
13
13
  });
14
14
  const rulesetFileContents = await Promise.all(files
15
15
  .filter((d) => d.isFile())
16
16
  .map((d) =>
17
- // eslint-disable-next-line security/detect-non-literal-fs-filename
17
+
18
18
  fs.readFile(path.resolve(dir, d.name), {
19
19
  encoding: 'utf8',
20
20
  })));
@@ -1,7 +1,7 @@
1
1
  import { octokitHeaders, OWNER, REPO } from '../../constants.mjs';
2
2
  import { octokit } from '../../octokit.mjs';
3
3
  export const createRepoVariable = async ({ name, value, }) => {
4
- // https://docs.github.com/ja/rest/actions/variables?apiVersion=2022-11-28#create-a-repository-variable
4
+
5
5
  await octokit.request('POST /repos/{owner}/{repo}/actions/variables', {
6
6
  owner: OWNER,
7
7
  repo: REPO,
@@ -1,7 +1,7 @@
1
1
  import { octokitHeaders, OWNER, REPO } from '../../constants.mjs';
2
2
  import { octokit } from '../../octokit.mjs';
3
3
  export const listRepoVariables = async () => {
4
- // https://docs.github.com/ja/rest/actions/variables?apiVersion=2022-11-28#list-repository-variables
4
+
5
5
  const result = await octokit.request('GET /repos/{owner}/{repo}/actions/variables', {
6
6
  owner: OWNER,
7
7
  repo: REPO,
@@ -1,7 +1,7 @@
1
1
  import { octokitHeaders, OWNER, REPO } from '../../constants.mjs';
2
2
  import { octokit } from '../../octokit.mjs';
3
3
  export const updateRepoVariable = async ({ name, value, }) => {
4
- // https://docs.github.com/en/rest/actions/variables#update-a-repository-variable
4
+
5
5
  await octokit.request('PATCH /repos/{owner}/{repo}/actions/variables/{name}', {
6
6
  owner: OWNER,
7
7
  repo: REPO,
@@ -1,21 +1,21 @@
1
1
  #!/usr/bin/env node
2
- // cspell:ignore lilpi
2
+
3
3
  import 'dotenv/config';
4
4
  import { isDirectlyExecuted } from 'ts-repo-utils';
5
5
  import { createRepoVariable, listRepoVariables, updateRepoVariable, } from './api/index.mjs';
6
6
  export const applyVariables = async () => {
7
7
  const variables = [
8
8
  {
9
- // noshiro-repo-settings-bot (App ID: 4476573)
10
- // Administration を含む強い権限を持つ。設定の backup / apply 専用。
9
+
10
+
11
11
  name: 'REPO_SETTINGS_BOT_CLIENT_ID',
12
12
  value: 'Iv23ctr4Lq9H3wOYRQrD',
13
13
  },
14
14
  {
15
- // noshiro-repo-automation-bot (App ID: 4478721)
16
- // Contents / Pull requests / Issues 権限。PR を作る自動化( pnpm-update )と
17
- // release で共用する。実際に使う権限は各ワークフローの permission-* 入力で
18
- // 絞る。
15
+
16
+
17
+
18
+
19
19
  name: 'REPO_AUTOMATION_BOT_CLIENT_ID',
20
20
  value: 'Iv23lilpiDh0twq2PErv',
21
21
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "github-settings-as-code",
3
- "version": "3.0.1",
3
+ "version": "3.0.2",
4
4
  "private": false,
5
5
  "keywords": [
6
6
  "github",
@@ -28,10 +28,10 @@
28
28
  "@octokit/core": "^7.0.8",
29
29
  "dedent": "^1.7.2",
30
30
  "dotenv": "^17.4.2",
31
- "octokit-safe-types": "^1.2.27",
32
- "ts-data-forge": "^14.6.2",
33
- "ts-fortress": "^12.1.0",
34
- "ts-repo-utils": "^10.5.0",
31
+ "octokit-safe-types": "^1.2.28",
32
+ "ts-data-forge": "^14.6.3",
33
+ "ts-fortress": "^12.1.1",
34
+ "ts-repo-utils": "^10.6.0",
35
35
  "ts-type-forge": "^9.2.2"
36
36
  },
37
37
  "devDependencies": {
@@ -40,10 +40,10 @@
40
40
  "@vitest/ui": "4.1.11",
41
41
  "cspell": "10.1.1",
42
42
  "eslint": "10.9.1",
43
- "eslint-config-typed": "5.11.0",
44
- "eslint-plugin-ts-data-forge": "0.6.1",
45
- "eslint-plugin-ts-fortress": "1.1.0",
46
- "eslint-plugin-ts-type-forge": "0.8.0",
43
+ "eslint-config-typed": "5.11.2",
44
+ "eslint-plugin-ts-data-forge": "0.7.0",
45
+ "eslint-plugin-ts-fortress": "1.2.0",
46
+ "eslint-plugin-ts-type-forge": "0.8.1",
47
47
  "jiti": "2.7.0",
48
48
  "npm-run-all2": "9.0.3",
49
49
  "prettier": "3.9.6",