Commit 61c0ed1

Mikhail Avdeev <39246971+easymikey@users.noreply.github.com>
2024-12-15 19:38:09
refactor: prepare jsr publish (#989)
1 parent 9f15fd4
scripts/build-jsr.mjs
@@ -0,0 +1,41 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import fs from 'node:fs'
+import path from 'node:path'
+const __dirname = path.dirname(new URL(import.meta.url).pathname)
+const root = path.resolve(__dirname, '..')
+const pkgJson = JSON.parse(
+  fs.readFileSync(path.resolve(root, 'package.json'), 'utf-8')
+)
+
+fs.writeFileSync(
+  path.resolve(cwd, 'jsr.json'),
+  JSON.stringify(
+    {
+      name: '@zx/zx',
+      version: pkgJson.version,
+      exports: {
+        '.': './src/index.ts',
+        './core': './src/core.ts',
+        './cli': './src/cli.ts',
+      },
+      publish: {
+        include: ['src', 'README.md'],
+      },
+    },
+    null,
+    2
+  )
+)
src/cli.ts
@@ -57,19 +57,19 @@ export function printUsage() {
    --prefix=<command>   prefix all commands
    --postfix=<command>  postfix all commands
    --cwd=<path>         set current directory
-   --eval=<js>, -e      evaluate script 
+   --eval=<js>, -e      evaluate script
    --ext=<.mjs>         default extension
    --install, -i        install dependencies
    --version, -v        print current zx version
    --help, -h           print help
    --repl               start repl
    --experimental       enables experimental features (deprecated)
- 
+
  ${chalk.italic('Full documentation:')} ${chalk.underline('https://google.github.io/zx/')}
 `)
 }
 
-export const argv = minimist(process.argv.slice(2), {
+export const argv: minimist.ParsedArgs = minimist(process.argv.slice(2), {
   string: ['shell', 'prefix', 'postfix', 'eval', 'cwd', 'ext'],
   boolean: [
     'version',
@@ -134,7 +134,7 @@ export async function runScript(script: string, ext = EXT) {
   await writeAndImport(script, filepath)
 }
 
-export async function scriptFromStdin(ext?: string) {
+export async function scriptFromStdin(ext?: string): Promise<boolean> {
   let script = ''
   if (!process.stdin.isTTY) {
     process.stdin.setEncoding('utf8')
@@ -178,7 +178,10 @@ export async function writeAndImport(
   }
 }
 
-export async function importPath(filepath: string, origin = filepath) {
+export async function importPath(
+  filepath: string,
+  origin = filepath
+): Promise<void> {
   const ext = path.extname(filepath)
   const base = path.basename(filepath)
   const dir = path.dirname(filepath)
@@ -206,6 +209,7 @@ export async function importPath(filepath: string, origin = filepath) {
     await installDeps(deps, dir)
   }
   injectGlobalRequire(origin)
+  // TODO: fix unanalyzable-dynamic-import to work correctly with jsr.io
   await import(url.pathToFileURL(filepath).toString())
 }
 
@@ -216,7 +220,7 @@ export function injectGlobalRequire(origin: string) {
   Object.assign(globalThis, { __filename, __dirname, require })
 }
 
-export function transformMarkdown(buf: Buffer) {
+export function transformMarkdown(buf: Buffer): string {
   const source = buf.toString()
   const output = []
   let state = 'root'
@@ -292,9 +296,9 @@ export function getVersion(): string {
 }
 
 export function isMain(
-  metaurl = import.meta.url,
-  scriptpath = process.argv[1]
-) {
+  metaurl: string = import.meta.url,
+  scriptpath: string = process.argv[1]
+): boolean {
   if (metaurl.startsWith('file:')) {
     const modulePath = url.fileURLToPath(metaurl).replace(/\.\w+$/, '')
     const mainPath = fs.realpathSync(scriptpath).replace(/\.\w+$/, '')
@@ -304,6 +308,6 @@ export function isMain(
   return false
 }
 
-export function normalizeExt(ext?: string) {
+export function normalizeExt(ext?: string): string | undefined {
   return ext ? path.parse(`foo.${ext}`).ext : ext
 }
src/core.ts
@@ -17,6 +17,7 @@ import {
   type IOType,
   spawn,
   spawnSync,
+  type ChildProcess,
 } from 'node:child_process'
 import { type Encoding } from 'node:crypto'
 import { type AsyncHook, AsyncLocalStorage, createHook } from 'node:async_hooks'
@@ -398,20 +399,20 @@ export class ProcessPromise extends Promise<ProcessOutput> {
   /**
    *  @deprecated Use $({halt: true})`cmd` instead.
    */
-  halt() {
+  halt(): this {
     return this
   }
 
   // Getters
-  get pid() {
+  get pid(): number | undefined {
     return this.child?.pid
   }
 
-  get cmd() {
+  get cmd(): string {
     return this._command
   }
 
-  get child() {
+  get child(): ChildProcess | undefined {
     return this._zurk?.child
   }
 
@@ -434,11 +435,11 @@ export class ProcessPromise extends Promise<ProcessOutput> {
     )
   }
 
-  get signal() {
+  get signal(): AbortSignal | undefined {
     return this._snapshot.signal || this._snapshot.ac?.signal
   }
 
-  get output() {
+  get output(): ProcessOutput | null {
     return this._output
   }
 
@@ -630,7 +631,7 @@ export class ProcessOutput extends Error {
     }
   }
 
-  toString() {
+  toString(): string {
     return this._combined
   }
 
@@ -638,11 +639,11 @@ export class ProcessOutput extends Error {
     return JSON.parse(this._combined)
   }
 
-  buffer() {
+  buffer(): Buffer {
     return Buffer.from(this._combined)
   }
 
-  blob(type = 'text/plain') {
+  blob(type = 'text/plain'): Blob {
     if (!globalThis.Blob)
       throw new Error(
         'Blob is not supported in this environment. Provide a polyfill'
@@ -650,37 +651,37 @@ export class ProcessOutput extends Error {
     return new Blob([this.buffer()], { type })
   }
 
-  text(encoding: Encoding = 'utf8') {
+  text(encoding: Encoding = 'utf8'): string {
     return encoding === 'utf8'
       ? this.toString()
       : this.buffer().toString(encoding)
   }
 
-  lines() {
+  lines(): string[] {
     return this.valueOf().split(/\r?\n/)
   }
 
-  valueOf() {
+  valueOf(): string {
     return this._combined.trim()
   }
 
-  get stdout() {
+  get stdout(): string {
     return this._stdout
   }
 
-  get stderr() {
+  get stderr(): string {
     return this._stderr
   }
 
-  get exitCode() {
+  get exitCode(): number | null {
     return this._code
   }
 
-  get signal() {
+  get signal(): NodeJS.Signals | null {
     return this._signal
   }
 
-  get duration() {
+  get duration(): number {
     return this._duration
   }
 
@@ -689,7 +690,7 @@ export class ProcessOutput extends Error {
     signal: NodeJS.Signals | null,
     stderr: string,
     from: string
-  ) {
+  ): string {
     let message = `exit code: ${code}`
     if (code != 0 || signal != null) {
       message = `${stderr || '\n'}    at ${from}`
@@ -704,7 +705,7 @@ export class ProcessOutput extends Error {
     return message
   }
 
-  static getErrorMessage(err: NodeJS.ErrnoException, from: string) {
+  static getErrorMessage(err: NodeJS.ErrnoException, from: string): string {
     return (
       `${err.message}\n` +
       `    errno: ${err.errno} (${errnoMessage(err.errno)})\n` +
@@ -713,7 +714,7 @@ export class ProcessOutput extends Error {
     )
   }
 
-  [inspect.custom]() {
+  [inspect.custom](): string {
     let stringify = (s: string, c: ChalkInstance) =>
       s.length === 0 ? "''" : c(inspect(s))
     return `ProcessOutput {
src/globals.ts
@@ -15,7 +15,7 @@
 import * as _ from './index.js'
 
 Object.assign(globalThis, _)
-
+// TODO: global types not working with jsr.io
 declare global {
   type ProcessPromise = _.ProcessPromise
   type ProcessOutput = _.ProcessOutput
src/goods.ts
@@ -27,19 +27,22 @@ import {
 export { default as path } from 'node:path'
 export * as os from 'node:os'
 
-export const argv = minimist(process.argv.slice(2))
+export const argv: minimist.ParsedArgs = minimist(process.argv.slice(2))
 export function updateArgv(args: string[]) {
   for (const k in argv) delete argv[k]
   Object.assign(argv, minimist(args))
 }
 
-export function sleep(duration: Duration) {
+export function sleep(duration: Duration): Promise<unknown> {
   return new Promise((resolve) => {
     setTimeout(resolve, parseDuration(duration))
   })
 }
 
-export async function fetch(url: RequestInfo, init?: RequestInit) {
+export async function fetch(
+  url: RequestInfo,
+  init?: RequestInit
+): Promise<Response> {
   $.log({ kind: 'fetch', url, init })
   return nodeFetch(url, init)
 }
@@ -89,7 +92,7 @@ export async function question(
   )
 }
 
-export async function stdin() {
+export async function stdin(): Promise<string> {
   let buf = ''
   process.stdin.setEncoding('utf8')
   for await (const chunk of process.stdin) {
@@ -149,7 +152,10 @@ export async function retry<T>(
   throw lastErr
 }
 
-export function* expBackoff(max: Duration = '60s', rand: Duration = '100ms') {
+export function* expBackoff(
+  max: Duration = '60s',
+  rand: Duration = '100ms'
+): Generator<number, void, unknown> {
   const maxMs = parseDuration(max)
   const randMs = parseDuration(rand)
   let n = 1
src/index.ts
@@ -40,13 +40,13 @@ export {
 /**
  *  @deprecated Use $`cmd`.nothrow() instead.
  */
-export function nothrow(promise: ProcessPromise) {
+export function nothrow(promise: ProcessPromise): ProcessPromise {
   return promise.nothrow()
 }
 
 /**
  * @deprecated Use $`cmd`.quiet() instead.
  */
-export function quiet(promise: ProcessPromise) {
+export function quiet(promise: ProcessPromise): ProcessPromise {
   return promise.quiet()
 }
src/util.ts
@@ -19,14 +19,14 @@ import { chalk } from './vendor-core.js'
 
 export { isStringLiteral } from './vendor-core.js'
 
-export function tempdir(prefix = `zx-${randomId()}`) {
+export function tempdir(prefix: string = `zx-${randomId()}`): string {
   const dirpath = path.join(os.tmpdir(), prefix)
   fs.mkdirSync(dirpath, { recursive: true })
 
   return dirpath
 }
 
-export function tempfile(name?: string, data?: string | Buffer) {
+export function tempfile(name?: string, data?: string | Buffer): string {
   const filepath = name
     ? path.join(tempdir(), name)
     : path.join(os.tmpdir(), `zx-${randomId()}`)
@@ -95,7 +95,7 @@ export function preferLocalBin(
 //   )
 // }
 
-export function quote(arg: string) {
+export function quote(arg: string): string {
   if (/^[a-z0-9/_.\-@:=]+$/i.test(arg) || arg === '') {
     return arg
   }
@@ -114,7 +114,7 @@ export function quote(arg: string) {
   )
 }
 
-export function quotePowerShell(arg: string) {
+export function quotePowerShell(arg: string): string {
   if (/^[a-z0-9/_.\-]+$/i.test(arg) || arg === '') {
     return arg
   }
test/it/build-jsr.test.js
@@ -0,0 +1,55 @@
+// Copyright 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { tempdir, $, path, fs } from '../../build/index.js'
+import { describe, before, after, it } from 'node:test'
+
+const __dirname = path.dirname(new URL(import.meta.url).pathname)
+const root = path.resolve(__dirname, '../../')
+const pkgJson = JSON.parse(
+  fs.readFileSync(path.resolve(root, 'package.json'), 'utf-8')
+)
+
+describe('jsr artefact', () => {
+  let tmp
+  let t$
+
+  before(async () => {
+    tmp = tempdir()
+    t$ = $({ cwd: tmp, quiet: true })
+    path.resolve(tmp, 'node_modules/zx')
+    await fs.outputJSON(path.resolve(tmp, 'package.json'), pkgJson)
+
+    await t$`npm i`
+
+    // copy all for jsr publish
+    await Promise.all(
+      ['src/', 'tsconfig.json', 'LICENSE', 'scripts/build-jsr.mjs'].map(
+        async (filepath) => {
+          return await fs.copy(
+            path.resolve(path.join(root, filepath)),
+            path.resolve(path.join(tmp, filepath))
+          )
+        }
+      )
+    )
+  })
+
+  after(() => fs.remove(tmp))
+
+  it('publish --dry-run --allow-dirty`', async () => {
+    await t$`node scripts/build-jsr.mjs`
+    await t$`npx jsr publish --dry-run --allow-dirty`
+  })
+})
package.json
@@ -70,6 +70,7 @@
     "pretest": "npm run build",
     "test": "npm run test:size && npm run fmt:check && npm run test:unit && npm run test:types && npm run test:license",
     "test:it": "node ./test/it/build.test.js",
+    "test:jsr": "node ./test/it/build-jsr.test.js",
     "test:unit": "node ./test/all.test.js",
     "test:coverage": "c8 -x build/deno.js -x build/vendor-extra.cjs -x build/vendor-core.cjs -x build/esblib.cjs -x 'test/**' -x scripts --check-coverage npm run test:unit",
     "test:circular": "madge --circular src/*",