Commit 61c0ed1
Changed files (9)
scripts
test
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/*",