Skip to content

Commit d5440a2

Browse files
authored
fix: add repository metadata to git package builds
1 parent 15561ff commit d5440a2

3 files changed

Lines changed: 216 additions & 1 deletion

File tree

azure-pipelines.yml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,11 +211,17 @@ stages:
211211
212212
- script: |
213213
set -euo pipefail
214+
REPOSITORY_REVISION=$(git -C "$(repoDir)" rev-parse HEAD)
214215
cd "$(packageFolder)"
216+
echo "Patching repository metadata in $(packageFolder)"
217+
node "$(Build.SourcesDirectory)/patchPackageRepository.js" \
218+
. \
219+
"$(repoUrl)" \
220+
"$REPOSITORY_REVISION"
215221
echo "Overriding publishConfig.registry in $(packageFolder)"
216222
npm pkg set publishConfig.registry="$(publishRegistryUrl)"
217223
cat package.json
218-
displayName: "Override publishConfig"
224+
displayName: "Patch package metadata"
219225
condition: eq(variables['packageSourceMode'], 'git')
220226
221227
- script: |
@@ -470,6 +476,10 @@ stages:
470476
set -euo pipefail
471477
echo "Published package metadata from Verdaccio:"
472478
npm view "$(packageNameFromArtifact)" --registry="$(verdaccioUrl)" --json
479+
if [ "${PACKAGESOURCE:-git}" = "git" ]; then
480+
REPOSITORY_JSON=$(npm view "$(packageNameFromArtifact)@$(packageVersionFromArtifact)" repository --registry="$(verdaccioUrl)" --json)
481+
node -e 'const repo=JSON.parse(process.argv[1]); if (!repo || repo.type !== "git" || typeof repo.url !== "string" || repo.url.length === 0 || typeof repo.revision !== "string" || !/^([0-9a-f]{40}|[0-9a-f]{64})$/.test(repo.revision)) { console.error("Published package repository metadata is invalid"); console.error(JSON.stringify(repo)); process.exit(1); }' "$REPOSITORY_JSON"
482+
fi
473483
echo "Verdaccio storage files:"
474484
find "$(Pipeline.Workspace)/verdaccio-storage" -type f | sort
475485
displayName: "Print Verdaccio registry content"

