esm.dev 1.5.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.
Files changed (65) hide show
  1. package/.prettierignore +4 -0
  2. package/LICENSE +21 -0
  3. package/README.md +68 -0
  4. package/commitlint.config.cjs +8 -0
  5. package/dist/cli.js +17 -0
  6. package/dist/commands/ESMDevCommand.js +6 -0
  7. package/dist/commands/InitCommand.js +40 -0
  8. package/dist/commands/LoginCommand.js +12 -0
  9. package/dist/commands/MustacheGeneratorCommand.js +9 -0
  10. package/dist/commands/RepublishCommand.js +11 -0
  11. package/dist/commands/StartCommand.js +16 -0
  12. package/dist/commands/TokenCommand.js +20 -0
  13. package/dist/commands/WaitForRegistryCommand.js +12 -0
  14. package/dist/commands/WatchCommand.js +12 -0
  15. package/dist/commands/mixins/CommandClass.js +1 -0
  16. package/dist/commands/mixins/ESMServed.js +9 -0
  17. package/dist/commands/mixins/ESMStorageSpecific.js +9 -0
  18. package/dist/commands/mixins/PackagePathSpecific.js +14 -0
  19. package/dist/commands/mixins/RegistrySpecific.js +9 -0
  20. package/dist/commands/mixins/Retryable.js +28 -0
  21. package/dist/commands/mixins/Servable.js +11 -0
  22. package/dist/commands/mixins/Watchable.js +9 -0
  23. package/dist/lib/MustacheGenerator.js +9 -0
  24. package/dist/lib/getPackageMeta.js +10 -0
  25. package/dist/lib/login.js +31 -0
  26. package/dist/lib/publish.js +5 -0
  27. package/dist/lib/queue.js +33 -0
  28. package/dist/lib/republish.js +9 -0
  29. package/dist/lib/server.js +23 -0
  30. package/dist/lib/unpublish.js +25 -0
  31. package/dist/lib/until.js +16 -0
  32. package/dist/lib/watch.js +61 -0
  33. package/dist/options/EnvOption.js +6 -0
  34. package/package.json +66 -0
  35. package/release.config.cjs +20 -0
  36. package/src/cli.ts +18 -0
  37. package/src/commands/ESMDevCommand.ts +8 -0
  38. package/src/commands/InitCommand.ts +57 -0
  39. package/src/commands/LoginCommand.ts +15 -0
  40. package/src/commands/MustacheGeneratorCommand.ts +10 -0
  41. package/src/commands/RepublishCommand.ts +15 -0
  42. package/src/commands/StartCommand.ts +21 -0
  43. package/src/commands/TokenCommand.ts +26 -0
  44. package/src/commands/WaitForRegistryCommand.ts +17 -0
  45. package/src/commands/WatchCommand.ts +15 -0
  46. package/src/commands/mixins/CommandClass.ts +3 -0
  47. package/src/commands/mixins/ESMServed.ts +12 -0
  48. package/src/commands/mixins/ESMStorageSpecific.ts +16 -0
  49. package/src/commands/mixins/PackagePathSpecific.ts +22 -0
  50. package/src/commands/mixins/RegistrySpecific.ts +12 -0
  51. package/src/commands/mixins/Retryable.ts +35 -0
  52. package/src/commands/mixins/Servable.ts +14 -0
  53. package/src/commands/mixins/Watchable.ts +13 -0
  54. package/src/commands/templates/init/docker-compose.yml.mustache +38 -0
  55. package/src/lib/MustacheGenerator.ts +10 -0
  56. package/src/lib/getPackageMeta.ts +15 -0
  57. package/src/lib/login.ts +35 -0
  58. package/src/lib/publish.ts +14 -0
  59. package/src/lib/queue.ts +39 -0
  60. package/src/lib/republish.ts +16 -0
  61. package/src/lib/server.ts +29 -0
  62. package/src/lib/unpublish.ts +43 -0
  63. package/src/lib/until.ts +22 -0
  64. package/src/lib/watch.ts +84 -0
  65. package/src/options/EnvOption.ts +27 -0
@@ -0,0 +1,43 @@
1
+ import { $ } from 'bun'
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
+ !(
26
+ error instanceof $.ShellError &&
27
+ error.stderr.includes('npm error code E404')
28
+ )
29
+ )
30
+ throw error
31
+ }
32
+ }
33
+
34
+ async function deleteESMCache(esmStoragePath: string, name: string) {
35
+ await glob(`${esmStoragePath}/**/${name}@*`).then((paths) =>
36
+ Promise.all(
37
+ paths.map((path) => {
38
+ console.info('Deleting', path)
39
+ return rm(path, { recursive: true })
40
+ }),
41
+ ),
42
+ )
43
+ }
@@ -0,0 +1,22 @@
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
+ }
@@ -0,0 +1,84 @@
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 republishPackage = () =>
27
+ republish(packagePath, opts).catch(console.error)
28
+
29
+ const debounceRepublish = queuedDebounce((filename: string = '') => {
30
+ console.info(`Change detected at ${packagePath}/${filename}`)
31
+ return republishPackage()
32
+ }, 1_000)
33
+
34
+ await queue(republishPackage)
35
+
36
+ if (opts.legacyMethod) {
37
+ return await legacyWatch(packagePath, () => debounceRepublish())
38
+ } else {
39
+ const watcher = fsWatch(packageRoot, { recursive: true }, (_, filename) =>
40
+ debounceRepublish(filename ?? ''),
41
+ )
42
+ return () => watcher.close()
43
+ }
44
+ }
45
+
46
+ async function legacyWatch(dirname: string, cb: StatsListener) {
47
+ const filename = await hashDirectory(dirname)
48
+ const watcher = watchFile(filename, cb)
49
+ let timer: NodeJS.Timeout | undefined
50
+ beginTimer()
51
+ return () => {
52
+ watcher.unref()
53
+ clearTimeout(timer)
54
+ }
55
+ function beginTimer() {
56
+ timer = setTimeout(
57
+ () => hashDirectory(dirname).catch(console.error).finally(beginTimer),
58
+ 1_000,
59
+ )
60
+ }
61
+ }
62
+
63
+ async function hashDirectory(dirname: string) {
64
+ const filename = `/tmp/${dirname.replace(/\//g, '-')}.hash.txt`
65
+ const files = await glob(`${dirname}/**/*`, { nodir: true })
66
+ const hashes = await Promise.all(
67
+ files.map(async (file) => {
68
+ const contents = await readFile(file, 'utf-8')
69
+ return createHash('sha256').update(contents).digest('hex')
70
+ }),
71
+ )
72
+ const newHash = hashes.join('')
73
+
74
+ let previousHash = ''
75
+
76
+ try {
77
+ previousHash = await readFile(filename, 'utf-8')
78
+ } catch (error: any) {
79
+ if (error.code !== 'ENOENT') throw error
80
+ }
81
+
82
+ if (newHash !== previousHash) await writeFile(filename, newHash)
83
+ return filename
84
+ }
@@ -0,0 +1,27 @@
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
+ }