-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
chore(e2e): pack with utoo pm-pack instead of pnpm #6013
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+262
−10
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,167 @@ | ||
| #!/usr/bin/env node | ||
| /** | ||
| * Pack every publishable workspace package into a tgz at the repo root using | ||
| * utoo's `ut pm-pack`, as a drop-in replacement for `pnpm -r pack` in the | ||
| * ecosystem-ci (E2E) workflow. | ||
| * | ||
| * Why this exists (utoo >= 1.1 quirks): | ||
| * - `ut pm-pack` resolves `workspace:` deps via the npm-style `workspaces` | ||
| * field in the root package.json -- NOT from pnpm-workspace.yaml. So we | ||
| * temporarily inject `workspaces` (mirrored from pnpm-workspace.yaml) for the | ||
| * duration of packing, then restore package.json. | ||
| * - `ut pm-pack` resolves `catalog:` deps from `.utoo.toml`, NOT from | ||
| * pnpm-workspace.yaml. We generate `.utoo.toml` from pnpm-workspace.yaml. | ||
| * - `ut pm-pack` does NOT apply `publishConfig` overrides the way npm/pnpm do. | ||
| * egg packages keep dev `exports` pointing at `src/*.ts` and override them to | ||
| * `dist/*.js` via `publishConfig.exports`, so we apply `publishConfig` onto | ||
| * each manifest before packing (then restore it), otherwise the tarballs ship | ||
| * `src` exports and downstream installs fail with MODULE_NOT_FOUND. | ||
| * - `ut pm-pack <path>` writes the tgz INTO the package dir (no | ||
| * --pack-destination). patch-project.ts expects all tgz at the repo root with | ||
| * npm-standard names, so we move them up. | ||
| * | ||
| * The `ut` binary can be overridden with UT_BIN (used by local validation to | ||
| * point at a pinned utoo version). The repository tree is restored afterward. | ||
| */ | ||
| import { execFileSync } from 'node:child_process'; | ||
| import fs from 'node:fs'; | ||
| import { glob } from 'node:fs/promises'; | ||
| import path from 'node:path'; | ||
|
|
||
| import yaml from 'js-yaml'; | ||
|
|
||
| import { generateUtooToml } from '../scripts/gen-utoo-catalog.mjs'; | ||
|
|
||
| const rootDir = path.join(import.meta.dirname, '..'); | ||
| const UT_BIN = process.env.UT_BIN || (process.platform === 'win32' ? 'ut.cmd' : 'ut'); | ||
|
|
||
| // publishConfig keys that are manifest fields consumers read, which npm/pnpm | ||
| // copy onto the published manifest at publish time. Use an allowlist so | ||
| // publish-only keys (access, tag, registry, ignore, ...) never leak into the | ||
| // packed package.json. Mirrors pnpm's publish-time overridable field set. | ||
| const PUBLISHABLE_MANIFEST_FIELDS = new Set([ | ||
| 'bin', | ||
| 'main', | ||
| 'exports', | ||
| 'types', | ||
| 'typings', | ||
| 'module', | ||
| 'browser', | ||
| 'esnext', | ||
| 'es2015', | ||
| 'unpkg', | ||
| 'umd:main', | ||
| ]); | ||
|
|
||
| const ws = yaml.load(fs.readFileSync(path.join(rootDir, 'pnpm-workspace.yaml'), 'utf8')); | ||
|
|
||
| // Read a file, returning null when it does not exist (avoids a TOCTOU | ||
| // existsSync check before reading). | ||
| function readFileOrNull(filePath) { | ||
| try { | ||
| return fs.readFileSync(filePath, 'utf8'); | ||
| } catch (err) { | ||
| if (err.code === 'ENOENT') return null; | ||
| throw err; | ||
| } | ||
| } | ||
|
|
||
| // Discover publishable packages exactly like patch-project.ts: glob each | ||
| // workspace pattern for package.json, skip private / nameless packages. | ||
| async function discoverPackages() { | ||
| const packages = []; | ||
| for (const pattern of ws.packages) { | ||
| for await (const entry of glob(`${pattern}/package.json`, { cwd: rootDir })) { | ||
| const pkgJsonPath = path.join(rootDir, entry); | ||
| try { | ||
| const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8')); | ||
| if (pkgJson.private || !pkgJson.name) continue; | ||
| packages.push({ name: pkgJson.name, dir: path.dirname(entry), version: pkgJson.version }); | ||
| } catch { | ||
| console.warn(`Warning: could not read ${pkgJsonPath}`); | ||
| } | ||
| } | ||
| } | ||
| return packages; | ||
| } | ||
|
|
||
| // npm-standard tarball name, matches patch-project.ts's expectation. | ||
| function tgzName(name, version) { | ||
| return `${name.replace('@', '').replace('/', '-')}-${version}.tgz`; | ||
| } | ||
|
|
||
| // Apply publishConfig manifest overrides (e.g. exports -> dist) the way | ||
| // npm/pnpm do at publish time, skipping publish-control-only keys. | ||
| function applyPublishConfig(manifest) { | ||
| const pc = manifest.publishConfig; | ||
| if (!pc) return manifest; | ||
| for (const [key, value] of Object.entries(pc)) { | ||
| if (PUBLISHABLE_MANIFEST_FIELDS.has(key)) manifest[key] = value; | ||
| } | ||
| return manifest; | ||
| } | ||
|
|
||
| async function main() { | ||
| const pkgJsonPath = path.join(rootDir, 'package.json'); | ||
| const originalPkgJson = fs.readFileSync(pkgJsonPath, 'utf8'); | ||
| const utooTomlPath = path.join(rootDir, '.utoo.toml'); | ||
| const originalUtooToml = readFileOrNull(utooTomlPath); | ||
|
|
||
| // package.json files we mutated and still owe a restore (path -> original). | ||
| const pendingRestores = new Map(); | ||
|
|
||
| try { | ||
| // 1. Generate .utoo.toml so pm-pack can resolve catalog:/catalog:<name>. | ||
| fs.writeFileSync(utooTomlPath, generateUtooToml(rootDir)); | ||
|
github-advanced-security[bot] marked this conversation as resolved.
Fixed
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| // 2. Inject npm-style `workspaces` so pm-pack can discover workspace pkgs. | ||
| const pkgJson = JSON.parse(originalPkgJson); | ||
| pkgJson.workspaces = ws.packages; | ||
| fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + '\n'); | ||
|
|
||
| const packages = await discoverPackages(); | ||
| console.log(`📦 Packing ${packages.length} packages with ${UT_BIN} pm-pack`); | ||
|
|
||
| for (const pkg of packages) { | ||
| // 3. Apply publishConfig (exports -> dist, etc.) before packing so the | ||
| // tarball ships the published manifest, then restore the source file. | ||
| const manifestPath = path.join(rootDir, pkg.dir, 'package.json'); | ||
| const originalManifest = fs.readFileSync(manifestPath, 'utf8'); | ||
| pendingRestores.set(manifestPath, originalManifest); | ||
| const manifest = applyPublishConfig(JSON.parse(originalManifest)); | ||
| fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n'); | ||
|
|
||
| execFileSync(UT_BIN, ['pm-pack', pkg.dir], { cwd: rootDir, stdio: 'inherit' }); | ||
|
|
||
| fs.writeFileSync(manifestPath, originalManifest); | ||
| pendingRestores.delete(manifestPath); | ||
|
|
||
| const file = tgzName(pkg.name, pkg.version); | ||
| const from = path.join(rootDir, pkg.dir, file); | ||
| const to = path.join(rootDir, file); | ||
| try { | ||
| fs.renameSync(from, to); | ||
| } catch (err) { | ||
| throw new Error(`Expected tarball not found: ${from}`, { cause: err }); | ||
| } | ||
| console.log(` -> ${file}`); | ||
| } | ||
| console.log(`✅ Packed ${packages.length} tarballs into ${rootDir}`); | ||
| } finally { | ||
| // Restore everything we touched. | ||
| fs.writeFileSync(pkgJsonPath, originalPkgJson); | ||
| for (const [manifestPath, original] of pendingRestores) { | ||
| fs.writeFileSync(manifestPath, original); | ||
| } | ||
| if (originalUtooToml === null) { | ||
| fs.rmSync(utooTomlPath, { force: true }); | ||
| } else { | ||
| fs.writeFileSync(utooTomlPath, originalUtooToml); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| main().catch((err) => { | ||
| console.error(err); | ||
| process.exit(1); | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| #!/usr/bin/env node | ||
| /** | ||
| * Generate `.utoo.toml` from `pnpm-workspace.yaml`. | ||
| * | ||
| * `ut pm-pack` (utoo >= 1.1) resolves `catalog:` / `catalog:<name>` protocols | ||
| * from `.utoo.toml`, NOT from `pnpm-workspace.yaml`. To keep | ||
| * `pnpm-workspace.yaml` the single source of truth, we generate the TOML mirror | ||
| * on demand instead of committing a hand-maintained copy. | ||
| * | ||
| * pnpm `catalog:` map -> `[catalog]` | ||
| * pnpm `catalogs.<name>` -> `[catalogs.<name>]` | ||
| * | ||
| * Usage: | ||
| * node scripts/gen-utoo-catalog.mjs # writes <root>/.utoo.toml | ||
| * node scripts/gen-utoo-catalog.mjs --print # print to stdout, write nothing | ||
| */ | ||
| import fs from 'node:fs'; | ||
| import path from 'node:path'; | ||
|
|
||
| import yaml from 'js-yaml'; | ||
|
|
||
| // TOML keys that are not bare-key-safe (A-Za-z0-9_-) must be quoted. | ||
| const BARE_KEY = /^[A-Za-z0-9_-]+$/; | ||
| function tomlKey(name) { | ||
| return BARE_KEY.test(name) ? name : JSON.stringify(name); | ||
| } | ||
| function tomlValue(version) { | ||
| // version specs are always strings; JSON.stringify gives a valid TOML basic string | ||
| return JSON.stringify(String(version)); | ||
| } | ||
|
|
||
| function renderCatalogTable(entries) { | ||
| return Object.keys(entries) | ||
| .sort() | ||
| .map((name) => `${tomlKey(name)} = ${tomlValue(entries[name])}`) | ||
| .join('\n'); | ||
| } | ||
|
|
||
| export function generateUtooToml(rootDir = process.cwd()) { | ||
| const wsPath = path.join(rootDir, 'pnpm-workspace.yaml'); | ||
| const ws = yaml.load(fs.readFileSync(wsPath, 'utf8')) ?? {}; | ||
|
|
||
| const blocks = [ | ||
| '# AUTO-GENERATED from pnpm-workspace.yaml by scripts/gen-utoo-catalog.mjs', | ||
| '# Do not edit by hand. Source of truth is pnpm-workspace.yaml.', | ||
| ]; | ||
|
|
||
| if (ws.catalog && Object.keys(ws.catalog).length > 0) { | ||
| blocks.push(`[catalog]\n${renderCatalogTable(ws.catalog)}`); | ||
| } | ||
|
|
||
| if (ws.catalogs && Object.keys(ws.catalogs).length > 0) { | ||
| for (const catalogName of Object.keys(ws.catalogs).sort()) { | ||
| const entries = ws.catalogs[catalogName]; | ||
| if (entries && Object.keys(entries).length > 0) { | ||
| blocks.push(`[catalogs.${tomlKey(catalogName)}]\n${renderCatalogTable(entries)}`); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return blocks.join('\n\n') + '\n'; | ||
| } | ||
|
|
||
| function main() { | ||
| const rootDir = process.cwd(); | ||
| const toml = generateUtooToml(rootDir); | ||
| if (process.argv.includes('--print')) { | ||
| process.stdout.write(toml); | ||
| return; | ||
| } | ||
| const out = path.join(rootDir, '.utoo.toml'); | ||
| fs.writeFileSync(out, toml); | ||
| console.log(`Wrote ${out} (${toml.split('\n').length} lines)`); | ||
| } | ||
|
|
||
| // Run main() only when invoked directly. Compare resolved paths (not | ||
| // `file://${argv[1]}`) so the check also holds on Windows, where argv[1] uses | ||
| // backslashes while import.meta.url is a forward-slash file URL. | ||
| if (process.argv[1] && import.meta.filename === path.resolve(process.argv[1])) { | ||
| main(); | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.