esm.dev 1.7.3 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/watch.js +11 -5
- package/dist/lib/watchIgnoreList.js +50 -0
- package/package.json +8 -5
- package/.prettierignore +0 -4
- package/commitlint.config.cjs +0 -8
- package/docker-bake.hcl +0 -24
- package/src/cli.ts +0 -21
- package/src/commands/ESMDevCommand.ts +0 -8
- package/src/commands/InitCommand.ts +0 -72
- package/src/commands/LoginCommand.ts +0 -15
- package/src/commands/MustacheGeneratorCommand.ts +0 -10
- package/src/commands/RepublishCommand.ts +0 -15
- package/src/commands/StartCommand.ts +0 -21
- package/src/commands/TokenCommand.ts +0 -26
- package/src/commands/VersionCommand.ts +0 -21
- package/src/commands/WaitForRegistryCommand.ts +0 -17
- package/src/commands/WatchCommand.ts +0 -15
- package/src/commands/mixins/CommandClass.ts +0 -3
- package/src/commands/mixins/ESMServed.ts +0 -12
- package/src/commands/mixins/ESMStorageSpecific.ts +0 -16
- package/src/commands/mixins/PackagePathSpecific.ts +0 -22
- package/src/commands/mixins/RegistrySpecific.ts +0 -12
- package/src/commands/mixins/Retryable.ts +0 -35
- package/src/commands/mixins/Servable.ts +0 -14
- package/src/commands/mixins/Watchable.ts +0 -13
- package/src/lib/MustacheGenerator.ts +0 -10
- package/src/lib/getPackageMeta.ts +0 -15
- package/src/lib/login.ts +0 -35
- package/src/lib/publish.ts +0 -14
- package/src/lib/queue.ts +0 -55
- package/src/lib/republish.ts +0 -16
- package/src/lib/server.ts +0 -40
- package/src/lib/unpublish.ts +0 -41
- package/src/lib/until.ts +0 -22
- package/src/lib/watch.ts +0 -100
- package/src/options/EnvOption.ts +0 -27
package/dist/lib/watch.js
CHANGED
|
@@ -5,6 +5,7 @@ import { readFile, writeFile } from 'node:fs/promises';
|
|
|
5
5
|
import { createHash } from 'node:crypto';
|
|
6
6
|
import { glob } from 'glob';
|
|
7
7
|
import { queue, queuedDebounce } from './queue.js';
|
|
8
|
+
import { getWatchIgnorer } from './watchIgnoreList.js';
|
|
8
9
|
export async function watch(packagePath, opts) {
|
|
9
10
|
const { name, packageRoot, private: prvte, } = await getPackageMeta(packagePath);
|
|
10
11
|
if (prvte) {
|
|
@@ -29,7 +30,9 @@ export async function watch(packagePath, opts) {
|
|
|
29
30
|
};
|
|
30
31
|
}
|
|
31
32
|
else {
|
|
32
|
-
const
|
|
33
|
+
const ignorer = await getWatchIgnorer(packagePath);
|
|
34
|
+
const watcher = fsWatch(packageRoot, { recursive: true }, (_, filename) => (filename && ignorer.ignores(filename)) ||
|
|
35
|
+
debounceRepublish(filename ?? ''));
|
|
33
36
|
return () => {
|
|
34
37
|
console.info('stop watching');
|
|
35
38
|
watcher.close();
|
|
@@ -38,7 +41,8 @@ export async function watch(packagePath, opts) {
|
|
|
38
41
|
}
|
|
39
42
|
}
|
|
40
43
|
async function legacyWatch(dirname, cb) {
|
|
41
|
-
const
|
|
44
|
+
const ignorer = await getWatchIgnorer(dirname);
|
|
45
|
+
const filename = await hashDirectory(dirname, ignorer);
|
|
42
46
|
const watcher = watchFile(filename, cb);
|
|
43
47
|
let timer;
|
|
44
48
|
beginTimer();
|
|
@@ -47,12 +51,14 @@ async function legacyWatch(dirname, cb) {
|
|
|
47
51
|
clearTimeout(timer);
|
|
48
52
|
};
|
|
49
53
|
function beginTimer() {
|
|
50
|
-
timer = setTimeout(() => hashDirectory(dirname
|
|
54
|
+
timer = setTimeout(() => hashDirectory(dirname, ignorer)
|
|
55
|
+
.catch(console.error)
|
|
56
|
+
.finally(beginTimer), 1_000);
|
|
51
57
|
}
|
|
52
58
|
}
|
|
53
|
-
async function hashDirectory(dirname) {
|
|
59
|
+
async function hashDirectory(dirname, ignorer) {
|
|
54
60
|
const filename = `/tmp/${dirname.replace(/\//g, '-')}.hash.txt`;
|
|
55
|
-
const files = await glob(`${dirname}/**/*`, { nodir: true });
|
|
61
|
+
const files = ignorer.filter(await glob(`${dirname}/**/*`, { nodir: true }));
|
|
56
62
|
const hashes = await Promise.all(files.map(async (file) => {
|
|
57
63
|
const contents = await readFile(file, 'utf-8');
|
|
58
64
|
return createHash('sha256').update(contents).digest('hex');
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import gitignore from 'ignore';
|
|
2
|
+
import { Minimatch } from 'minimatch';
|
|
3
|
+
import { createReadStream } from 'node:fs';
|
|
4
|
+
import { access, constants } from 'node:fs/promises';
|
|
5
|
+
import * as path from 'node:path';
|
|
6
|
+
import { createInterface as createReadlineInterface } from 'node:readline/promises';
|
|
7
|
+
export async function getWatchIgnorer(dirname) {
|
|
8
|
+
const ignoreList = await getIgnoreList(dirname);
|
|
9
|
+
return ignoreList?.basename === '.gitignore'
|
|
10
|
+
? createGitIgnore(ignoreList.ignoreList)
|
|
11
|
+
: ignoreList?.basename === '.npmignore'
|
|
12
|
+
? createNPMIgnore(ignoreList.ignoreList)
|
|
13
|
+
: createNullIgnore();
|
|
14
|
+
}
|
|
15
|
+
function createNullIgnore() {
|
|
16
|
+
return { ignores: () => false, filter: (filenames) => filenames };
|
|
17
|
+
}
|
|
18
|
+
function createNPMIgnore(list) {
|
|
19
|
+
const mms = list.map((pattern) => new Minimatch(pattern));
|
|
20
|
+
const ignores = (filename) => mms.some((mm) => mm.match(filename));
|
|
21
|
+
return {
|
|
22
|
+
ignores,
|
|
23
|
+
filter: (filenames) => filenames.filter((filename) => !ignores(filename)),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function createGitIgnore(list) {
|
|
27
|
+
return gitignore().add(list);
|
|
28
|
+
}
|
|
29
|
+
async function getIgnoreList(dirname) {
|
|
30
|
+
for (const basename of ['.npmignore', '.gitignore']) {
|
|
31
|
+
const filename = path.join(dirname, basename);
|
|
32
|
+
try {
|
|
33
|
+
await access(filename, constants.F_OK);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
const rl = createReadlineInterface({
|
|
39
|
+
input: createReadStream(filename),
|
|
40
|
+
});
|
|
41
|
+
const ignoreList = [];
|
|
42
|
+
for await (const line of rl)
|
|
43
|
+
if (line)
|
|
44
|
+
ignoreList.push(line.trim());
|
|
45
|
+
return {
|
|
46
|
+
basename,
|
|
47
|
+
ignoreList,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "esm.dev",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.0",
|
|
4
4
|
"description": "TypeScript library template",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"esm.dev": "bun ./src/cli.ts",
|
|
17
17
|
"prepare": "husky",
|
|
18
18
|
"start": "docker compose up --detach --remove-orphans",
|
|
19
|
-
"stop": "docker compose down"
|
|
19
|
+
"stop": "docker compose down",
|
|
20
|
+
"test": "find . -name \"*.test.ts\" -exec sh -c 'bun test \"$1\" || kill \"$PPID\"' sh {} \\;"
|
|
20
21
|
},
|
|
21
22
|
"repository": {
|
|
22
23
|
"type": "git",
|
|
@@ -52,18 +53,20 @@
|
|
|
52
53
|
},
|
|
53
54
|
"dependencies": {
|
|
54
55
|
"clipanion": "^4.0.0-rc.4",
|
|
55
|
-
"clipanion-generator-command": "^1.3.
|
|
56
|
+
"clipanion-generator-command": "^1.3.1",
|
|
56
57
|
"escape-string-regexp": "^5.0.0",
|
|
57
58
|
"glob": "^11.0.2",
|
|
58
59
|
"http-proxy": "^1.18.1",
|
|
60
|
+
"ignore": "^7.0.4",
|
|
59
61
|
"lodash.debounce": "^4.0.8",
|
|
62
|
+
"minimatch": "^10.0.1",
|
|
60
63
|
"mustache": "^4.2.0",
|
|
61
|
-
"npm": "^11.4.
|
|
64
|
+
"npm": "^11.4.1",
|
|
62
65
|
"ramda": "^0.30.1",
|
|
63
66
|
"throat": "^6.0.2",
|
|
64
67
|
"tslib": "^2.8.1",
|
|
65
68
|
"typanion": "^3.14.0",
|
|
66
|
-
"zod": "^3.25.
|
|
69
|
+
"zod": "^3.25.23",
|
|
67
70
|
"zx": "^8.5.4"
|
|
68
71
|
}
|
|
69
72
|
}
|
package/.prettierignore
DELETED
package/commitlint.config.cjs
DELETED
package/docker-bake.hcl
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
group "default" {
|
|
2
|
-
targets = ["app"]
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
variable "TAG" {
|
|
6
|
-
validation {
|
|
7
|
-
condition = TAG == regex("^(?:\\d+\\.\\d+\\.\\d+)?$", TAG)
|
|
8
|
-
error_message = "The variable 'TAG' has to be in the 'x.x.x' format."
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
variable "IMAGE" {
|
|
13
|
-
default = "ghcr.io/johngeorgewright/esm.dev"
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
target "app" {
|
|
17
|
-
context = "."
|
|
18
|
-
dockerfile = "Dockerfile"
|
|
19
|
-
platforms = ["linux/amd64", "linux/arm64"]
|
|
20
|
-
tags = [
|
|
21
|
-
"${IMAGE}:latest",
|
|
22
|
-
notequal("", TAG) ? "${IMAGE}:${TAG}" : ""
|
|
23
|
-
]
|
|
24
|
-
}
|
package/src/cli.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { runExit } from 'clipanion'
|
|
3
|
-
import { WatchCommand } from './commands/WatchCommand.js'
|
|
4
|
-
import { RepublishCommand } from './commands/RepublishCommand.js'
|
|
5
|
-
import { LoginCommand } from './commands/LoginCommand.js'
|
|
6
|
-
import { TokenCommand } from './commands/TokenCommand.js'
|
|
7
|
-
import { WaitForRegistryCommand } from './commands/WaitForRegistryCommand.js'
|
|
8
|
-
import { StartCommand } from './commands/StartCommand.js'
|
|
9
|
-
import { InitCommand } from './commands/InitCommand.js'
|
|
10
|
-
import { VersionCommand } from './commands/VersionCommand.js'
|
|
11
|
-
|
|
12
|
-
runExit([
|
|
13
|
-
InitCommand,
|
|
14
|
-
LoginCommand,
|
|
15
|
-
TokenCommand,
|
|
16
|
-
RepublishCommand,
|
|
17
|
-
StartCommand,
|
|
18
|
-
VersionCommand,
|
|
19
|
-
WaitForRegistryCommand,
|
|
20
|
-
WatchCommand,
|
|
21
|
-
])
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { Command } from 'clipanion'
|
|
2
|
-
import { RegistrySpecific } from './mixins/RegistrySpecific.js'
|
|
3
|
-
import { ESMStorageSpecific } from './mixins/ESMStorageSpecific.js'
|
|
4
|
-
import { PackagePathSpecific } from './mixins/PackagePathSpecific.js'
|
|
5
|
-
|
|
6
|
-
export abstract class ESMDevCommand extends ESMStorageSpecific(
|
|
7
|
-
RegistrySpecific(PackagePathSpecific(Command)),
|
|
8
|
-
) {}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import { ESMDevCommand } from './ESMDevCommand.js'
|
|
2
|
-
import { PackagePathSpecific } from './mixins/PackagePathSpecific.js'
|
|
3
|
-
import { MustacheGeneratorCommand } from './MustacheGeneratorCommand.js'
|
|
4
|
-
import { Option } from 'clipanion'
|
|
5
|
-
import { isNumber } from 'typanion'
|
|
6
|
-
|
|
7
|
-
export class InitCommand extends PackagePathSpecific(MustacheGeneratorCommand) {
|
|
8
|
-
static override paths = [['init']]
|
|
9
|
-
|
|
10
|
-
static override usage = ESMDevCommand.Usage({
|
|
11
|
-
description: 'Initialises your repo ready to work with ESM.sh locally',
|
|
12
|
-
examples: [
|
|
13
|
-
['Create a minimal template', 'esm.dev init'],
|
|
14
|
-
["Specify the packages you're developing", 'esm.dev init packages/*'],
|
|
15
|
-
[
|
|
16
|
-
'Specify different ports',
|
|
17
|
-
'esm.dev init --port 3001 --esm-port 8081 --registry-port 4444',
|
|
18
|
-
],
|
|
19
|
-
],
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
readonly esmPort = Option.String('-e,--esm-port', '8080', {
|
|
23
|
-
description: 'The port of the ESM Server',
|
|
24
|
-
validator: isNumber(),
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
readonly esmStoragePath = Option.String(
|
|
28
|
-
'-s,--esm-storage-path',
|
|
29
|
-
'./docker-storage/esm/esmd',
|
|
30
|
-
{
|
|
31
|
-
description: "Path to ESM.sh's storage",
|
|
32
|
-
},
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
readonly port = Option.String('-p,--port', '3000', {
|
|
36
|
-
description: "The server's port",
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
readonly outputDirectory = Option.String('-o,--output-dir', '.', {
|
|
40
|
-
description: 'The output directory',
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
readonly registryPort = Option.String('-r,--registry-port', '4873', {
|
|
44
|
-
description: 'The port of your local registry',
|
|
45
|
-
validator: isNumber(),
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
esmURL!: URL
|
|
49
|
-
npmRegistryURL!: URL
|
|
50
|
-
packages!: { path: string; basename: string }[]
|
|
51
|
-
|
|
52
|
-
override async execute() {
|
|
53
|
-
const path = await import('node:path')
|
|
54
|
-
this.templateDir = path.resolve(
|
|
55
|
-
import.meta.dirname,
|
|
56
|
-
'..',
|
|
57
|
-
'..',
|
|
58
|
-
'templates',
|
|
59
|
-
'init',
|
|
60
|
-
)
|
|
61
|
-
this.destinationDir = this.outputDirectory
|
|
62
|
-
this.packages = this.packagePaths.map((packagePath) => ({
|
|
63
|
-
path: packagePath,
|
|
64
|
-
basename: path.basename(packagePath),
|
|
65
|
-
}))
|
|
66
|
-
await this.removeDestinationFile(
|
|
67
|
-
'docker-compose.yaml',
|
|
68
|
-
'docker-compose.yml.mustache',
|
|
69
|
-
)
|
|
70
|
-
await super.execute()
|
|
71
|
-
}
|
|
72
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { Command } from 'clipanion'
|
|
2
|
-
import { RegistrySpecific } from './mixins/RegistrySpecific.js'
|
|
3
|
-
|
|
4
|
-
export class LoginCommand extends RegistrySpecific(Command) {
|
|
5
|
-
static override paths = [['login']]
|
|
6
|
-
|
|
7
|
-
static override usage = Command.Usage({
|
|
8
|
-
description: 'Login in to the NPM registry',
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
override async execute() {
|
|
12
|
-
const { login } = await import('../lib/login.js')
|
|
13
|
-
return login(this.registry)
|
|
14
|
-
}
|
|
15
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { GeneratorCommand } from 'clipanion-generator-command'
|
|
2
|
-
import { MustacheGenerator } from '../lib/MustacheGenerator.js'
|
|
3
|
-
|
|
4
|
-
export abstract class MustacheGeneratorCommand extends GeneratorCommand {
|
|
5
|
-
override get generator() {
|
|
6
|
-
return new MustacheGenerator(this.templateDir, this.destinationDir, [
|
|
7
|
-
'.mustache',
|
|
8
|
-
])
|
|
9
|
-
}
|
|
10
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { ESMDevCommand } from './ESMDevCommand.js'
|
|
2
|
-
|
|
3
|
-
export class RepublishCommand extends ESMDevCommand {
|
|
4
|
-
static override paths = [['republish']]
|
|
5
|
-
|
|
6
|
-
static override usage = ESMDevCommand.Usage({
|
|
7
|
-
description:
|
|
8
|
-
'Removes the references, of given packages, from the ESM server and registry and republishes.',
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
override async execute() {
|
|
12
|
-
const { republish } = await import('../lib/republish.js')
|
|
13
|
-
await this.eachPackagePath((packagePath) => republish(packagePath, this))
|
|
14
|
-
}
|
|
15
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { ESMDevCommand } from './ESMDevCommand.js'
|
|
2
|
-
import { Servable } from './mixins/Servable.js'
|
|
3
|
-
import { Watchable } from './mixins/Watchable.js'
|
|
4
|
-
import { ESMServed } from './mixins/ESMServed.js'
|
|
5
|
-
|
|
6
|
-
export class StartCommand extends Servable(
|
|
7
|
-
Watchable(ESMServed(ESMDevCommand)),
|
|
8
|
-
) {
|
|
9
|
-
static override paths = [['start']]
|
|
10
|
-
|
|
11
|
-
static override usage = ESMDevCommand.Usage({
|
|
12
|
-
description: 'Runs a server and concurrently watches a file system',
|
|
13
|
-
})
|
|
14
|
-
|
|
15
|
-
override async execute() {
|
|
16
|
-
const { watch } = await import('../lib/watch.js')
|
|
17
|
-
const { serve } = await import('../lib/server.js')
|
|
18
|
-
serve(this.port, this.esmOrigin)
|
|
19
|
-
await this.eachPackagePath((packagePath) => watch(packagePath, this))
|
|
20
|
-
}
|
|
21
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { Command } from 'clipanion'
|
|
2
|
-
import { RegistrySpecific } from './mixins/RegistrySpecific.js'
|
|
3
|
-
|
|
4
|
-
export class TokenCommand extends RegistrySpecific(Command) {
|
|
5
|
-
static override paths = [['token']]
|
|
6
|
-
|
|
7
|
-
static override usage = Command.Usage({
|
|
8
|
-
description: 'Get the NPM token',
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
override async execute() {
|
|
12
|
-
const { default: escapeStringRegexp } = await import('escape-string-regexp')
|
|
13
|
-
const npmrc = await Bun.file(`~/.npmrc`).text()
|
|
14
|
-
const url = new URL(this.registry)
|
|
15
|
-
const regex = new RegExp(
|
|
16
|
-
`^//${escapeStringRegexp(url.hostname)}/:_authToken=(.*)$`,
|
|
17
|
-
'm',
|
|
18
|
-
)
|
|
19
|
-
const match = regex.exec(npmrc)
|
|
20
|
-
if (!match) {
|
|
21
|
-
this.context.stderr.write(`Token has not been created yet\n`)
|
|
22
|
-
return 1
|
|
23
|
-
}
|
|
24
|
-
this.context.stdout.write(match[1] + '\n')
|
|
25
|
-
}
|
|
26
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { Command } from 'clipanion'
|
|
2
|
-
import { readFile } from 'node:fs/promises'
|
|
3
|
-
|
|
4
|
-
export class VersionCommand extends Command {
|
|
5
|
-
static override paths = [['version']]
|
|
6
|
-
|
|
7
|
-
static override usage = Command.Usage({
|
|
8
|
-
description: 'Display the version of esm.dev',
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
override async execute(): Promise<number | void> {
|
|
12
|
-
const path = await import('node:path')
|
|
13
|
-
const pckg = JSON.parse(
|
|
14
|
-
await readFile(
|
|
15
|
-
path.resolve(import.meta.dirname, '..', '..', 'package.json'),
|
|
16
|
-
'utf-8',
|
|
17
|
-
),
|
|
18
|
-
)
|
|
19
|
-
this.context.stdout.write(`${pckg.version}\n`)
|
|
20
|
-
}
|
|
21
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { Command } from 'clipanion'
|
|
2
|
-
import { RegistrySpecific } from './mixins/RegistrySpecific.js'
|
|
3
|
-
import { Retryable } from './mixins/Retryable.js'
|
|
4
|
-
|
|
5
|
-
export class WaitForRegistryCommand extends Retryable(
|
|
6
|
-
RegistrySpecific(Command),
|
|
7
|
-
) {
|
|
8
|
-
static override paths = [['wait-for-registry'], ['wfr']]
|
|
9
|
-
|
|
10
|
-
static override usage = Command.Usage({
|
|
11
|
-
description: 'wait for the registry to be online',
|
|
12
|
-
})
|
|
13
|
-
|
|
14
|
-
override async execute() {
|
|
15
|
-
return this.retry(this.registry)
|
|
16
|
-
}
|
|
17
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { ESMDevCommand } from './ESMDevCommand.js'
|
|
2
|
-
import { Watchable } from './mixins/Watchable.js'
|
|
3
|
-
|
|
4
|
-
export class WatchCommand extends Watchable(ESMDevCommand) {
|
|
5
|
-
static override paths = [['watch']]
|
|
6
|
-
|
|
7
|
-
static override usage = ESMDevCommand.Usage({
|
|
8
|
-
description: 'Watches directories and republishes on changes',
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
override async execute() {
|
|
12
|
-
const { watch } = await import('../lib/watch.js')
|
|
13
|
-
await this.eachPackagePath((packagePath) => watch(packagePath, this))
|
|
14
|
-
}
|
|
15
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { EnvOption } from '../../options/EnvOption.js'
|
|
2
|
-
import type { CommandClass } from './CommandClass.js'
|
|
3
|
-
|
|
4
|
-
export function ESMServed<T extends CommandClass>(Base: T) {
|
|
5
|
-
abstract class ESMServed extends Base {
|
|
6
|
-
readonly esmOrigin = EnvOption('ESM_ORIGIN', '-e,--esm-origin', {
|
|
7
|
-
description: 'The base URL of the ESM Server',
|
|
8
|
-
})
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
return ESMServed
|
|
12
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import type { CommandClass } from './CommandClass.js'
|
|
2
|
-
import { EnvOption } from '../../options/EnvOption.js'
|
|
3
|
-
|
|
4
|
-
export function ESMStorageSpecific<T extends CommandClass>(Base: T) {
|
|
5
|
-
abstract class ESMStorageSpecific extends Base {
|
|
6
|
-
readonly esmStoragePath = EnvOption(
|
|
7
|
-
'ESM_STORAGE_PATH',
|
|
8
|
-
'-s,--esm-storage-path',
|
|
9
|
-
{
|
|
10
|
-
description: "Path to ESM.sh's storage",
|
|
11
|
-
},
|
|
12
|
-
)
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
return ESMStorageSpecific
|
|
16
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { Option } from 'clipanion'
|
|
2
|
-
import type { CommandClass } from './CommandClass.js'
|
|
3
|
-
|
|
4
|
-
export function PackagePathSpecific<T extends CommandClass>(Base: T) {
|
|
5
|
-
abstract class PackagePathSpecific extends Base {
|
|
6
|
-
readonly packagePaths = Option.Rest({
|
|
7
|
-
name: 'packages',
|
|
8
|
-
})
|
|
9
|
-
|
|
10
|
-
protected async eachPackagePath(cb: (packagePath: string) => Promise<any>) {
|
|
11
|
-
const { glob } = await import('glob')
|
|
12
|
-
const packagePaths = (
|
|
13
|
-
await Promise.all(
|
|
14
|
-
this.packagePaths.map((packagePath) => glob(packagePath)),
|
|
15
|
-
)
|
|
16
|
-
).flat()
|
|
17
|
-
await Promise.all(packagePaths.map(cb))
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return PackagePathSpecific
|
|
22
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { CommandClass } from './CommandClass.js'
|
|
2
|
-
import { EnvOption } from '../../options/EnvOption.js'
|
|
3
|
-
|
|
4
|
-
export function RegistrySpecific<TBase extends CommandClass>(Base: TBase) {
|
|
5
|
-
abstract class RegistrySpecific extends Base {
|
|
6
|
-
readonly registry = EnvOption('NPM_REGISTRY', '-r,--registry', {
|
|
7
|
-
description: 'The URL of your local registry',
|
|
8
|
-
})
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
return RegistrySpecific
|
|
12
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { Option } from 'clipanion'
|
|
2
|
-
import { isNumber } from 'typanion'
|
|
3
|
-
import type { CommandClass } from './CommandClass.js'
|
|
4
|
-
|
|
5
|
-
export function Retryable<T extends CommandClass>(Base: T) {
|
|
6
|
-
abstract class Retryable extends Base {
|
|
7
|
-
readonly timeout = Option.String('-t,--timeout', '10000', {
|
|
8
|
-
description: 'The amount of total ms to keep trying for',
|
|
9
|
-
validator: isNumber(),
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
readonly interval = Option.String('-i,--interval', '300', {
|
|
13
|
-
description: 'The amount of ms inbetween each attempt',
|
|
14
|
-
validator: isNumber(),
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
protected async retry(endpoint: string) {
|
|
18
|
-
const { until } = await import('../../lib/until.js')
|
|
19
|
-
if (
|
|
20
|
-
!(await until({
|
|
21
|
-
...this,
|
|
22
|
-
try: async (signal) => {
|
|
23
|
-
const response = await fetch(endpoint, { signal })
|
|
24
|
-
return response.ok
|
|
25
|
-
},
|
|
26
|
-
}))
|
|
27
|
-
) {
|
|
28
|
-
this.context.stderr.write(`${endpoint} is not available\n`)
|
|
29
|
-
return 1
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return Retryable
|
|
35
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { isNumber } from 'typanion'
|
|
2
|
-
import { EnvOption } from '../../options/EnvOption.js'
|
|
3
|
-
import type { CommandClass } from './CommandClass.js'
|
|
4
|
-
|
|
5
|
-
export function Servable<T extends CommandClass>(Base: T) {
|
|
6
|
-
abstract class Servable extends Base {
|
|
7
|
-
readonly port = EnvOption('PORT', '-p,--port', {
|
|
8
|
-
description: 'The port to run the server on',
|
|
9
|
-
validator: isNumber(),
|
|
10
|
-
})
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
return Servable
|
|
14
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { Option } from 'clipanion'
|
|
2
|
-
import type { CommandClass } from './CommandClass.js'
|
|
3
|
-
|
|
4
|
-
export function Watchable<T extends CommandClass>(Base: T) {
|
|
5
|
-
abstract class Watchable extends Base {
|
|
6
|
-
readonly legacyMethod = Option.Boolean('-l,--legacy-method', false, {
|
|
7
|
-
description:
|
|
8
|
-
'Use a less performant, legacy, watch method. Sometimes needed in docker or vagrant environments.',
|
|
9
|
-
})
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
return Watchable
|
|
13
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import Mustache from 'mustache'
|
|
2
|
-
import { Generator } from 'clipanion-generator-command/Generator'
|
|
3
|
-
|
|
4
|
-
export class MustacheGenerator extends Generator {
|
|
5
|
-
override renderTemplate(templateContext: any, template: string) {
|
|
6
|
-
return Mustache.render(template, templateContext, undefined, {
|
|
7
|
-
escape: (x) => x,
|
|
8
|
-
})
|
|
9
|
-
}
|
|
10
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { readFile, stat as getStat } from 'node:fs/promises'
|
|
2
|
-
import * as path from 'node:path'
|
|
3
|
-
|
|
4
|
-
export async function getPackageMeta(
|
|
5
|
-
packagePath: string,
|
|
6
|
-
): Promise<{ name: string; packageRoot: string; private?: boolean }> {
|
|
7
|
-
const stat = await getStat(packagePath)
|
|
8
|
-
const packageRoot = stat.isDirectory()
|
|
9
|
-
? packagePath
|
|
10
|
-
: path.dirname(packagePath)
|
|
11
|
-
const pckg = JSON.parse(
|
|
12
|
-
await readFile(path.join(packageRoot, 'package.json'), 'utf-8'),
|
|
13
|
-
)
|
|
14
|
-
return { name: pckg.name, packageRoot, private: pckg.private }
|
|
15
|
-
}
|
package/src/lib/login.ts
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'node:child_process'
|
|
2
|
-
|
|
3
|
-
export function login(registry: string) {
|
|
4
|
-
return new Promise<number>((resolve) => {
|
|
5
|
-
const child = spawn('bunx', [
|
|
6
|
-
'npm',
|
|
7
|
-
'login',
|
|
8
|
-
'--registry',
|
|
9
|
-
registry,
|
|
10
|
-
'--quiet',
|
|
11
|
-
])
|
|
12
|
-
|
|
13
|
-
child.stderr.on('data', (d) => console.error(d.toString()))
|
|
14
|
-
|
|
15
|
-
child.stdout.on('data', (d) => {
|
|
16
|
-
const data = d.toString()
|
|
17
|
-
switch (true) {
|
|
18
|
-
case /username/i.test(data):
|
|
19
|
-
child.stdin.write('esm.dev\n')
|
|
20
|
-
break
|
|
21
|
-
case /password/i.test(data):
|
|
22
|
-
child.stdin.write('esm.dev\n')
|
|
23
|
-
break
|
|
24
|
-
case /email/i.test(data):
|
|
25
|
-
child.stdin.write('esm.dev@esm.dev.com')
|
|
26
|
-
break
|
|
27
|
-
case /logged in as/i.test(data):
|
|
28
|
-
child.stdin.end()
|
|
29
|
-
break
|
|
30
|
-
}
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
child.on('exit', resolve)
|
|
34
|
-
})
|
|
35
|
-
}
|
package/src/lib/publish.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { $ } from 'zx'
|
|
2
|
-
import * as path from 'node:path'
|
|
3
|
-
|
|
4
|
-
export async function publish({
|
|
5
|
-
packageRoot,
|
|
6
|
-
registry,
|
|
7
|
-
}: {
|
|
8
|
-
packageRoot: string
|
|
9
|
-
registry: string
|
|
10
|
-
}) {
|
|
11
|
-
await $({
|
|
12
|
-
cwd: path.resolve(packageRoot),
|
|
13
|
-
})`bunx npm publish --registry ${registry}`
|
|
14
|
-
}
|
package/src/lib/queue.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import debounce from 'lodash.debounce'
|
|
2
|
-
import throat from 'throat'
|
|
3
|
-
|
|
4
|
-
export async function queue<TResult, TArgs extends any[] = []>(
|
|
5
|
-
signal: AbortSignal,
|
|
6
|
-
fn: (...args: TArgs) => Promise<TResult>,
|
|
7
|
-
): Promise<TResult> {
|
|
8
|
-
return _queue((...args) => {
|
|
9
|
-
signal.throwIfAborted()
|
|
10
|
-
return fn(...(args as any))
|
|
11
|
-
})
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
const _queue = throat.default(1)
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Just like debounce, but the first call will add a promise to the queue
|
|
18
|
-
* and that promise won't resolve until the debounced function is finally called.
|
|
19
|
-
*/
|
|
20
|
-
export function queuedDebounce<Args extends unknown[], R>(
|
|
21
|
-
fn: (...args: Args) => R,
|
|
22
|
-
delay: number,
|
|
23
|
-
signal: AbortSignal,
|
|
24
|
-
): (...args: Args) => Promise<R> {
|
|
25
|
-
let promiseWithResolvers: PromiseWithResolvers<R> | undefined
|
|
26
|
-
|
|
27
|
-
signal.addEventListener('abort', (reason) =>
|
|
28
|
-
promiseWithResolvers?.reject(reason),
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
const debounced = debounce(async (...args: Args) => {
|
|
32
|
-
try {
|
|
33
|
-
const result = await fn(...args)
|
|
34
|
-
promiseWithResolvers?.resolve(result)
|
|
35
|
-
} catch (error: any) {
|
|
36
|
-
promiseWithResolvers?.reject(error)
|
|
37
|
-
} finally {
|
|
38
|
-
promiseWithResolvers = undefined
|
|
39
|
-
}
|
|
40
|
-
}, delay)
|
|
41
|
-
|
|
42
|
-
return (...args: Args) => {
|
|
43
|
-
const { promise } = start()
|
|
44
|
-
debounced(...args)
|
|
45
|
-
return promise
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function start() {
|
|
49
|
-
if (!promiseWithResolvers) {
|
|
50
|
-
promiseWithResolvers = Promise.withResolvers<R>()
|
|
51
|
-
queue(signal, async () => promiseWithResolvers?.promise)
|
|
52
|
-
}
|
|
53
|
-
return promiseWithResolvers
|
|
54
|
-
}
|
|
55
|
-
}
|
package/src/lib/republish.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { getPackageMeta } from './getPackageMeta.js'
|
|
2
|
-
import { publish } from './publish.js'
|
|
3
|
-
import { unpublish } from './unpublish.js'
|
|
4
|
-
|
|
5
|
-
export async function republish(
|
|
6
|
-
packagePath: string,
|
|
7
|
-
opts: {
|
|
8
|
-
registry: string
|
|
9
|
-
esmStoragePath: string
|
|
10
|
-
},
|
|
11
|
-
) {
|
|
12
|
-
console.info(`Republishing ${packagePath}`)
|
|
13
|
-
const { name, packageRoot } = await getPackageMeta(packagePath)
|
|
14
|
-
await unpublish({ ...opts, name })
|
|
15
|
-
await publish({ ...opts, packageRoot })
|
|
16
|
-
}
|
package/src/lib/server.ts
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import { queue } from './queue.js'
|
|
2
|
-
import { createServer } from 'node:http'
|
|
3
|
-
import httpProxy from 'http-proxy'
|
|
4
|
-
import { addAbortSignal } from 'node:stream'
|
|
5
|
-
|
|
6
|
-
export function serve(port: number, esmOrigin: string) {
|
|
7
|
-
const proxy = httpProxy.createProxyServer()
|
|
8
|
-
const abortController = new AbortController()
|
|
9
|
-
const signal = abortController.signal
|
|
10
|
-
|
|
11
|
-
const server = createServer((req, res) => {
|
|
12
|
-
queue(
|
|
13
|
-
signal,
|
|
14
|
-
() =>
|
|
15
|
-
new Promise<void>((resolve, reject) => {
|
|
16
|
-
console.info('Proxying', req.url)
|
|
17
|
-
addAbortSignal(signal, req)
|
|
18
|
-
addAbortSignal(signal, res)
|
|
19
|
-
req.on('error', reject)
|
|
20
|
-
res.on('error', reject)
|
|
21
|
-
res.on('close', resolve)
|
|
22
|
-
proxy.web(req, res, { target: esmOrigin })
|
|
23
|
-
}),
|
|
24
|
-
).catch((error) => {
|
|
25
|
-
res.statusCode = 500
|
|
26
|
-
res.setHeader('Content-Type', 'application/json; charset=utf-8')
|
|
27
|
-
res.write(JSON.stringify(error))
|
|
28
|
-
res.end()
|
|
29
|
-
})
|
|
30
|
-
}).listen(port, () => {
|
|
31
|
-
console.info('ESM proxy server listining on', server.address())
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
return () => {
|
|
35
|
-
console.info('closing the server')
|
|
36
|
-
abortController.abort()
|
|
37
|
-
server.closeAllConnections()
|
|
38
|
-
server.close()
|
|
39
|
-
}
|
|
40
|
-
}
|
package/src/lib/unpublish.ts
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { $, ProcessOutput } from 'zx'
|
|
2
|
-
import { glob } from 'glob'
|
|
3
|
-
import { rm } from 'node:fs/promises'
|
|
4
|
-
|
|
5
|
-
export async function unpublish({
|
|
6
|
-
registry,
|
|
7
|
-
esmStoragePath,
|
|
8
|
-
name,
|
|
9
|
-
}: {
|
|
10
|
-
registry: string
|
|
11
|
-
esmStoragePath: string
|
|
12
|
-
name: string
|
|
13
|
-
}) {
|
|
14
|
-
await Promise.all([
|
|
15
|
-
unpublishPackage(registry, name),
|
|
16
|
-
deleteESMCache(esmStoragePath, name),
|
|
17
|
-
])
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
async function unpublishPackage(registry: string, name: string) {
|
|
21
|
-
try {
|
|
22
|
-
await $`bunx npm unpublish --registry ${registry} --force ${name}`
|
|
23
|
-
} catch (error) {
|
|
24
|
-
if (
|
|
25
|
-
error instanceof ProcessOutput &&
|
|
26
|
-
!error.stderr.includes('npm error code E404')
|
|
27
|
-
)
|
|
28
|
-
throw error
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async function deleteESMCache(esmStoragePath: string, name: string) {
|
|
33
|
-
await glob(`${esmStoragePath}/**/${name}@*`).then((paths) =>
|
|
34
|
-
Promise.all(
|
|
35
|
-
paths.map((path) => {
|
|
36
|
-
console.info('Deleting', path)
|
|
37
|
-
return rm(path, { recursive: true })
|
|
38
|
-
}),
|
|
39
|
-
),
|
|
40
|
-
)
|
|
41
|
-
}
|
package/src/lib/until.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { setTimeout } from 'node:timers/promises'
|
|
2
|
-
|
|
3
|
-
export async function until({
|
|
4
|
-
interval,
|
|
5
|
-
timeout,
|
|
6
|
-
try: Try,
|
|
7
|
-
}: {
|
|
8
|
-
interval: number
|
|
9
|
-
timeout: number
|
|
10
|
-
try(signal: AbortSignal): Promise<boolean>
|
|
11
|
-
}) {
|
|
12
|
-
const signal = AbortSignal.timeout(timeout)
|
|
13
|
-
while (!signal.aborted) {
|
|
14
|
-
try {
|
|
15
|
-
if (await Try(signal)) return true
|
|
16
|
-
} catch (error) {}
|
|
17
|
-
try {
|
|
18
|
-
await setTimeout(interval, { signal })
|
|
19
|
-
} catch (error) {}
|
|
20
|
-
}
|
|
21
|
-
return false
|
|
22
|
-
}
|
package/src/lib/watch.ts
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { watch as fsWatch, watchFile, type StatsListener } from 'node:fs'
|
|
2
|
-
import { republish } from './republish.js'
|
|
3
|
-
import { getPackageMeta } from './getPackageMeta.js'
|
|
4
|
-
import { readFile, writeFile } from 'node:fs/promises'
|
|
5
|
-
import { createHash } from 'node:crypto'
|
|
6
|
-
import { glob } from 'glob'
|
|
7
|
-
import { queue, queuedDebounce } from './queue.js'
|
|
8
|
-
|
|
9
|
-
export async function watch(
|
|
10
|
-
packagePath: string,
|
|
11
|
-
opts: { registry: string; esmStoragePath: string; legacyMethod?: boolean },
|
|
12
|
-
): Promise<() => void> {
|
|
13
|
-
const {
|
|
14
|
-
name,
|
|
15
|
-
packageRoot,
|
|
16
|
-
private: prvte,
|
|
17
|
-
} = await getPackageMeta(packagePath)
|
|
18
|
-
|
|
19
|
-
if (prvte) {
|
|
20
|
-
console.info(`${name} is a private package... ignoring`)
|
|
21
|
-
return () => {}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
console.info('Watching', packagePath)
|
|
25
|
-
|
|
26
|
-
const abortController = new AbortController()
|
|
27
|
-
const signal = abortController.signal
|
|
28
|
-
|
|
29
|
-
const republishPackage = () =>
|
|
30
|
-
republish(packagePath, opts).catch(console.error)
|
|
31
|
-
|
|
32
|
-
const debounceRepublish = queuedDebounce(
|
|
33
|
-
(filename: string = '') => {
|
|
34
|
-
console.info(`Change detected at ${packagePath}/${filename}`)
|
|
35
|
-
return republishPackage()
|
|
36
|
-
},
|
|
37
|
-
1_000,
|
|
38
|
-
signal,
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
await queue(signal, republishPackage)
|
|
42
|
-
|
|
43
|
-
if (opts.legacyMethod) {
|
|
44
|
-
const close = await legacyWatch(packagePath, () => debounceRepublish())
|
|
45
|
-
return () => {
|
|
46
|
-
console.info('stop watching')
|
|
47
|
-
close()
|
|
48
|
-
abortController.abort()
|
|
49
|
-
}
|
|
50
|
-
} else {
|
|
51
|
-
const watcher = fsWatch(packageRoot, { recursive: true }, (_, filename) =>
|
|
52
|
-
debounceRepublish(filename ?? ''),
|
|
53
|
-
)
|
|
54
|
-
return () => {
|
|
55
|
-
console.info('stop watching')
|
|
56
|
-
watcher.close()
|
|
57
|
-
abortController.abort()
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async function legacyWatch(dirname: string, cb: StatsListener) {
|
|
63
|
-
const filename = await hashDirectory(dirname)
|
|
64
|
-
const watcher = watchFile(filename, cb)
|
|
65
|
-
let timer: NodeJS.Timeout | undefined
|
|
66
|
-
beginTimer()
|
|
67
|
-
return () => {
|
|
68
|
-
watcher.unref()
|
|
69
|
-
clearTimeout(timer)
|
|
70
|
-
}
|
|
71
|
-
function beginTimer() {
|
|
72
|
-
timer = setTimeout(
|
|
73
|
-
() => hashDirectory(dirname).catch(console.error).finally(beginTimer),
|
|
74
|
-
1_000,
|
|
75
|
-
)
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async function hashDirectory(dirname: string) {
|
|
80
|
-
const filename = `/tmp/${dirname.replace(/\//g, '-')}.hash.txt`
|
|
81
|
-
const files = await glob(`${dirname}/**/*`, { nodir: true })
|
|
82
|
-
const hashes = await Promise.all(
|
|
83
|
-
files.map(async (file) => {
|
|
84
|
-
const contents = await readFile(file, 'utf-8')
|
|
85
|
-
return createHash('sha256').update(contents).digest('hex')
|
|
86
|
-
}),
|
|
87
|
-
)
|
|
88
|
-
const newHash = hashes.join('')
|
|
89
|
-
|
|
90
|
-
let previousHash = ''
|
|
91
|
-
|
|
92
|
-
try {
|
|
93
|
-
previousHash = await readFile(filename, 'utf-8')
|
|
94
|
-
} catch (error: any) {
|
|
95
|
-
if (error.code !== 'ENOENT') throw error
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (newHash !== previousHash) await writeFile(filename, newHash)
|
|
99
|
-
return filename
|
|
100
|
-
}
|
package/src/options/EnvOption.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { Option } from 'clipanion'
|
|
2
|
-
import type { StrictValidator } from 'typanion'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* @see https://github.com/arcanis/clipanion/issues/174
|
|
6
|
-
*/
|
|
7
|
-
export function EnvOption<T extends {}>(
|
|
8
|
-
envName: string,
|
|
9
|
-
descriptor: string,
|
|
10
|
-
opts: { description?: string; validator: StrictValidator<unknown, T> },
|
|
11
|
-
): T
|
|
12
|
-
|
|
13
|
-
export function EnvOption(
|
|
14
|
-
envName: string,
|
|
15
|
-
descriptor: string,
|
|
16
|
-
opts?: { description?: string },
|
|
17
|
-
): string
|
|
18
|
-
|
|
19
|
-
export function EnvOption<T extends {}>(
|
|
20
|
-
envName: string,
|
|
21
|
-
descriptor: string,
|
|
22
|
-
opts: { description?: string; validator?: StrictValidator<unknown, T> } = {},
|
|
23
|
-
) {
|
|
24
|
-
return process.env[envName]
|
|
25
|
-
? Option.String(descriptor, process.env[envName], opts)
|
|
26
|
-
: Option.String(descriptor, { ...opts, required: true })
|
|
27
|
-
}
|