UNPKG

@deep-foundation/npm-packager

Version:

NPM packager package for [Deep](https://deep.foundation/). It handles installation and publication of `deep-package` packages. This package is included in each [Deep](https://deep.foundation/) instance, there is no need to install it manually.

285 lines (269 loc) 12.1 kB
async ({ deep, gql, data: { triggeredByLinkId, newLink } }) => { const deepFileName = 'deep.json'; const fs = await deep.import('fs'); const makeTempDirectory = async () => { const os = await deep.import('os'); const { v4: uuid } = await deep.import('uuid'); const baseTempDirectory = os.tmpdir(); const randomId = uuid(); const tempDirectory = [baseTempDirectory,randomId].join('/'); fs.mkdirSync(tempDirectory); console.log(tempDirectory); return tempDirectory; }; const npmInstall = async (packageName, tempDirectory) => { const execSync = (await deep.import('child_process')).execSync; const command = `npm --prefix "${tempDirectory}" i ${packageName}`; const output = execSync(command, { encoding: 'utf-8', cwd: tempDirectory }); console.log(`${command}\n`, output); return output; }; const npmLogin = async (token, tempDirectory) => { const execSync = (await deep.import('child_process')).execSync; const command = `npm set "//registry.npmjs.org/:_authToken" ${token}`; const output = execSync(command, { encoding: 'utf-8', cwd: tempDirectory }); console.log(`${command}\n`, output); return output; }; const makePackagePath = (tempDirectory, packageName) => [tempDirectory, 'node_modules', packageName].join('/'); const makeDeepJsonPath = (packagePath) => [packagePath, deepFileName].join('/'); const makePackageJsonPath = (packagePath) => [packagePath, 'package.json'].join('/'); const loadNpmToken = async () => { const containTreeId = await deep.id('@deep-foundation/core', 'containTree'); const tokenTypeId = await deep.id('@deep-foundation/npm-packager', 'Token'); const { data: [{ value: npmTokenValue } = {}] = []} = await deep.select({ up: { tree_id: { _eq: containTreeId }, parent: { id: { _eq: triggeredByLinkId } }, link: { type_id: { _eq: tokenTypeId } } } }); return npmTokenValue?.value; }; const deepImport = async (deepJson, packageJson) => { if (deepJson.package.name !== packageJson.name) { throw new Error(`Package name is not synchronized between ${deepFileName} and package.json files. ${deepFileName} package name: ${deepJson.package.name}. package.json package name: ${packageJson.name}.`); } if (deepJson.package.version !== packageJson.version) { throw new Error(`Package version is not synchronized between ${deepFileName} and package.json files. ${deepFileName} package version: ${deepJson.package.version}. package.json package version: ${packageJson.version}.`); } const packager = new (await deep.import('@deep-foundation/deeplinks/imports/packager.js')).Packager(deep); const imported = await packager.import(deepJson); console.log(imported); if (imported?.errors?.length) throw imported; return imported; }; const getDeepPackagesList = async (rootPath) => { const execSync = (await deep.import('child_process')).execSync; const deepFileName = 'deep.json'; const deepFileNameLength = deepFileName.length; const command = `find . -name ${deepFileName}`; const output = execSync(command, { encoding: 'utf-8', cwd: rootPath }); console.log('', `${command}\n`, output); const packages = output .split(/\r?\n/) .filter(line => line.trim()) .map(line => line.slice(2).slice(0, -deepFileNameLength - 1)) .map(line => line.split('/node_modules/')); return packages; }; const getDeepPackagesDependencies = async (rootPath, packages, packageName) => { const dictionary = {}; for (const pkg of packages) { const packagePath = [rootPath, pkg.join('/node_modules/')].join('/'); console.log('packagePath', packagePath); const packageJsonPath = makePackageJsonPath(packagePath); console.log('packageJsonPath', packageJsonPath); if (!fs.existsSync(packageJsonPath)) { throw new Error(`package.json for dependency ${pkg} is not found at ${packageJsonPath}. Looks like ${packageName} does not contain ${pkg} dependency in package.json.`); } const packageJson = await deep.import(packageJsonPath); console.log('packageJson', packageJson); const deepJsonPath = makeDeepJsonPath(packagePath); console.log('deepJsonPath', deepJsonPath); if (!fs.existsSync(deepJsonPath)) { throw new Error(`deep.json for dependency ${pkg} is not found at ${deepJsonPath}. Looks like ${pkg} installed, but it does not contain deep.json. Make sure ${pkg} is a deep package.`); } const deepJson = await deep.import(deepJsonPath); console.log('deepJson', deepJson); const dependencies = packageJson.dependencies ?? {}; console.log('dependencies', dependencies); const dependencyPackageName = pkg.at(-1); console.log('dependencyPackageName', dependencyPackageName); if (dictionary[dependencyPackageName]) { if (packageJson.version === dictionary[dependencyPackageName].packageJson.version) { console.log(`${dependencyPackageName}@${packageJson.version} was already added to a list of dependencies, no need to add it again.`) continue; } else { throw new Error(`Multiple versions of the same package are not supported yet. ${dependencyPackageName}@${dictionary[dependencyPackageName].packageJson.version} was already added to a list of dependencies from ${dictionary[dependencyPackageName].packagePath}. But ${packageName} also contains ${dependencyPackageName}@${packageJson.version} dependency at ${packagePath}.`); } } dictionary[dependencyPackageName] = { deepJson, packageJson, dependencies, packagePath }; } for (const pkg in dictionary) { const sourceDependencies = dictionary[pkg].dependencies; const targetDependencies = []; for (const dependency in sourceDependencies) { if (dictionary[dependency]) { targetDependencies.push(dependency); } } dictionary[pkg].dependencies = targetDependencies; } return dictionary; } const buildInstallationQueueCore = (deepPackagesDependencies, queue, set, packageName) => { const dependencies = deepPackagesDependencies[packageName].dependencies; for (const dependency of dependencies) { if (!set[dependency]) { buildInstallationQueueCore(deepPackagesDependencies, queue, set, dependency); } } if(!set[packageName]) { const deepJson = deepPackagesDependencies[packageName].deepJson; const packageJson = deepPackagesDependencies[packageName].packageJson; queue.push({ name: packageName, deepJson, packageJson }); set[packageName] = true; } } const buildInstallationQueue = (deepPackagesDependencies, queue, set) => { for (const packageName in deepPackagesDependencies) { buildInstallationQueueCore(deepPackagesDependencies, queue, set, packageName); } } const getExistingPackages = async (packageNames) => { const packageTypeId = await deep.id('@deep-foundation/core', 'Package'); const packageVersionTypeId = await deep.id('@deep-foundation/core', 'PackageVersion'); const { data: packages } = await deep.select({ type_id: { _eq: packageTypeId }, string: { value: { _in: packageNames } } }, { name: 'GET_EXISTING_PACKAGES_WITH_VERSIONS', returning: ` id name: value versions: in(where: {type_id: {_eq: ${packageVersionTypeId}}, string: {value: {_is_null: false}}}) { id version: value } ` }) console.log('packages', packages); const existingPackages = packages.reduce( (accumulator, currentValue) => { const packageId = currentValue?.id; const packageName = currentValue?.name?.value; if (currentValue?.versions.length !== 1) { throw new Error(`'${packageName}' package must have exactly one version. Now it has ${currentValue?.versions.length} versions.`); } if (accumulator[packageName]) { throw new Error(`Multiple packages with name '${packageName}' exist.`) } const packageVersion = currentValue?.versions?.[0]?.version?.value; accumulator[packageName] = { id: packageId, version: packageVersion }; return accumulator; }, {} ); console.log('existingPackages', existingPackages); return existingPackages; }; if (!triggeredByLinkId) { throw new Error('Install link should be inserted using JWT token (role link), it cannot be inserted using hasura secret (role admin).'); } const { data: [{ value: { value: packageQuery } }] } = await deep.select({ id: newLink.to_id }); const packageQueryParts = packageQuery.split('@'); if (packageQueryParts.length === 3) { const packageVersion = packageQueryParts.pop(); } const packageName = packageQueryParts.join('@'); if (!packageName) { throw new Error('Package query value is empty.'); } const tempDirectory = await makeTempDirectory(); let deepJson; let packageJson; const installationQueue = []; const installationSet = {}; try { const npmToken = await loadNpmToken(); if (npmToken) { await npmLogin(npmToken, tempDirectory); } const nodeModulesPath = [tempDirectory, 'node_modules'].join('/'); await npmInstall(packageQuery, tempDirectory); const packagePath = makePackagePath(tempDirectory, packageName); const deepJsonPath = makeDeepJsonPath(packagePath); const packageJsonPath = makePackageJsonPath(packagePath); deepJson = await deep.import(deepJsonPath); packageJson = await deep.import(packageJsonPath); const packages = await getDeepPackagesList(nodeModulesPath); console.log('packages', packages); const deepPackagesDependencies = await getDeepPackagesDependencies(nodeModulesPath, packages, packageName); delete deepPackagesDependencies[packageName]; console.log('deepPackagesDependencies', deepPackagesDependencies); buildInstallationQueue(deepPackagesDependencies, installationQueue, installationSet); console.log('installationQueue', installationQueue); console.log('installationSet', installationSet); } finally { fs.rmSync(tempDirectory, { recursive: true, force: true }); } const existingPackages = await getExistingPackages(installationQueue.map(e => e.name)); console.log('existingPackages', existingPackages); for (const dependencyPackage of installationQueue) { const dependencyPackageName = dependencyPackage.name; const existingPackage = existingPackages[dependencyPackageName]; if (existingPackage) { await deep.insert({ type_id: await deep.id('@deep-foundation/npm-packager', 'Used'), from_id: newLink.id, to_id: existingPackage.id, }); } else { const importedDependency = await deepImport(dependencyPackage.deepJson, dependencyPackage.packageJson); if (importedDependency?.errors?.length > 0 || !importedDependency?.packageId) { console.log(`Unable to install dependency ${dependencyPackageName}.`, importedDependency); throw new Error(`Unable to install dependency ${dependencyPackageName}.`); } await deep.insert({ type_id: await deep.id('@deep-foundation/npm-packager', 'Installed'), from_id: newLink.id, to_id: importedDependency.packageId, }); // TODO: Should it be inserted? // await deep.insert({ // type_id: await deep.id('@deep-foundation/core', 'Contain'), // from_id: newLink.from_id, // to_id: importedDependency.packageId, // }); } } const imported = await deepImport(deepJson, packageJson); await deep.insert({ type_id: await deep.id('@deep-foundation/core', 'Contain'), from_id: newLink.from_id, to_id: imported.packageId, }); await deep.insert({ type_id: await deep.id('@deep-foundation/npm-packager', 'Installed'), from_id: newLink.id, to_id: imported.packageId, }); return imported; }