registry icon indicating copy to clipboard operation
registry copied to clipboard

Typings for schema of a npm package's `package.json`

Open rozzzly opened this issue 8 years ago • 14 comments

Okay, so I'm trying to find a type definition for the schema/deserialized content of a package.json file. You know the manifest as it were for an npm package, exists at the root of our projects, contains stuff like:

  "author": "rozzzly",
  "license": "MIT",
  "dependencies": {
    "immutable": "^3.8.1
  }

...but I can't find one. It's been like 45+ minutes and even though I'm a seasoned internets lurker, I still can't find it! I'm positive this must exist somewhere, its too common not to. But it's buried somewhere in hundreds of thousands of results referring to publishing npm packages, resolving type definitions, etc.

So far I've tried dozens search queries with google, github via org: typings _______ / org: DefinitelyTyped _______, and of course via the cli typings search _______.

I still can't find it. I know one of you out there has to have used it before, might you be kind enough to share a link?


Additionally, what can we do so other developers looking for the same thing have an easier time? From my point of view, this essentially means getting the desired .d.ts higher in Google search results. I've got a few thoughts on how this could be achieved, but want to hear from you first.

rozzzly avatar Jul 02 '16 13:07 rozzzly

Okay, so I'm trying to find a type definition for the schema/deserialized content of a package.json file.

Are you referring to the json schema for package.json? That's probably in schemastore.org.

Or are you looking for the typings of immutable? which you can install by typings install immutable

unional avatar Jul 02 '16 19:07 unional

@unional I'm pretty sure he's after a type definition for package.json.

We can add one to the registry, I've done something similar with other type definitions (like JSON schema). I know I would like a definition for package.json and bower.json, etc.

blakeembrey avatar Jul 02 '16 20:07 blakeembrey

You can do a type definition for a json file? That I did not know! How does that get used? I mean json is basically a JavaScript object literal which can easily be typed. But how would that definition be applied to a JSON file?

johnnyreilly avatar Jul 02 '16 21:07 johnnyreilly

@blakeembrey yes that is what I meant.

@johnnyreilly

how would that definition be applied to a JSON file?

To put it simply, just parse it into a plain old js object. Moreover, we can use an interface to describe an object, so by extension, a json schema can also be modeled with little difficulty.

Imagine we read package.json via readFile() from node's fs module. a string containing content of entire file JSON.parse() any ...wait, what?! that's not very useful. I'm lazy, I love autocompletion. And I love being able to use inline documentation to answer simple questions without having to do the following: switch to chrome. open new tab, google for somepackage's docs, choose the for the official docs, then ctrl+f to search relevant section of the docs docs.

With a proper definition, I get autocomplete for the schema which makes development quicker, and documentation which lets me find solutions easier. ...and I just realized I made the argument for having typing themselves! If it's something us developers interface with programmatically, we should have a type definition for it so we can interface with said data structure in the best way possible.

So the idea is to do something like

export interface npmPackageSchema {
    /** The name of the package. **/
    name: string;
    /** Optionally disallow npm from indexing this package**/
    private?: boolean;
    /** A list of the packages this package depends on. **/
    dependencies?: {
        /** A depended-upon package and its version (semver, filepath, etc) **/
        [pkgName: string]: string;
    };
    /**
     * Ensure that any "nonstandard" properties can
     * still be accessed without making a fuss. 
     **/
    [NonStandardProperty: string]: any;

};
const inspectPackage = (): npmPackageSchema => {
    // Some read file operations...


    //   You can assume the variable `content` is a string 
    //     with the contents of `package.json`.
    return JSON.parse(content); 
        // The type of the return value is `any`. It which undergoes implicit type 
        // assertion (aka a cast) to the return type of the  function, npmPackageSchema.
});

Then anything that uses the result of that function would get access to properties defined on that schema.

rozzzly avatar Jul 03 '16 00:07 rozzzly

Nice @rozzzly :+1:

johnnyreilly avatar Jul 03 '16 06:07 johnnyreilly

Because many different platforms, build targets, tools, plugins, or what-have-you use package.json as a centralized location for storing configuration data, one should not be surprised that their package.json file contains fields not described by the official schema. This shouldn't be too much of an issue thanks to type intersections.

please someone come up with a prettier naming convention for the interface :cold_sweat:

