Comparing version 0.0.14 to 0.1.0
{ | ||
"name": "stayte", | ||
"version": "0.0.14", | ||
"version": "0.1.0", | ||
"description": "The persistent state library for the modern web", | ||
@@ -51,3 +51,5 @@ "license": "MIT", | ||
"dev": "rollup --config rollup.config.mjs --watch", | ||
"build": "rm -rf dist && rollup --config rollup.config.mjs" | ||
"build": "rm -rf dist && rollup --config rollup.config.mjs", | ||
"clean": "rm -rf dist", | ||
"fclean": "rm -rf dist .turbo node_modules" | ||
}, | ||
@@ -54,0 +56,0 @@ "devDependencies": { |
const fs = require('fs') | ||
const path = require('path') | ||
const { spawn } = require('child_process') | ||
const packageManager = detectPackageManager() | ||
const init_cwd = process.env.INIT_CWD | ||
const cwd = process.cwd() | ||
// db that contains every patch references | ||
// a file is related to a specific package version or a package name | ||
const PATCH = { | ||
'next@14.2.13': 'patches/next@14.2.13.patch' | ||
} | ||
// If the init cwd is not defined, something is wrong | ||
@@ -19,27 +14,89 @@ // and install could not be done | ||
const packageJson = JSON.parse(fs.readFileSync(`${init_cwd}/package.json`, 'utf-8')) | ||
if (process.argv[2] === 'child') { | ||
// Add a delay to be sure pnpm or any other package manager | ||
// is done with its job about the package.json and we can | ||
// safely rewrite it | ||
setTimeout(() => { | ||
// Get the package.json and the patched dependencies | ||
const packageJson = getPackageJson(init_cwd) | ||
// Get the patched dependencies from the fetched package.json | ||
const patchedDependencies = getPatchedDependencies(packageJson, packageManager) | ||
// Get the new patched dependencies from the process.argv comming from the parent process | ||
const newPatchedDependencies = JSON.parse(process.argv[3] || '{}') | ||
// If there is no patched dependencies, we create it in the package.json | ||
if (!packageJson.patchedDependencies) { | ||
packageJson.patchedDependencies = {} | ||
} | ||
// Merge the new patched dependencies with the old ones | ||
setPatchedDependencies(packageJson, packageManager, { | ||
...patchedDependencies, | ||
...newPatchedDependencies | ||
}) | ||
const patchedDependenciesCount = Object.keys(packageJson.patchedDependencies).length | ||
const scannedPackages = getScannedPackages() | ||
// Rewrite the package.json | ||
fs.writeFileSync(`${init_cwd}/package.json`, JSON.stringify(packageJson, null, 2), 'utf-8') | ||
// Loop over the patch object and check if the package is already patched | ||
// if not, we apply the patch in the current directory where statye will be installed | ||
// and update the package.json with the new patch | ||
for (const [name, patch] of Object.entries(PATCH)) { | ||
// Once the package.json is updated, we can exit the child process | ||
// just in case | ||
process.exit(0) | ||
}, 500) | ||
// If the package is not installed on every project, we skip it | ||
const packageName = name.split('@')[0] | ||
if (!scannedPackages.find((packageJson) => (packageJson?.dependencies || {})[packageName])) { | ||
continue | ||
} else { | ||
// db that contains every patch references | ||
// a file is related to a specific package version or a package name | ||
const PATCH = { | ||
'next@14.2.13': 'patches/next@14.2.13.patch', | ||
'next@14.2.14': 'patches/next@14.2.14.patch' | ||
} | ||
if (!packageJson.patchedDependencies[name]) { | ||
const patchPath = `${cwd}/${patch}` | ||
const patchContent = fs.readFileSync(patchPath, 'utf-8') | ||
const cwd = process.cwd() | ||
let missingPatches = {} | ||
let patchedDependencies = {} | ||
// For now we don't patch npm packages | ||
// because the pnpm, bun patch format is not compatible with npm for now | ||
if (packageManager === 'npm') { | ||
return | ||
} | ||
// In case of a monorepo, we need to fetch every package.json recursively | ||
const scannedPackages = getScannedPackages(init_cwd) | ||
// Loop over the patch object and check if the package is already patched | ||
// if not, we apply the patch in the current directory where statye will be installed | ||
// and update the package.json with the new patch | ||
for (const [name, patch] of Object.entries(PATCH)) { | ||
const [packageName, patchVersion] = name.split('@') | ||
const currentPackageJson = scannedPackages.find((packageJson) => (packageJson?.dependencies || {})[packageName]) | ||
// If the package is not installed on every project, we skip it | ||
// No need to patch | ||
if (!currentPackageJson) { | ||
continue | ||
} | ||
// If the package is installed but the version is not supported | ||
// it's mean there is no patch for this version | ||
if (currentPackageJson?.dependencies?.[packageName] !== patchVersion) { | ||
missingPatches[packageName] = currentPackageJson.dependencies[packageName] | ||
continue | ||
} | ||
// If there is a patch for this package, reset the missing patch object | ||
delete missingPatches[packageName] | ||
if (packageManager !== 'npm') { | ||
patchedDependencies[name] = patch | ||
} | ||
// Changing the patch name for npm because "patch-package" | ||
// don't support the "@" character in patch name | ||
const newPatchName = packageManager === 'npm' | ||
? patch.replace('@', '+') | ||
: patch | ||
const newPatchPath = `${init_cwd}/${newPatchName}` | ||
// Mount the patch directory if it doesn't exist | ||
if (!fs.existsSync(`${init_cwd}/patches`)) { | ||
@@ -49,50 +106,116 @@ fs.mkdirSync(`${init_cwd}/patches`) | ||
fs.writeFileSync(`${init_cwd}/${patch}`, patchContent, 'utf-8') | ||
packageJson.patchedDependencies[name] = patch | ||
// If the patch doesn't exist, we create it | ||
if (!fs.existsSync(newPatchPath)) { | ||
const patchContent = fs.readFileSync(`${cwd}/${patch}`, 'utf-8') | ||
fs.writeFileSync(newPatchPath, patchContent, 'utf-8') | ||
} | ||
} | ||
} | ||
// Raise an error if there is no patch for some dependencies | ||
// that is required to be patched | ||
if (Object.keys(missingPatches).length > 0) { | ||
const message = ` | ||
Could not install stayte because some dependencies as the wrong version | ||
${Object.entries(missingPatches).map(([packageName, version]) => ` - ${packageName}@${version} founded`).join('\n')} | ||
// We only write the package.json if the number of patched dependencies has changed | ||
// this is to avoid unnecessary writes | ||
const newPatchedDependenciesCount = Object.keys(packageJson.patchedDependencies).length | ||
if (newPatchedDependenciesCount > patchedDependenciesCount) { | ||
fs.writeFileSync(`${init_cwd}/package.json`, JSON.stringify(packageJson, null, 2), 'utf-8') | ||
Please read the documentation to know how to fix this issue | ||
https://stayte.vercel.app/docs/installation | ||
` | ||
throw new Error(message) | ||
} | ||
// We only write the package.json if the number of patched dependencies has changed | ||
// this is to avoid unnecessary writes | ||
// Update the file in a detached process to avoid pnpm rewriting package.json | ||
if (Object.keys(patchedDependencies).length > 0) { | ||
const args = [ | ||
`${__filename}`, | ||
'child', | ||
JSON.stringify(patchedDependencies) | ||
] | ||
const child = spawn('node', args, { detached: true }) | ||
// unlink the child event loop from the main event loop | ||
child.unref() | ||
// By leaving the main process, child can leave alone and do its job | ||
// in a separate process | ||
process.exit(0) | ||
} | ||
function getScannedPackages(path) { | ||
const packages = searchFiles(path, 'package.json') | ||
return packages.map((packageJson) => { | ||
const packageJsonContent = fs.readFileSync(packageJson, 'utf-8') | ||
const packageJsonObject = JSON.parse(packageJsonContent) | ||
return packageJsonObject | ||
}) | ||
} | ||
function searchFiles(dir, fileName) { | ||
let foundedFiles = [] | ||
try { | ||
// read the contents of the directory | ||
const files = fs.readdirSync(dir) | ||
// search through the files | ||
for (const file of files) { | ||
// build the full path of the file | ||
const filePath = path.join(dir, file); | ||
// get the file stats | ||
const fileStat = fs.statSync(filePath); | ||
// if the file is a directory, recursively search the directory | ||
if (fileStat.isDirectory() && !file.match(/node_modules|.git|.turbo|\./)) { | ||
foundedFiles.push(...searchFiles(filePath, fileName)) | ||
} else if (file.endsWith(fileName)) { | ||
foundedFiles.push(filePath); | ||
} | ||
} | ||
} catch (err) { | ||
console.error(err); | ||
} | ||
return foundedFiles; | ||
} | ||
} | ||
function getScannedPackages() { | ||
const packages = searchFiles(init_cwd, 'package.json') | ||
return packages.map((packageJson) => { | ||
const packageJsonContent = fs.readFileSync(packageJson, 'utf-8') | ||
const packageJsonObject = JSON.parse(packageJsonContent) | ||
return packageJsonObject | ||
}) | ||
function detectPackageManager() { | ||
const npmAgent = process?.env?.npm_config_user_agent || '' | ||
const allowedPackageManagers = ['npm', 'bun', 'yarn', 'pnpm'] | ||
const packageManager = npmAgent.split('/')[0] | ||
if (!allowedPackageManagers.includes(packageManager)) { | ||
throw new Error('Cannot detect package manager') | ||
} | ||
return packageManager | ||
} | ||
function searchFiles(dir, fileName) { | ||
let foundedFiles = [] | ||
try { | ||
// read the contents of the directory | ||
const files = fs.readdirSync(dir) | ||
function getPatchedDependencies(packageJson, packageManager) { | ||
if (packageManager === 'npm') { | ||
return {} | ||
} | ||
if (packageManager === 'bun') { | ||
return packageJson?.patchedDependencies || {} | ||
} | ||
if (packageManager === 'pnpm') { | ||
return packageJson?.pnpm?.patchedDependencies || {} | ||
} | ||
} | ||
// search through the files | ||
for (const file of files) { | ||
// build the full path of the file | ||
const filePath = path.join(dir, file); | ||
// get the file stats | ||
const fileStat = fs.statSync(filePath); | ||
// if the file is a directory, recursively search the directory | ||
if (fileStat.isDirectory() && !file.match(/node_modules|.git|.turbo|\./)) { | ||
foundedFiles.push(...searchFiles(filePath, fileName)) | ||
} else if (file.endsWith(fileName)) { | ||
foundedFiles.push(filePath); | ||
} | ||
function setPatchedDependencies(packageJson, packageManager, patchedDependencies) { | ||
if (packageManager === 'bun') { | ||
packageJson.patchedDependencies = patchedDependencies | ||
} else if (packageManager === 'pnpm') { | ||
if (!packageJson.pnpm) { | ||
packageJson.pnpm = {} | ||
} | ||
} catch (err) { | ||
console.error(err); | ||
packageJson.pnpm.patchedDependencies = patchedDependencies | ||
} | ||
return foundedFiles; | ||
} | ||
} | ||
function getPackageJson(path) { | ||
return JSON.parse(fs.readFileSync(`${path}/package.json`, 'utf-8')) | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
36196
35
399
3
1