Skip to content
This repository was archived by the owner on May 14, 2025. It is now read-only.

Commit 0d070c7

Browse files
committed
Merge remote-tracking branch 'template/development' into chore/merge-sync-template
2 parents 7a24b8a + 0deed0e commit 0d070c7

21 files changed

+3012
-1353
lines changed

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
MY_SECRET="MY_SECRET"

.eslintrc

Lines changed: 20 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,10 @@
22
"root": true,
33
"parser": "@typescript-eslint/parser",
44
"parserOptions": {
5-
"project": [
6-
"./tsconfig.json"
7-
]
5+
"project": ["./tsconfig.json"]
86
},
9-
"plugins": [
10-
"@typescript-eslint",
11-
"sonarjs"
12-
],
13-
"extends": [
14-
"eslint:recommended",
15-
"plugin:@typescript-eslint/recommended",
16-
"plugin:sonarjs/recommended"
17-
],
7+
"plugins": ["@typescript-eslint", "sonarjs", "filename-rules"],
8+
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:sonarjs/recommended"],
189
"ignorePatterns": [
1910
"**/*.js",
2011
"**/*.d.ts",
@@ -28,31 +19,18 @@
2819
"./dist"
2920
],
3021
"rules": {
31-
"prefer-arrow-callback": [
32-
"warn",
33-
{
34-
"allowNamedFunctions": true
35-
}
36-
],
37-
"func-style": [
38-
"warn",
39-
"declaration",
40-
{
41-
"allowArrowFunctions": false
42-
}
43-
],
22+
"filename-rules/match": [2, "/^(e2e\\.ts$|.*\\/e2e\\.ts$|[a-z0-9]+(?:[-._a-z0-9]+)*\\.ts|\\.[a-z0-9]+)$/"],
23+
"prefer-arrow-callback": ["warn", { "allowNamedFunctions": true }],
24+
"func-style": ["warn", "declaration", { "allowArrowFunctions": false }],
4425
"@typescript-eslint/no-floating-promises": "error",
4526
"@typescript-eslint/no-non-null-assertion": "error",
4627
"constructor-super": "error",
4728
"no-invalid-this": "off",
48-
"@typescript-eslint/no-invalid-this": [
49-
"error"
50-
],
51-
"no-restricted-syntax": [
52-
"error",
53-
"ForInStatement"
54-
],
29+
"@typescript-eslint/no-invalid-this": "error",
30+
"no-restricted-syntax": ["error", "ForInStatement"],
5531
"use-isnan": "error",
32+
"no-unneeded-ternary": "error",
33+
"no-nested-ternary": "error",
5634
"@typescript-eslint/no-unused-vars": [
5735
"error",
5836
{
@@ -74,98 +52,15 @@
7452
"sonarjs/no-identical-expressions": "error",
7553
"@typescript-eslint/naming-convention": [
7654
"error",
77-
{
78-
"selector": "interface",
79-
"format": [
80-
"PascalCase"
81-
],
82-
"custom": {
83-
"regex": "^I[A-Z]",
84-
"match": false
85-
}
86-
},
87-
{
88-
"selector": "memberLike",
89-
"modifiers": [
90-
"private"
91-
],
92-
"format": [
93-
"camelCase"
94-
],
95-
"leadingUnderscore": "require"
96-
},
97-
{
98-
"selector": "typeLike",
99-
"format": [
100-
"PascalCase"
101-
]
102-
},
103-
{
104-
"selector": "typeParameter",
105-
"format": [
106-
"PascalCase"
107-
],
108-
"prefix": [
109-
"T"
110-
]
111-
},
112-
{
113-
"selector": "variable",
114-
"format": [
115-
"camelCase",
116-
"UPPER_CASE"
117-
],
118-
"leadingUnderscore": "allow",
119-
"trailingUnderscore": "allow"
120-
},
121-
{
122-
"selector": "variable",
123-
"format": [
124-
"camelCase"
125-
],
126-
"leadingUnderscore": "allow",
127-
"trailingUnderscore": "allow"
128-
},
129-
{
130-
"selector": "variable",
131-
"modifiers": [
132-
"destructured"
133-
],
134-
"format": null
135-
},
136-
{
137-
"selector": "variable",
138-
"types": [
139-
"boolean"
140-
],
141-
"format": [
142-
"PascalCase"
143-
],
144-
"prefix": [
145-
"is",
146-
"should",
147-
"has",
148-
"can",
149-
"did",
150-
"will",
151-
"does"
152-
]
153-
},
154-
{
155-
"selector": "variableLike",
156-
"format": [
157-
"camelCase"
158-
]
159-
},
160-
{
161-
"selector": [
162-
"function",
163-
"variable"
164-
],
165-
"format": [
166-
"camelCase"
167-
]
168-
}
55+
{ "selector": "interface", "format": ["StrictPascalCase"], "custom": { "regex": "^I[A-Z]", "match": false } },
56+
{ "selector": "memberLike", "modifiers": ["private"], "format": ["strictCamelCase"], "leadingUnderscore": "require" },
57+
{ "selector": "typeLike", "format": ["StrictPascalCase"] },
58+
{ "selector": "typeParameter", "format": ["StrictPascalCase"], "prefix": ["T"] },
59+
{ "selector": "variable", "format": ["strictCamelCase", "UPPER_CASE"], "leadingUnderscore": "allow", "trailingUnderscore": "allow" },
60+
{ "selector": "variable", "modifiers": ["destructured"], "format": null },
61+
{ "selector": "variable", "types": ["boolean"], "format": ["StrictPascalCase"], "prefix": ["is", "should", "has", "can", "did", "will", "does"] },
62+
{ "selector": "variableLike", "format": ["strictCamelCase"] },
63+
{ "selector": ["function", "variable"], "format": ["strictCamelCase"] }
16964
]
17065
}
171-
}
66+
}

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