export interface npmPackageSchemaPlugin_ava {
    /**
     * Configuration options for `ava`
     **/
    ava: {
        /** Use the `tap` reporter? **/
        tap?: boolean;
        /** Number of worker processes used to run your tests. **/
        concurrency?: number;
        /** An array of glob expressions to use when searching for tests. **/
        files?: string[];
    };
}

const merged: npmPackageSchema & npmPackageSchemaPlugin_ava = inspectPackage() as any;

It would also be possible to define the the merged interface by creating a new interface which extends the base schema and the additions. fun fact: an interface can't implement anything but can extend multiple other interfaces and even classes! A class by comparison can implement any multiple interfaces, but can only extend one class.

export interface foo {
    bar?: boolean;
}
export interface bar {
    foo?: string;
}
export interface MySchema extends npmPackageSchema, foo, bar { 
    // would get kinda ugly if you have many schema mixed in,
    // ones with long identifiers moreover,
    // to have this just empty looks weird to me...
}

Perhaps the best solution could be:

export type MySchema = npmPackageSchema & foo  & bar;
// alternate way to format that...
export type MySchema = npmPackageSchema
    & npmPackageSchemaPlugin_foo
    & npmPackageSchemaPlugin_bar;
// personally I prefer this
export type MySchema = ((npmPackageSchema)
    & npmPackageSchemaPlugin_foo
    & npmPackageSchemaPlugin_bar
);

rozzzly avatar Jul 04 '16 05:07 rozzzly

I'm thinking of module augmentation in that case:

// official
declare module 'npm' {
  interface packageJson {
    ...
  }
}

// ava
declare module 'npm' {
  interface packageJson {
    // ava specific configuration
   }
}

Maybe put this under common/npm?

unional avatar Jul 04 '16 05:07 unional

Ahhh even better, much more palatable. I only started using typescript a few months ago, haven't really used module in any of the typings I've written; sticking to the es6 import/export syntax.

rozzzly avatar Jul 04 '16 10:07 rozzzly

It might be better to keep this sort of thing global? It's tricky, but I wouldn't want to take the npm name in case someone actually wants to write the module for it (most likely). Using globals, though, you can just rely on general interface merging without module augmentation also.

declare interface PackageJson {
  main?: string
}

declare interface PackageJson {
  ...
}

blakeembrey avatar Jul 06 '16 04:07 blakeembrey

However, we could also provide npm's PackageJson interface as an actual export for the NPM typings if someone wants to start them.

blakeembrey avatar Jul 06 '16 04:07 blakeembrey

And @blakeembrey, I agree about the name. It would be presumptuous to just randomly publish this with the name: npm. My thinking is:

  • [ ] Start a rough draft in the next few days, just get a few of the fields w/ complete documentation to give an idea of what this would look like
    • globally declare an interface in the manner which you describe above.
    • Use PackageJson as the name of that interface.
    • Use PackageJson as the tentative name for this typing (ie: in .typings.json)
  • [ ] Create a small example of how interface merging could be achieved
    • Might as well reuse the ava example; create another documented interface declaration with the name PackageJson
  • [ ] Open an issue on the [npm/npm] repo (unless there is a better place to bring this up?)
    • Ask for guidance/blessing/feedback
    • IMO the PackageJson interface would ideally be published as a part of a typing named npm; hence, why I italicised the word "tentative" above. I cant think of anything off the top of my head. but I'm sure npm has some other things that could benefit from typedefs. Apparently their programmatic API isn't meant for consumption...
    • I imagine the npm team would want to be the curator/maintainer, fine by me! I just want to make this a thing. This would give them an easy opportunity to get "official" typings with little/no effort.
    • If for whatever reason, they're not so keen on the idea, I just publish the package to the typings registry as PackageJson
  • [ ] For that issue on [npm/npm] include a .gif of PackageJson intellisense in vscode
    • Would get peoples attention, visualize the benefits of this

    • Everybody reacts like:

      Computer Kid Thumbs-Up

rozzzly avatar Jul 10 '16 06:07 rozzzly

Awesome gif!

johnnyreilly avatar Jul 10 '16 06:07 johnnyreilly

Would this help? https://github.com/bcherny/json-schema-to-typescript

unional avatar Jul 21 '16 04:07 unional

If we load an unknown package.json dynamically (not part of the build) it would be nice to have a pessimistic structure of how the JSON should look after parsing, something like this:

https://stackoverflow.com/questions/51794585/is-there-a-basic-ts-type-for-package-json

ORESoftware avatar Aug 10 '18 22:08 ORESoftware