Skip to content

Commit 432e620

Browse files
committed
feat(types): generate types for imgix url params
0 parents  commit 432e620

10 files changed

+497
-0
lines changed

.gitignore

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
lerna-debug.log*
8+
9+
# Diagnostic reports (https://nodejs.org/api/report.html)
10+
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11+
12+
# Runtime data
13+
pids
14+
*.pid
15+
*.seed
16+
*.pid.lock
17+
18+
# Directory for instrumented libs generated by jscoverage/JSCover
19+
lib-cov
20+
21+
# Coverage directory used by tools like istanbul
22+
coverage
23+
*.lcov
24+
25+
# nyc test coverage
26+
.nyc_output
27+
28+
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29+
.grunt
30+
31+
# Bower dependency directory (https://bower.io/)
32+
bower_components
33+
34+
# node-waf configuration
35+
.lock-wscript
36+
37+
# Compiled binary addons (https://nodejs.org/api/addons.html)
38+
build/Release
39+
40+
# Dependency directories
41+
node_modules/
42+
jspm_packages/
43+
44+
# TypeScript v1 declaration files
45+
typings/
46+
47+
# TypeScript cache
48+
*.tsbuildinfo
49+
50+
# Optional npm cache directory
51+
.npm
52+
53+
# Optional eslint cache
54+
.eslintcache
55+
56+
# Microbundle cache
57+
.rpt2_cache/
58+
.rts2_cache_cjs/
59+
.rts2_cache_es/
60+
.rts2_cache_umd/
61+
62+
# Optional REPL history
63+
.node_repl_history
64+
65+
# Output of 'npm pack'
66+
*.tgz
67+
68+
# Yarn Integrity file
69+
.yarn-integrity
70+
71+
# dotenv environment variables file
72+
.env
73+
.env.test
74+
75+
# parcel-bundler cache (https://parceljs.org/)
76+
.cache
77+
78+
# Next.js build output
79+
.next
80+
81+
# Nuxt.js build / generate output
82+
.nuxt
83+
dist
84+
85+
# Gatsby files
86+
.cache/
87+
# Comment in the public line in if your project uses Gatsby and *not* Next.js
88+
# https://nextjs.org/blog/next-9-1#public-directory-support
89+
# public
90+
91+
# vuepress build output
92+
.vuepress/dist
93+
94+
# Serverless directories
95+
.serverless/
96+
97+
# FuseBox cache
98+
.fusebox/
99+
100+
# DynamoDB Local files
101+
.dynamodb/
102+
103+
# TernJS port file
104+
.tern-port
105+
106+
# pre-build artefacts
107+
src/generated
108+
*.tgz

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.md

