Full Code of g-plane/tiny-package-manager for AI

master 643fe89cf76e cached
19 files
21.0 KB
5.7k tokens
18 symbols
1 requests
Download .txt
Repository: g-plane/tiny-package-manager
Branch: master
Commit: 643fe89cf76e
Files: 19
Total size: 21.0 KB

Directory structure:
gitextract_g2jw__t_/

├── .editorconfig
├── .eslintrc.yml
├── .github/
│   └── FUNDING.yml
├── .gitignore
├── .vscode/
│   └── settings.json
├── LICENSE
├── README.md
├── azure-pipelines.yml
├── dprint.json
├── package.json
├── src/
│   ├── cli.ts
│   ├── index.ts
│   ├── install.ts
│   ├── list.ts
│   ├── lock.ts
│   ├── log.ts
│   ├── resolve.ts
│   └── utils.ts
└── tsconfig.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

================================================
FILE: .eslintrc.yml
================================================
extends:
  - gplane
  - gplane/node
  - gplane/typescript


================================================
FILE: .github/FUNDING.yml
================================================
github: g-plane


================================================
FILE: .gitignore
================================================
dist/
node_modules/
yarn-error.log


================================================
FILE: .vscode/settings.json
================================================
{
  "eslint.validate": [
    "javascript",
    "javascriptreact",
    {
      "language": "typescript",
      "autoFix": true
    }
  ]
}


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2018-present Pig Fang

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Tiny Package Manager

> A very very simple demo and guide for explaining package manager.

## Introduction

