@ucdjs/release-scripts 0.1.0-beta.36 → 0.1.0-beta.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +299 -4
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
package/dist/index.mjs
CHANGED
|
@@ -362,7 +362,9 @@ function normalizeReleaseScriptsOptions(options) {
|
|
|
362
362
|
} : DEFAULT_TYPES,
|
|
363
363
|
npm: {
|
|
364
364
|
otp: npm.otp,
|
|
365
|
-
provenance: npm.provenance ?? true
|
|
365
|
+
provenance: npm.provenance ?? true,
|
|
366
|
+
access: npm.access ?? "public",
|
|
367
|
+
runBuild: npm.runBuild ?? true
|
|
366
368
|
},
|
|
367
369
|
prompts: {
|
|
368
370
|
versions: prompts.versions ?? !isCI,
|
|
@@ -482,7 +484,19 @@ async function checkoutBranch(branch, workspaceRoot) {
|
|
|
482
484
|
}
|
|
483
485
|
return ok(false);
|
|
484
486
|
} catch (error) {
|
|
485
|
-
|
|
487
|
+
const gitError = toGitError("checkoutBranch", error);
|
|
488
|
+
logger.error(`Git checkout failed: ${gitError.message}`);
|
|
489
|
+
if (gitError.stderr) logger.error(`Git stderr: ${gitError.stderr}`);
|
|
490
|
+
try {
|
|
491
|
+
const branchResult = await run("git", ["branch", "-a"], { nodeOptions: {
|
|
492
|
+
cwd: workspaceRoot,
|
|
493
|
+
stdio: "pipe"
|
|
494
|
+
} });
|
|
495
|
+
logger.verbose(`Available branches:\n${branchResult.stdout}`);
|
|
496
|
+
} catch {
|
|
497
|
+
logger.verbose("Could not list available branches");
|
|
498
|
+
}
|
|
499
|
+
return err(gitError);
|
|
486
500
|
}
|
|
487
501
|
}
|
|
488
502
|
async function pullLatestChanges(branch, workspaceRoot) {
|
|
@@ -656,6 +670,60 @@ async function getGroupedFilesByCommitSha(workspaceRoot, from, to) {
|
|
|
656
670
|
return err(toGitError("getGroupedFilesByCommitSha", error));
|
|
657
671
|
}
|
|
658
672
|
}
|
|
673
|
+
/**
|
|
674
|
+
* Create a git tag for a package release
|
|
675
|
+
* @param packageName - The package name (e.g., "@scope/name")
|
|
676
|
+
* @param version - The version to tag (e.g., "1.2.3")
|
|
677
|
+
* @param workspaceRoot - The root directory of the workspace
|
|
678
|
+
* @returns Result indicating success or failure
|
|
679
|
+
*/
|
|
680
|
+
async function createPackageTag(packageName, version, workspaceRoot) {
|
|
681
|
+
const tagName = `${packageName}@${version}`;
|
|
682
|
+
try {
|
|
683
|
+
logger.info(`Creating tag: ${farver.green(tagName)}`);
|
|
684
|
+
await runIfNotDry("git", ["tag", tagName], { nodeOptions: {
|
|
685
|
+
cwd: workspaceRoot,
|
|
686
|
+
stdio: "pipe"
|
|
687
|
+
} });
|
|
688
|
+
return ok(void 0);
|
|
689
|
+
} catch (error) {
|
|
690
|
+
return err(toGitError("createPackageTag", error));
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Push a specific tag to the remote repository
|
|
695
|
+
* @param tagName - The tag name to push
|
|
696
|
+
* @param workspaceRoot - The root directory of the workspace
|
|
697
|
+
* @returns Result indicating success or failure
|
|
698
|
+
*/
|
|
699
|
+
async function pushTag(tagName, workspaceRoot) {
|
|
700
|
+
try {
|
|
701
|
+
logger.info(`Pushing tag: ${farver.green(tagName)}`);
|
|
702
|
+
await runIfNotDry("git", [
|
|
703
|
+
"push",
|
|
704
|
+
"origin",
|
|
705
|
+
tagName
|
|
706
|
+
], { nodeOptions: {
|
|
707
|
+
cwd: workspaceRoot,
|
|
708
|
+
stdio: "pipe"
|
|
709
|
+
} });
|
|
710
|
+
return ok(void 0);
|
|
711
|
+
} catch (error) {
|
|
712
|
+
return err(toGitError("pushTag", error));
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Create and push a package tag in one operation
|
|
717
|
+
* @param packageName - The package name
|
|
718
|
+
* @param version - The version to tag
|
|
719
|
+
* @param workspaceRoot - The root directory of the workspace
|
|
720
|
+
* @returns Result indicating success or failure
|
|
721
|
+
*/
|
|
722
|
+
async function createAndPushPackageTag(packageName, version, workspaceRoot) {
|
|
723
|
+
const createResult = await createPackageTag(packageName, version, workspaceRoot);
|
|
724
|
+
if (!createResult.ok) return createResult;
|
|
725
|
+
return pushTag(`${packageName}@${version}`, workspaceRoot);
|
|
726
|
+
}
|
|
659
727
|
|
|
660
728
|
//#endregion
|
|
661
729
|
//#region src/core/changelog.ts
|
|
@@ -1281,6 +1349,51 @@ function getAllAffectedPackages(graph, changedPackages) {
|
|
|
1281
1349
|
return affected;
|
|
1282
1350
|
}
|
|
1283
1351
|
/**
|
|
1352
|
+
* Calculate the order in which packages should be published
|
|
1353
|
+
*
|
|
1354
|
+
* Performs topological sorting to ensure dependencies are published before dependents.
|
|
1355
|
+
* Assigns a "level" to each package based on its depth in the dependency tree.
|
|
1356
|
+
*
|
|
1357
|
+
* This is used by the publish command to publish packages in the correct order.
|
|
1358
|
+
*
|
|
1359
|
+
* @param graph - Dependency graph
|
|
1360
|
+
* @param packagesToPublish - Set of package names to publish
|
|
1361
|
+
* @returns Array of packages in publish order with their dependency level
|
|
1362
|
+
*/
|
|
1363
|
+
function getPackagePublishOrder(graph, packagesToPublish) {
|
|
1364
|
+
const result = [];
|
|
1365
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1366
|
+
const toUpdate = new Set(packagesToPublish);
|
|
1367
|
+
const packagesToProcess = new Set(packagesToPublish);
|
|
1368
|
+
for (const pkg of packagesToPublish) {
|
|
1369
|
+
const deps = graph.dependents.get(pkg);
|
|
1370
|
+
if (deps) for (const dep of deps) {
|
|
1371
|
+
packagesToProcess.add(dep);
|
|
1372
|
+
toUpdate.add(dep);
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
function visit(pkgName, level) {
|
|
1376
|
+
if (visited.has(pkgName)) return;
|
|
1377
|
+
visited.add(pkgName);
|
|
1378
|
+
const pkg = graph.packages.get(pkgName);
|
|
1379
|
+
if (!pkg) return;
|
|
1380
|
+
const allDeps = [...pkg.workspaceDependencies, ...pkg.workspaceDevDependencies];
|
|
1381
|
+
let maxDepLevel = level;
|
|
1382
|
+
for (const dep of allDeps) if (toUpdate.has(dep)) {
|
|
1383
|
+
visit(dep, level);
|
|
1384
|
+
const depResult = result.find((r) => r.package.name === dep);
|
|
1385
|
+
if (depResult && depResult.level >= maxDepLevel) maxDepLevel = depResult.level + 1;
|
|
1386
|
+
}
|
|
1387
|
+
result.push({
|
|
1388
|
+
package: pkg,
|
|
1389
|
+
level: maxDepLevel
|
|
1390
|
+
});
|
|
1391
|
+
}
|
|
1392
|
+
for (const pkg of toUpdate) visit(pkg, 0);
|
|
1393
|
+
result.sort((a, b) => a.level - b.level);
|
|
1394
|
+
return result;
|
|
1395
|
+
}
|
|
1396
|
+
/**
|
|
1284
1397
|
* Create version updates for all packages affected by dependency changes
|
|
1285
1398
|
*
|
|
1286
1399
|
* When a package is updated, all packages that depend on it should also be updated.
|
|
@@ -1741,11 +1854,193 @@ async function prepareWorkflow(options) {
|
|
|
1741
1854
|
};
|
|
1742
1855
|
}
|
|
1743
1856
|
|
|
1857
|
+
//#endregion
|
|
1858
|
+
//#region src/core/npm.ts
|
|
1859
|
+
function toNPMError(operation, error, code) {
|
|
1860
|
+
return {
|
|
1861
|
+
type: "npm",
|
|
1862
|
+
operation,
|
|
1863
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1864
|
+
code
|
|
1865
|
+
};
|
|
1866
|
+
}
|
|
1867
|
+
/**
|
|
1868
|
+
* Get the NPM registry URL
|
|
1869
|
+
* Respects NPM_CONFIG_REGISTRY environment variable, defaults to npmjs.org
|
|
1870
|
+
*/
|
|
1871
|
+
function getRegistryURL() {
|
|
1872
|
+
return process.env.NPM_CONFIG_REGISTRY || "https://registry.npmjs.org";
|
|
1873
|
+
}
|
|
1874
|
+
/**
|
|
1875
|
+
* Fetch package metadata from NPM registry
|
|
1876
|
+
* @param packageName - The package name (e.g., "lodash" or "@scope/name")
|
|
1877
|
+
* @returns Result with package metadata or error
|
|
1878
|
+
*/
|
|
1879
|
+
async function getPackageMetadata(packageName) {
|
|
1880
|
+
try {
|
|
1881
|
+
const registry = getRegistryURL();
|
|
1882
|
+
const encodedName = packageName.startsWith("@") ? `@${encodeURIComponent(packageName.slice(1))}` : encodeURIComponent(packageName);
|
|
1883
|
+
const response = await fetch(`${registry}/${encodedName}`, { headers: { Accept: "application/json" } });
|
|
1884
|
+
if (!response.ok) {
|
|
1885
|
+
if (response.status === 404) return err(toNPMError("getPackageMetadata", `Package not found: ${packageName}`, "E404"));
|
|
1886
|
+
return err(toNPMError("getPackageMetadata", `HTTP ${response.status}: ${response.statusText}`));
|
|
1887
|
+
}
|
|
1888
|
+
return ok(await response.json());
|
|
1889
|
+
} catch (error) {
|
|
1890
|
+
return err(toNPMError("getPackageMetadata", error, "ENETWORK"));
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
/**
|
|
1894
|
+
* Check if a specific package version exists on NPM
|
|
1895
|
+
* @param packageName - The package name
|
|
1896
|
+
* @param version - The version to check (e.g., "1.2.3")
|
|
1897
|
+
* @returns Result with boolean (true if version exists) or error
|
|
1898
|
+
*/
|
|
1899
|
+
async function checkVersionExists(packageName, version) {
|
|
1900
|
+
const metadataResult = await getPackageMetadata(packageName);
|
|
1901
|
+
if (!metadataResult.ok) {
|
|
1902
|
+
if (metadataResult.error.code === "E404") return ok(false);
|
|
1903
|
+
return err(metadataResult.error);
|
|
1904
|
+
}
|
|
1905
|
+
return ok(version in metadataResult.value.versions);
|
|
1906
|
+
}
|
|
1907
|
+
/**
|
|
1908
|
+
* Build a package before publishing
|
|
1909
|
+
* @param packageName - The package name to build
|
|
1910
|
+
* @param workspaceRoot - Path to the workspace root
|
|
1911
|
+
* @param options - Normalized release scripts options
|
|
1912
|
+
* @returns Result indicating success or failure
|
|
1913
|
+
*/
|
|
1914
|
+
async function buildPackage(packageName, workspaceRoot, options) {
|
|
1915
|
+
if (!options.npm.runBuild) return ok(void 0);
|
|
1916
|
+
try {
|
|
1917
|
+
await runIfNotDry("pnpm", [
|
|
1918
|
+
"--filter",
|
|
1919
|
+
packageName,
|
|
1920
|
+
"build"
|
|
1921
|
+
], { nodeOptions: {
|
|
1922
|
+
cwd: workspaceRoot,
|
|
1923
|
+
stdio: "inherit"
|
|
1924
|
+
} });
|
|
1925
|
+
return ok(void 0);
|
|
1926
|
+
} catch (error) {
|
|
1927
|
+
return err(toNPMError("buildPackage", error));
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
/**
|
|
1931
|
+
* Publish a package to NPM
|
|
1932
|
+
* Uses pnpm to handle workspace protocol and catalog: resolution automatically
|
|
1933
|
+
* @param packageName - The package name to publish
|
|
1934
|
+
* @param workspaceRoot - Path to the workspace root
|
|
1935
|
+
* @param options - Normalized release scripts options
|
|
1936
|
+
* @returns Result indicating success or failure
|
|
1937
|
+
*/
|
|
1938
|
+
async function publishPackage(packageName, workspaceRoot, options) {
|
|
1939
|
+
const args = [
|
|
1940
|
+
"--filter",
|
|
1941
|
+
packageName,
|
|
1942
|
+
"publish",
|
|
1943
|
+
"--access",
|
|
1944
|
+
options.npm.access,
|
|
1945
|
+
"--no-git-checks"
|
|
1946
|
+
];
|
|
1947
|
+
if (options.npm.otp) args.push("--otp", options.npm.otp);
|
|
1948
|
+
if (process.env.NPM_CONFIG_TAG) args.push("--tag", process.env.NPM_CONFIG_TAG);
|
|
1949
|
+
const env = { ...process.env };
|
|
1950
|
+
if (options.npm.provenance) env.NPM_CONFIG_PROVENANCE = "true";
|
|
1951
|
+
try {
|
|
1952
|
+
await runIfNotDry("pnpm", args, { nodeOptions: {
|
|
1953
|
+
cwd: workspaceRoot,
|
|
1954
|
+
stdio: "inherit",
|
|
1955
|
+
env
|
|
1956
|
+
} });
|
|
1957
|
+
return ok(void 0);
|
|
1958
|
+
} catch (error) {
|
|
1959
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
1960
|
+
return err(toNPMError("publishPackage", error, errorMessage.includes("E403") ? "E403" : errorMessage.includes("EPUBLISHCONFLICT") ? "EPUBLISHCONFLICT" : errorMessage.includes("EOTP") ? "EOTP" : void 0));
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1744
1964
|
//#endregion
|
|
1745
1965
|
//#region src/workflows/publish.ts
|
|
1746
1966
|
async function publishWorkflow(options) {
|
|
1747
|
-
logger.
|
|
1748
|
-
|
|
1967
|
+
logger.section("📦 Publishing Packages");
|
|
1968
|
+
const discovered = await discoverWorkspacePackages(options.workspaceRoot, options);
|
|
1969
|
+
if (!discovered.ok) exitWithError(`Failed to discover packages: ${discovered.error.message}`);
|
|
1970
|
+
const workspacePackages = discovered.value;
|
|
1971
|
+
logger.item(`Found ${workspacePackages.length} packages in workspace`);
|
|
1972
|
+
const graph = buildPackageDependencyGraph(workspacePackages);
|
|
1973
|
+
const publicPackages = workspacePackages.filter((pkg) => !pkg.packageJson.private);
|
|
1974
|
+
logger.item(`Publishing ${publicPackages.length} public packages (private packages excluded)`);
|
|
1975
|
+
if (publicPackages.length === 0) {
|
|
1976
|
+
logger.warn("No public packages to publish");
|
|
1977
|
+
return;
|
|
1978
|
+
}
|
|
1979
|
+
const publishOrder = getPackagePublishOrder(graph, new Set(publicPackages.map((p) => p.name)));
|
|
1980
|
+
const status = {
|
|
1981
|
+
published: [],
|
|
1982
|
+
skipped: [],
|
|
1983
|
+
failed: []
|
|
1984
|
+
};
|
|
1985
|
+
for (const order of publishOrder) {
|
|
1986
|
+
const pkg = order.package;
|
|
1987
|
+
const version = pkg.version;
|
|
1988
|
+
const packageName = pkg.name;
|
|
1989
|
+
logger.section(`📦 ${farver.cyan(packageName)} ${farver.gray(`(level ${order.level})`)}`);
|
|
1990
|
+
logger.step(`Checking if ${farver.cyan(`${packageName}@${version}`)} exists on NPM...`);
|
|
1991
|
+
const existsResult = await checkVersionExists(packageName, version);
|
|
1992
|
+
if (!existsResult.ok) {
|
|
1993
|
+
logger.error(`Failed to check version: ${existsResult.error.message}`);
|
|
1994
|
+
status.failed.push(packageName);
|
|
1995
|
+
exitWithError(`Publishing failed for ${packageName}: ${existsResult.error.message}`, "Check your network connection and NPM registry access");
|
|
1996
|
+
}
|
|
1997
|
+
if (existsResult.value) {
|
|
1998
|
+
logger.info(`Version ${farver.cyan(version)} already exists on NPM, skipping`);
|
|
1999
|
+
status.skipped.push(packageName);
|
|
2000
|
+
continue;
|
|
2001
|
+
}
|
|
2002
|
+
if (options.npm.runBuild) {
|
|
2003
|
+
logger.step(`Building ${farver.cyan(packageName)}...`);
|
|
2004
|
+
const buildResult = await buildPackage(packageName, options.workspaceRoot, options);
|
|
2005
|
+
if (!buildResult.ok) {
|
|
2006
|
+
logger.error(`Failed to build package: ${buildResult.error.message}`);
|
|
2007
|
+
status.failed.push(packageName);
|
|
2008
|
+
exitWithError(`Publishing failed for ${packageName}: build failed`, "Check your build scripts and dependencies");
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
logger.step(`Publishing ${farver.cyan(`${packageName}@${version}`)} to NPM...`);
|
|
2012
|
+
const publishResult = await publishPackage(packageName, options.workspaceRoot, options);
|
|
2013
|
+
if (!publishResult.ok) {
|
|
2014
|
+
logger.error(`Failed to publish: ${publishResult.error.message}`);
|
|
2015
|
+
status.failed.push(packageName);
|
|
2016
|
+
let hint;
|
|
2017
|
+
if (publishResult.error.code === "E403") hint = "Authentication failed. Ensure your NPM token or OIDC configuration is correct";
|
|
2018
|
+
else if (publishResult.error.code === "EPUBLISHCONFLICT") hint = "Version conflict. The version may have been published recently";
|
|
2019
|
+
else if (publishResult.error.code === "EOTP") hint = "2FA/OTP required. Provide the otp option or use OIDC authentication";
|
|
2020
|
+
exitWithError(`Publishing failed for ${packageName}`, hint);
|
|
2021
|
+
}
|
|
2022
|
+
logger.success(`Published ${farver.cyan(`${packageName}@${version}`)}`);
|
|
2023
|
+
status.published.push(packageName);
|
|
2024
|
+
logger.step(`Creating git tag ${farver.cyan(`${packageName}@${version}`)}...`);
|
|
2025
|
+
const tagResult = await createAndPushPackageTag(packageName, version, options.workspaceRoot);
|
|
2026
|
+
if (!tagResult.ok) {
|
|
2027
|
+
logger.error(`Failed to create/push tag: ${tagResult.error.message}`);
|
|
2028
|
+
logger.warn(`Package was published but tag was not created. You may need to create it manually.`);
|
|
2029
|
+
} else logger.success(`Created and pushed tag ${farver.cyan(`${packageName}@${version}`)}`);
|
|
2030
|
+
}
|
|
2031
|
+
logger.section("📊 Publishing Summary");
|
|
2032
|
+
logger.item(`${farver.green("✓")} Published: ${status.published.length} package(s)`);
|
|
2033
|
+
if (status.published.length > 0) for (const pkg of status.published) logger.item(` ${farver.green("•")} ${pkg}`);
|
|
2034
|
+
if (status.skipped.length > 0) {
|
|
2035
|
+
logger.item(`${farver.yellow("⚠")} Skipped (already exists): ${status.skipped.length} package(s)`);
|
|
2036
|
+
for (const pkg of status.skipped) logger.item(` ${farver.yellow("•")} ${pkg}`);
|
|
2037
|
+
}
|
|
2038
|
+
if (status.failed.length > 0) {
|
|
2039
|
+
logger.item(`${farver.red("✖")} Failed: ${status.failed.length} package(s)`);
|
|
2040
|
+
for (const pkg of status.failed) logger.item(` ${farver.red("•")} ${pkg}`);
|
|
2041
|
+
}
|
|
2042
|
+
if (status.failed.length > 0) exitWithError(`Publishing completed with ${status.failed.length} failure(s)`);
|
|
2043
|
+
logger.success("All packages published successfully!");
|
|
1749
2044
|
}
|
|
1750
2045
|
|
|
1751
2046
|
//#endregion
|