.github/empty-string-checker.ts

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import * as core from "@actions/core";
2+
import { Octokit } from "@octokit/rest";
3+
import simpleGit from "simple-git";
4+
5+
const token = process.env.GITHUB_TOKEN;
6+
const [owner, repo] = process.env.GITHUB_REPOSITORY?.split("/") || [];
7+
const pullNumber = process.env.GITHUB_PR_NUMBER || process.env.PULL_REQUEST_NUMBER || "0";
8+
const baseRef = process.env.GITHUB_BASE_REF;
9+
10+
if (!token || !owner || !repo || pullNumber === "0" || !baseRef) {
11+
core.setFailed("Missing required environment variables.");
12+
process.exit(1);
13+
}
14+
15+
const octokit = new Octokit({ auth: token });
16+
const git = simpleGit();
17+
18+
async function main() {
19+
try {
20+
const { data: pullRequest } = await octokit.pulls.get({
21+
owner,
22+
repo,
23+
pull_number: parseInt(pullNumber, 10),
24+
});
25+
26+
const baseSha = pullRequest.base.sha;
27+
const headSha = pullRequest.head.sha;
28+
29+
await git.fetch(["origin", baseSha, headSha]);
30+
31+
const diff = await git.diff([`${baseSha}...${headSha}`]);
32+
33+
core.info("Checking for empty strings...");
34+
const violations = parseDiffForEmptyStrings(diff);
35+
36+
if (violations.length > 0) {
37+
violations.forEach(({ file, line, content }) => {
38+
core.warning(
39+
"Detected an empty string.\n\nIf this is during variable initialization, consider using a different approach.\nFor more information, visit: https://www.github.com/ubiquity/ts-template/issues/31",
40+
{
41+
file,
42+
startLine: line,
43+
}
44+
);
45+
});
46+
47+
// core.setFailed(`${violations.length} empty string${violations.length > 1 ? "s" : ""} detected in the code.`);
48+
49+
await octokit.rest.checks.create({
50+
owner,
51+
repo,
52+
name: "Empty String Check",
53+
head_sha: headSha,
54+
status: "completed",
55+
conclusion: violations.length > 0 ? "failure" : "success",
56+
output: {
57+
title: "Empty String Check Results",
58+
summary: `Found ${violations.length} violation${violations.length !== 1 ? "s" : ""}`,
59+
annotations: violations.map((v) => ({
60+
path: v.file,
61+
start_line: v.line,
62+
end_line: v.line,
63+
annotation_level: "warning",
64+
message: "Empty string found",
65+
raw_details: v.content,
66+
})),
67+
},
68+
});
69+
} else {
70+
core.info("No empty strings found.");
71+
}
72+
} catch (error) {
73+
core.setFailed(`An error occurred: ${error instanceof Error ? error.message : String(error)}`);
74+
}
75+
}
76+
77+
function parseDiffForEmptyStrings(diff: string) {
78+
const violations: Array<{ file: string; line: number; content: string }> = [];
79+
const diffLines = diff.split("\n");
80+
81+
let currentFile: string;
82+
let headLine = 0;
83+
let inHunk = false;
84+
85+
diffLines.forEach((line) => {
86+
const hunkHeaderMatch = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(line);
87+
if (hunkHeaderMatch) {
88+
headLine = parseInt(hunkHeaderMatch[1], 10);
89+
inHunk = true;
90+
return;
91+
}
92+
93+
if (line.startsWith("--- a/") || line.startsWith("+++ b/")) {
94+
currentFile = line.slice(6);
95+
inHunk = false;
96+
return;
97+
}
98+
99+
// Only process TypeScript files
100+
if (!currentFile?.endsWith(".ts")) {
101+
return;
102+
}
103+
104+
if (inHunk && line.startsWith("+")) {
105+
// Check for empty strings in TypeScript syntax
106+
if (/^\+.*""/.test(line)) {
107+
// Ignore empty strings in comments
108+
if (!line.trim().startsWith("//") && !line.trim().startsWith("*")) {
109+
// Ignore empty strings in template literals
110+
if (!/`[^`]*\$\{[^}]*\}[^`]*`/.test(line)) {
111+
violations.push({
112+
file: currentFile,
113+
line: headLine,
114+
content: line.substring(1).trim(),
115+
});
116+
}
117+
}
118+
}
119+
headLine++;
120+
} else if (!line.startsWith("-")) {
121+
headLine++;
122+
}
123+
});
124+
125+
return violations;
126+
}
127+
main().catch((error) => {
128+
core.setFailed(`Error running empty string check: ${error instanceof Error ? error.message : String(error)}`);
129+
});