.prettierrc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"arrowParens": "always",
3+
"printWidth": 80,
4+
"proseWrap": "always",
5+
"singleQuote": true,
6+
"trailingComma": "all"
7+
}

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2022 Jamie Mason
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# typescript-imgix-url-params
2+
3+
> TypeScript definitions of imgix's URL parameters.
4+
5+
![NPM version](http://img.shields.io/npm/v/typescript-imgix-url-params.svg?style=flat-square)](https://www.npmjs.com/package/typescript-imgix-url-params)
6+
[![NPM downloads](http://img.shields.io/npm/dm/typescript-imgix-url-params.svg?style=flat-square)](https://www.npmjs.com/package/typescript-imgix-url-params)
7+
[![Build Status](https://img.shields.io/github/workflow/status/JamieMason/typescript-imgix-url-params/ci)](https://github.com/JamieMason/typescript-imgix-url-params/actions)
8+
[![Maintainability](https://api.codeclimate.com/v1/badges/516439365fdd0e3c6526/maintainability)](https://codeclimate.com/github/JamieMason/typescript-imgix-url-params/maintainability)
9+
10+
## 🌩 Installation
11+
12+
```bash
13+
npm install --save-dev typescript-imgix-url-params
14+
```
15+
16+
## ⚠️ Status
17+
18+
This package was created on 15 Nov 2022, it is new but please give it a try and
19+
[give your feedback](https://github.com/JamieMason/typescript-imgix-url-params/issues/new).
20+
21+
## 🕹 Usage
22+
23+
```ts
24+
import type { ImgixUrl } from 'typescript-imgix-url-params';
25+
26+
const params: Partial<ImgixUrl.Params> = {
27+
w: 100,
28+
h: 100,
29+
fm: 'pjpg',
30+
};
31+
```
32+
33+
## ⚙️ Contributing
34+
35+
To update this package we regenerate the schema based on the latest contents from https://github.com/imgix/imgix-url-params:
36+
37+
1. Bump `imgix-url-params` in package.json.
38+
1. `yarn install`
39+
1. `yarn build`
40+
1. `npm publish .`

package.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"name": "typescript-imgix-url-params",
3+
"description": "TypeScript definitions of imgix's URL parameters",
4+
"version": "0.0.0",
5+
"author": "Jamie Mason <jamie@foldleft.io> (https://github.com/JamieMason)",
6+
"bugs": "https://github.com/JamieMason/typescript-imgix-url-params/issues",
7+
"devDependencies": {
8+
"@types/node": "18.11.9",
9+
"@types/prettier": "2.7.1",
10+
"imgix-url-params": "11.13.0",
11+
"prettier": "2.7.1",
12+
"ts-node": "10.9.1",
13+
"typescript": "4.8.4"
14+
},
15+
"files": [
16+
"dist/index.d.ts"
17+
],
18+
"homepage": "https://github.com/JamieMason/typescript-imgix-url-params#readme",
19+
"keywords": [
20+
"imgix",
21+
"types",
22+
"typescript"
23+
],
24+
"license": "MIT",
25+
"repository": "JamieMason/typescript-imgix-url-params.git",
26+
"scripts": {
27+
"build": "rm -rf dist && ts-node src/build.ts",
28+
"prebuild": "rm -rf src/generated && ts-node src/generate-schema.ts",
29+
"prepack": "yarn run build"
30+
},
31+
"types": "dist/index.d.ts"
32+
}

src/build.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { mkdirSync, writeFileSync } from 'fs';
2+
import { resolve } from 'path';
3+
import { format } from 'prettier';
4+
import { getSource } from './get-source';
5+
6+
const dirPath = resolve(__dirname, '../dist');
7+
const filePath = resolve(__dirname, '../dist/index.d.ts');
8+
const source = getSource();
9+
10+
mkdirSync(dirPath, { recursive: true });
11+
12+
writeFileSync(
13+
filePath,
14+
format(source, {
15+
parser: 'typescript',
16+
}),
17+
'utf8',
18+
);

src/generate-schema.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { mkdirSync, writeFileSync } from 'fs';
2+
import { resolve } from 'path';
3+
import { format } from 'prettier';
4+
5+
const schema = require('imgix-url-params/dist/parameters');
6+
const dirPath = resolve(__dirname, 'generated');
7+
const filePath = resolve(__dirname, 'generated/schema.ts');
8+
const source = `
9+
export type Schema = typeof schema;
10+
11+
export const schema = ${JSON.stringify(schema)} as const;
12+
`;
13+
14+
mkdirSync(dirPath, { recursive: true });
15+
16+
writeFileSync(
17+
filePath,
18+
format(source, {
19+
parser: 'typescript',
20+
}),
21+
'utf8',
22+
);

src/get-source.ts

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { schema, Schema } from './generated/schema';
2+
3+
type Params = Schema['parameters'];
4+
type ParamSpec = Params[keyof Params];
5+
type Expects = ParamSpec['expects'];
6+
type Expect = Expects[number];
7+
type CategoryValues = Schema['categoryValues'];
8+
type CategoryValue = CategoryValues[number];
9+
10+
export function getSource(): string {
11+
return `
12+
export namespace ImgixUrl {
13+
${getTypes()}
14+
}
15+
`;
16+
}
17+
18+
export function getTypes(): string {
19+
const allEntries: [string, ParamSpec][] = Object.entries(schema.parameters);
20+
return [
21+
getHexColorType(),
22+
getColorKeywordValueType(schema),
23+
getFontValueType(schema),
24+
...schema.categoryValues.map(
25+
(category) =>
26+
`/** @see {@link https://docs.imgix.com/apis/rendering/${category}} */
27+
${getParamsInterface(
28+
getCategoryInterfaceName(category),
29+
allEntries.filter(([_key, value]) => value.category === category),
30+
)}`,
31+
),
32+
getImgixUrlParamsType(schema),
33+
].join('\n\n');
34+
}
35+
36+
function getImgixUrlParamsType(schema: Schema): string {
37+
return `
38+
/** @see {@link https://docs.imgix.com/apis/rendering} */
39+
export type Params = ${schema.categoryValues
40+
.map((category) => getCategoryInterfaceName(category))
41+
.join(' & ')}`;
42+
}
43+
44+
function getCategoryInterfaceName(category: CategoryValue): string {
45+
return `${kebabToPascalCase(category)}Params`;
46+
}
47+
48+
function kebabToPascalCase(kebab: string): string {
49+
return kebab
50+
.split('-')
51+
.map((s) => `${s.charAt(0).toUpperCase()}${s.slice(1)}`)
52+
.join('');
53+
}
54+
55+
function getParamsInterface(
56+
name: string,
57+
entries: [string, ParamSpec][],
58+
): string {
59+
const props: string[] = entries.map(([key, value]) => {
60+
const type = getPropTypes(value);
61+
const tsdocLines = [
62+
value.short_description,
63+
'url' in value && `@see {@link ${value.url}}`,
64+
]
65+
.filter(Boolean)
66+
.map((line) => ` * ${line}`);
67+
const tsdoc = ['/**', ...tsdocLines, ' */'].join('\n');
68+
return [tsdoc, `'${key}': ${type};`].join('\n');
69+
});
70+
71+
return [`export interface ${name} {`, ...props, '}'].join('\n\n');
72+
}
73+
74+
function getHexColorType(): string {
75+
return 'export type HexColor = `#${string}`';
76+
}
77+
78+
function getColorKeywordValueType(schema: Schema): string {
79+
return `export type ColorKeywordValue = ${schema.colorKeywordValues
80+
.map((s) => `'${s}'`)
81+
.join(' | ')}`;
82+
}
83+
84+
function getFontValueType(schema: Schema): string {
85+
return `export type FontValue = ${schema.fontValues
86+
.map((s) => `'${s}'`)
87+
.join(' | ')}`;
88+
}
89+
90+
function getPropTypes({ expects }: ParamSpec): string {
91+
if (!Array.isArray(expects)) return 'unknown';
92+
return Array.from(new Set(expects.map((expect) => getPropType(expect)))).join(
93+
' | ',
94+
);
95+
}
96+
97+
function getPropType(expect: Expect): string {
98+
if (!expect || typeof expect !== 'object') return 'unknown';
99+
if ('0' in expect || '1' in expect || '2' in expect || '3' in expect)
100+
return 'unknown';
101+
if (expect.type === 'boolean') return 'boolean';
102+
if (expect.type === 'color_keyword') return 'ColorKeywordValue';
103+
if (expect.type === 'font') return 'FontValue';
104+
if (expect.type === 'hex_color') return 'HexColor';
105+
if (expect.type === 'integer') return 'number';
106+
if (expect.type === 'list')
107+
return expect.possible_values.map((s) => `'${s}'`).join(' | ');
108+
if (expect.type === 'number') return 'number';
109+
if (expect.type === 'path') return 'string';
110+
if (expect.type === 'ratio') return 'number';
111+
if (expect.type === 'string') return 'string';
112+
if (expect.type === 'timestamp') return 'string';
113+
if (expect.type === 'unit_scalar') return 'number';
114+
if (expect.type === 'url') return 'string';
115+
return 'unknown';
116+
}

0 commit comments

Comments
 (0)