@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.
157 lines • 24.4 kB
JSON
{
"package": {
"name": "@deep-foundation/npm-packager",
"version": "0.0.45"
},
"data": [
{
"package": {
"dependencyId": 0,
"containValue": "Type"
},
"id": 1
},
{
"package": {
"dependencyId": 0,
"containValue": "Any"
},
"id": 2
},
{
"package": {
"dependencyId": 0,
"containValue": "PackageQuery"
},
"id": 3
},
{
"package": {
"dependencyId": 0,
"containValue": "SyncTextFile"
},
"id": 4
},
{
"package": {
"dependencyId": 0,
"containValue": "Handler"
},
"id": 5
},
{
"package": {
"dependencyId": 0,
"containValue": "HandleInsert"
},
"id": 6
},
{
"package": {
"dependencyId": 0,
"containValue": "dockerSupportsJs"
},
"id": 7
},
{
"package": {
"dependencyId": 0,
"containValue": "Package"
},
"id": 8
},
{
"package": {
"dependencyId": 0,
"containValue": "Value"
},
"id": 9
},
{
"package": {
"dependencyId": 0,
"containValue": "String"
},
"id": 10
},
{
"id": "Install",
"type": 1,
"from": 2,
"to": 3
},
{
"id": "installCode",
"type": 4,
"value": {
"value": "async ({ deep, gql, data: { triggeredByLinkId, newLink } }) => {\n const deepFileName = 'deep.json';\n const fs = await deep.import('fs');\n\n const makeTempDirectory = async () => {\n const os = await deep.import('os');\n const { v4: uuid } = await deep.import('uuid');\n \n const baseTempDirectory = os.tmpdir();\n const randomId = uuid();\n const tempDirectory = [baseTempDirectory,randomId].join('/');\n fs.mkdirSync(tempDirectory);\n console.log(tempDirectory);\n return tempDirectory;\n };\n const npmInstall = async (packageName, tempDirectory) => {\n const execSync = (await deep.import('child_process')).execSync;\n\n const command = `npm --prefix \"${tempDirectory}\" i ${packageName}`;\n const output = execSync(command, { \n encoding: 'utf-8',\n cwd: tempDirectory\n });\n console.log(`${command}\\n`, output);\n return output;\n };\n const npmLogin = async (token, tempDirectory) => {\n const execSync = (await deep.import('child_process')).execSync;\n \n const command = `npm set \"//registry.npmjs.org/:_authToken\" ${token}`;\n const output = execSync(command, { \n encoding: 'utf-8',\n cwd: tempDirectory\n });\n console.log(`${command}\\n`, output);\n return output;\n };\n const makePackagePath = (tempDirectory, packageName) => [tempDirectory, 'node_modules', packageName].join('/');\n const makeDeepJsonPath = (packagePath) => [packagePath, deepFileName].join('/');\n const makePackageJsonPath = (packagePath) => [packagePath, 'package.json'].join('/');\n const loadNpmToken = async () => {\n const containTreeId = await deep.id('@deep-foundation/core', 'containTree');\n const tokenTypeId = await deep.id('@deep-foundation/npm-packager', 'Token');\n const { data: [{ value: npmTokenValue } = {}] = []} = await deep.select({\n up: {\n tree_id: { _eq: containTreeId },\n parent: { id: { _eq: triggeredByLinkId } },\n link: { type_id: { _eq: tokenTypeId } }\n }\n });\n return npmTokenValue?.value;\n };\n const deepImport = async (deepJson, packageJson) => {\n if (deepJson.package.name !== packageJson.name) {\n throw new Error(`Package name is not synchronized between ${deepFileName} and package.json files.\n ${deepFileName} package name: ${deepJson.package.name}.\n package.json package name: ${packageJson.name}.`);\n }\n if (deepJson.package.version !== packageJson.version) {\n throw new Error(`Package version is not synchronized between ${deepFileName} and package.json files.\n ${deepFileName} package version: ${deepJson.package.version}.\n package.json package version: ${packageJson.version}.`);\n }\n const packager = new (await deep.import('@deep-foundation/deeplinks/imports/packager.js')).Packager(deep);\n const imported = await packager.import(deepJson);\n console.log(imported);\n if (imported?.errors?.length) throw imported;\n return imported;\n };\n const getDeepPackagesList = async (rootPath) => {\n const execSync = (await deep.import('child_process')).execSync;\n\n const deepFileName = 'deep.json';\n const deepFileNameLength = deepFileName.length;\n\n const command = `find . -name ${deepFileName}`;\n const output = execSync(command, { \n encoding: 'utf-8',\n cwd: rootPath\n });\n console.log('', `${command}\\n`, output);\n\n const packages = output\n .split(/\\r?\\n/)\n .filter(line => line.trim())\n .map(line => line.slice(2).slice(0, -deepFileNameLength - 1))\n .map(line => line.split('/node_modules/'));\n return packages;\n };\n const getDeepPackagesDependencies = async (rootPath, packages, packageName) => {\n const dictionary = {};\n for (const pkg of packages) {\n const packagePath = [rootPath, pkg.join('/node_modules/')].join('/');\n console.log('packagePath', packagePath);\n const packageJsonPath = makePackageJsonPath(packagePath);\n console.log('packageJsonPath', packageJsonPath);\n if (!fs.existsSync(packageJsonPath)) {\n throw new Error(`package.json for dependency ${pkg} is not found at ${packageJsonPath}. Looks like ${packageName} does not contain ${pkg} dependency in package.json.`);\n }\n const packageJson = await deep.import(packageJsonPath);\n console.log('packageJson', packageJson);\n const deepJsonPath = makeDeepJsonPath(packagePath);\n console.log('deepJsonPath', deepJsonPath);\n if (!fs.existsSync(deepJsonPath)) {\n 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.`);\n }\n const deepJson = await deep.import(deepJsonPath);\n console.log('deepJson', deepJson);\n const dependencies = packageJson.dependencies ?? {};\n console.log('dependencies', dependencies);\n const dependencyPackageName = pkg.at(-1);\n console.log('dependencyPackageName', dependencyPackageName);\n if (dictionary[dependencyPackageName]) {\n if (packageJson.version === dictionary[dependencyPackageName].packageJson.version) {\n console.log(`${dependencyPackageName}@${packageJson.version} was already added to a list of dependencies, no need to add it again.`)\n continue;\n } else {\n throw new Error(`Multiple versions of the same package are not supported yet.\n${dependencyPackageName}@${dictionary[dependencyPackageName].packageJson.version} was already added to a list of dependencies from ${dictionary[dependencyPackageName].packagePath}.\nBut ${packageName} also contains ${dependencyPackageName}@${packageJson.version} dependency at ${packagePath}.`);\n }\n }\n dictionary[dependencyPackageName] = { deepJson, packageJson, dependencies, packagePath };\n }\n for (const pkg in dictionary) {\n const sourceDependencies = dictionary[pkg].dependencies;\n const targetDependencies = [];\n for (const dependency in sourceDependencies)\n {\n if (dictionary[dependency]) {\n targetDependencies.push(dependency);\n }\n }\n dictionary[pkg].dependencies = targetDependencies;\n }\n return dictionary;\n }\n const buildInstallationQueueCore = (deepPackagesDependencies, queue, set, packageName) => {\n const dependencies = deepPackagesDependencies[packageName].dependencies;\n for (const dependency of dependencies) {\n if (!set[dependency]) {\n buildInstallationQueueCore(deepPackagesDependencies, queue, set, dependency);\n }\n }\n if(!set[packageName]) {\n const deepJson = deepPackagesDependencies[packageName].deepJson;\n const packageJson = deepPackagesDependencies[packageName].packageJson;\n queue.push({ name: packageName, deepJson, packageJson });\n set[packageName] = true;\n }\n }\n const buildInstallationQueue = (deepPackagesDependencies, queue, set) => {\n for (const packageName in deepPackagesDependencies) {\n buildInstallationQueueCore(deepPackagesDependencies, queue, set, packageName);\n }\n }\n const getExistingPackages = async (packageNames) => {\n const packageTypeId = await deep.id('@deep-foundation/core', 'Package');\n const packageVersionTypeId = await deep.id('@deep-foundation/core', 'PackageVersion');\n const { data: packages } = await deep.select({\n type_id: { _eq: packageTypeId },\n string: { value: { _in: packageNames } }\n }, {\n name: 'GET_EXISTING_PACKAGES_WITH_VERSIONS',\n returning: `\n id\n name: value\n versions: in(where: {type_id: {_eq: ${packageVersionTypeId}}, string: {value: {_is_null: false}}}) {\n id\n version: value\n }\n `\n })\n console.log('packages', packages);\n const existingPackages = packages.reduce(\n (accumulator, currentValue) => {\n const packageId = currentValue?.id;\n const packageName = currentValue?.name?.value;\n if (currentValue?.versions.length !== 1) {\n throw new Error(`'${packageName}' package must have exactly one version. Now it has ${currentValue?.versions.length} versions.`);\n }\n if (accumulator[packageName]) {\n throw new Error(`Multiple packages with name '${packageName}' exist.`)\n }\n const packageVersion = currentValue?.versions?.[0]?.version?.value;\n accumulator[packageName] = { id: packageId, version: packageVersion };\n return accumulator;\n },\n {}\n );\n console.log('existingPackages', existingPackages);\n return existingPackages;\n };\n\n if (!triggeredByLinkId) {\n throw new Error('Install link should be inserted using JWT token (role link), it cannot be inserted using hasura secret (role admin).');\n }\n\n const { data: [{ value: { value: packageQuery } }] } = await deep.select({ id: newLink.to_id });\n const packageQueryParts = packageQuery.split('@');\n if (packageQueryParts.length === 3) {\n const packageVersion = packageQueryParts.pop();\n }\n const packageName = packageQueryParts.join('@');\n if (!packageName) {\n throw new Error('Package query value is empty.');\n }\n const tempDirectory = await makeTempDirectory();\n let deepJson;\n let packageJson;\n const installationQueue = [];\n const installationSet = {};\n try {\n const npmToken = await loadNpmToken();\n if (npmToken) {\n await npmLogin(npmToken, tempDirectory);\n }\n const nodeModulesPath = [tempDirectory, 'node_modules'].join('/');\n await npmInstall(packageQuery, tempDirectory);\n const packagePath = makePackagePath(tempDirectory, packageName);\n const deepJsonPath = makeDeepJsonPath(packagePath);\n const packageJsonPath = makePackageJsonPath(packagePath);\n deepJson = await deep.import(deepJsonPath);\n packageJson = await deep.import(packageJsonPath);\n\n const packages = await getDeepPackagesList(nodeModulesPath);\n console.log('packages', packages);\n \n const deepPackagesDependencies = await getDeepPackagesDependencies(nodeModulesPath, packages, packageName);\n delete deepPackagesDependencies[packageName];\n console.log('deepPackagesDependencies', deepPackagesDependencies);\n \n buildInstallationQueue(deepPackagesDependencies, installationQueue, installationSet);\n \n console.log('installationQueue', installationQueue);\n console.log('installationSet', installationSet);\n } finally {\n fs.rmSync(tempDirectory, { recursive: true, force: true });\n }\n \n const existingPackages = await getExistingPackages(installationQueue.map(e => e.name));\n console.log('existingPackages', existingPackages);\n\n for (const dependencyPackage of installationQueue) {\n const dependencyPackageName = dependencyPackage.name;\n const existingPackage = existingPackages[dependencyPackageName];\n if (existingPackage) {\n await deep.insert({\n type_id: await deep.id('@deep-foundation/npm-packager', 'Used'),\n from_id: newLink.id,\n to_id: existingPackage.id,\n });\n } else {\n const importedDependency = await deepImport(dependencyPackage.deepJson, dependencyPackage.packageJson);\n if (importedDependency?.errors?.length > 0 || !importedDependency?.packageId) {\n console.log(`Unable to install dependency ${dependencyPackageName}.`, importedDependency);\n throw new Error(`Unable to install dependency ${dependencyPackageName}.`);\n }\n await deep.insert({\n type_id: await deep.id('@deep-foundation/npm-packager', 'Installed'),\n from_id: newLink.id,\n to_id: importedDependency.packageId,\n });\n // TODO: Should it be inserted?\n // await deep.insert({\n // type_id: await deep.id('@deep-foundation/core', 'Contain'),\n // from_id: newLink.from_id,\n // to_id: importedDependency.packageId,\n // });\n }\n }\n\n const imported = await deepImport(deepJson, packageJson);\n await deep.insert({\n type_id: await deep.id('@deep-foundation/core', 'Contain'),\n from_id: newLink.from_id,\n to_id: imported.packageId,\n });\n await deep.insert({\n type_id: await deep.id('@deep-foundation/npm-packager', 'Installed'),\n from_id: newLink.id,\n to_id: imported.packageId,\n });\n return imported;\n}"
}
},
{
"id": "installCodeHandler",
"type": 5,
"from": 7,
"to": "installCode"
},
{
"id": "installCodeHandleInsert",
"type": 6,
"from": "Install",
"to": "installCodeHandler"
},
{
"id": "Publish",
"type": 1,
"from": 8,
"to": 3
},
{
"id": "publishCode",
"type": 4,
"value": {
"value": "async ({ deep, require, gql, data: { triggeredByLinkId, newLink } }) => {\n const fs = await deep.import('fs');\n const encoding = 'utf8';\n const deepPackageKeyWord = 'deep-package';\n \n const makeTempDirectory = async () => {\n const os = await deep.import('os');\n const { v4: uuid } = await deep.import('uuid');\n \n const baseTempDirectory = os.tmpdir();\n const randomId = uuid();\n const tempDirectory = [baseTempDirectory,randomId].join('/');\n fs.mkdirSync(tempDirectory);\n console.log(tempDirectory);\n return tempDirectory;\n };\n const npmInstall = async (packageName, installationPath, setTildaPrefix) => {\n const execSync = (await deep.import('child_process')).execSync;\n\n if (setTildaPrefix === true) {\n const savePrefixCommand = `npm --prefix \"${installationPath}\" config set save-prefix=\"~\"`;\n try {\n const output = execSync(savePrefixCommand, { \n encoding: 'utf-8',\n cwd: installationPath\n }).toString();\n console.log(`${savePrefixCommand}\\n`, output);\n } catch(error) {\n return {\n rejected: error\n };\n }\n }\n \n let result;\n const command = `npm --prefix \"${installationPath}\" i ${packageName}`;\n try {\n const output = execSync(command, { \n encoding: 'utf-8',\n cwd: installationPath\n }).toString();\n console.log(`${command}\\n`, output);\n result = {\n resolved: {\n status: 0,\n stdout: output\n }\n };\n } catch(error) {\n return {\n rejected: error\n };\n }\n\n if (setTildaPrefix === true) {\n const deletePrefixCommand = `npm --prefix \"${installationPath}\" config delete save-prefix`;\n try {\n const output = execSync(deletePrefixCommand, { \n encoding: 'utf-8',\n cwd: installationPath\n }).toString();\n console.log(`${deletePrefixCommand}\\n`, output);\n } catch(error) {\n return {\n rejected: error\n };\n }\n }\n\n return result;\n };\n const npmLogin = async (token, tempDirectory) => {\n const execSync = (await deep.import('child_process')).execSync;\n \n const command = `npm set \"//registry.npmjs.org/:_authToken\" ${token}`;\n const output = execSync(command, { \n encoding: 'utf-8',\n cwd: tempDirectory\n });\n console.log(`${command}\\n`, output);\n return output;\n };\n const npmPublish = async (tempDirectory) => {\n const execSync = (await deep.import('child_process')).execSync;\n \n const command = `npm publish --access public`;\n const output = execSync(command, { \n encoding: 'utf-8',\n cwd: tempDirectory\n });\n console.log(`${command}\\n`, output);\n return output;\n };\n const makeDeepPackagePath = (tempDirectory, packageName) => [tempDirectory, 'node_modules', packageName].join('/');\n const makeDeepJsonPath = (packagePath) => [packagePath, 'deep.json'].join('/');\n const makePackageJsonPath = (packagePath) => [packagePath, 'package.json'].join('/');\n const deepExport = async (packageId) => {\n const packager = new (await deep.import('@deep-foundation/deeplinks/imports/packager.js')).Packager(deep);\n const exported = await packager.export({ packageLinkId: packageId });\n console.log(exported);\n if (exported?.errors?.length) throw exported;\n return exported;\n };\n const loadNpmToken = async () => {\n const containTreeId = await deep.id('@deep-foundation/core', 'containTree');\n const tokenTypeId = await deep.id('@deep-foundation/npm-packager', 'Token');\n const { data: [{ value: npmTokenValue } = {}] = []} = await deep.select({\n up: {\n tree_id: { _eq: containTreeId },\n parent: { id: { _eq: triggeredByLinkId } },\n link: { type_id: { _eq: tokenTypeId } }\n }\n });\n return npmTokenValue?.value;\n };\n const loadFromJson = (path) => {\n if (!fs.existsSync(path)) {\n throw new Error(`${path} is not found.`);\n }\n const json = fs.readFileSync(path, { encoding });\n if (!json) {\n throw new Error(`${path} is empty or was not read properly.`);\n }\n console.log(`Parsing ${path} ...`);\n const data = JSON.parse(json);\n console.log(`Parsing ${path} finished.`);\n return data;\n }\n const saveAsJson = (path, data) => {\n fs.writeFileSync(path, JSON.stringify(data, null, 2), { encoding });\n }\n const updateVersion = async (deepJsonPath, packageJsonPath, localVersion) => {\n const semver = await deep.import('semver');\n\n const deepPackage = loadFromJson(deepJsonPath);\n const npmPackage = loadFromJson(packageJsonPath);\n\n const oldNpmVersion = npmPackage?.version || '0.0.0';\n\n const nextVersion = semver.gt(localVersion, oldNpmVersion) ? localVersion : semver.inc(oldNpmVersion, 'patch');\n\n npmPackage.version = nextVersion;\n if (!deepPackage.package) {\n deepPackage.package = {};\n }\n deepPackage.package.version = nextVersion; \n\n saveAsJson(deepJsonPath, deepPackage);\n saveAsJson(packageJsonPath, npmPackage);\n\n return nextVersion;\n };\n const addKeyword = (packageJsonPath, keyword) => {\n const npmPackage = loadFromJson(packageJsonPath);\n if (npmPackage?.keywords?.length > 0) {\n if (!npmPackage.keywords.includes(keyword)) {\n npmPackage.keywords.push(keyword); \n }\n } else {\n npmPackage.keywords = [ keyword ];\n }\n saveAsJson(packageJsonPath, npmPackage);\n };\n const installDependencies = async (packagePath, dependencies) => {\n for (const dependency of dependencies) {\n const dependencyPackageName = `${dependency.name}@${dependency.version}`;\n const installationResult = await npmInstall(dependencyPackageName, packagePath, true);\n if (installationResult?.rejected) {\n console.log(`Unable to install ${dependencyPackageName} dependency.`)\n throw installationResult.rejected;\n } else if (!installationResult?.resolved) {\n throw new Error(`Unsupported NPM dependency installation result for ${dependencyPackageName} dependency package.`);\n }\n }\n }\n\n if (!triggeredByLinkId) {\n throw new Error('Publish link should be inserted using JWT token (role link), it cannot be inserted using hasura secret (role admin).');\n }\n\n const { data: [packageQuery] } = await deep.select({ id: newLink.to_id });\n const packageName = packageQuery?.value?.value;\n if (!packageName) {\n throw new Error('Package query value is empty.');\n }\n const packageVersionTypeId = await deep.id('@deep-foundation/core', 'PackageVersion');\n const { data: [{ versions: [{ version: { value: localVersion = undefined } = {}} = {}] = []} = {}] = []} = await deep.select({ id: newLink.from_id }, { returning: `id versions: in(where: { type_id: { _eq: ${packageVersionTypeId} } }) { id type_id version: value }` });\n if (!localVersion) {\n throw new Error('Package must have a version.');\n }\n\n const packageId = newLink.from_id;\n const { data: [{ value: actualPackageName }]} = await deep.select(\n { link_id: { _eq: packageId } },\n {\n table: 'strings',\n returning: 'value'\n }\n );\n if (packageName !== actualPackageName) {\n throw new Error('Package query value should be equal to actual package name.');\n }\n const tempDirectory = await makeTempDirectory();\n try {\n const npmToken = await loadNpmToken();\n if (!npmToken) {\n throw new Error('NPM token is required to publish package. NPM token should be contained by user that does insert publish link.');\n }\n await npmLogin(npmToken, tempDirectory);\n const installationResult = await npmInstall(packageName, tempDirectory, false);\n let deepPackagePath; \n let packageJsonPath;\n if (installationResult?.resolved) {\n deepPackagePath = makeDeepPackagePath(tempDirectory, packageName);\n packageJsonPath = makePackageJsonPath(deepPackagePath);\n } else if(installationResult?.rejected) {\n deepPackagePath = tempDirectory;\n packageJsonPath = makePackageJsonPath(deepPackagePath);\n const packageJson = {\n name: packageName\n };\n saveAsJson(packageJsonPath, packageJson);\n } else {\n throw new Error(`Unsupported NPM installation result for ${packageName} package.`);\n }\n console.log('deepPackagePath', deepPackagePath);\n console.log('packageJsonPath', packageJsonPath);\n \n const pkg = await deepExport(packageId);\n console.log(pkg);\n await installDependencies(deepPackagePath, pkg.dependencies);\n const deepJsonPath = makeDeepJsonPath(deepPackagePath);\n saveAsJson(deepJsonPath, pkg);\n const nextVersion = await updateVersion(deepJsonPath, packageJsonPath, localVersion);\n addKeyword(packageJsonPath, deepPackageKeyWord);\n await npmPublish(deepPackagePath);\n if (nextVersion !== localVersion) {\n await deep.update({\n link: {\n type_id: { _eq: await deep.id('@deep-foundation/core', 'PackageVersion') },\n to_id: { _eq: packageId },\n },\n }, { value: nextVersion }, { table: 'strings' });\n }\n } finally {\n fs.rmSync(tempDirectory, { recursive: true, force: true });\n }\n}"
}
},
{
"id": "publishCodeHandler",
"type": 5,
"from": 7,
"to": "publishCode"
},
{
"id": "publishCodeHandleInsert",
"type": 6,
"from": "Publish",
"to": "publishCodeHandler"
},
{
"id": "Token",
"type": 1
},
{
"id": "tokenValue",
"type": 9,
"from": "Token",
"to": 10
},
{
"id": "Used",
"type": 1,
"from": "Install",
"to": 8
},
{
"id": "Installed",
"type": 1,
"from": "Install",
"to": 8
}
],
"errors": [],
"dependencies": [
{
"name": "@deep-foundation/core",
"version": "0.0.1"
}
]
}