@i-santos/create-package-starter 1.5.0-beta.12 → 1.5.0-beta.13

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.
Files changed (2) hide show
  1. package/lib/run.js +173 -68
  2. package/package.json +1 -1
package/lib/run.js CHANGED
@@ -45,7 +45,7 @@ function usage() {
45
45
  ' create-package-starter setup-github [--repo <owner/repo>] [--default-branch <branch>] [--ruleset <path>] [--dry-run]',
46
46
  ' create-package-starter setup-beta [--dir <directory>] [--repo <owner/repo>] [--beta-branch <branch>] [--default-branch <branch>] [--release-auth github-token|pat|app|manual-trigger] [--force] [--dry-run] [--yes]',
47
47
  ' create-package-starter open-pr [--repo <owner/repo>] [--base <branch>] [--head <branch>] [--title <text>] [--body <text>] [--body-file <path>] [--template <path>] [--draft] [--auto-merge] [--watch-checks] [--check-timeout <minutes>] [--yes] [--dry-run]',
48
- ' create-package-starter release-cycle [--repo <owner/repo>] [--mode auto|open-pr|publish] [--phase code|full] [--track auto|beta|stable] [--promote-stable] [--promote-type patch|minor|major] [--promote-summary <text>] [--head <branch>] [--base <branch>] [--title <text>] [--body-file <path>] [--update-pr-description] [--draft] [--auto-merge] [--watch-checks] [--check-timeout <minutes>] [--confirm-merges] [--merge-when-green] [--merge-method squash|merge|rebase] [--wait-release-pr] [--release-pr-timeout <minutes>] [--merge-release-pr] [--verify-npm] [--confirm-cleanup] [--sync-base auto|rebase|merge|off] [--no-resume] [--no-cleanup] [--yes] [--dry-run]',
48
+ ' create-package-starter release-cycle [--repo <owner/repo>] [--mode auto|open-pr|publish] [--phase code|full] [--track auto|beta|stable] [--promote-stable] [--promote-type patch|minor|major] [--promote-summary <text>] [--head <branch>] [--base <branch>] [--title <text>] [--body-file <path>] [--npm-package <name>] [--update-pr-description] [--draft] [--auto-merge] [--watch-checks] [--check-timeout <minutes>] [--confirm-merges] [--merge-when-green] [--merge-method squash|merge|rebase] [--wait-release-pr] [--release-pr-timeout <minutes>] [--merge-release-pr] [--verify-npm] [--confirm-cleanup] [--sync-base auto|rebase|merge|off] [--no-resume] [--no-cleanup] [--yes] [--dry-run]',
49
49
  ' create-package-starter promote-stable [--dir <directory>] [--type patch|minor|major] [--summary <text>] [--dry-run]',
50
50
  ' create-package-starter setup-npm [--dir <directory>] [--publish-first] [--dry-run]',
51
51
  '',
@@ -561,6 +561,7 @@ function parseReleaseCycleArgs(argv) {
561
561
  base: '',
562
562
  title: '',
563
563
  bodyFile: '',
564
+ npmPackages: [],
564
565
  updatePrDescription: false,
565
566
  draft: false,
566
567
  autoMerge: true,
@@ -645,6 +646,12 @@ function parseReleaseCycleArgs(argv) {
645
646
  continue;
646
647
  }
647
648
 
649
+ if (token === '--npm-package') {
650
+ args.npmPackages.push(parseValueFlag(argv, i, '--npm-package'));
651
+ i += 1;
652
+ continue;
653
+ }
654
+
648
655
  if (token === '--update-pr-description') {
649
656
  args.updatePrDescription = true;
650
657
  continue;
@@ -1992,64 +1999,145 @@ function waitForPromotionPr(repo, timeoutMinutes, deps) {
1992
1999
  throw new Error(`Timed out waiting for promotion PR after ${timeoutMinutes} minutes.`);
1993
2000
  }
1994
2001
 
1995
- function getRemotePackageVersion(repo, ref, deps) {
1996
- const endpoint = `/repos/${repo}/contents/package.json?ref=${encodeURIComponent(ref)}`;
2002
+ function encodePathForGitHubContent(pathValue) {
2003
+ return String(pathValue || '')
2004
+ .split('/')
2005
+ .filter(Boolean)
2006
+ .map((segment) => encodeURIComponent(segment))
2007
+ .join('/');
2008
+ }
2009
+
2010
+ function getRemotePackageVersionFromPath(repo, ref, packageJsonPath, deps) {
2011
+ const safePath = encodePathForGitHubContent(packageJsonPath);
2012
+ const endpoint = `/repos/${repo}/contents/${safePath}?ref=${encodeURIComponent(ref)}`;
1997
2013
  const contentResponse = ghApiJson(deps, 'GET', endpoint);
1998
2014
  if (!contentResponse.content) {
1999
- throw new Error(`Could not read package.json content from ${repo}@${ref}.`);
2015
+ throw new Error(`Could not read ${packageJsonPath} content from ${repo}@${ref}.`);
2000
2016
  }
2001
2017
 
2002
2018
  const decoded = Buffer.from(String(contentResponse.content).replace(/\n/g, ''), 'base64').toString('utf8');
2003
2019
  const parsed = parseJsonSafely(decoded, {});
2004
2020
  if (!parsed.name || !parsed.version) {
2005
- throw new Error(`package.json from ${repo}@${ref} must include name and version.`);
2021
+ throw new Error(`${packageJsonPath} from ${repo}@${ref} must include name and version.`);
2006
2022
  }
2007
2023
 
2008
2024
  return {
2009
2025
  name: parsed.name,
2010
- version: parsed.version
2026
+ version: parsed.version,
2027
+ packageJsonPath
2011
2028
  };
2012
2029
  }
2013
2030
 
2014
- function validateNpmPublishedVersionAndTag(packageName, expectedVersion, expectedTag, timeoutMinutes, deps) {
2031
+ function getRemotePackageVersion(repo, ref, deps) {
2032
+ return getRemotePackageVersionFromPath(repo, ref, 'package.json', deps);
2033
+ }
2034
+
2035
+ function listPullRequestFiles(repo, prNumber, deps) {
2036
+ const files = ghApiJson(deps, 'GET', `/repos/${repo}/pulls/${prNumber}/files?per_page=100`);
2037
+ return Array.isArray(files) ? files : [];
2038
+ }
2039
+
2040
+ function resolveExpectedNpmPackages(repo, releasePrNumber, targetRef, expectedTag, args, deps) {
2041
+ const explicitPackages = Array.isArray(args.npmPackages) ? args.npmPackages : [];
2042
+ const files = listPullRequestFiles(repo, releasePrNumber, deps);
2043
+ const packageJsonPaths = [...new Set(
2044
+ files
2045
+ .filter((file) => file && file.status !== 'removed' && typeof file.filename === 'string')
2046
+ .map((file) => file.filename)
2047
+ .filter((fileName) => fileName.endsWith('/package.json') || fileName === 'package.json')
2048
+ )];
2049
+
2050
+ const fallbackPaths = packageJsonPaths.length > 0 ? packageJsonPaths : ['package.json'];
2051
+ const resolved = fallbackPaths
2052
+ .map((filePath) => getRemotePackageVersionFromPath(repo, targetRef, filePath, deps))
2053
+ .filter((pkg) => pkg && pkg.name && pkg.version);
2054
+
2055
+ const byName = new Map();
2056
+ for (const pkg of resolved) {
2057
+ if (!byName.has(pkg.name)) {
2058
+ byName.set(pkg.name, pkg);
2059
+ }
2060
+ }
2061
+
2062
+ if (explicitPackages.length === 0) {
2063
+ return [...byName.values()];
2064
+ }
2065
+
2066
+ const filtered = [];
2067
+ const missing = [];
2068
+ for (const pkgName of explicitPackages) {
2069
+ if (byName.has(pkgName)) {
2070
+ filtered.push(byName.get(pkgName));
2071
+ } else {
2072
+ missing.push(pkgName);
2073
+ }
2074
+ }
2075
+
2076
+ if (missing.length > 0) {
2077
+ throw new Error(
2078
+ [
2079
+ `Could not resolve expected npm package(s) from release PR #${releasePrNumber}: ${missing.join(', ')}`,
2080
+ `Resolved package names: ${[...byName.keys()].join(', ') || 'none'}`,
2081
+ `Expected tag: ${expectedTag}`
2082
+ ].join('\n')
2083
+ );
2084
+ }
2085
+
2086
+ return filtered;
2087
+ }
2088
+
2089
+ function validateNpmPublishedPackages(packageTargets, expectedTag, timeoutMinutes, deps) {
2015
2090
  const timeoutAt = nowMs(deps) + timeoutMinutes * 60 * 1000;
2016
- let lastObservedVersion = '';
2017
- let lastObservedTagVersion = '';
2091
+ const observations = {};
2018
2092
  const isStableTrack = expectedTag === 'latest';
2019
2093
  const onProgress = typeof deps.onNpmValidationProgress === 'function' ? deps.onNpmValidationProgress : null;
2020
2094
  let lastProgressAt = 0;
2021
2095
 
2022
2096
  while (nowMs(deps) <= timeoutAt) {
2023
- const versionResult = deps.exec('npm', ['view', packageName, 'version', '--json']);
2024
- const tagsResult = deps.exec('npm', ['view', packageName, 'dist-tags', '--json']);
2025
- if (versionResult.status === 0 && tagsResult.status === 0) {
2026
- const observedVersion = String(parseJsonSafely(versionResult.stdout || '""', '') || '');
2027
- const tags = parseJsonSafely(tagsResult.stdout || '{}', {});
2028
- const observedTagVersion = tags && tags[expectedTag] ? String(tags[expectedTag]) : '';
2029
- lastObservedVersion = observedVersion;
2030
- lastObservedTagVersion = observedTagVersion;
2031
-
2032
- // npm "version" reflects the default dist-tag (usually latest), so beta verification
2033
- // must rely on the expected dist-tag value instead of global version.
2034
- const versionMatches = isStableTrack ? observedVersion === expectedVersion : true;
2035
- if (versionMatches && observedTagVersion === expectedVersion) {
2036
- return {
2037
- status: 'pass',
2038
- observedVersion,
2039
- observedTagVersion
2040
- };
2097
+ let allPass = true;
2098
+ for (const target of packageTargets) {
2099
+ const versionResult = deps.exec('npm', ['view', target.name, 'version', '--json']);
2100
+ const tagsResult = deps.exec('npm', ['view', target.name, 'dist-tags', '--json']);
2101
+ let observedVersion = '';
2102
+ let observedTagVersion = '';
2103
+ if (versionResult.status === 0) {
2104
+ observedVersion = String(parseJsonSafely(versionResult.stdout || '""', '') || '');
2105
+ }
2106
+ if (tagsResult.status === 0) {
2107
+ const tags = parseJsonSafely(tagsResult.stdout || '{}', {});
2108
+ observedTagVersion = tags && tags[expectedTag] ? String(tags[expectedTag]) : '';
2041
2109
  }
2042
2110
 
2043
- const now = nowMs(deps);
2044
- if (onProgress && (lastProgressAt === 0 || now - lastProgressAt >= 30_000)) {
2045
- lastProgressAt = now;
2046
- onProgress({
2047
- observedVersion,
2048
- observedTagVersion,
2049
- expectedVersion,
2050
- expectedTag
2051
- });
2111
+ const versionMatches = isStableTrack ? observedVersion === target.version : true;
2112
+ const tagMatches = observedTagVersion === target.version;
2113
+ const passed = versionMatches && tagMatches;
2114
+ if (!passed) {
2115
+ allPass = false;
2052
2116
  }
2117
+
2118
+ observations[target.name] = {
2119
+ name: target.name,
2120
+ expectedVersion: target.version,
2121
+ observedVersion,
2122
+ observedTagVersion,
2123
+ passed
2124
+ };
2125
+ }
2126
+
2127
+ if (allPass) {
2128
+ return {
2129
+ status: 'pass',
2130
+ observations
2131
+ };
2132
+ }
2133
+
2134
+ const now = nowMs(deps);
2135
+ if (onProgress && (lastProgressAt === 0 || now - lastProgressAt >= 30_000)) {
2136
+ lastProgressAt = now;
2137
+ onProgress({
2138
+ expectedTag,
2139
+ observations
2140
+ });
2053
2141
  }
2054
2142
 
2055
2143
  waitForNextPoll(timeoutAt, 10000, deps);
@@ -2057,8 +2145,7 @@ function validateNpmPublishedVersionAndTag(packageName, expectedVersion, expecte
2057
2145
 
2058
2146
  return {
2059
2147
  status: 'timeout',
2060
- observedVersion: lastObservedVersion,
2061
- observedTagVersion: lastObservedTagVersion
2148
+ observations
2062
2149
  };
2063
2150
  }
2064
2151
 
@@ -3164,36 +3251,45 @@ async function runReleaseCycle(args, dependencies = {}) {
3164
3251
  reporter.start('release-cycle-verify-npm', 'Validating npm publish and dist-tag...');
3165
3252
  const targetRef = args.promoteStable ? DEFAULT_BASE_BRANCH : DEFAULT_BETA_BRANCH;
3166
3253
  const expectedTag = requestedTrack === 'stable' ? 'latest' : 'beta';
3167
- const remotePackage = getRemotePackageVersion(gitContext.repo, targetRef, deps);
3168
- const npmValidation = validateNpmPublishedVersionAndTag(
3169
- remotePackage.name,
3170
- remotePackage.version,
3254
+ const targetPackages = resolveExpectedNpmPackages(
3255
+ gitContext.repo,
3256
+ mergedReleasePr.number,
3257
+ targetRef,
3258
+ expectedTag,
3259
+ args,
3260
+ deps
3261
+ );
3262
+ const npmValidation = validateNpmPublishedPackages(
3263
+ targetPackages,
3171
3264
  expectedTag,
3172
3265
  args.releasePrTimeout,
3173
3266
  {
3174
3267
  ...deps,
3175
- onNpmValidationProgress: ({ observedVersion, observedTagVersion, expectedVersion: expectedVersionValue, expectedTag: expectedTagValue }) => {
3176
- logStep(
3177
- 'run',
3178
- `Waiting npm propagation... expected ${expectedTagValue}=${expectedVersionValue}; observed version=${observedVersion || 'n/a'}, ${expectedTagValue}=${observedTagVersion || 'n/a'}.`
3179
- );
3268
+ onNpmValidationProgress: ({ expectedTag: expectedTagValue, observations }) => {
3269
+ const statusLine = Object.values(observations)
3270
+ .map((entry) => `${entry.name}: expected ${expectedTagValue}=${entry.expectedVersion}, observed version=${entry.observedVersion || 'n/a'}, ${expectedTagValue}=${entry.observedTagVersion || 'n/a'}`)
3271
+ .join(' | ');
3272
+ logStep('run', `Waiting npm propagation... ${statusLine}`);
3180
3273
  }
3181
3274
  }
3182
3275
  );
3183
3276
  if (npmValidation.status !== 'pass') {
3184
3277
  summary.npmValidation = `failed (${expectedTag})`;
3278
+ const observedLines = Object.values(npmValidation.observations || {})
3279
+ .map((entry) => `${entry.name}: version=${entry.observedVersion || 'n/a'}, ${expectedTag}=${entry.observedTagVersion || 'n/a'}`);
3280
+ const expectedLines = targetPackages
3281
+ .map((pkg) => `${pkg.name}@${pkg.version}`);
3185
3282
  throw new Error(
3186
3283
  [
3187
3284
  'npm validation failed after release merge.',
3188
- `Expected: ${remotePackage.name}@${remotePackage.version} with dist-tag ${expectedTag}`,
3189
- `Observed version: ${npmValidation.observedVersion || 'n/a'}`,
3190
- `Observed tag (${expectedTag}): ${npmValidation.observedTagVersion || 'n/a'}`
3285
+ `Expected (${expectedTag}): ${expectedLines.join(', ')}`,
3286
+ ...observedLines
3191
3287
  ].join('\n')
3192
3288
  );
3193
3289
  }
3194
- reporter.ok('release-cycle-verify-npm', `${remotePackage.name}@${remotePackage.version} validated on tag ${expectedTag}.`);
3195
- summary.actionsPerformed.push(`npm validation: ${remotePackage.name}@${remotePackage.version} (${expectedTag})`);
3196
- summary.npmValidation = `pass (${expectedTag} -> ${remotePackage.version})`;
3290
+ reporter.ok('release-cycle-verify-npm', `${targetPackages.length} package(s) validated on tag ${expectedTag}.`);
3291
+ summary.actionsPerformed.push(`npm validation: ${targetPackages.map((pkg) => `${pkg.name}@${pkg.version}`).join(', ')} (${expectedTag})`);
3292
+ summary.npmValidation = `pass (${expectedTag} -> ${targetPackages.map((pkg) => pkg.version).join(', ')})`;
3197
3293
  npmValidationPassed = true;
3198
3294
  } else if (!args.verifyNpm) {
3199
3295
  summary.actionsSkipped.push('verify npm');
@@ -3303,36 +3399,45 @@ async function runReleaseCycle(args, dependencies = {}) {
3303
3399
  reporter.start('release-cycle-verify-npm', 'Validating npm publish and dist-tag...');
3304
3400
  const targetRef = effectivePublishTrack === 'stable' ? DEFAULT_BASE_BRANCH : DEFAULT_BETA_BRANCH;
3305
3401
  const expectedTag = effectivePublishTrack === 'stable' ? 'latest' : 'beta';
3306
- const remotePackage = getRemotePackageVersion(gitContext.repo, targetRef, deps);
3307
- const npmValidation = validateNpmPublishedVersionAndTag(
3308
- remotePackage.name,
3309
- remotePackage.version,
3402
+ const targetPackages = resolveExpectedNpmPackages(
3403
+ gitContext.repo,
3404
+ releasePr.number,
3405
+ targetRef,
3406
+ expectedTag,
3407
+ args,
3408
+ deps
3409
+ );
3410
+ const npmValidation = validateNpmPublishedPackages(
3411
+ targetPackages,
3310
3412
  expectedTag,
3311
3413
  args.releasePrTimeout,
3312
3414
  {
3313
3415
  ...deps,
3314
- onNpmValidationProgress: ({ observedVersion, observedTagVersion, expectedVersion: expectedVersionValue, expectedTag: expectedTagValue }) => {
3315
- logStep(
3316
- 'run',
3317
- `Waiting npm propagation... expected ${expectedTagValue}=${expectedVersionValue}; observed version=${observedVersion || 'n/a'}, ${expectedTagValue}=${observedTagVersion || 'n/a'}.`
3318
- );
3416
+ onNpmValidationProgress: ({ expectedTag: expectedTagValue, observations }) => {
3417
+ const statusLine = Object.values(observations)
3418
+ .map((entry) => `${entry.name}: expected ${expectedTagValue}=${entry.expectedVersion}, observed version=${entry.observedVersion || 'n/a'}, ${expectedTagValue}=${entry.observedTagVersion || 'n/a'}`)
3419
+ .join(' | ');
3420
+ logStep('run', `Waiting npm propagation... ${statusLine}`);
3319
3421
  }
3320
3422
  }
3321
3423
  );
3322
3424
  if (npmValidation.status !== 'pass') {
3323
3425
  summary.npmValidation = `failed (${expectedTag})`;
3426
+ const observedLines = Object.values(npmValidation.observations || {})
3427
+ .map((entry) => `${entry.name}: version=${entry.observedVersion || 'n/a'}, ${expectedTag}=${entry.observedTagVersion || 'n/a'}`);
3428
+ const expectedLines = targetPackages
3429
+ .map((pkg) => `${pkg.name}@${pkg.version}`);
3324
3430
  throw new Error(
3325
3431
  [
3326
3432
  'npm validation failed after release merge.',
3327
- `Expected: ${remotePackage.name}@${remotePackage.version} with dist-tag ${expectedTag}`,
3328
- `Observed version: ${npmValidation.observedVersion || 'n/a'}`,
3329
- `Observed tag (${expectedTag}): ${npmValidation.observedTagVersion || 'n/a'}`
3433
+ `Expected (${expectedTag}): ${expectedLines.join(', ')}`,
3434
+ ...observedLines
3330
3435
  ].join('\n')
3331
3436
  );
3332
3437
  }
3333
- reporter.ok('release-cycle-verify-npm', `${remotePackage.name}@${remotePackage.version} validated on tag ${expectedTag}.`);
3334
- summary.actionsPerformed.push(`npm validation: ${remotePackage.name}@${remotePackage.version} (${expectedTag})`);
3335
- summary.npmValidation = `pass (${expectedTag} -> ${remotePackage.version})`;
3438
+ reporter.ok('release-cycle-verify-npm', `${targetPackages.length} package(s) validated on tag ${expectedTag}.`);
3439
+ summary.actionsPerformed.push(`npm validation: ${targetPackages.map((pkg) => `${pkg.name}@${pkg.version}`).join(', ')} (${expectedTag})`);
3440
+ summary.npmValidation = `pass (${expectedTag} -> ${targetPackages.map((pkg) => pkg.version).join(', ')})`;
3336
3441
  npmValidationPassed = true;
3337
3442
  } else if (!args.verifyNpm) {
3338
3443
  summary.npmValidation = 'skipped';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@i-santos/create-package-starter",
3
- "version": "1.5.0-beta.12",
3
+ "version": "1.5.0-beta.13",
4
4
  "description": "Scaffold new npm packages with a standardized Changesets release workflow",
5
5
  "license": "MIT",
6
6
  "author": "Igor Santos",