mirror of
https://github.com/signalapp/Signal-Desktop.git
synced 2026-10-02 18:00:44 +01:00
Upgrade danger setup and add tailwind deps rule
Co-authored-by: Jamie <113370520+jamiebuilds-signal@users.noreply.github.com>
This commit is contained in:
1 parent
7c86546268
commit
d2f4c9b1ee
17 files changed
+465
-1684
No files matched your search
@@ -11,8 +11,6 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||
with:
|
||||
fetch-depth: 0 # fetch all history
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061 # v4
|
||||
- name: Setup node.js
|
||||
@@ -21,7 +19,7 @@ jobs:
|
||||
node-version-file: '.nvmrc'
|
||||
package-manager-cache: false # Avoid cache key clashes
|
||||
- name: Install danger node_modules
|
||||
run: cd danger && pnpm install
|
||||
run: (cd danger && pnpm install --ignore-workspace)
|
||||
- name: Run DangerJS
|
||||
run: pnpm run danger:ci
|
||||
env:
|
||||
|
||||
+1
-1
@@ -1695,7 +1695,7 @@
|
||||
"test/**",
|
||||
".*rc.js",
|
||||
"ci.js",
|
||||
"dangerfile.js",
|
||||
"dangerfile.mjs",
|
||||
"preload.wrapper.ts",
|
||||
"rolldown.config.ts"
|
||||
],
|
||||
|
||||
@@ -6866,30 +6866,6 @@ Signal Desktop makes use of the following open source projects.
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
## endanger
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Jamie Kyle
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
## enhanced-resolve
|
||||
|
||||
Copyright JS Foundation and other contributors
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2026 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// @ts-check
|
||||
|
||||
/** @type {typeof import("danger").danger} */
|
||||
// @ts-expect-error
|
||||
export const danger = globalThis.danger;
|
||||
|
||||
/** @type {typeof import("danger").warn} */
|
||||
// @ts-expect-error
|
||||
export const warn = globalThis.warn;
|
||||
|
||||
/** @type {typeof import("danger").fail} */
|
||||
// @ts-expect-error
|
||||
export const fail = globalThis.fail;
|
||||
|
||||
/** @type {typeof import("danger").message} */
|
||||
// @ts-expect-error
|
||||
export const message = globalThis.message;
|
||||
|
||||
/** @type {typeof import("danger").markdown} */
|
||||
// @ts-expect-error
|
||||
export const markdown = globalThis.markdown;
|
||||
+4
-5
@@ -1,12 +1,11 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"danger": "12.3.4",
|
||||
"endanger": "7.0.4",
|
||||
"semver": "7.7.4",
|
||||
"typescript": "5.6.3"
|
||||
"danger": "13.0.7",
|
||||
"js-yaml": "4.1.0",
|
||||
"semver": "7.7.4"
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"ignoredBuiltDependencies": [
|
||||
"core-js"
|
||||
]
|
||||
}
|
||||
|
||||
Generated
+165
-860
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,7 @@
|
||||
// Copyright 2026 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// @ts-check
|
||||
|
||||
import './rules/enforcePackageJsonPinnedDeps.mjs';
|
||||
import './rules/enforcePnpmLockfileDepsIntegrity.mjs';
|
||||
import './rules/enforceTailwindDepsMatch.mjs';
|
||||
@@ -1,35 +0,0 @@
|
||||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { run } from 'endanger';
|
||||
|
||||
import packageJsonVersionsShouldBePinned from './rules/packageJsonVersionsShouldBePinned';
|
||||
import pnpmLockDepsShouldHaveIntegrity from './rules/pnpmLockDepsShouldHaveIntegrity';
|
||||
|
||||
function isGitDeletedError(error: unknown) {
|
||||
return (
|
||||
typeof error === 'object' &&
|
||||
error != null &&
|
||||
error['exitCode'] === 128 &&
|
||||
error['command']?.startsWith('git show ')
|
||||
);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
await run(
|
||||
packageJsonVersionsShouldBePinned(),
|
||||
pnpmLockDepsShouldHaveIntegrity()
|
||||
);
|
||||
} catch (error: unknown) {
|
||||
if (!isGitDeletedError(error)) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// oxlint-disable-next-line promise/prefer-await-to-then
|
||||
main().catch(error => {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright 2026 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// @ts-check
|
||||
import { fail } from '../danger-exports.mjs';
|
||||
import { resolve } from 'node:path';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import * as semver from 'semver';
|
||||
|
||||
const rootDir = resolve(import.meta.dirname, '..', '..');
|
||||
const pkgPath = resolve(rootDir, 'package.json');
|
||||
const pkgContents = readFileSync(pkgPath, 'utf8');
|
||||
const pkgJson = JSON.parse(pkgContents);
|
||||
|
||||
const depTypes = [
|
||||
'dependencies',
|
||||
'devDependencies',
|
||||
'peerDependencies',
|
||||
'optionalDependencies',
|
||||
];
|
||||
|
||||
/**
|
||||
* @param {string} depSpec
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isPinnedVersion(depSpec) {
|
||||
if (depSpec.startsWith('https:')) {
|
||||
return depSpec.includes('#');
|
||||
}
|
||||
/** @type {string} */
|
||||
let version;
|
||||
if (depSpec.startsWith('workspace:')) {
|
||||
version = depSpec.replace(/^workspace:/, '');
|
||||
} else {
|
||||
version = depSpec;
|
||||
}
|
||||
return semver.valid(version) != null;
|
||||
}
|
||||
|
||||
for (const depType of depTypes) {
|
||||
const deps = pkgJson[depType];
|
||||
if (deps == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const [depName, depSpec] of Object.entries(deps)) {
|
||||
if (typeof depSpec === 'string' && !isPinnedVersion(depSpec)) {
|
||||
fail(
|
||||
`**Pin package.json versions**\n` +
|
||||
`All package.json versions should be pinned to a specific version.\n` +
|
||||
`See ${depName}@${depSpec} in package.json#${depType}.`,
|
||||
'package.json'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2026 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// @ts-check
|
||||
import { fail } from '../danger-exports.mjs';
|
||||
import { resolve } from 'node:path';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import * as YAML from 'js-yaml';
|
||||
|
||||
const rootDir = resolve(import.meta.dirname, '..', '..');
|
||||
const lockPath = resolve(rootDir, 'pnpm-lock.yaml');
|
||||
const lockContents = readFileSync(lockPath, 'utf8');
|
||||
/** @type {any} */
|
||||
const lockYaml = YAML.load(lockContents);
|
||||
|
||||
for (const name of Object.keys(lockYaml.packages)) {
|
||||
const spec = lockYaml.packages[name];
|
||||
|
||||
if (spec.resolution?.integrity == null) {
|
||||
fail(
|
||||
`**Dependency resolution missing integrity**\n` +
|
||||
`All dependencies should have a resolution with an integrity field.\n` +
|
||||
`You may need to override it or provide it manually.\n` +
|
||||
`\n` +
|
||||
`See "${name}".`,
|
||||
'pnpm-lock.yaml'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright 2026 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// @ts-check
|
||||
import { fail } from '../danger-exports.mjs';
|
||||
import { resolve } from 'node:path';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import * as YAML from 'js-yaml';
|
||||
|
||||
const rootDir = resolve(import.meta.dirname, '..', '..');
|
||||
const pkgPath = resolve(rootDir, 'package.json');
|
||||
const pkgContents = readFileSync(pkgPath, 'utf8');
|
||||
const pkgJson = JSON.parse(pkgContents);
|
||||
|
||||
const lockPath = resolve(rootDir, 'pnpm-lock.yaml');
|
||||
const lockContents = readFileSync(lockPath, 'utf8');
|
||||
/** @type {any} */
|
||||
const lockYaml = YAML.load(lockContents);
|
||||
|
||||
const expectedVersion = pkgJson.devDependencies.tailwindcss;
|
||||
if (typeof expectedVersion !== 'string') {
|
||||
throw new TypeError('Missing tailwindcss package version');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} pkgName
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isTailwindPackage(pkgName) {
|
||||
return pkgName === 'tailwindcss' || pkgName.startsWith('@tailwindcss/');
|
||||
}
|
||||
|
||||
for (const depType of ['dependencies', 'devDependencies']) {
|
||||
for (const [depName, depSpec] of Object.entries(pkgJson[depType])) {
|
||||
if (isTailwindPackage(depName) && depSpec !== expectedVersion) {
|
||||
fail(
|
||||
`**Tailwind package versions must all match**\n` +
|
||||
`Expected to match tailwindcss@${expectedVersion}\n` +
|
||||
`See ${depName}@${depSpec} in package.json#${depType}.`,
|
||||
'package.json'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const depKey of Object.keys(lockYaml.packages)) {
|
||||
const match = depKey.match(/^((?:@[^\/]+\/)?[^@]+)@(.+)$/);
|
||||
if (match == null) {
|
||||
throw new Error(`Could not parse "${depKey}"`);
|
||||
}
|
||||
|
||||
const [, depName, depSpec] = match;
|
||||
|
||||
if (isTailwindPackage(depName) && depSpec !== expectedVersion) {
|
||||
fail(
|
||||
`**Tailwind package versions must all match**\n` +
|
||||
`Expected to match tailwindcss@${expectedVersion}\n` +
|
||||
`See ${depName}@${depSpec} in pnpm-lock.yaml#packages.`,
|
||||
'pnpm-lock.yaml'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import type { File } from 'endanger';
|
||||
import { Rule } from 'endanger';
|
||||
import semver from 'semver';
|
||||
|
||||
function isPinnedVersion(spec: string): boolean {
|
||||
if (spec.startsWith('https:')) {
|
||||
return spec.includes('#');
|
||||
}
|
||||
let version: string;
|
||||
if (spec.startsWith('workspace:')) {
|
||||
version = spec.replace(/^workspace:/, '');
|
||||
} else {
|
||||
version = spec;
|
||||
}
|
||||
return semver.valid(version) != null;
|
||||
}
|
||||
|
||||
async function getLineContaining(file: File, text: string) {
|
||||
const lines = await file.lines();
|
||||
for (const line of lines) {
|
||||
// oxlint-disable-next-line no-await-in-loop
|
||||
if (await line.contains(text)) {
|
||||
return line;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const dependencyTypes = [
|
||||
'dependencies',
|
||||
'devDependencies',
|
||||
'peerDependencies',
|
||||
'optionalDependencies',
|
||||
];
|
||||
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
export default function packageJsonVersionsShouldBePinned(): Rule<any, any> {
|
||||
return new Rule({
|
||||
match: {
|
||||
files: ['**/package.json', '!**/node_modules/**'],
|
||||
},
|
||||
messages: {
|
||||
packageJsonVersionsShouldBePinned: `
|
||||
**Pin package.json versions**
|
||||
All package.json versions should be pinned to a specific version.
|
||||
See {depName}@{depVersion} in {filePath}#{dependencyType}.
|
||||
`,
|
||||
},
|
||||
async run({ files, context }) {
|
||||
for (const file of files.modifiedOrCreated) {
|
||||
// oxlint-disable-next-line no-await-in-loop
|
||||
const pkg = await file.json();
|
||||
for (const dependencyType of dependencyTypes) {
|
||||
const deps = pkg[dependencyType];
|
||||
if (deps == null) {
|
||||
continue;
|
||||
}
|
||||
for (const depName of Object.keys(deps)) {
|
||||
const depVersion = deps[depName];
|
||||
if (!isPinnedVersion(depVersion)) {
|
||||
// oxlint-disable-next-line no-await-in-loop
|
||||
const line = await getLineContaining(
|
||||
file,
|
||||
`"${depName}": "${depVersion}"`
|
||||
);
|
||||
context.warn(
|
||||
'packageJsonVersionsShouldBePinned',
|
||||
{
|
||||
file,
|
||||
line: line ?? undefined,
|
||||
},
|
||||
{
|
||||
depName,
|
||||
depVersion,
|
||||
filePath: file.path,
|
||||
dependencyType,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { Rule } from 'endanger';
|
||||
|
||||
function assert(condition: boolean, message: string): asserts condition {
|
||||
if (!condition) {
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
|
||||
function isObject(value: unknown): value is object {
|
||||
return typeof value === 'object' && value != null;
|
||||
}
|
||||
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
function has<T extends object, const K extends T[any]>(
|
||||
value: T,
|
||||
key: K
|
||||
): value is T & Record<K, T[K]> {
|
||||
return Object.hasOwn(value, key);
|
||||
}
|
||||
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
export default function pnpmLockDepsShouldHaveIntegrity(): Rule<any, any> {
|
||||
return new Rule({
|
||||
match: {
|
||||
files: ['pnpm-lock.yaml'],
|
||||
},
|
||||
messages: {
|
||||
missingIntegrity: `
|
||||
**Dependency resolution missing integrity**
|
||||
All dependencies should have a resolution with an integrity field.
|
||||
You may need to override it or provide it manually.
|
||||
|
||||
See "{name}".
|
||||
`,
|
||||
},
|
||||
async run({ files, context }) {
|
||||
for (const file of files.modifiedOrCreated) {
|
||||
// oxlint-disable-next-line no-await-in-loop
|
||||
const contents: unknown = await file.yaml();
|
||||
|
||||
assert(
|
||||
isObject(contents) &&
|
||||
has(contents, 'packages') &&
|
||||
isObject(contents.packages),
|
||||
'pnpm.yaml should be object'
|
||||
);
|
||||
|
||||
for (const [name, spec] of Object.entries(contents.packages)) {
|
||||
assert(
|
||||
isObject(spec) &&
|
||||
has(spec, 'resolution') &&
|
||||
isObject(spec.resolution),
|
||||
`${name} spec should be object`
|
||||
);
|
||||
|
||||
if (!has(spec.resolution, 'integrity')) {
|
||||
context.fail('missingIntegrity', { file }, { name });
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
// Copyright 2022 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// Ensuring that the root directory is the same as this file before we load any
|
||||
// danger code. This is needed so we can run danger with the danger/package.json
|
||||
// file in CI
|
||||
process.chdir(__dirname);
|
||||
require('./danger/rules.ts');
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright 2026 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// @ts-check
|
||||
|
||||
import './danger/rules.mjs';
|
||||
+1
-2
@@ -251,7 +251,7 @@
|
||||
"cross-env": "7.0.3",
|
||||
"css-loader": "7.1.2",
|
||||
"csv-parse": "5.5.6",
|
||||
"danger": "12.3.3",
|
||||
"danger": "13.0.7",
|
||||
"dashdash": "2.0.0",
|
||||
"debug": "4.3.7",
|
||||
"direction": "1.0.4",
|
||||
@@ -261,7 +261,6 @@
|
||||
"electron-mocha": "13.0.1",
|
||||
"emoji-regex": "10.4.0",
|
||||
"encoding": "0.1.13",
|
||||
"endanger": "7.0.4",
|
||||
"enhanced-resolve": "5.18.3",
|
||||
"enquirer": "2.4.1",
|
||||
"eslint": "10.1.0",
|
||||
|
||||
Generated
+114
-592
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user