.github/knip.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import type { KnipConfig } from "knip";
2+
3+
const config: KnipConfig = {
4+
entry: ["build/index.ts", ".github/empty-string-checker.ts"],
5+
project: ["src/**/*.ts"],
6+
ignore: ["src/types/config.ts", "**/__mocks__/**", "**/__fixtures__/**"],
7+
ignoreExportsUsedInFile: true,
8+
// eslint can also be safely ignored as per the docs: https://knip.dev/guides/handling-issues#eslint--jest
9+
ignoreDependencies: ["eslint-config-prettier", "eslint-plugin-prettier", "@types/jest", "@mswjs/data"],
10+
eslint: true,
11+
};
12+
13+
export default config;

.github/workflows/build.yml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Build
2+
3+
on:
4+
push:
5+
pull_request:
6+
workflow_dispatch:
7+
8+
permissions:
9+
contents: read
10+
11+
jobs:
12+
build:
13+
runs-on: ubuntu-22.04
14+
15+
steps:
16+
- name: Check out repository
17+
uses: actions/checkout@v4
18+
19+
- name: Set up Node.js
20+
uses: actions/setup-node@v4
21+
with:
22+
node-version: 20.10.0
23+
24+
- name: Build
25+
run: |
26+
yarn
27+
yarn build
28+
29+
- name: Upload build artifact
30+
uses: actions/upload-artifact@v4
31+
with:
32+
name: static
33+
path: static

.github/workflows/conventional-commits.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ jobs:
88
name: Conventional Commits
99
runs-on: ubuntu-latest
1010
steps:
11-
- uses: actions/checkout@v3
11+
- uses: actions/checkout@v4
1212
- uses: ubiquity/action-conventional-commits@master

.github/workflows/deploy.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: Deploy to Cloudflare Pages
2+
3+
on:
4+
workflow_run:
5+
workflows: ["Build"]
6+
types:
7+
- completed
8+
9+
jobs:
10+
deploy-to-cloudflare:
11+
name: Automatic Cloudflare Deploy
12+
runs-on: ubuntu-22.04
13+
steps:
14+
- name: Deploy to Cloudflare
15+
if: ${{ github.event.workflow_run.conclusion == 'success' }}
16+
uses: ubiquity/cloudflare-deploy-action@main
17+
with:
18+
repository: ${{ github.repository }}
19+
production_branch: ${{ github.event.repository.default_branch }}
20+
build_artifact_name: "static"
21+
output_directory: "static"
22+
current_branch: ${{ github.event.workflow_run.head_branch }}
23+
cloudflare_account_id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
24+
cloudflare_api_token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
25+
commit_sha: ${{ github.event.workflow_run.head_sha }}
26+
workflow_run_id: ${{ github.event.workflow_run.id }}
27+
app_id: ${{ secrets.APP_ID }}
28+
app_private_key: ${{ secrets.APP_PRIVATE_KEY }}

0 commit comments

Comments
 (0)