{
  "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"
    }
  ]
}