Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion common/autoinstallers/license-header/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ function addLicenseHeader(targetDir, licenseContent, options = {}) {
.join("\n") +
(commentSuffix ? "\n" + commentSuffix : "") +
"\n\n";
const licenseHeader = licensedText.trim();
const licenseHeaders = [
licenseHeader,
licenseHeader.replace(/\n/g, "\r\n"),
];

function traverseDir(currentDir) {
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
Expand Down Expand Up @@ -88,7 +93,14 @@ function addLicenseHeader(targetDir, licenseContent, options = {}) {
const originalContent = fs.readFileSync(filePath, "utf8");

// Check if the license already exists (simple match at the beginning)
if (!force && (originalContent.startsWith(licensedText.trim()) || bashHeaders.some(_header => originalContent.startsWith(_header.trim())) || originalContent.startsWith(rushPreHeader.trim()))) {
if (
!force &&
(licenseHeaders.some(_header => originalContent.startsWith(_header)) ||
bashHeaders.some(_header =>
originalContent.startsWith(_header.trim()),
) ||
originalContent.startsWith(rushPreHeader.trim()))
) {
return;
}

Expand Down
123 changes: 123 additions & 0 deletions common/autoinstallers/license-header/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/**
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
* SPDX-License-Identifier: MIT
*/

const assert = require("node:assert/strict");
const { execFileSync } = require("node:child_process");
const fs = require("node:fs");
const os = require("node:os");
const path = require("node:path");
const { afterEach, test } = require("node:test");

const canonicalHeader = `/**
* Copyright (c) 2025 Bytedance Ltd. and/or its affiliates
* SPDX-License-Identifier: MIT
*/`;
const temporaryDirectories = [];

afterEach(() => {
for (const directory of temporaryDirectories.splice(0)) {
fs.rmSync(directory, { recursive: true, force: true });
}
});

function createFixtureRepository(files) {
const root = fs.mkdtempSync(
path.join(os.tmpdir(), "flowgram-license-header-")
);
temporaryDirectories.push(root);

const scriptDirectory = path.join(
root,
"common",
"autoinstallers",
"license-header"
);
fs.mkdirSync(scriptDirectory, { recursive: true });
fs.copyFileSync(
__filename.replace(/\.test\.js$/, ".js"),
path.join(scriptDirectory, "index.js")
);
fs.writeFileSync(path.join(root, ".gitignore"), "node_modules\n");

for (const [relativePath, content] of Object.entries(files)) {
const filePath = path.join(root, relativePath);
fs.mkdirSync(path.dirname(filePath), { recursive: true });
fs.writeFileSync(filePath, content);
}

return { root, scriptDirectory };
}

function runLicenseHeaderScript(scriptDirectory) {
const nodeModulesDirectory = path.dirname(
path.dirname(require.resolve("ignore/package.json"))
);

return execFileSync(process.execPath, ["index.js"], {
cwd: scriptDirectory,
encoding: "utf8",
env: {
...process.env,
NODE_PATH: nodeModulesDirectory,
},
});
}

test("leaves canonical LF and CRLF headers byte-identical", () => {
const lfContent = `${canonicalHeader}\n\nconst lf = true;\n`;
const crlfContent = lfContent.replace(/\n/g, "\r\n");
const { root, scriptDirectory } = createFixtureRepository({
"fixtures/lf.ts": lfContent,
"fixtures/crlf.ts": crlfContent,
});

runLicenseHeaderScript(scriptDirectory);

assert.equal(
fs.readFileSync(path.join(root, "fixtures/lf.ts"), "utf8"),
lfContent
);
assert.equal(
fs.readFileSync(path.join(root, "fixtures/crlf.ts"), "utf8"),
crlfContent
);
});

test("adds one canonical header to an unlicensed file and stays idempotent", () => {
const source = "const value = 1;\n";
const { root, scriptDirectory } = createFixtureRepository({
"fixtures/missing.ts": source,
});

runLicenseHeaderScript(scriptDirectory);
const firstRun = fs.readFileSync(
path.join(root, "fixtures/missing.ts"),
"utf8"
);
runLicenseHeaderScript(scriptDirectory);
const secondRun = fs.readFileSync(
path.join(root, "fixtures/missing.ts"),
"utf8"
);

assert.equal(firstRun, `${canonicalHeader}\n\n${source}`);
assert.equal(secondRun, firstRun);
});

test("leaves a recognized shebang byte-identical", () => {
const source = '#!/usr/bin/env node\r\nconsole.log("ok");\r\n';
const { root, scriptDirectory } = createFixtureRepository({
"fixtures/command.js": source,
});

runLicenseHeaderScript(scriptDirectory);
runLicenseHeaderScript(scriptDirectory);

const result = fs.readFileSync(
path.join(root, "fixtures/command.js"),
"utf8"
);
assert.equal(result, source);
});
3 changes: 2 additions & 1 deletion common/autoinstallers/license-header/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
"private": true,
"author": "289056872@qq.com",
"scripts": {
"license-header-fix": "node ./index.js"
"license-header-fix": "node ./index.js",
"test": "node --test index.test.js"
},
"dependencies": {
"ignore": "^7.0.4"
Expand Down