create-app: add a yarn.lock seed file

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2024-02-02 11:50:54 +01:00
parent 6a5702f087
commit c420081a20
6 changed files with 171 additions and 1 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/create-app': patch
---
Add a seed file for `yarn.lock` in newly created apps. This file is downloaded directly from `https://github.com/backstage/backstage` at the time of creating a new project, ensuring that users always receive the latest version. The purpose of the seed file is to initialize the lock file with known good versions of individual dependencies that have had bad new releases published. The seed file will have no effect if the dependency is not present, it can not be used to install additional packages.
+3 -1
View File
@@ -29,7 +29,8 @@
"clean": "backstage-cli package clean",
"prepack": "node scripts/prepack.js",
"postpack": "node scripts/postpack.js",
"start": "yarn nodemon --"
"start": "yarn nodemon --",
"add-lock-seed": "node scripts/add-lock-seed.js"
},
"dependencies": {
"@backstage/cli-common": "workspace:^",
@@ -49,6 +50,7 @@
"@types/inquirer": "^8.1.3",
"@types/node": "^18.17.8",
"@types/recursive-readdir": "^2.2.0",
"msw": "^1.0.0",
"nodemon": "^3.0.1",
"ts-node": "^10.0.0"
},
@@ -0,0 +1,80 @@
#!/usr/bin/env node
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const fs = require('fs-extra');
const path = require('path');
const YARN_REGISTRY = 'https://registry.yarnpkg.com';
const NPM_REGISTRY = 'https://registry.npmjs.org';
const SEED_FILE = 'seed-yarn.lock';
function formatLockEntry(package, query, version, distData) {
let header = `${package}@${query}`;
if (package.includes('@')) {
header = `"${header}"`;
}
header += ':';
return [
'',
header,
` version "${version}"`,
` resolved "${distData.tarball.replace(NPM_REGISTRY, YARN_REGISTRY)}#${
distData.shasum
}"`,
` integrity ${distData.integrity}`,
'',
].join('\n');
}
async function main(package, query, version) {
if (!package || !query || !version) {
console.error(
`Usage: yarn add-lock-seed <package-name> <query> <version>
Example: yarn lock-seed @backstage/cli ^1.0.0 1.2.3`,
);
return false;
}
const res = await fetch(`${YARN_REGISTRY}/${package}/${version}`);
if (!res.ok) {
console.error(
`Failed to fetch package info for ${package} v${version}: ${await res.text()}`,
);
return false;
}
const data = await res.json();
const entry = formatLockEntry(package, query, version, data.dist);
const lockSeedPath = path.resolve(__dirname, `../${SEED_FILE}`);
await fs.appendFile(lockSeedPath, entry, 'utf8');
console.log(`Added the following entry to ${SEED_FILE}:\n${entry}`);
return true;
}
main(...process.argv.slice(2))
.then(ok => process.exit(ok ? 0 : 1))
.catch(err => {
console.error(err.stack);
process.exit(1);
});
+24
View File
@@ -0,0 +1,24 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
// This file is used to seed the yarn.lock file in newly created projects.
// It can be used to work around issues in new dependency versions that would otherwise
// both break new apps and the E2E tests in this repo.
//
// In @backstage/create-app this file is read directly from the repo, rather than from
// the published package. In the E2E tests it's instead loaded directly from the workspace.
// Lines starting with "//" are trimmed, so they can be used to add extra context to the entries.
//
// To add a new entry, run and commit the result:
//
// yarn add-lock-seed <package> <query> <version>
//
// package: the name of the package, e.g. @testing-library/react
// query: the version query to pin the version for, e.g. ^14.0.0
// version: the version to pin to, must be in range of the query, e.g. 14.11.0
// Fix for https://github.com/testing-library/jest-dom/issues/574
"@testing-library/jest-dom@^6.0.0":
version "6.3.0"
resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-6.3.0.tgz#e8d308e0c0e91d882340cbbfdea0e4daa7987d36"
integrity sha512-hJVIrkFizEQxoWsGBlycTcQhrpoCH4DhXfrnHFFXgkx3Xdm15zycsq5Ep+vpw4W8S0NJa8cxDHcuJib+1tEbhg==
+17
View File
@@ -30,6 +30,7 @@ import {
templatingTask,
tryInitGitRepository,
readGitConfig,
fetchYarnLockSeedTask,
} from './lib/tasks';
const DEFAULT_BRANCH = 'master';
@@ -110,6 +111,8 @@ export default async (opts: OptionValues): Promise<void> => {
await moveAppTask(tempDir, appDir, answers.name);
}
const fetchedYarnLockSeed = await fetchYarnLockSeedTask(appDir);
if (gitConfig) {
if (await tryInitGitRepository(appDir)) {
// Since we don't know whether we were able to init git before we
@@ -128,6 +131,20 @@ export default async (opts: OptionValues): Promise<void> => {
chalk.green(`🥇 Successfully created ${chalk.cyan(answers.name)}`),
);
Task.log();
if (!fetchedYarnLockSeed) {
Task.log(
chalk.yellow(
[
'Warning: Failed to fetch the yarn.lock seed file.',
' You may end up with incompatible dependencies that break the app.',
' If you run into any errors, please search the issues at',
' https://github.com/backstage/backstage/issues for potential solutions',
].join('\n'),
),
);
}
Task.section('All set! Now you might want to');
if (opts.skipInstall) {
Task.log(
+42
View File
@@ -304,3 +304,45 @@ export async function tryInitGitRepository(dir: string) {
return false;
}
}
/**
* This fetches the yarn.lock seed file at https://github.com/backstage/backstage/blob/master/packages/create-app/seed-yarn.lock
* Its purpose is to lock individual dependencies with broken releases to known working versions.
* This flow is decoupled from the release of the create-app package in order to avoid
* the need to re-publish the create-app package whenever we want to update the seed file.
*
* @returns true if the yarn.lock seed file was fetched successfully
*/
export async function fetchYarnLockSeedTask(dir: string) {
try {
await Task.forItem('fetching', 'yarn.lock seed', async () => {
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);
const res = await fetch(
'https://raw.githubusercontent.com/backstage/backstage/master/packages/create-app/seed-yarn.lock',
{
signal: controller.signal,
},
);
if (!res.ok) {
throw new Error(
`Request failed with status ${res.status} ${res.statusText}`,
);
}
const initialYarnLockContent = await res.text();
await fs.writeFile(
resolvePath(dir, 'yarn.lock'),
initialYarnLockContent
.split('\n')
.filter(l => !l.startsWith('//'))
.join('\n'),
'utf8',
);
});
return true;
} catch {
return false;
}
}