fix publish script

This commit is contained in:
Carl-Gerhard Lindesvärd
2024-08-10 00:05:29 +02:00
parent 4c23b842a3
commit 3179cd93e8
6 changed files with 261 additions and 222 deletions

View File

@@ -9,7 +9,7 @@
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@openpanel/sdk": "1.0.0", "@openpanel/sdk": "1.0.0-local",
"request-ip": "^3.3.0" "request-ip": "^3.3.0"
}, },
"peerDependencies": { "peerDependencies": {

View File

@@ -9,7 +9,7 @@
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@openpanel/web": "1.0.0" "@openpanel/web": "1.0.0-local"
}, },
"peerDependencies": { "peerDependencies": {
"next": "^12.0.0 || ^13.0.0 || ^14.0.0", "next": "^12.0.0 || ^13.0.0 || ^14.0.0",

View File

@@ -9,7 +9,7 @@
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@openpanel/sdk": "1.0.0" "@openpanel/sdk": "1.0.0-local"
}, },
"devDependencies": { "devDependencies": {
"@openpanel/eslint-config": "workspace:*", "@openpanel/eslint-config": "workspace:*",

View File

@@ -9,7 +9,7 @@
"typecheck": "tsc --noEmit" "typecheck": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@openpanel/sdk": "1.0.0" "@openpanel/sdk": "1.0.0-local"
}, },
"devDependencies": { "devDependencies": {
"@openpanel/eslint-config": "workspace:*", "@openpanel/eslint-config": "workspace:*",

16
pnpm-lock.yaml generated
View File

@@ -1168,8 +1168,8 @@ importers:
packages/sdks/express: packages/sdks/express:
dependencies: dependencies:
'@openpanel/sdk': '@openpanel/sdk':
specifier: 1.0.0 specifier: 1.0.0-local
version: 1.0.0 version: link:../sdk
express: express:
specifier: ^3.0.0 || ^4.0.0 specifier: ^3.0.0 || ^4.0.0
version: 4.18.2 version: 4.18.2
@@ -1208,8 +1208,8 @@ importers:
packages/sdks/nextjs: packages/sdks/nextjs:
dependencies: dependencies:
'@openpanel/web': '@openpanel/web':
specifier: 1.0.0 specifier: 1.0.0-local
version: 1.0.0 version: link:../web
next: next:
specifier: ^12.0.0 || ^13.0.0 || ^14.0.0 specifier: ^12.0.0 || ^13.0.0 || ^14.0.0
version: 14.1.0(react-dom@18.2.0)(react@18.2.0) version: 14.1.0(react-dom@18.2.0)(react@18.2.0)
@@ -1248,8 +1248,8 @@ importers:
packages/sdks/react-native: packages/sdks/react-native:
dependencies: dependencies:
'@openpanel/sdk': '@openpanel/sdk':
specifier: 1.0.0 specifier: 1.0.0-local
version: 1.0.0 version: link:../sdk
expo-application: expo-application:
specifier: ^5 specifier: ^5
version: 5.3.1(expo@50.0.7) version: 5.3.1(expo@50.0.7)
@@ -1315,8 +1315,8 @@ importers:
packages/sdks/web: packages/sdks/web:
dependencies: dependencies:
'@openpanel/sdk': '@openpanel/sdk':
specifier: 1.0.0 specifier: 1.0.0-local
version: 1.0.0 version: link:../sdk
devDependencies: devDependencies:
'@openpanel/eslint-config': '@openpanel/eslint-config':
specifier: workspace:* specifier: workspace:*

View File

@@ -1,146 +1,115 @@
import { exec, execSync } from 'node:child_process'; import { execSync } from 'node:child_process';
import fs from 'node:fs'; import fs from 'node:fs';
import path from 'node:path'; import path from 'node:path';
import arg from 'arg'; import arg from 'arg';
import type { ReleaseType } from 'semver'; import type { ReleaseType } from 'semver';
import semver, { RELEASE_TYPES } from 'semver'; import semver, { RELEASE_TYPES } from 'semver';
// Types
interface PackageJson {
name: string;
version: string;
dependencies?: Record<string, string>;
devDependencies?: Record<string, string>;
[key: string]: any;
}
interface PackageInfo extends PackageJson {
nextVersion: string;
localPath: string;
}
interface PublishConfig {
registry: string;
test: boolean;
}
// Utility functions
const workspacePath = (relativePath: string) => const workspacePath = (relativePath: string) =>
path.resolve(__dirname, '../../', relativePath); path.resolve(__dirname, '../../', relativePath);
function savePackageJson(absPath: string, data: Record<string, any>) { const savePackageJson = (absPath: string, data: PackageJson) => {
fs.writeFileSync(absPath, JSON.stringify(data, null, 2), 'utf-8'); fs.writeFileSync(absPath, JSON.stringify(data, null, 2), 'utf-8');
} };
function exit(message: string, error?: unknown) {
console.log(`\n\n❌ ${message}`);
if (error instanceof Error) {
console.log(`Error: ${error.message}`);
console.log(error);
} else if (typeof error === 'string') {
console.log(`Error: ${error}`);
}
console.log('\n');
const exit = (message: string, error?: unknown) => {
console.error(`\n\n❌ ${message}`);
if (error) console.error('Error:', error);
process.exit(1); process.exit(1);
} };
function checkUncommittedChanges() { const checkUncommittedChanges = () => {
try { try {
const changedFiles = execSync('git ls-files --exclude-standard --others') const uncommittedFiles = execSync('git status --porcelain')
.toString() .toString()
.trim(); .trim();
if (changedFiles !== '') { if (uncommittedFiles) throw new Error('Uncommitted changes detected');
throw new Error('Uncommitted changes');
}
execSync('git diff HEAD --exit-code');
console.log('✅ No uncommitted changes'); console.log('✅ No uncommitted changes');
} catch (error) { } catch (error) {
exit('Uncommitted changes'); exit('Uncommitted changes', error);
}
} }
};
function getNextVersion(version: string, type: ReleaseType) { const getNextVersion = (version: string, type: ReleaseType): string => {
const nextVersion = semver.inc(version, type); const nextVersion = semver.inc(version, type);
if (!nextVersion) { if (!nextVersion) throw new Error('Invalid version');
throw new Error('Invalid version'); return type.startsWith('pre')
} ? nextVersion.replace(/-.*$/, '-beta')
: nextVersion;
if (type.startsWith('pre')) {
return nextVersion.replace(/-.*$/, '-beta');
}
return nextVersion;
}
type IPackageJson = {
type?: string;
name: string;
version: string;
dependencies: Record<string, string>;
devDependencies: Record<string, string>;
}; };
type IPackageJsonWithExtra = IPackageJson & { // Core functions
nextVersion: string; const loadPackages = (
localPath: string; releaseType: ReleaseType
}; ): Record<string, PackageInfo> => {
const sdksPath = workspacePath('./packages/sdks');
function main() {
const args = arg({
'--name': String,
'--publish': Boolean,
'--test': Boolean,
'--skip-git': Boolean,
// Semver
'--type': String, // major, minor, patch, premajor, preminor, prepatch, or prerelease
});
if (!args['--skip-git']) {
checkUncommittedChanges();
}
const pkgName = args['--name'];
const type = args['--type'] as ReleaseType;
const test = args['--test'];
const publish = args['--publish'];
const packages: Record<string, IPackageJsonWithExtra> = {};
const registry = test
? 'http://localhost:4873'
: 'https://registry.npmjs.org';
if (!RELEASE_TYPES.includes(type)) {
return exit(
`Invalid release type. Valid types are: ${RELEASE_TYPES.join(', ')}`
);
}
if (!pkgName) {
return exit('--name is requred');
}
// Get all SDKs
const sdks = fs const sdks = fs
.readdirSync(workspacePath('./packages/sdks'), { .readdirSync(sdksPath, { withFileTypes: true })
withFileTypes: true, .filter((dirent) => dirent.isDirectory() && !dirent.name.startsWith('.'))
.map((dirent) => dirent.name);
return Object.fromEntries(
sdks.map((sdk) => {
const pkgPath = path.join(sdksPath, sdk, 'package.json');
const pkgJson = JSON.parse(
fs.readFileSync(pkgPath, 'utf-8')
) as PackageJson;
return [
pkgJson.name,
{
...pkgJson,
version: pkgJson.version.replace(/-local$/, ''),
nextVersion: getNextVersion(pkgJson.version, releaseType),
localPath: `./packages/sdks/${sdk}`,
},
];
}) })
.filter((item) => item.isDirectory() && !item.name.match(/^[._]/))
.map((item) => item.name);
// Get all SDK package.json
for (const name of sdks) {
const pkgJson = fs.readFileSync(
workspacePath(`./packages/sdks/${name}/package.json`),
'utf-8'
); );
const parsed = JSON.parse(pkgJson) as IPackageJsonWithExtra; };
parsed.nextVersion = getNextVersion(parsed.version, type);
parsed.localPath = `./packages/sdks/${name}`;
packages[parsed.name] = parsed;
}
const target = packages[pkgName]; const findDependents = (
packages: Record<string, PackageInfo>,
if (!target) { targetName: string
return exit('Selected package does not exist'); ): string[] => {
} const dependents = new Set([targetName]);
const findDeps = (name: string) => {
// Find if any package is dependent on the target Object.entries(packages).forEach(([pkgName, pkg]) => {
const dependents: string[] = [target.name]; if (pkg.dependencies?.[name] && !dependents.has(pkgName)) {
function findDependents(visitPackageName: string) { dependents.add(pkgName);
Object.entries(packages).forEach(([_name, pkg]) => { findDeps(pkgName);
if (pkg.dependencies?.[visitPackageName]) {
dependents.push(pkg.name);
findDependents(pkg.name);
} }
}); });
} };
findDependents(target.name); findDeps(targetName);
return Array.from(dependents);
};
function updatePackageJsonForRelease(name: string) { const updatePackageJsonForRelease = (
packages: Record<string, PackageInfo>,
name: string
): void => {
const { nextVersion, localPath, ...restPkgJson } = packages[name]!; const { nextVersion, localPath, ...restPkgJson } = packages[name]!;
const newPkgJson = JSON.parse( const newPkgJson: PackageJson = {
JSON.stringify({
...restPkgJson, ...restPkgJson,
private: false, private: false,
type: 'module', type: 'module',
@@ -165,93 +134,163 @@ function main() {
: {}), : {}),
}, },
version: nextVersion, version: nextVersion,
dependencies: Object.entries(restPkgJson.dependencies || {}).reduce( dependencies: Object.fromEntries(
(acc, [depName, depVersion]) => { Object.entries(restPkgJson.dependencies || {}).map(
const dep = packages[depName]; ([depName, depVersion]) => [
if (!dep) { depName,
return { ...acc, [depName]: depVersion }; packages[depName]?.nextVersion || depVersion,
} ]
)
return {
...acc,
[depName]: dependents.includes(depName)
? dep.nextVersion
: depVersion,
};
},
{}
), ),
}) };
) as IPackageJson;
if (name === '@openpanel/nextjs') { if (name === '@openpanel/nextjs') delete newPkgJson.type;
delete newPkgJson.type;
}
savePackageJson(workspacePath(`${localPath}/package.json`), newPkgJson); savePackageJson(workspacePath(`${localPath}/package.json`), newPkgJson);
packages[name]!.dependencies = newPkgJson.dependencies; packages[name]!.dependencies = newPkgJson.dependencies;
} };
dependents.forEach((dependent) => { const buildPackages = (
console.log( packages: Record<string, PackageInfo>,
`📦 ${dependent} · Old Version: ${packages[dependent]?.version} · Next Version: ${packages[dependent]?.nextVersion}` dependents: string[]
); ): void => {
updatePackageJsonForRelease(dependent); const versionEnvs = dependents.map((dep) => {
}); const envName = dep
const versionEnvs = dependents.map((dependent) => {
const { nextVersion } = packages[dependent]!;
const env = dependent
.replace(/@openpanel\//g, '') .replace(/@openpanel\//g, '')
.toUpperCase() .toUpperCase()
.replace(/\//g, '_') .replace(/[/-]/g, '_');
.replace('-', '_'); return `--env.${envName}_VERSION=${packages[dep]!.nextVersion}`;
return `--env.${env}_VERSION=${nextVersion}`;
}); });
console.log('versionEnvs', versionEnvs); dependents.forEach((dep) => {
console.log(`🔨 Building ${dep}`);
dependents.forEach((dependent) => {
console.log(`🔨 Building ${dependent}`);
execSync(`pnpm build ${versionEnvs.join(' ')}`, { execSync(`pnpm build ${versionEnvs.join(' ')}`, {
cwd: workspacePath(packages[dependent]!.localPath), cwd: workspacePath(packages[dep]!.localPath),
}); });
}); });
};
// Publish const publishPackages = (
if (publish) { packages: Record<string, PackageInfo>,
if (test) { dependents: string[],
config: PublishConfig
): void => {
if (config.test) {
execSync('rm -rf ~/.local/share/verdaccio/storage/@openpanel'); execSync('rm -rf ~/.local/share/verdaccio/storage/@openpanel');
} }
dependents.forEach((dependent) => {
console.log(`🚀 Publishing ${dependent} to ${registry}`); dependents.forEach((dep) => {
execSync(`npm publish --access=public --registry ${registry}`, { console.log(`🚀 Publishing ${dep} to ${config.registry}`);
cwd: workspacePath(packages[dependent]!.localPath), execSync(`npm publish --access=public --registry ${config.registry}`, {
cwd: workspacePath(packages[dep]!.localPath),
}); });
if (dependent === '@openpanel/web') { if (dep === '@openpanel/web') {
execSync( execSync(
`cp ${workspacePath('packages/sdks/web/dist/src/tracker.global.js')} ${workspacePath('./apps/public/public/op1.js')}` `cp ${workspacePath('packages/sdks/web/dist/src/tracker.global.js')} ${workspacePath('./apps/public/public/op1.js')}`
); );
} }
}); });
};
// Restoring package.json const restoreAndUpdateLocal = (
packages: Record<string, PackageInfo>,
dependents: string[]
): void => {
const filesToRestore = dependents const filesToRestore = dependents
.map((dependent) => workspacePath(packages[dependent]!.localPath)) .map((dep) => workspacePath(packages[dep]!.localPath))
.join(' '); .join(' ');
execSync(`git checkout ${filesToRestore}`); execSync(`git checkout ${filesToRestore}`);
// // Save new versions only 😈 dependents.forEach((dep) => {
dependents.forEach((dependent) => { const { nextVersion, localPath, ...restPkgJson } = packages[dep]!;
const { nextVersion, localPath, ...restPkgJson } = packages[dependent]!; console.log(`🚀 Updating ${dep} (${nextVersion}-local)`);
console.log(`🚀 Saving ${dependent} (${nextVersion})`);
savePackageJson(workspacePath(`${localPath}/package.json`), { const updatedPkgJson: PackageJson = {
...restPkgJson, ...restPkgJson,
version: nextVersion, version: `${nextVersion}-local`,
dependencies: Object.fromEntries(
Object.entries(restPkgJson.dependencies || {}).map(
([depName, depVersion]) => [
depName,
dependents.includes(depName)
? `${packages[depName]!.nextVersion}-local`
: packages[depName]
? `${packages[depName]!.version}-local`
: depVersion,
]
)
),
devDependencies: Object.fromEntries(
Object.entries(restPkgJson.devDependencies || {}).map(
([depName, depVersion]) => [
depName,
dependents.includes(depName)
? `${packages[depName]!.nextVersion}-local`
: packages[depName]
? `${packages[depName]!.version}-local`
: depVersion,
]
)
),
};
savePackageJson(workspacePath(`${localPath}/package.json`), updatedPkgJson);
}); });
};
function main() {
// Main execution
const args = arg({
'--name': String,
'--publish': Boolean,
'--test': Boolean,
'--skip-git': Boolean,
'--type': String,
}); });
if (!args['--skip-git']) {
checkUncommittedChanges();
}
if (!args['--name']) {
return exit('--name is required');
}
if (!RELEASE_TYPES.includes(args['--type'] as ReleaseType)) {
return exit(
`Invalid release type. Valid types are: ${RELEASE_TYPES.join(', ')}`
);
}
const packages = loadPackages(args['--type'] as ReleaseType);
const target = packages[args['--name']];
if (!target) {
return exit('Selected package does not exist');
}
const dependents = findDependents(packages, target.name);
dependents.forEach((dep) => {
console.log(
`📦 ${dep} · Old Version: ${packages[dep]!.version} · Next Version: ${packages[dep]!.nextVersion}`
);
updatePackageJsonForRelease(packages, dep);
});
buildPackages(packages, dependents);
if (args['--publish']) {
const config: PublishConfig = {
registry: args['--test']
? 'http://localhost:4873'
: 'https://registry.npmjs.org',
test: args['--test'] || false,
};
publishPackages(packages, dependents, config);
restoreAndUpdateLocal(packages, dependents);
} }
console.log('✅ All done!'); console.log('✅ All done!');