As a JavaScript developer, you may use package manager like [npm](https://www.npmjs.com/) or [Yarn](https://yarnpkg.com/)
frequently.

However, do you know how a package manager works? Or, you may be curious about how to build a package manager.

Well, the purpose of this guide is not to let you re-invent a new wheel.
There is no need to do that because both npm and Yarn are mature and stable enough.
The purpose is just to explain how a package manager works under the hood.
You can read the code, and the comments will explain how it works.

Note: To simplify the guide and make it as simple as possible,
this demo doesn't handle some edge cases and catch errors and exceptions.
If you are really curious about that,
it's recommended to read the source code of [npm](https://github.com/npm/npm) or [Yarn](https://github.com/yarnpkg/yarn).

## Features

- [x] Download packages to `node_modules` directory.
- [x] Simple CLI.
- [x] Simply resolve dependency conflicts.
- [x] Flatten dependencies tree.
- [x] Support lock file. (Like `yarn.lock` or `package-lock.json`)
- [x] Add a new package through CLI. (Like `yarn add` or `npm i <package>` command)
- [ ] Run lifecycle scripts. (`preinstall` and `postinstall`)
- [ ] Symlink the `bin` files.

## How to start?

Read the source code in the `src` directory.
You can read the `src/index.ts` file in the beginning.

If you would like to try this simple package manager,
just install it globally:

Via Yarn:

```
$ yarn global add tiny-package-manager
```

Via npm:

```
$ npm i -g tiny-package-manager
```

Then just go to a directory which contains valid `package.json` and run:

```
$ tiny-pm
```

## License

MIT License (c) 2018-present [Pig Fang](https://gplane.win/)


================================================
FILE: azure-pipelines.yml
================================================
pool:
  vmImage: 'Ubuntu-16.04'

steps:
- task: NodeTool@0
  inputs:
    versionSpec: '10.x'
  displayName: 'Install Node.js'

- script: |
    yarn
    yarn test
  displayName: 'Run tests'


================================================
FILE: dprint.json
================================================
{
  "extends": "https://dprint.gplane.win/2024-01.json"
}


================================================
FILE: package.json
================================================
{
  "name": "tiny-package-manager",
  "version": "1.1.0",
  "description": "The tiny package manager.",
  "main": "dist/index.js",
  "repository": "g-plane/tiny-package-manager",
  "author": "Pig Fang <g-plane@hotmail.com>",
  "license": "MIT",
  "private": false,
  "bin": {
    "tiny-pm": "dist/cli.js"
  },
  "files": [
    "dist"
  ],
  "scripts": {
    "dev": "tsc -p . -w",
    "build": "tsc -p .",
    "prepublishOnly": "npm run build",
    "lint": "eslint . -f=beauty --ext=.ts",
    "test": "npm run lint",
    "fmt": "dprint fmt"
  },
  "engines": {
    "node": ">8"
  },
  "dependencies": {
    "find-up": "^5.0.0",
    "fs-extra": "^11.1.0",
    "js-yaml": "^4.1.0",
    "log-update": "^4.0.0",
    "progress": "^2.0.3",
    "semver": "^7.3.8",
    "tar": "^6.1.13",
    "undici": "^5.26.4",
    "yargs": "^17.6.2"
  },
  "devDependencies": {
    "@gplane/tsconfig": "^5.0.0",
    "@types/fs-extra": "^11.0.1",
    "@types/js-yaml": "^4.0.5",
    "@types/progress": "^2.0.5",
    "@types/semver": "^7.3.13",
    "@types/tar": "^6.1.3",
    "@types/yargs": "^17.0.20",
    "@typescript-eslint/eslint-plugin": "^5.49.0",
    "@typescript-eslint/parser": "^5.49.0",
    "dprint": "^0.45.0",
    "eslint": "^8.33.0",
    "eslint-config-gplane": "^6.2.2",
    "eslint-formatter-beauty": "^3.0.0",
    "typescript": "^4.9.4"
  }
}


================================================
FILE: src/cli.ts
================================================
import yargs from 'yargs'
import pm from '.'

/*
 * This file is for CLI usage.
 * There isn't too much logic about package manager here.
 * For details please consult the documentation of `yargs` module.
 */

yargs
  .usage('tiny-pm <command> [args]')
  .version()
  .alias('v', 'version')
  .help()
  .alias('h', 'help')
  .command(
    'install',
    'Install the dependencies.',
    (argv) => {
      argv.option('production', {
        type: 'boolean',
        description: 'Install production dependencies only.',
      })

      argv.boolean('save-dev')
      argv.boolean('dev')
      argv.alias('D', 'dev')

      return argv
    },
    pm
  )
  .command(
    '*',
    'Install the dependencies.',
    (argv) =>
      argv.option('production', {
        type: 'boolean',
        description: 'Install production dependencies only.',
      }),
    pm
  )
  .parse()


================================================
FILE: src/index.ts
================================================
import findUp from 'find-up'
import * as fs from 'fs-extra'
import type yargs from 'yargs'
import install from './install'
import list, { PackageJson } from './list'
import * as lock from './lock'
import * as log from './log'
import * as utils from './utils'

/**
 * Welcome to learning about how to build a package manager.
 * In this guide I will tell you how to build a
 * very very simple package manager like npm or Yarn.
 *
 * I will use ES2017 syntax in this guide,
 * so please make sure you know about it.
 *
 * Also this guide is written in TypeScript.
 * Don't worry if you don't know TypeScript,
 * just treat it as JavaScript with some type annotations.
 * If you have learned Flow, that's great,
 * because they are similar.
 *
 * To make this guide as simple as possible,
 * I haven't handled some edge cases.
 *
 * Good luck and let's start!   :)
 *
 * This is just the main file of the whole tiny package manager,
 * but not all of the logic,
 * because I split them into different modules and files for better management.
 */

export default async function(args: yargs.Arguments) {
  // Find and read the `package.json`.
  const jsonPath = (await findUp('package.json'))!
  const root = await fs.readJson(jsonPath)

  /*
   * If we are adding new packages by running `tiny-pm install <packageName>`,
   * collect them through CLI arguments.
   * This purpose is to behaves like `npm i <packageName>` or `yarn add`.
   */
  const additionalPackages = args._.slice(1)
  if (additionalPackages.length) {
    if (args['save-dev'] || args.dev) {
      root.devDependencies = root.devDependencies || {}
      /*
       * At this time we don't specific version now, so set it empty.
       * And we will fill it later after fetched the information.
       */
      additionalPackages.forEach((pkg) => (root.devDependencies[pkg] = ''))
    } else {
      root.dependencies = root.dependencies || {}
      /*
       * At this time we don't specific version now, so set it empty.
       * And we will fill it later after fetched the information.
       */
      additionalPackages.forEach((pkg) => (root.dependencies[pkg] = ''))
    }
  }

  /*
   * In production mode,
   * we just need to resolve production dependencies.
   */
  if (args.production) {
    delete root.devDependencies
  }

  // Read the lock file
  await lock.readLock()

  // Generate the dependencies information.
  const info = await list(root)

  // Save the lock file asynchronously.
  lock.writeLock()

  /*
   * Prepare for the progress bar.
   * Note that we re-compute the number of packages.
   * Because of the duplication,
   * number of resolved packages is not equivalent to
   * the number of packages to be installed.
   */
  log.prepareInstall(
    Object.keys(info.topLevel).length + info.unsatisfied.length
  )

  // Install top level packages.
  await Promise.all(
    Object.entries(info.topLevel).map(([name, { url }]) => install(name, url))
  )

  // Install packages which have conflicts.
  await Promise.all(
    info.unsatisfied.map((item) => install(item.name, item.url, `/node_modules/${item.parent}`))
  )

  beautifyPackageJson(root)

  // Save the `package.json` file.
  fs.writeJson(jsonPath, root, { spaces: 2 })

  // That's all! Everything should be finished if no errors occurred.
}

/**
 * Beautify the `dependencies` field and `devDependencies` field.
 */
function beautifyPackageJson(packageJson: PackageJson) {
  if (packageJson.dependencies) {
    packageJson.dependencies = utils.sortKeys(packageJson.dependencies)
  }

  if (packageJson.devDependencies) {
    packageJson.devDependencies = utils.sortKeys(packageJson.devDependencies)
  }
}


================================================
FILE: src/install.ts
================================================
import * as fs from 'fs-extra'
import * as tar from 'tar'
import { request } from 'undici'
import * as log from './log'

export default async function(name: string, url: string, location = '') {
  // Prepare for the directory which is for installation
  const path = `${process.cwd()}${location}/node_modules/${name}`

  // Create directories recursively.
  await fs.mkdirp(path)

  const response = await request(url)
  /*
   * The response body is a readable stream
   * and the `tar.extract` accepts a readable stream,
   * so we don't need to create a file to disk,
   * and just extract the stuff directly.
   */
  response.body
    ?.pipe(tar.extract({ cwd: path, strip: 1 }))
    .on('close', log.tickInstalling) // Update the progress bar
}


================================================
FILE: src/list.ts
================================================
import * as semver from 'semver'
import * as lock from './lock'
import * as log from './log'
import resolve from './resolve'

interface DependenciesMap {
  [dependency: string]: string
}
// eslint-disable-next-line @typescript-eslint/no-type-alias
type DependencyStack = Array<{
  name: string,
  version: string,
  dependencies: { [dep: string]: string },
}>
export interface PackageJson {
  dependencies?: DependenciesMap
  devDependencies?: DependenciesMap
}

/*
 * The `topLevel` variable is to flatten packages tree
 * to avoid duplication.
 */
const topLevel: {
  [name: string]: { url: string, version: string },
} = Object.create(null)

/*
 * However, there may be dependencies conflicts,
 * so this variable is for that.
 */
const unsatisfied: Array<{ name: string, parent: string, url: string }> = []

async function collectDeps(
  name: string,
  constraint: string,
  stack: DependencyStack = [],
) {
  // Retrieve a single manifest by name from the lock.
  const fromLock = lock.getItem(name, constraint)

  /*
   * Fetch the manifest information.
   * If that manifest is not existed in the lock,
   * fetch it from network.
   */
  const manifest = fromLock || (await resolve(name))

  // Add currently resolving module to CLI
  log.logResolving(name)

  /*
   * Use the latest version of a package
   * while it will conform the semantic version.
   * However, if no semantic version is specified,
   * use the latest version.
   */
  const versions = Object.keys(manifest)
  const matched = constraint
    ? semver.maxSatisfying(versions, constraint)
    : versions[versions.length - 1] // The last one is the latest.
  if (!matched) {
    throw new Error('Cannot resolve suitable package.')
  }

  const matchedManifest = manifest[matched]!

  if (!topLevel[name]) {
    /*
     * If this package is not existed in the `topLevel` map,
     * just put it.
     */
    topLevel[name] = { url: matchedManifest.dist.tarball, version: matched }
  } else if (semver.satisfies(topLevel[name]!.version, constraint)) {
    const conflictIndex = checkStackDependencies(name, matched, stack)
    if (conflictIndex === -1) {
      /*
       * Remember to return this function to skip the dependencies checking.
       * This may avoid dependencies circulation.
       */
      return
    }
    /*
     * Because of the module resolution algorithm of Node.js,
     * there may be some conflicts in the dependencies of dependency.
     * How to check it? See the `checkStackDependencies` function below.
     * ----------------------------
     * We just need information of the previous **two** dependencies
     * of the dependency which has conflicts.
     * :(  Not sure if it's right.
     */
    unsatisfied.push({
      name,
      parent: stack
        .map(({ name }) => name) // eslint-disable-line no-shadow
        .slice(conflictIndex - 2)
        .join('/node_modules/'),
      url: matchedManifest.dist.tarball,
    })
  } else {
    /*
     * Yep, the package is already existed in that map,
     * but it has conflicts because of the semantic version.
     * So we should add a record.
     */
    unsatisfied.push({
      name,
      parent: stack.at(-1)!.name,
      url: matchedManifest.dist.tarball,
    })
  }

  // Don't forget to collect the dependencies of our dependencies.
  const dependencies = matchedManifest.dependencies ?? {}

  // Save the manifest to the new lock.
  lock.updateOrCreate(`${name}@${constraint}`, {
    version: matched,
    url: matchedManifest.dist.tarball,
    shasum: matchedManifest.dist.shasum,
    dependencies,
  })

  /*
   * Collect the dependencies of dependency,
   * so it's time to be deeper.
   */
  if (dependencies) {
    stack.push({
      name,
      version: matched,
      dependencies,
    })
    await Promise.all(
      Object.entries(dependencies)
        // The filter below is to prevent dependency circulation
        .filter(([dep, range]) => !hasCirculation(dep, range, stack))
        .map(([dep, range]) => collectDeps(dep, range, stack.slice()))
    )
    stack.pop()
  }

  /*
   * Return the semantic version range to
   * add missing semantic version range in `package.json`.
   */
  if (!constraint) {
    return { name, version: `^${matched}` }
  }
}

/**
 * This function is to check if there are conflicts in the
 * dependencies of dependency, not the top level dependencies.
 */
function checkStackDependencies(
  name: string,
  version: string,
  stack: DependencyStack,
) {
  return stack.findIndex(({ dependencies }) => {
    const semverRange = dependencies[name]
    /*
     * If this package is not as a dependency of another package,
     * this is safe and we just return `true`.
     */
    if (!semverRange) {
      return true
    }

    // Semantic version checking.
    return semver.satisfies(version, semverRange)
  })
}

/**
 * This function is to check if there is dependency circulation.
 *
 * If a package is existed in the stack and it satisfy the semantic version,
 * it turns out that there is dependency circulation.
 */
function hasCirculation(name: string, range: string, stack: DependencyStack) {
  return stack.some(
    (item) => item.name === name && semver.satisfies(item.version, range)
  )
}

/**
 * To simplify this guide,
 * We intend to support `dependencies` and `devDependencies` fields only.
 */
export default async function(rootManifest: PackageJson) {
  /*
   * For both production dependencies and development dependencies,
   * if the package name and the semantic version are returned,
   * we should add them to the `package.json` file.
   * This is necessary when adding new packages.
   */

  // Process production dependencies
  if (rootManifest.dependencies) {
    ;(
      await Promise.all(
        Object.entries(rootManifest.dependencies).map((pair) => collectDeps(...pair))
      )
    )
      .filter(Boolean)
      .forEach(
        (item) => (rootManifest.dependencies![item!.name] = item!.version)
      )
  }

  // Process development dependencies
  if (rootManifest.devDependencies) {
    ;(
      await Promise.all(
        Object.entries(rootManifest.devDependencies).map((pair) => collectDeps(...pair))
      )
    )
      .filter(Boolean)
      .forEach(
        (item) => (rootManifest.devDependencies![item!.name] = item!.version)
      )
  }

  return { topLevel, unsatisfied }
}


================================================
FILE: src/lock.ts
================================================
import * as fs from 'fs-extra'
import * as yaml from 'js-yaml'
import type { Manifest } from './resolve'
import * as utils from './utils'

// Define the type of the lock tree.
interface Lock {
  [index: string]: {
    version: string,
    url: string,
    shasum: string,
    dependencies: { [dependency: string]: string },
  }
}

// ------------ The LOCK is here. ---------------------

/*
 * Why we use two separated locks?
 * This is useful when removing packages.
 * When adding or removing packages,
 * the lock file can be updated automatically without any manual operations.
 */

/*
 * This is the old lock.
 * The old lock is only for reading from the lock file,
 * so the old lock should be read only except reading the lock file.
 */
const oldLock: Lock = Object.create(null)

/*
 * This is the new lock.
 * The new lock is only for writing to the lock file,
 * so the new lock should be written only except saving the lock file.
 */
const newLock: Lock = Object.create(null)

// ----------------------------------------------------

/**
 * Save the information of a package to the lock.
 * If that information is not existed in the lock, create it.
 * Otherwise, just update it.
 */
export function updateOrCreate(name: string, info: Lock[string]) {
  // Create it if that information is not existed in the lock.
  if (!newLock[name]) {
    newLock[name] = Object.create(null)
  }

  // Then update it.
  Object.assign(newLock[name]!, info)
}

/**
 * Retrieve the information of a package by name and it's semantic
 * version range.
 *
 * Note that we don't return the data directly.
 * That is, we just do format the data,
 * which make the data structure similar to npm registry.
 *
 * This can let us avoid changing the logic of the `collectDeps`
 * function in the `list` module.
 */
export function getItem(name: string, constraint: string): Manifest | null {
  /*
   * Retrieve an item by a key from the lock.
   * The format of the key is similar and inspired by Yarn's `yarn.lock` file.
   */
  const item = oldLock[`${name}@${constraint}`]

  // Return `null` instead of `undefined` if we cannot find that.
  if (!item) {
    return null
  }

  // Convert the data structure as the comment above.
  return {
    [item.version]: {
      dependencies: item.dependencies,
      dist: { shasum: item.shasum, tarball: item.url },
    },
  }
}

/**
 * Simply save the lock file.
 */
export async function writeLock() {
  /*
   * Sort the keys of the lock before save it.
   * This is necessary because each time you use the package manager,
   * the order will not be same.
   * Sort it makes useful for git diff.
   */
  await fs.writeFile(
    './tiny-pm.yml',
    yaml.dump(utils.sortKeys(newLock), { noRefs: true })
  )
}

/**
 * Simply read the lock file.
 * Skip it if we cannot find the lock file.
 */
export async function readLock() {
  if (await fs.pathExists('./tiny-pm.yml')) {
    Object.assign(
      oldLock,
      yaml.load(await fs.readFile('./tiny-pm.yml', 'utf-8'))
    )
  }
}


================================================
FILE: src/log.ts
================================================
import logUpdate from 'log-update'
import ProgressBar from 'progress'

let progress: ProgressBar

/**
 * Update currently resolved module.
 * This is similar to Yarn.
 */
export function logResolving(name: string) {
  logUpdate(`[1/2] Resolving: ${name}`)
}

/**
 * Use a friendly progress bar.
 */
export function prepareInstall(count: number) {
  logUpdate('[1/2] Finished resolving.')
  progress = new ProgressBar('[2/2] Installing [:bar]', {
    complete: '#',
    total: count,
  })
}

/**
 * This is for update the progress bar
 * once tarball extraction is finished.
 */
export function tickInstalling() {
  progress.tick()
}


================================================
FILE: src/resolve.ts
================================================
import { request } from 'undici'

// Just type definition and this can be ignored.
export interface Manifest {
  [version: string]: {
    dependencies?: { [dep: string]: string },
    dist: { shasum: string, tarball: string },
  }
}

// This allows us use a custom npm registry.
const REGISTRY = process.env.REGISTRY || 'https://registry.npmjs.org/'

/*
 * Use cache to prevent duplicated network request,
 * when asking the same package.
 */
const cache: { [dep: string]: Manifest } = Object.create(null)

export default async function(name: string): Promise<Manifest> {
  /*
   * If the requested package manifest is existed in cache,
   * just return it directly.
   */
  const cached = cache[name]
  if (cached) {
    return cached
  }

  const response = await request(`${REGISTRY}${name}`)

  const json = (await response.body.json()) as
    | { error: string }
    | { versions: Manifest }
  if ('error' in json) {
    throw new ReferenceError(`No such package: ${name}`)
  }

  // Add the manifest info to cache and return it.
  return (cache[name] = json.versions)
}


================================================
FILE: src/utils.ts
================================================
export function sortKeys<T extends { [key: string]: any }>(obj: T) {
  return Object.fromEntries(
    Object.entries(obj).sort(([a], [b]) => a.localeCompare(b))
  )
}


================================================
FILE: tsconfig.json
================================================
{
  "extends": "@gplane/tsconfig",
  "compilerOptions": {
    "target": "es2020",
    "module": "commonjs",
    "outDir": "dist"
  }
}
Download .txt
gitextract_g2jw__t_/

├── .editorconfig
├── .eslintrc.yml
├── .github/
│   └── FUNDING.yml
├── .gitignore
├── .vscode/
│   └── settings.json
├── LICENSE
├── README.md
├── azure-pipelines.yml
├── dprint.json
├── package.json
├── src/
│   ├── cli.ts
│   ├── index.ts
│   ├── install.ts
│   ├── list.ts
│   ├── lock.ts
│   ├── log.ts
│   ├── resolve.ts
│   └── utils.ts
└── tsconfig.json
Download .txt
SYMBOL INDEX (18 symbols across 6 files)

FILE: src/index.ts
  function beautifyPackageJson (line 112) | function beautifyPackageJson(packageJson: PackageJson) {

FILE: src/list.ts
  type DependenciesMap (line 6) | interface DependenciesMap {
  type DependencyStack (line 10) | type DependencyStack = Array<{
  type PackageJson (line 15) | interface PackageJson {
  function collectDeps (line 34) | async function collectDeps(
  function checkStackDependencies (line 156) | function checkStackDependencies(
  function hasCirculation (line 182) | function hasCirculation(name: string, range: string, stack: DependencySt...

FILE: src/lock.ts
  type Lock (line 7) | interface Lock {
  function updateOrCreate (line 46) | function updateOrCreate(name: string, info: Lock[string]) {
  function getItem (line 67) | function getItem(name: string, constraint: string): Manifest | null {
  function writeLock (line 91) | async function writeLock() {
  function readLock (line 108) | async function readLock() {

FILE: src/log.ts
  function logResolving (line 10) | function logResolving(name: string) {
  function prepareInstall (line 17) | function prepareInstall(count: number) {
  function tickInstalling (line 29) | function tickInstalling() {

FILE: src/resolve.ts
  type Manifest (line 4) | interface Manifest {
  constant REGISTRY (line 12) | const REGISTRY = process.env.REGISTRY || 'https://registry.npmjs.org/'

FILE: src/utils.ts
  function sortKeys (line 1) | function sortKeys<T extends { [key: string]: any }>(obj: T) {
Condensed preview — 19 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (23K chars).
[
  {
    "path": ".editorconfig",
    "chars": 146,
    "preview": "root = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ni"
  },
  {
    "path": ".eslintrc.yml",
    "chars": 58,
    "preview": "extends:\n  - gplane\n  - gplane/node\n  - gplane/typescript\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "chars": 16,
    "preview": "github: g-plane\n"
  },
  {
    "path": ".gitignore",
    "chars": 35,
    "preview": "dist/\nnode_modules/\nyarn-error.log\n"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 138,
    "preview": "{\n  \"eslint.validate\": [\n    \"javascript\",\n    \"javascriptreact\",\n    {\n      \"language\": \"typescript\",\n      \"autoFix\":"
  },
  {
    "path": "LICENSE",
    "chars": 1073,
    "preview": "MIT License\n\nCopyright (c) 2018-present Pig Fang\n\nPermission is hereby granted, free of charge, to any person obtaining "
  },
  {
    "path": "README.md",
    "chars": 1836,
    "preview": "# Tiny Package Manager\n\n> A very very simple demo and guide for explaining package manager.\n\n## Introduction\n\nAs a JavaS"
  },
  {
    "path": "azure-pipelines.yml",
    "chars": 189,
    "preview": "pool:\n  vmImage: 'Ubuntu-16.04'\n\nsteps:\n- task: NodeTool@0\n  inputs:\n    versionSpec: '10.x'\n  displayName: 'Install Nod"
  },
  {
    "path": "dprint.json",
    "chars": 58,
    "preview": "{\n  \"extends\": \"https://dprint.gplane.win/2024-01.json\"\n}\n"
  },
  {
    "path": "package.json",
    "chars": 1337,
    "preview": "{\n  \"name\": \"tiny-package-manager\",\n  \"version\": \"1.1.0\",\n  \"description\": \"The tiny package manager.\",\n  \"main\": \"dist/"
  },
  {
    "path": "src/cli.ts",
    "chars": 874,
    "preview": "import yargs from 'yargs'\nimport pm from '.'\n\n/*\n * This file is for CLI usage.\n * There isn't too much logic about pack"
  },
  {
    "path": "src/index.ts",
    "chars": 3660,
    "preview": "import findUp from 'find-up'\nimport * as fs from 'fs-extra'\nimport type yargs from 'yargs'\nimport install from './instal"
  },
  {
    "path": "src/install.ts",
    "chars": 749,
    "preview": "import * as fs from 'fs-extra'\nimport * as tar from 'tar'\nimport { request } from 'undici'\nimport * as log from './log'\n"
  },
  {
    "path": "src/list.ts",
    "chars": 6342,
    "preview": "import * as semver from 'semver'\nimport * as lock from './lock'\nimport * as log from './log'\nimport resolve from './reso"
  },
  {
    "path": "src/lock.ts",
    "chars": 3011,
    "preview": "import * as fs from 'fs-extra'\nimport * as yaml from 'js-yaml'\nimport type { Manifest } from './resolve'\nimport * as uti"
  },
  {
    "path": "src/log.ts",
    "chars": 633,
    "preview": "import logUpdate from 'log-update'\nimport ProgressBar from 'progress'\n\nlet progress: ProgressBar\n\n/**\n * Update currentl"
  },
  {
    "path": "src/resolve.ts",
    "chars": 1076,
    "preview": "import { request } from 'undici'\n\n// Just type definition and this can be ignored.\nexport interface Manifest {\n  [versio"
  },
  {
    "path": "src/utils.ts",
    "chars": 167,
    "preview": "export function sortKeys<T extends { [key: string]: any }>(obj: T) {\n  return Object.fromEntries(\n    Object.entries(obj"
  },
  {
    "path": "tsconfig.json",
    "chars": 135,
    "preview": "{\n  \"extends\": \"@gplane/tsconfig\",\n  \"compilerOptions\": {\n    \"target\": \"es2020\",\n    \"module\": \"commonjs\",\n    \"outDir\""
  }
]

About this extraction

This page contains the full source code of the g-plane/tiny-package-manager GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 19 files (21.0 KB), approximately 5.7k tokens, and a symbol index with 18 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!