patchPackageRepository.js

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
const fs = require("fs");
2+
const path = require("path");
3+
const { logError } = require("./lib/logger");
4+
const { readPackageJson } = require("./lib/packageJson");
5+
6+
/**
7+
* @typedef {{type: string, url: string, revision: string}} RepositoryMetadata
8+
*/
9+
10+
/**
11+
* @typedef {{
12+
* [key: string]: unknown,
13+
* name?: string,
14+
* version?: string,
15+
* displayName?: string,
16+
* dependencies?: Record<string, string>,
17+
* repository?: string | (Record<string, unknown> & Partial<RepositoryMetadata>),
18+
* }} PackageManifest
19+
*/
20+
21+
/**
22+
* @param {string} value
23+
* @param {string} name
24+
* @returns {void}
25+
*/
26+
const assertNonEmptyString = function (value, name) {
27+
if (typeof value !== "string" || value.trim() === "") {
28+
throw new Error(`${name} is required`);
29+
}
30+
};
31+
32+
/**
33+
* @param {string} packageFolder
34+
* @param {string} repoUrl
35+
* @param {string} revision
36+
* @returns {PackageManifest & {repository: RepositoryMetadata}}
37+
*/
38+
const patchPackageRepository = function (packageFolder, repoUrl, revision) {
39+
assertNonEmptyString(packageFolder, "packageFolder");
40+
assertNonEmptyString(repoUrl, "repoUrl");
41+
assertNonEmptyString(revision, "revision");
42+
43+
const packageJsonPath = path.resolve(packageFolder, "package.json");
44+
const packageJson = /** @type {PackageManifest} */ (
45+
readPackageJson(packageJsonPath)
46+
);
47+
const existingRepository =
48+
packageJson.repository &&
49+
typeof packageJson.repository === "object" &&
50+
!Array.isArray(packageJson.repository)
51+
? packageJson.repository
52+
: {};
53+
54+
packageJson.repository = {
55+
...existingRepository,
56+
type: "git",
57+
url: repoUrl,
58+
revision,
59+
};
60+
61+
fs.writeFileSync(
62+
packageJsonPath,
63+
`${JSON.stringify(packageJson, null, 2)}\n`,
64+
);
65+
66+
return /** @type {PackageManifest & {repository: RepositoryMetadata}} */ (
67+
packageJson
68+
);
69+
};
70+
71+
if (require.main === module) {
72+
if (process.argv.length < 5) {
73+
logError(
74+
"Usage: node patchPackageRepository.js <package-folder> <repo-url> <revision>",
75+
);
76+
process.exit(1);
77+
}
78+
79+
try {
80+
patchPackageRepository(process.argv[2], process.argv[3], process.argv[4]);
81+
console.log("Patched package repository metadata");
82+
} catch (err) {
83+
logError(err.message);
84+
process.exit(1);
85+
}
86+
}
87+
88+
module.exports = {
89+
patchPackageRepository,
90+
};
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/* eslint-disable no-undef */
2+
require("should");
3+
const fs = require("fs");
4+
const path = require("path");
5+
const {
6+
getTmpDir,
7+
createTmpDir,
8+
removeTmpDir,
9+
writeJsonFile,
10+
} = require("./utils");
11+
const { patchPackageRepository } = require("../patchPackageRepository");
12+
13+
describe("patchPackageRepository.js", function () {
14+
const tmpDir = getTmpDir("test-patch-package-repository");
15+
const repoUrl = "https://github.com/openupm/test-package";
16+
const revision = "0123456789abcdef0123456789abcdef01234567";
17+
18+
beforeEach(function () {
19+
removeTmpDir("test-patch-package-repository");
20+
createTmpDir("test-patch-package-repository");
21+
});
22+
23+
afterEach(function () {
24+
removeTmpDir("test-patch-package-repository");
25+
});
26+
27+
it("adds repository metadata when missing", function () {
28+
writeJsonFile(path.resolve(tmpDir, "package.json"), {
29+
name: "package-a",
30+
version: "1.0.0",
31+
});
32+
33+
const result = patchPackageRepository(tmpDir, repoUrl, revision);
34+
35+
result.repository.should.deepEqual({
36+
type: "git",
37+
url: repoUrl,
38+
revision,
39+
});
40+
41+
const saved = JSON.parse(
42+
fs.readFileSync(path.resolve(tmpDir, "package.json"), "utf8"),
43+
);
44+
saved.repository.should.deepEqual(result.repository);
45+
});
46+
47+
it("replaces string repository metadata", function () {
48+
writeJsonFile(path.resolve(tmpDir, "package.json"), {
49+
name: "package-a",
50+
version: "1.0.0",
51+
repository: "https://example.invalid/old.git",
52+
});
53+
54+
const result = patchPackageRepository(tmpDir, repoUrl, revision);
55+
56+
result.repository.should.deepEqual({
57+
type: "git",
58+
url: repoUrl,
59+
revision,
60+
});
61+
});
62+
63+
it("preserves extra object repository metadata", function () {
64+
writeJsonFile(path.resolve(tmpDir, "package.json"), {
65+
name: "package-a",
66+
version: "1.0.0",
67+
repository: {
68+
type: "git",
69+
url: "https://example.invalid/old.git",
70+
directory: "Packages/package-a",
71+
},
72+
});
73+
74+
const result = patchPackageRepository(tmpDir, repoUrl, revision);
75+
76+
result.repository.should.deepEqual({
77+
type: "git",
78+
url: repoUrl,
79+
revision,
80+
directory: "Packages/package-a",
81+
});
82+
});
83+
84+
it("preserves unrelated manifest fields", function () {
85+
writeJsonFile(path.resolve(tmpDir, "package.json"), {
86+
name: "package-a",
87+
version: "1.0.0",
88+
displayName: "Package A",
89+
dependencies: {
90+
"package-b": "2.0.0",
91+
},
92+
});
93+
94+
const result = patchPackageRepository(tmpDir, repoUrl, revision);
95+
96+
result.name.should.equal("package-a");
97+
result.version.should.equal("1.0.0");
98+
result.displayName.should.equal("Package A");
99+
result.dependencies.should.deepEqual({
100+
"package-b": "2.0.0",
101+
});
102+
});
103+
104+
it("rejects a missing package manifest", function () {
105+
(() => patchPackageRepository(tmpDir, repoUrl, revision)).should.throw(
106+
/ENOENT/,
107+
);
108+
});
109+
110+
it("rejects an invalid package manifest", function () {
111+
fs.writeFileSync(path.resolve(tmpDir, "package.json"), "{");
112+
113+
(() => patchPackageRepository(tmpDir, repoUrl, revision)).should.throw();
114+
});
115+
});

0 commit comments

Comments
 (0)