WIP: bootstrap and partial real Solana watcher implementation

This commit is contained in:
2026-08-16 09:17:45 +00:00
commit dc23412c3f
7232 changed files with 1687637 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
'use strict';
const BINARY_TYPES = ['nodebuffer', 'arraybuffer', 'fragments'];
const hasBlob = typeof Blob !== 'undefined';
if (hasBlob) BINARY_TYPES.push('blob');
module.exports = {
BINARY_TYPES,
CLOSE_TIMEOUT: 30000,
EMPTY_BUFFER: Buffer.alloc(0),
GUID: '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',
hasBlob,
kForOnEventAttribute: Symbol('kIsForOnEventAttribute'),
kListener: Symbol('kListener'),
kStatusCode: Symbol('status-code'),
kWebSocket: Symbol('websocket'),
NOOP: () => {}
};

View File

@@ -0,0 +1,633 @@
import { describe, expect, expectTypeOf, test } from "vitest";
import * as z from "zod/v4";
describe("basic refinement functionality", () => {
test("should create a new schema instance when refining", () => {
const obj1 = z.object({
first: z.string(),
second: z.string(),
});
const obj2 = obj1.partial().strict();
const obj3 = obj2.refine((data) => data.first || data.second, "Either first or second should be filled in.");
expect(obj1 === (obj2 as any)).toEqual(false);
expect(obj2 === (obj3 as any)).toEqual(false);
});
test("should validate according to refinement logic", () => {
const schema = z
.object({
first: z.string(),
second: z.string(),
})
.partial()
.strict()
.refine((data) => data.first || data.second, "Either first or second should be filled in.");
// Should fail on empty object
expect(() => schema.parse({})).toThrow();
// Should pass with first property
expect(schema.parse({ first: "a" })).toEqual({ first: "a" });
// Should pass with second property
expect(schema.parse({ second: "a" })).toEqual({ second: "a" });
// Should pass with both properties
expect(schema.parse({ first: "a", second: "a" })).toEqual({ first: "a", second: "a" });
});
test("should validate strict mode correctly", () => {
const schema = z
.object({
first: z.string(),
second: z.string(),
})
.partial()
.strict();
// Should throw on extra properties
expect(() => schema.parse({ third: "adsf" })).toThrow();
});
});
describe("refinement with custom error messages", () => {
test("should use custom error message when validation fails", () => {
const validationSchema = z
.object({
email: z.string().email(),
password: z.string(),
confirmPassword: z.string(),
})
.refine((data) => data.password === data.confirmPassword, "Both password and confirmation must match");
const result = validationSchema.safeParse({
email: "aaaa@gmail.com",
password: "aaaaaaaa",
confirmPassword: "bbbbbbbb",
});
expect(result.success).toEqual(false);
if (!result.success) {
expect(result.error.issues[0].message).toEqual("Both password and confirmation must match");
}
});
});
describe("async refinements", () => {
test("should support async refinement functions", async () => {
const validationSchema = z
.object({
email: z.string().email(),
password: z.string(),
confirmPassword: z.string(),
})
.refine(
(data) => Promise.resolve().then(() => data.password === data.confirmPassword),
"Both password and confirmation must match"
);
// Should pass with matching passwords
const validData = {
email: "aaaa@gmail.com",
password: "password",
confirmPassword: "password",
};
await expect(validationSchema.parseAsync(validData)).resolves.toEqual(validData);
// Should fail with non-matching passwords
await expect(
validationSchema.parseAsync({
email: "aaaa@gmail.com",
password: "password",
confirmPassword: "different",
})
).rejects.toThrow();
});
});
describe("early termination options", () => {
test("should abort early with continue: false", () => {
const schema = z
.string()
.superRefine((val, ctx) => {
if (val.length < 2) {
ctx.addIssue({
code: "custom",
message: "BAD",
continue: false,
});
}
})
.refine((_) => false);
const result = schema.safeParse("");
expect(result.success).toEqual(false);
if (!result.success) {
expect(result.error.issues.length).toEqual(1);
expect(result.error.issues[0].message).toEqual("BAD");
}
});
test("should abort early with fatal: true", () => {
const schema = z
.string()
.superRefine((val, ctx) => {
if (val.length < 2) {
ctx.addIssue({
code: "custom",
fatal: true,
message: "BAD",
});
}
})
.refine((_) => false);
const result = schema.safeParse("");
expect(result.success).toEqual(false);
if (!result.success) {
expect(result.error.issues.length).toEqual(1);
expect(result.error.issues[0].message).toEqual("BAD");
}
});
test("should abort early with abort flag", () => {
const schema = z
.string()
.refine((_) => false, { abort: true })
.refine((_) => false);
const result = schema.safeParse("");
expect(result.success).toEqual(false);
if (!result.success) {
expect(result.error.issues.length).toEqual(1);
}
});
});
describe("custom error paths", () => {
test("should use custom path in error message", () => {
const result = z
.object({ password: z.string(), confirm: z.string() })
.refine((data) => data.confirm === data.password, { path: ["confirm"] })
.safeParse({ password: "asdf", confirm: "qewr" });
expect(result.success).toEqual(false);
if (!result.success) {
expect(result.error.issues[0].path).toEqual(["confirm"]);
}
});
});
describe("superRefine functionality", () => {
test("should support multiple validation rules", () => {
const Strings = z.array(z.string()).superRefine((val, ctx) => {
if (val.length > 3) {
ctx.addIssue({
input: val,
code: "too_big",
origin: "array",
maximum: 3,
inclusive: true,
exact: true,
message: "Too many items 😡",
});
}
if (val.length !== new Set(val).size) {
ctx.addIssue({
input: val,
code: "custom",
message: `No duplicates allowed.`,
});
}
});
// Should fail with too many items and duplicates
const result = Strings.safeParse(["asfd", "asfd", "asfd", "asfd"]);
expect(result.success).toEqual(false);
if (!result.success) {
expect(result.error.issues.length).toEqual(2);
expect(result.error.issues[0].message).toEqual("Too many items 😡");
expect(result.error.issues[1].message).toEqual("No duplicates allowed.");
}
// Should pass with valid input
const validArray = ["asfd", "qwer"];
expect(Strings.parse(validArray)).toEqual(validArray);
});
test("should support async superRefine", async () => {
const Strings = z.array(z.string()).superRefine(async (val, ctx) => {
if (val.length > 3) {
ctx.addIssue({
input: val,
code: "too_big",
origin: "array",
maximum: 3,
inclusive: true,
message: "Too many items 😡",
});
}
if (val.length !== new Set(val).size) {
ctx.addIssue({
input: val,
code: "custom",
message: `No duplicates allowed.`,
});
}
});
// Should fail with too many items and duplicates
const result = await Strings.safeParseAsync(["asfd", "asfd", "asfd", "asfd"]);
expect(result.success).toEqual(false);
if (!result.success) {
expect(result.error.issues.length).toEqual(2);
}
// Should pass with valid input
const validArray = ["asfd", "qwer"];
await expect(Strings.parseAsync(validArray)).resolves.toEqual(validArray);
});
test("should test continuability of custom issues", () => {
// Default continue behavior - allows subsequent refinements
const defaultContinue = z
.string()
.superRefine((_, ctx) => {
ctx.addIssue({ code: "custom", message: "First issue" });
})
.refine(() => false, "Second issue");
expect(defaultContinue.safeParse("test")).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "custom",
"message": "First issue",
"path": []
},
{
"code": "custom",
"path": [],
"message": "Second issue"
}
]],
"success": false,
}
`);
// Explicit continue: false - prevents subsequent refinements
const explicitContinueFalse = z
.string()
.superRefine((_, ctx) => {
ctx.addIssue({ code: "custom", message: "First issue", continue: false });
})
.refine(() => false, "Second issue");
expect(explicitContinueFalse.safeParse("test")).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "custom",
"message": "First issue",
"path": []
}
]],
"success": false,
}
`);
// Multiple issues in same refinement - both always added regardless of continue
const multipleInSame = z.string().superRefine((_, ctx) => {
ctx.addIssue({ code: "custom", message: "First", continue: false });
ctx.addIssue({ code: "custom", message: "Second" });
});
expect(multipleInSame.safeParse("test")).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"code": "custom",
"message": "First",
"path": []
},
{
"code": "custom",
"message": "Second",
"path": []
}
]],
"success": false,
}
`);
});
test("should accept string as shorthand for custom error message", () => {
const schema = z.string().superRefine((_, ctx) => {
ctx.addIssue("bad stuff");
});
const result = schema.safeParse("asdf");
expect(result.success).toEqual(false);
if (!result.success) {
expect(result.error.issues).toHaveLength(1);
expect(result.error.issues[0].message).toEqual("bad stuff");
}
});
test("should respect fatal flag in superRefine", () => {
const schema = z
.string()
.superRefine((val, ctx) => {
if (val === "") {
ctx.addIssue({
input: val,
code: "custom",
message: "foo",
fatal: true,
});
}
})
.superRefine((val, ctx) => {
if (val !== " ") {
ctx.addIssue({
input: val,
code: "custom",
message: "bar",
});
}
});
const result = schema.safeParse("");
expect(result.success).toEqual(false);
if (!result.success) {
expect(result.error.issues.length).toEqual(1);
expect(result.error.issues[0].message).toEqual("foo");
}
});
});
describe("chained refinements", () => {
test("should collect all validation errors when appropriate", () => {
const objectSchema = z
.object({
length: z.number(),
size: z.number(),
})
.refine(({ length }) => length > 5, {
path: ["length"],
message: "length greater than 5",
})
.refine(({ size }) => size > 7, {
path: ["size"],
message: "size greater than 7",
});
// Should fail with one error
const r1 = objectSchema.safeParse({
length: 4,
size: 9,
});
expect(r1.success).toEqual(false);
if (!r1.success) {
expect(r1.error.issues.length).toEqual(1);
expect(r1.error.issues[0].path).toEqual(["length"]);
}
// Should fail with two errors
const r2 = objectSchema.safeParse({
length: 4,
size: 3,
});
expect(r2.success).toEqual(false);
if (!r2.success) {
expect(r2.error.issues.length).toEqual(2);
}
// Should pass with valid input
const validData = {
length: 6,
size: 8,
};
expect(objectSchema.parse(validData)).toEqual(validData);
});
test("should run superRefine validation even when base schema validation fails when 'when' is defined and returns true", () => {
const baseSchema = z.object({
foo: z.number(),
bar: z.number(),
});
const schema = baseSchema.superRefine(
(data, ctx) => {
if (data.foo > 10) {
ctx.addIssue({
code: "custom",
message: "foo must be less than 10",
});
}
},
{
when: ({ value }) => baseSchema.pick({ foo: true }).safeParse(value).success,
}
);
const result = schema.safeParse({
foo: 11,
});
expect(result.success).toEqual(false);
if (!result.success) {
expect(result.error.issues.length).toEqual(2);
expect(result.error.issues[0].message).toEqual("Invalid input: expected number, received undefined");
expect(result.error.issues[1].message).toEqual("foo must be less than 10");
}
});
test("should not run superRefine validation when 'when' is defined and returns false", () => {
const baseSchema = z.object({
foo: z.number(),
bar: z.number(),
});
const schema = baseSchema.superRefine(
(data, ctx) => {
if (data.foo > 10) {
ctx.addIssue({
code: "custom",
message: "foo must be less than 10",
});
}
},
{
when: ({ value }) => baseSchema.safeParse(value).success,
}
);
const result = schema.safeParse({
foo: 11,
});
expect(result.success).toEqual(false);
if (!result.success) {
expect(result.error.issues.length).toEqual(1);
expect(result.error.issues[0].message).toEqual("Invalid input: expected number, received undefined");
}
});
});
describe("type refinement with type guards", () => {
test("type guard narrows output type", () => {
const schema = z.string().refine((s): s is "a" => s === "a");
expectTypeOf<z.input<typeof schema>>().toEqualTypeOf<string>();
expectTypeOf<z.output<typeof schema>>().toEqualTypeOf<"a">();
});
test("non-type-guard refine does not narrow", () => {
const schema = z.string().refine((s) => s.length > 0);
expectTypeOf<z.input<typeof schema>>().toEqualTypeOf<string>();
expectTypeOf<z.output<typeof schema>>().toEqualTypeOf<string>();
});
// TODO: Implement type narrowing for superRefine
// test("superRefine - type narrowing", () => {
// type NarrowType = { type: string; age: number };
// const schema = z
// .object({
// type: z.string(),
// age: z.number(),
// })
// .nullable()
// .superRefine((arg, ctx): arg is NarrowType => {
// if (!arg) {
// ctx.addIssue({
// input: arg,
// code: "custom",
// message: "cannot be null",
// fatal: true,
// });
// return false;
// }
// return true;
// });
//
// expectTypeOf<z.infer<typeof schema>>().toEqualTypeOf<NarrowType>();
//
// expect(schema.safeParse({ type: "test", age: 0 }).success).toEqual(true);
// expect(schema.safeParse(null).success).toEqual(false);
// });
});
test("when", () => {
const schema = z
.strictObject({
password: z.string().min(8),
confirmPassword: z.string(),
other: z.string(),
})
.refine(
(data) => {
// console.log("running check...");
// console.log(data);
// console.log(data.password);
return data.password === data.confirmPassword;
},
{
message: "Passwords do not match",
path: ["confirmPassword"],
when(payload) {
if (payload.value === undefined) return false;
if (payload.value === null) return false;
// no issues with confirmPassword or password
return payload.issues.every((iss) => iss.path?.[0] !== "confirmPassword" && iss.path?.[0] !== "password");
},
}
);
expect(schema.safeParse(undefined)).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "object",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected object, received undefined"
}
]],
"success": false,
}
`);
expect(schema.safeParse(null)).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"expected": "object",
"code": "invalid_type",
"path": [],
"message": "Invalid input: expected object, received null"
}
]],
"success": false,
}
`);
expect(
schema.safeParse({
password: "asdf",
confirmPassword: "asdfg",
other: "qwer",
})
).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"origin": "string",
"code": "too_small",
"minimum": 8,
"inclusive": true,
"path": [
"password"
],
"message": "Too small: expected string to have >=8 characters"
}
]],
"success": false,
}
`);
expect(
schema.safeParse({
password: "asdf",
confirmPassword: "asdfg",
other: 1234,
})
).toMatchInlineSnapshot(`
{
"error": [ZodError: [
{
"origin": "string",
"code": "too_small",
"minimum": 8,
"inclusive": true,
"path": [
"password"
],
"message": "Too small: expected string to have >=8 characters"
},
{
"expected": "string",
"code": "invalid_type",
"path": [
"other"
],
"message": "Invalid input: expected string, received number"
}
]],
"success": false,
}
`);
});

View File

@@ -0,0 +1,22 @@
import { expectType, expectAssignable } from "tsd";
import slowRedact from ".";
import type { redactFn, redactFnNoSerialize } from ".";
// should return redactFn
expectType<redactFn>(slowRedact());
expectType<redactFn>(slowRedact({ paths: [] }));
expectType<redactFn>(slowRedact({ paths: ["some.path"] }));
expectType<redactFn>(slowRedact({ paths: [], censor: "[REDACTED]" }));
expectType<redactFn>(slowRedact({ paths: [], strict: true }));
expectType<redactFn>(slowRedact({ paths: [], serialize: JSON.stringify }));
expectType<redactFn>(slowRedact({ paths: [], serialize: true }));
expectType<redactFnNoSerialize>(slowRedact({ paths: [], serialize: false }));
expectType<redactFn>(slowRedact({ paths: [], remove: true }));
// should return string
expectType<string>(slowRedact()(""));
// should return string or T
expectAssignable<string | { someField: string }>(
slowRedact()({ someField: "someValue" })
);

View File

@@ -0,0 +1,4 @@
import * as z from "./external.cjs";
export { z };
export * from "./external.cjs";
export default z;

View File

@@ -0,0 +1,5 @@
import assertClassBrand from "./assertClassBrand.js";
function _classStaticPrivateMethodGet(s, a, t) {
return assertClassBrand(a, s), t;
}
export { _classStaticPrivateMethodGet as default };

View File

@@ -0,0 +1,7 @@
/* eslint-disable ts/ban-ts-comment */
// @ts-ignore optional peer dep
export type * as jsdomTypes from 'jsdom'
// @ts-ignore optional peer dep
export type * as happyDomTypes from 'happy-dom'

View File

@@ -0,0 +1,37 @@
/**
* @fileoverview Types for the config-array package.
* @author Nicholas C. Zakas
*/
/**
* A file matcher used in `files` and `ignores`.
*/
export type FileMatcher = string | ((filePath: string) => boolean);
/**
* An entry in a config's `files` array.
*
* A subarray means all matchers must match.
*/
export type FilesMatcher = FileMatcher | FileMatcher[];
/**
* The config types allowed in the `extraConfigTypes` option.
*/
export type ExtraConfigType = "array" | "function";
export interface ConfigObject {
/**
* The base path for files and ignores.
*/
basePath?: string;
/**
* The files to include.
*/
files?: FilesMatcher[];
/**
* The files to exclude.
*/
ignores?: FileMatcher[];
/**
* The name of the config object.
*/
name?: string;
[key: string]: unknown;
}

View File

@@ -0,0 +1,60 @@
/**
* @fileoverview Rule to check for ambiguous div operator in regexes
* @author Matt DuVall <http://www.mattduvall.com>
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../types').Rule.RuleModule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description:
"Disallow equal signs explicitly at the beginning of regular expressions",
recommended: false,
frozen: true,
url: "https://eslint.org/docs/latest/rules/no-div-regex",
},
fixable: "code",
schema: [],
messages: {
unexpected:
"A regular expression literal can be confused with '/='.",
},
},
create(context) {
const sourceCode = context.sourceCode;
return {
Literal(node) {
const token = sourceCode.getFirstToken(node);
if (
token.type === "RegularExpression" &&
token.value[1] === "="
) {
context.report({
node,
messageId: "unexpected",
fix(fixer) {
return fixer.replaceTextRange(
[token.range[0] + 1, token.range[0] + 2],
"[=]",
);
},
});
}
},
};
},
};

View File

@@ -0,0 +1,18 @@
"use strict";
// THIS CODE WAS AUTOMATICALLY GENERATED
// DO NOT EDIT THIS CODE BY HAND
// RUN THE FOLLOWING COMMAND FROM THE WORKSPACE ROOT TO REGENERATE:
// npx nx generate-lib repo
Object.defineProperty(exports, "__esModule", { value: true });
exports.esnext_weakref = void 0;
const base_config_1 = require("./base-config");
const es2015_symbol_wellknown_1 = require("./es2015.symbol.wellknown");
exports.esnext_weakref = {
libs: [es2015_symbol_wellknown_1.es2015_symbol_wellknown],
variables: [
['WeakRef', base_config_1.TYPE_VALUE],
['WeakRefConstructor', base_config_1.TYPE],
['FinalizationRegistry', base_config_1.TYPE_VALUE],
['FinalizationRegistryConstructor', base_config_1.TYPE],
],
};

View File

@@ -0,0 +1,9 @@
import type { TSESLint } from '@typescript-eslint/utils';
export declare const phrases: {
readonly TSInterfaceDeclaration: 'Interface';
readonly TSTypeLiteral: 'Type literal';
};
declare const _default: TSESLint.RuleModule<"functionTypeOverCallableType" | "unexpectedThisOnFunctionOnlyInterface", [], import("../../rules").ESLintPluginDocs, TSESLint.RuleListener> & {
name: string;
};
export default _default;

View File

@@ -0,0 +1 @@
export { _ as default } from "../esm/_object_without_properties_loose.js";

View File

@@ -0,0 +1,10 @@
'use strict';
module.exports = {
BINARY_TYPES: ['nodebuffer', 'arraybuffer', 'fragments'],
GUID: '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',
kStatusCode: Symbol('status-code'),
kWebSocket: Symbol('websocket'),
EMPTY_BUFFER: Buffer.alloc(0),
NOOP: () => {}
};

View File

@@ -0,0 +1,12 @@
{{# def.definitions }}
{{# def.errors }}
{{# def.setupKeyword }}
{{# def.$data }}
{{# def.numberKeyword }}
{{ var $op = $keyword == 'maxLength' ? '>' : '<'; }}
if ({{# def.$dataNotType:'number' }} {{# def.strLength }} {{=$op}} {{=$schemaValue}}) {
{{ var $errorKeyword = $keyword; }}
{{# def.error:'_limitLength' }}
} {{? $breakOnError }} else { {{?}}

View File

@@ -0,0 +1,323 @@
# tinyexec 📟
> A minimal package for executing commands
This package was created to provide a minimal way of interacting with child
processes without having to manually deal with streams, piping, etc.
## Installing
```sh
$ npm i -S tinyexec
```
## Usage
A process can be spawned and awaited like so:
```ts
import {x} from 'tinyexec';
const result = await x('ls', ['-l']);
// result.stdout - the stdout as a string
// result.stderr - the stderr as a string
// result.exitCode - the process exit code as a number
```
By default, tinyexec does not throw on nonzero exit codes. Check `result.exitCode` or pass `{throwOnError: true}`.
Output is returned exactly as produced; trailing newlines are not trimmed. If you need trimming, do it explicitly:
```ts
const clean = result.stdout.replace(/\r?\n$/, '');
```
You may also iterate over the lines of output via an async loop:
```ts
import {x} from 'tinyexec';
const proc = x('ls', ['-l']);
for await (const line of proc) {
// line will be from stderr/stdout in the order you'd see it in a term
}
```
### Options
Options can be passed to have finer control over spawning of the process:
```ts
await x('ls', [], {
timeout: 1000
});
```
The options object can have the following properties:
- `signal` - an `AbortSignal` to allow aborting of the execution
- `timeout` - time in milliseconds at which the process will be forcibly killed
- `persist` - if `true`, the process will continue after the host exits
- `stdin` - `string` or another `Result` that will be used as the input to the process
- `nodeOptions` - any valid options to node's underlying `spawn` function
- `throwOnError` - if true, non-zero exit codes will throw an error
- `nodePath` - if `false`, `node_modules/.bin` directories and the current node executable's directory will not be prepended to `PATH` (defaults to `true`)
### Passing a string to stdin
You can pass a string to `stdin`, which is useful for whitespace-sensitive values and for secrets that shouldnt be exposed in shell history:
```ts
const result = await x('gh', ['auth', 'login', '--with-token'], {
stdin: process.env.GITHUB_TOKEN
});
console.log(result.exitCode);
```
### Piping to another process
You can pipe a process to another via the `pipe` method:
```ts
const proc1 = x('ls', ['-l']);
const proc2 = proc1.pipe('grep', ['.js']);
const result = await proc2;
console.log(result.stdout);
```
`pipe` takes the same options as a regular execution. For example, you can
pass a timeout to the pipe call:
```ts
proc1.pipe('grep', ['.js'], {
timeout: 2000
});
```
### Killing a process
You can kill the process via the `kill` method:
```ts
const proc = x('ls');
proc.kill();
// or with a signal
proc.kill('SIGHUP');
```
### Node modules/binaries
By default, node's available binaries from `node_modules` will be accessible
in your command.
For example, in a repo which has `eslint` installed:
```ts
await x('eslint', ['.']);
```
In this example, `eslint` will come from the locally installed `node_modules`.
If you'd rather not have `node_modules/.bin` (or the directory of the current
`node` executable) prepended to `PATH`, pass `nodePath: false`:
```ts
await x('eslint', ['.'], {nodePath: false});
```
### Using an abort signal
An abort signal can be passed to a process in order to abort it at a later
time. This will result in the process being killed and `aborted` being set
to `true`.
```ts
const aborter = new AbortController();
const proc = x('node', ['./foo.mjs'], {
signal: aborter.signal
});
// elsewhere...
aborter.abort();
await proc;
proc.aborted; // true
proc.killed; // true
```
### Using with command strings
If you need to continue supporting commands as strings (e.g. "command arg0 arg1"),
you can use [args-tokenizer](https://github.com/TrySound/args-tokenizer),
a lightweight library for parsing shell command strings into an array.
```ts
import {x} from 'tinyexec';
import {tokenizeArgs} from 'args-tokenizer';
const commandString = 'echo "Hello, World!"';
const [command, ...args] = tokenizeArgs(commandString);
const result = await x(command, args);
result.stdout; // Hello, World!
```
### Synchronous
You can use `xSync` for synchronous (blocking) execution:
```ts
import {xSync} from 'tinyexec';
const result = xSync('ls', ['-l']);
// result.stdout - the stdout as a string
// result.stderr - the stderr as a string
// result.exitCode - the process exit code as a number
```
Like the async API, you can iterate over lines:
```ts
const result = xSync('ls', ['-l']);
for (const line of result) {
// line will be from stdout then stderr
}
```
Since the synchronous API blocks the event loop, there are some features that are supported in the async API that the sync API does not support:
- `signal`
- `persist`
- `kill()` method
- `stdin` piping
- `pipe()` method
Other options like `timeout`, `throwOnError`, and `nodeOptions` work the same way.
## API
Calling `x(command[, args])` returns an awaitable `Result` which has the
following API methods and properties available:
### `pipe(command[, args[, options]])`
Pipes the current command to another. For example:
```ts
x('ls', ['-l'])
.pipe('grep', ['js']);
```
The parameters are as follows:
- `command` - the command to execute (_without any arguments_)
- `args` - an array of arguments
- `options` - options object
### `process`
The underlying Node.js `ChildProcess`. tinyexec keeps the surface minimal and does not reexpose every child_process method/event. Use `proc.process` for advanced access (streams, events, etc.).
```ts
const proc = x('node', ['./foo.mjs']);
proc.process?.stdout?.on('data', (chunk) => {
// ...
});
proc.process?.once('close', (code) => {
// ...
});
```
### `kill([signal])`
Kills the current process with the specified signal. By default, this will
use the `SIGTERM` signal.
For example:
```ts
const proc = x('ls');
proc.kill();
```
### `pid`
The current process ID. For example:
```ts
const proc = x('ls');
proc.pid; // number
```
### `aborted`
Whether the process has been aborted or not (via the `signal` originally
passed in the options object).
For example:
```ts
const proc = x('ls');
proc.aborted; // bool
```
### `killed`
Whether the process has been killed or not (e.g. via `kill()` or an abort
signal).
For example:
```ts
const proc = x('ls');
proc.killed; // bool
```
### `exitCode`
The exit code received when the process completed execution.
For example:
```ts
const proc = x('ls');
proc.exitCode; // number (e.g. 1)
```
## Comparison with other libraries
`tinyexec` aims to provide a lightweight layer on top of Node's own
`child_process` API.
Some clear benefits compared to other libraries are that `tinyexec` will be much lighter, have a much
smaller footprint and will have a less abstract interface (less "magic"). It
will also have equal security and cross-platform support to popular
alternatives.
There are various features other libraries include which we are unlikely
to ever implement, as they would prevent us from providing a lightweight layer.
For example, if you'd like write scripts rather than individual commands, and
prefer to use templating, we'd definitely recommend
[zx](https://github.com/google/zx). zx is a much higher level library which
does some of the same work `tinyexec` does but behind a template string
interface.
Similarly, libraries like `execa` will provide helpers for various things
like passing files as input to processes. We opt not to support features like
this since many of them are easy to do yourself (using Node's own APIs).

View File

@@ -0,0 +1,29 @@
'use strict'
module.exports = joinLinesWithIndentation
/**
* @typedef {object} JoinLinesWithIndentationParams
* @property {string} input The string to split and reformat.
* @property {string} [ident] The indentation string. Default: ` ` (4 spaces).
* @property {string} [eol] The end of line sequence to use when rejoining
* the lines. Default: `'\n'`.
*/
/**
* Given a string with line separators, either `\r\n` or `\n`, add indentation
* to all lines subsequent to the first line and rejoin the lines using an
* end of line sequence.
*
* @param {JoinLinesWithIndentationParams} input
*
* @returns {string} A string with lines subsequent to the first indented
* with the given indentation sequence.
*/
function joinLinesWithIndentation ({ input, ident = ' ', eol = '\n' }) {
const lines = input.split(/\r?\n/)
for (let i = 1; i < lines.length; i += 1) {
lines[i] = ident + lines[i]
}
return lines.join(eol)
}

View File

@@ -0,0 +1,322 @@
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
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 http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
/// <reference no-default-lib="true"/>
/////////////////////////////
/// Windows Script Host APIS
/////////////////////////////
interface ActiveXObject {
new (s: string): any;
}
declare var ActiveXObject: ActiveXObject;
interface ITextWriter {
Write(s: string): void;
WriteLine(s: string): void;
Close(): void;
}
interface TextStreamBase {
/**
* The column number of the current character position in an input stream.
*/
Column: number;
/**
* The current line number in an input stream.
*/
Line: number;
/**
* Closes a text stream.
* It is not necessary to close standard streams; they close automatically when the process ends. If
* you close a standard stream, be aware that any other pointers to that standard stream become invalid.
*/
Close(): void;
}
interface TextStreamWriter extends TextStreamBase {
/**
* Sends a string to an output stream.
*/
Write(s: string): void;
/**
* Sends a specified number of blank lines (newline characters) to an output stream.
*/
WriteBlankLines(intLines: number): void;
/**
* Sends a string followed by a newline character to an output stream.
*/
WriteLine(s: string): void;
}
interface TextStreamReader extends TextStreamBase {
/**
* Returns a specified number of characters from an input stream, starting at the current pointer position.
* Does not return until the ENTER key is pressed.
* Can only be used on a stream in reading mode; causes an error in writing or appending mode.
*/
Read(characters: number): string;
/**
* Returns all characters from an input stream.
* Can only be used on a stream in reading mode; causes an error in writing or appending mode.
*/
ReadAll(): string;
/**
* Returns an entire line from an input stream.
* Although this method extracts the newline character, it does not add it to the returned string.
* Can only be used on a stream in reading mode; causes an error in writing or appending mode.
*/
ReadLine(): string;
/**
* Skips a specified number of characters when reading from an input text stream.
* Can only be used on a stream in reading mode; causes an error in writing or appending mode.
* @param characters Positive number of characters to skip forward. (Backward skipping is not supported.)
*/
Skip(characters: number): void;
/**
* Skips the next line when reading from an input text stream.
* Can only be used on a stream in reading mode, not writing or appending mode.
*/
SkipLine(): void;
/**
* Indicates whether the stream pointer position is at the end of a line.
*/
AtEndOfLine: boolean;
/**
* Indicates whether the stream pointer position is at the end of a stream.
*/
AtEndOfStream: boolean;
}
declare var WScript: {
/**
* Outputs text to either a message box (under WScript.exe) or the command console window followed by
* a newline (under CScript.exe).
*/
Echo(s: any): void;
/**
* Exposes the write-only error output stream for the current script.
* Can be accessed only while using CScript.exe.
*/
StdErr: TextStreamWriter;
/**
* Exposes the write-only output stream for the current script.
* Can be accessed only while using CScript.exe.
*/
StdOut: TextStreamWriter;
Arguments: { length: number; Item(n: number): string; };
/**
* The full path of the currently running script.
*/
ScriptFullName: string;
/**
* Forces the script to stop immediately, with an optional exit code.
*/
Quit(exitCode?: number): number;
/**
* The Windows Script Host build version number.
*/
BuildVersion: number;
/**
* Fully qualified path of the host executable.
*/
FullName: string;
/**
* Gets/sets the script mode - interactive(true) or batch(false).
*/
Interactive: boolean;
/**
* The name of the host executable (WScript.exe or CScript.exe).
*/
Name: string;
/**
* Path of the directory containing the host executable.
*/
Path: string;
/**
* The filename of the currently running script.
*/
ScriptName: string;
/**
* Exposes the read-only input stream for the current script.
* Can be accessed only while using CScript.exe.
*/
StdIn: TextStreamReader;
/**
* Windows Script Host version
*/
Version: string;
/**
* Connects a COM object's event sources to functions named with a given prefix, in the form prefix_event.
*/
ConnectObject(objEventSource: any, strPrefix: string): void;
/**
* Creates a COM object.
* @param strProgiID
* @param strPrefix Function names in the form prefix_event will be bound to this object's COM events.
*/
CreateObject(strProgID: string, strPrefix?: string): any;
/**
* Disconnects a COM object from its event sources.
*/
DisconnectObject(obj: any): void;
/**
* Retrieves an existing object with the specified ProgID from memory, or creates a new one from a file.
* @param strPathname Fully qualified path to the file containing the object persisted to disk.
* For objects in memory, pass a zero-length string.
* @param strProgID
* @param strPrefix Function names in the form prefix_event will be bound to this object's COM events.
*/
GetObject(strPathname: string, strProgID?: string, strPrefix?: string): any;
/**
* Suspends script execution for a specified length of time, then continues execution.
* @param intTime Interval (in milliseconds) to suspend script execution.
*/
Sleep(intTime: number): void;
};
/**
* WSH is an alias for WScript under Windows Script Host
*/
declare var WSH: typeof WScript;
/**
* Represents an Automation SAFEARRAY
*/
declare class SafeArray<T = any> {
private constructor();
private SafeArray_typekey: SafeArray<T>;
}
/**
* Allows enumerating over a COM collection, which may not have indexed item access.
*/
interface Enumerator<T = any> {
/**
* Returns true if the current item is the last one in the collection, or the collection is empty,
* or the current item is undefined.
*/
atEnd(): boolean;
/**
* Returns the current item in the collection
*/
item(): T;
/**
* Resets the current item in the collection to the first item. If there are no items in the collection,
* the current item is set to undefined.
*/
moveFirst(): void;
/**
* Moves the current item to the next item in the collection. If the enumerator is at the end of
* the collection or the collection is empty, the current item is set to undefined.
*/
moveNext(): void;
}
interface EnumeratorConstructor {
new <T = any>(safearray: SafeArray<T>): Enumerator<T>;
new <T = any>(collection: { Item(index: any): T; }): Enumerator<T>;
new <T = any>(collection: any): Enumerator<T>;
}
declare var Enumerator: EnumeratorConstructor;
/**
* Enables reading from a COM safe array, which might have an alternate lower bound, or multiple dimensions.
*/
interface VBArray<T = any> {
/**
* Returns the number of dimensions (1-based).
*/
dimensions(): number;
/**
* Takes an index for each dimension in the array, and returns the item at the corresponding location.
*/
getItem(dimension1Index: number, ...dimensionNIndexes: number[]): T;
/**
* Returns the smallest available index for a given dimension.
* @param dimension 1-based dimension (defaults to 1)
*/
lbound(dimension?: number): number;
/**
* Returns the largest available index for a given dimension.
* @param dimension 1-based dimension (defaults to 1)
*/
ubound(dimension?: number): number;
/**
* Returns a Javascript array with all the elements in the VBArray. If there are multiple dimensions,
* each successive dimension is appended to the end of the array.
* Example: [[1,2,3],[4,5,6]] becomes [1,2,3,4,5,6]
*/
toArray(): T[];
}
interface VBArrayConstructor {
new <T = any>(safeArray: SafeArray<T>): VBArray<T>;
}
declare var VBArray: VBArrayConstructor;
/**
* Automation date (VT_DATE)
*/
declare class VarDate {
private constructor();
private VarDate_typekey: VarDate;
}
interface DateConstructor {
new (vd: VarDate): Date;
}
interface Date {
getVarDate: () => VarDate;
}

View File

@@ -0,0 +1,147 @@
export type Schema =
| ObjectSchema
| ArraySchema
| StringSchema
| NumberSchema
| IntegerSchema
| BooleanSchema
| NullSchema;
// export type JsonType = "object" | "array" | "string" | "number" | "boolean" | "null" | "integer";
// export interface JSONSchema {
// type?: string ;
// $id?: string ;
// id?: string ;
// $schema?: string ;
// $ref?: string ;
// $anchor?: string ;
// $defs?: { [key: string]: JSONSchema } ;
// definitions?: { [key: string]: JSONSchema } ;
// $comment?: string ;
// title?: string ;
// description?: string ;
// default?: unknown ;
// examples?: unknown[] ;
// readOnly?: boolean ;
// writeOnly?: boolean ;
// deprecated?: boolean ;
// allOf?: JSONSchema[] ;
// anyOf?: JSONSchema[] ;
// oneOf?: JSONSchema[] ;
// not?: JSONSchema ;
// if?: JSONSchema ;
// then?: JSONSchema ;
// else?: JSONSchema ;
// enum?: Array<string | number | boolean | null> ;
// const?: string | number | boolean | null ;
// [k: string]: unknown;
// /** A special key used as an intermediate representation of extends-style relationships. Omitted as a $ref with additional properties. */
// // _ref?: JSONSchema;
// _prefault?: unknown ;
// }
export type _JSONSchema = boolean | JSONSchema;
export type JSONSchema = {
[k: string]: unknown;
$schema?:
| "https://json-schema.org/draft/2020-12/schema"
| "http://json-schema.org/draft-07/schema#"
| "http://json-schema.org/draft-04/schema#";
$id?: string;
$anchor?: string;
$ref?: string;
$dynamicRef?: string;
$dynamicAnchor?: string;
$vocabulary?: Record<string, boolean>;
$comment?: string;
$defs?: Record<string, JSONSchema>;
type?: "object" | "array" | "string" | "number" | "boolean" | "null" | "integer";
additionalItems?: _JSONSchema;
unevaluatedItems?: _JSONSchema;
prefixItems?: _JSONSchema[];
items?: _JSONSchema | _JSONSchema[];
contains?: _JSONSchema;
additionalProperties?: _JSONSchema;
unevaluatedProperties?: _JSONSchema;
properties?: Record<string, _JSONSchema>;
patternProperties?: Record<string, _JSONSchema>;
dependentSchemas?: Record<string, _JSONSchema>;
propertyNames?: _JSONSchema;
if?: _JSONSchema;
then?: _JSONSchema;
else?: _JSONSchema;
allOf?: JSONSchema[];
anyOf?: JSONSchema[];
oneOf?: JSONSchema[];
not?: _JSONSchema;
multipleOf?: number;
maximum?: number;
exclusiveMaximum?: number | boolean;
minimum?: number;
exclusiveMinimum?: number | boolean;
maxLength?: number;
minLength?: number;
pattern?: string;
maxItems?: number;
minItems?: number;
uniqueItems?: boolean;
maxContains?: number;
minContains?: number;
maxProperties?: number;
minProperties?: number;
required?: string[];
dependentRequired?: Record<string, string[]>;
enum?: Array<string | number | boolean | null>;
const?: string | number | boolean | null;
// metadata
id?: string;
title?: string;
description?: string;
default?: unknown;
deprecated?: boolean;
readOnly?: boolean;
writeOnly?: boolean;
nullable?: boolean;
examples?: unknown[];
format?: string;
contentMediaType?: string;
contentEncoding?: string;
contentSchema?: JSONSchema;
// internal
_prefault?: unknown;
};
// for backwards compatibility
export type BaseSchema = JSONSchema;
export interface ObjectSchema extends JSONSchema {
type: "object";
}
export interface ArraySchema extends JSONSchema {
type: "array";
}
export interface StringSchema extends JSONSchema {
type: "string";
}
export interface NumberSchema extends JSONSchema {
type: "number";
}
export interface IntegerSchema extends JSONSchema {
type: "integer";
}
export interface BooleanSchema extends JSONSchema {
type: "boolean";
}
export interface NullSchema extends JSONSchema {
type: "null";
}

View File

@@ -0,0 +1,89 @@
'use strict';
const path = require('path');
const locatePath = require('locate-path');
const pathExists = require('path-exists');
const stop = Symbol('findUp.stop');
module.exports = async (name, options = {}) => {
let directory = path.resolve(options.cwd || '');
const {root} = path.parse(directory);
const paths = [].concat(name);
const runMatcher = async locateOptions => {
if (typeof name !== 'function') {
return locatePath(paths, locateOptions);
}
const foundPath = await name(locateOptions.cwd);
if (typeof foundPath === 'string') {
return locatePath([foundPath], locateOptions);
}
return foundPath;
};
// eslint-disable-next-line no-constant-condition
while (true) {
// eslint-disable-next-line no-await-in-loop
const foundPath = await runMatcher({...options, cwd: directory});
if (foundPath === stop) {
return;
}
if (foundPath) {
return path.resolve(directory, foundPath);
}
if (directory === root) {
return;
}
directory = path.dirname(directory);
}
};
module.exports.sync = (name, options = {}) => {
let directory = path.resolve(options.cwd || '');
const {root} = path.parse(directory);
const paths = [].concat(name);
const runMatcher = locateOptions => {
if (typeof name !== 'function') {
return locatePath.sync(paths, locateOptions);
}
const foundPath = name(locateOptions.cwd);
if (typeof foundPath === 'string') {
return locatePath.sync([foundPath], locateOptions);
}
return foundPath;
};
// eslint-disable-next-line no-constant-condition
while (true) {
const foundPath = runMatcher({...options, cwd: directory});
if (foundPath === stop) {
return;
}
if (foundPath) {
return path.resolve(directory, foundPath);
}
if (directory === root) {
return;
}
directory = path.dirname(directory);
}
};
module.exports.exists = pathExists;
module.exports.sync.exists = pathExists.sync;
module.exports.stop = stop;

View File

@@ -0,0 +1,805 @@
/*
Copyright (C) 2012-2013 Yusuke Suzuki <utatane.tea@gmail.com>
Copyright (C) 2012 Ariya Hidayat <ariya.hidayat@gmail.com>
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*jslint vars:false, bitwise:true*/
/*jshint indent:4*/
/*global exports:true*/
(function clone(exports) {
'use strict';
var Syntax,
VisitorOption,
VisitorKeys,
BREAK,
SKIP,
REMOVE;
function deepCopy(obj) {
var ret = {}, key, val;
for (key in obj) {
if (obj.hasOwnProperty(key)) {
val = obj[key];
if (typeof val === 'object' && val !== null) {
ret[key] = deepCopy(val);
} else {
ret[key] = val;
}
}
}
return ret;
}
// based on LLVM libc++ upper_bound / lower_bound
// MIT License
function upperBound(array, func) {
var diff, len, i, current;
len = array.length;
i = 0;
while (len) {
diff = len >>> 1;
current = i + diff;
if (func(array[current])) {
len = diff;
} else {
i = current + 1;
len -= diff + 1;
}
}
return i;
}
Syntax = {
AssignmentExpression: 'AssignmentExpression',
AssignmentPattern: 'AssignmentPattern',
ArrayExpression: 'ArrayExpression',
ArrayPattern: 'ArrayPattern',
ArrowFunctionExpression: 'ArrowFunctionExpression',
AwaitExpression: 'AwaitExpression', // CAUTION: It's deferred to ES7.
BlockStatement: 'BlockStatement',
BinaryExpression: 'BinaryExpression',
BreakStatement: 'BreakStatement',
CallExpression: 'CallExpression',
CatchClause: 'CatchClause',
ChainExpression: 'ChainExpression',
ClassBody: 'ClassBody',
ClassDeclaration: 'ClassDeclaration',
ClassExpression: 'ClassExpression',
ComprehensionBlock: 'ComprehensionBlock', // CAUTION: It's deferred to ES7.
ComprehensionExpression: 'ComprehensionExpression', // CAUTION: It's deferred to ES7.
ConditionalExpression: 'ConditionalExpression',
ContinueStatement: 'ContinueStatement',
DebuggerStatement: 'DebuggerStatement',
DirectiveStatement: 'DirectiveStatement',
DoWhileStatement: 'DoWhileStatement',
EmptyStatement: 'EmptyStatement',
ExportAllDeclaration: 'ExportAllDeclaration',
ExportDefaultDeclaration: 'ExportDefaultDeclaration',
ExportNamedDeclaration: 'ExportNamedDeclaration',
ExportSpecifier: 'ExportSpecifier',
ExpressionStatement: 'ExpressionStatement',
ForStatement: 'ForStatement',
ForInStatement: 'ForInStatement',
ForOfStatement: 'ForOfStatement',
FunctionDeclaration: 'FunctionDeclaration',
FunctionExpression: 'FunctionExpression',
GeneratorExpression: 'GeneratorExpression', // CAUTION: It's deferred to ES7.
Identifier: 'Identifier',
IfStatement: 'IfStatement',
ImportExpression: 'ImportExpression',
ImportDeclaration: 'ImportDeclaration',
ImportDefaultSpecifier: 'ImportDefaultSpecifier',
ImportNamespaceSpecifier: 'ImportNamespaceSpecifier',
ImportSpecifier: 'ImportSpecifier',
Literal: 'Literal',
LabeledStatement: 'LabeledStatement',
LogicalExpression: 'LogicalExpression',
MemberExpression: 'MemberExpression',
MetaProperty: 'MetaProperty',
MethodDefinition: 'MethodDefinition',
ModuleSpecifier: 'ModuleSpecifier',
NewExpression: 'NewExpression',
ObjectExpression: 'ObjectExpression',
ObjectPattern: 'ObjectPattern',
PrivateIdentifier: 'PrivateIdentifier',
Program: 'Program',
Property: 'Property',
PropertyDefinition: 'PropertyDefinition',
RestElement: 'RestElement',
ReturnStatement: 'ReturnStatement',
SequenceExpression: 'SequenceExpression',
SpreadElement: 'SpreadElement',
Super: 'Super',
SwitchStatement: 'SwitchStatement',
SwitchCase: 'SwitchCase',
TaggedTemplateExpression: 'TaggedTemplateExpression',
TemplateElement: 'TemplateElement',
TemplateLiteral: 'TemplateLiteral',
ThisExpression: 'ThisExpression',
ThrowStatement: 'ThrowStatement',
TryStatement: 'TryStatement',
UnaryExpression: 'UnaryExpression',
UpdateExpression: 'UpdateExpression',
VariableDeclaration: 'VariableDeclaration',
VariableDeclarator: 'VariableDeclarator',
WhileStatement: 'WhileStatement',
WithStatement: 'WithStatement',
YieldExpression: 'YieldExpression'
};
VisitorKeys = {
AssignmentExpression: ['left', 'right'],
AssignmentPattern: ['left', 'right'],
ArrayExpression: ['elements'],
ArrayPattern: ['elements'],
ArrowFunctionExpression: ['params', 'body'],
AwaitExpression: ['argument'], // CAUTION: It's deferred to ES7.
BlockStatement: ['body'],
BinaryExpression: ['left', 'right'],
BreakStatement: ['label'],
CallExpression: ['callee', 'arguments'],
CatchClause: ['param', 'body'],
ChainExpression: ['expression'],
ClassBody: ['body'],
ClassDeclaration: ['id', 'superClass', 'body'],
ClassExpression: ['id', 'superClass', 'body'],
ComprehensionBlock: ['left', 'right'], // CAUTION: It's deferred to ES7.
ComprehensionExpression: ['blocks', 'filter', 'body'], // CAUTION: It's deferred to ES7.
ConditionalExpression: ['test', 'consequent', 'alternate'],
ContinueStatement: ['label'],
DebuggerStatement: [],
DirectiveStatement: [],
DoWhileStatement: ['body', 'test'],
EmptyStatement: [],
ExportAllDeclaration: ['source'],
ExportDefaultDeclaration: ['declaration'],
ExportNamedDeclaration: ['declaration', 'specifiers', 'source'],
ExportSpecifier: ['exported', 'local'],
ExpressionStatement: ['expression'],
ForStatement: ['init', 'test', 'update', 'body'],
ForInStatement: ['left', 'right', 'body'],
ForOfStatement: ['left', 'right', 'body'],
FunctionDeclaration: ['id', 'params', 'body'],
FunctionExpression: ['id', 'params', 'body'],
GeneratorExpression: ['blocks', 'filter', 'body'], // CAUTION: It's deferred to ES7.
Identifier: [],
IfStatement: ['test', 'consequent', 'alternate'],
ImportExpression: ['source'],
ImportDeclaration: ['specifiers', 'source'],
ImportDefaultSpecifier: ['local'],
ImportNamespaceSpecifier: ['local'],
ImportSpecifier: ['imported', 'local'],
Literal: [],
LabeledStatement: ['label', 'body'],
LogicalExpression: ['left', 'right'],
MemberExpression: ['object', 'property'],
MetaProperty: ['meta', 'property'],
MethodDefinition: ['key', 'value'],
ModuleSpecifier: [],
NewExpression: ['callee', 'arguments'],
ObjectExpression: ['properties'],
ObjectPattern: ['properties'],
PrivateIdentifier: [],
Program: ['body'],
Property: ['key', 'value'],
PropertyDefinition: ['key', 'value'],
RestElement: [ 'argument' ],
ReturnStatement: ['argument'],
SequenceExpression: ['expressions'],
SpreadElement: ['argument'],
Super: [],
SwitchStatement: ['discriminant', 'cases'],
SwitchCase: ['test', 'consequent'],
TaggedTemplateExpression: ['tag', 'quasi'],
TemplateElement: [],
TemplateLiteral: ['quasis', 'expressions'],
ThisExpression: [],
ThrowStatement: ['argument'],
TryStatement: ['block', 'handler', 'finalizer'],
UnaryExpression: ['argument'],
UpdateExpression: ['argument'],
VariableDeclaration: ['declarations'],
VariableDeclarator: ['id', 'init'],
WhileStatement: ['test', 'body'],
WithStatement: ['object', 'body'],
YieldExpression: ['argument']
};
// unique id
BREAK = {};
SKIP = {};
REMOVE = {};
VisitorOption = {
Break: BREAK,
Skip: SKIP,
Remove: REMOVE
};
function Reference(parent, key) {
this.parent = parent;
this.key = key;
}
Reference.prototype.replace = function replace(node) {
this.parent[this.key] = node;
};
Reference.prototype.remove = function remove() {
if (Array.isArray(this.parent)) {
this.parent.splice(this.key, 1);
return true;
} else {
this.replace(null);
return false;
}
};
function Element(node, path, wrap, ref) {
this.node = node;
this.path = path;
this.wrap = wrap;
this.ref = ref;
}
function Controller() { }
// API:
// return property path array from root to current node
Controller.prototype.path = function path() {
var i, iz, j, jz, result, element;
function addToPath(result, path) {
if (Array.isArray(path)) {
for (j = 0, jz = path.length; j < jz; ++j) {
result.push(path[j]);
}
} else {
result.push(path);
}
}
// root node
if (!this.__current.path) {
return null;
}
// first node is sentinel, second node is root element
result = [];
for (i = 2, iz = this.__leavelist.length; i < iz; ++i) {
element = this.__leavelist[i];
addToPath(result, element.path);
}
addToPath(result, this.__current.path);
return result;
};
// API:
// return type of current node
Controller.prototype.type = function () {
var node = this.current();
return node.type || this.__current.wrap;
};
// API:
// return array of parent elements
Controller.prototype.parents = function parents() {
var i, iz, result;
// first node is sentinel
result = [];
for (i = 1, iz = this.__leavelist.length; i < iz; ++i) {
result.push(this.__leavelist[i].node);
}
return result;
};
// API:
// return current node
Controller.prototype.current = function current() {
return this.__current.node;
};
Controller.prototype.__execute = function __execute(callback, element) {
var previous, result;
result = undefined;
previous = this.__current;
this.__current = element;
this.__state = null;
if (callback) {
result = callback.call(this, element.node, this.__leavelist[this.__leavelist.length - 1].node);
}
this.__current = previous;
return result;
};
// API:
// notify control skip / break
Controller.prototype.notify = function notify(flag) {
this.__state = flag;
};
// API:
// skip child nodes of current node
Controller.prototype.skip = function () {
this.notify(SKIP);
};
// API:
// break traversals
Controller.prototype['break'] = function () {
this.notify(BREAK);
};
// API:
// remove node
Controller.prototype.remove = function () {
this.notify(REMOVE);
};
Controller.prototype.__initialize = function(root, visitor) {
this.visitor = visitor;
this.root = root;
this.__worklist = [];
this.__leavelist = [];
this.__current = null;
this.__state = null;
this.__fallback = null;
if (visitor.fallback === 'iteration') {
this.__fallback = Object.keys;
} else if (typeof visitor.fallback === 'function') {
this.__fallback = visitor.fallback;
}
this.__keys = VisitorKeys;
if (visitor.keys) {
this.__keys = Object.assign(Object.create(this.__keys), visitor.keys);
}
};
function isNode(node) {
if (node == null) {
return false;
}
return typeof node === 'object' && typeof node.type === 'string';
}
function isProperty(nodeType, key) {
return (nodeType === Syntax.ObjectExpression || nodeType === Syntax.ObjectPattern) && 'properties' === key;
}
function candidateExistsInLeaveList(leavelist, candidate) {
for (var i = leavelist.length - 1; i >= 0; --i) {
if (leavelist[i].node === candidate) {
return true;
}
}
return false;
}
Controller.prototype.traverse = function traverse(root, visitor) {
var worklist,
leavelist,
element,
node,
nodeType,
ret,
key,
current,
current2,
candidates,
candidate,
sentinel;
this.__initialize(root, visitor);
sentinel = {};
// reference
worklist = this.__worklist;
leavelist = this.__leavelist;
// initialize
worklist.push(new Element(root, null, null, null));
leavelist.push(new Element(null, null, null, null));
while (worklist.length) {
element = worklist.pop();
if (element === sentinel) {
element = leavelist.pop();
ret = this.__execute(visitor.leave, element);
if (this.__state === BREAK || ret === BREAK) {
return;
}
continue;
}
if (element.node) {
ret = this.__execute(visitor.enter, element);
if (this.__state === BREAK || ret === BREAK) {
return;
}
worklist.push(sentinel);
leavelist.push(element);
if (this.__state === SKIP || ret === SKIP) {
continue;
}
node = element.node;
nodeType = node.type || element.wrap;
candidates = this.__keys[nodeType];
if (!candidates) {
if (this.__fallback) {
candidates = this.__fallback(node);
} else {
throw new Error('Unknown node type ' + nodeType + '.');
}
}
current = candidates.length;
while ((current -= 1) >= 0) {
key = candidates[current];
candidate = node[key];
if (!candidate) {
continue;
}
if (Array.isArray(candidate)) {
current2 = candidate.length;
while ((current2 -= 1) >= 0) {
if (!candidate[current2]) {
continue;
}
if (candidateExistsInLeaveList(leavelist, candidate[current2])) {
continue;
}
if (isProperty(nodeType, candidates[current])) {
element = new Element(candidate[current2], [key, current2], 'Property', null);
} else if (isNode(candidate[current2])) {
element = new Element(candidate[current2], [key, current2], null, null);
} else {
continue;
}
worklist.push(element);
}
} else if (isNode(candidate)) {
if (candidateExistsInLeaveList(leavelist, candidate)) {
continue;
}
worklist.push(new Element(candidate, key, null, null));
}
}
}
}
};
Controller.prototype.replace = function replace(root, visitor) {
var worklist,
leavelist,
node,
nodeType,
target,
element,
current,
current2,
candidates,
candidate,
sentinel,
outer,
key;
function removeElem(element) {
var i,
key,
nextElem,
parent;
if (element.ref.remove()) {
// When the reference is an element of an array.
key = element.ref.key;
parent = element.ref.parent;
// If removed from array, then decrease following items' keys.
i = worklist.length;
while (i--) {
nextElem = worklist[i];
if (nextElem.ref && nextElem.ref.parent === parent) {
if (nextElem.ref.key < key) {
break;
}
--nextElem.ref.key;
}
}
}
}
this.__initialize(root, visitor);
sentinel = {};
// reference
worklist = this.__worklist;
leavelist = this.__leavelist;
// initialize
outer = {
root: root
};
element = new Element(root, null, null, new Reference(outer, 'root'));
worklist.push(element);
leavelist.push(element);
while (worklist.length) {
element = worklist.pop();
if (element === sentinel) {
element = leavelist.pop();
target = this.__execute(visitor.leave, element);
// node may be replaced with null,
// so distinguish between undefined and null in this place
if (target !== undefined && target !== BREAK && target !== SKIP && target !== REMOVE) {
// replace
element.ref.replace(target);
}
if (this.__state === REMOVE || target === REMOVE) {
removeElem(element);
}
if (this.__state === BREAK || target === BREAK) {
return outer.root;
}
continue;
}
target = this.__execute(visitor.enter, element);
// node may be replaced with null,
// so distinguish between undefined and null in this place
if (target !== undefined && target !== BREAK && target !== SKIP && target !== REMOVE) {
// replace
element.ref.replace(target);
element.node = target;
}
if (this.__state === REMOVE || target === REMOVE) {
removeElem(element);
element.node = null;
}
if (this.__state === BREAK || target === BREAK) {
return outer.root;
}
// node may be null
node = element.node;
if (!node) {
continue;
}
worklist.push(sentinel);
leavelist.push(element);
if (this.__state === SKIP || target === SKIP) {
continue;
}
nodeType = node.type || element.wrap;
candidates = this.__keys[nodeType];
if (!candidates) {
if (this.__fallback) {
candidates = this.__fallback(node);
} else {
throw new Error('Unknown node type ' + nodeType + '.');
}
}
current = candidates.length;
while ((current -= 1) >= 0) {
key = candidates[current];
candidate = node[key];
if (!candidate) {
continue;
}
if (Array.isArray(candidate)) {
current2 = candidate.length;
while ((current2 -= 1) >= 0) {
if (!candidate[current2]) {
continue;
}
if (isProperty(nodeType, candidates[current])) {
element = new Element(candidate[current2], [key, current2], 'Property', new Reference(candidate, current2));
} else if (isNode(candidate[current2])) {
element = new Element(candidate[current2], [key, current2], null, new Reference(candidate, current2));
} else {
continue;
}
worklist.push(element);
}
} else if (isNode(candidate)) {
worklist.push(new Element(candidate, key, null, new Reference(node, key)));
}
}
}
return outer.root;
};
function traverse(root, visitor) {
var controller = new Controller();
return controller.traverse(root, visitor);
}
function replace(root, visitor) {
var controller = new Controller();
return controller.replace(root, visitor);
}
function extendCommentRange(comment, tokens) {
var target;
target = upperBound(tokens, function search(token) {
return token.range[0] > comment.range[0];
});
comment.extendedRange = [comment.range[0], comment.range[1]];
if (target !== tokens.length) {
comment.extendedRange[1] = tokens[target].range[0];
}
target -= 1;
if (target >= 0) {
comment.extendedRange[0] = tokens[target].range[1];
}
return comment;
}
function attachComments(tree, providedComments, tokens) {
// At first, we should calculate extended comment ranges.
var comments = [], comment, len, i, cursor;
if (!tree.range) {
throw new Error('attachComments needs range information');
}
// tokens array is empty, we attach comments to tree as 'leadingComments'
if (!tokens.length) {
if (providedComments.length) {
for (i = 0, len = providedComments.length; i < len; i += 1) {
comment = deepCopy(providedComments[i]);
comment.extendedRange = [0, tree.range[0]];
comments.push(comment);
}
tree.leadingComments = comments;
}
return tree;
}
for (i = 0, len = providedComments.length; i < len; i += 1) {
comments.push(extendCommentRange(deepCopy(providedComments[i]), tokens));
}
// This is based on John Freeman's implementation.
cursor = 0;
traverse(tree, {
enter: function (node) {
var comment;
while (cursor < comments.length) {
comment = comments[cursor];
if (comment.extendedRange[1] > node.range[0]) {
break;
}
if (comment.extendedRange[1] === node.range[0]) {
if (!node.leadingComments) {
node.leadingComments = [];
}
node.leadingComments.push(comment);
comments.splice(cursor, 1);
} else {
cursor += 1;
}
}
// already out of owned node
if (cursor === comments.length) {
return VisitorOption.Break;
}
if (comments[cursor].extendedRange[0] > node.range[1]) {
return VisitorOption.Skip;
}
}
});
cursor = 0;
traverse(tree, {
leave: function (node) {
var comment;
while (cursor < comments.length) {
comment = comments[cursor];
if (node.range[1] < comment.extendedRange[0]) {
break;
}
if (node.range[1] === comment.extendedRange[0]) {
if (!node.trailingComments) {
node.trailingComments = [];
}
node.trailingComments.push(comment);
comments.splice(cursor, 1);
} else {
cursor += 1;
}
}
// already out of owned node
if (cursor === comments.length) {
return VisitorOption.Break;
}
if (comments[cursor].extendedRange[0] > node.range[1]) {
return VisitorOption.Skip;
}
}
});
return tree;
}
exports.Syntax = Syntax;
exports.traverse = traverse;
exports.replace = replace;
exports.attachComments = attachComments;
exports.VisitorKeys = VisitorKeys;
exports.VisitorOption = VisitorOption;
exports.Controller = Controller;
exports.cloneEnvironment = function () { return clone({}); };
return exports;
}(exports));
/* vim: set sw=4 ts=4 et tw=80 : */

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2021 Paolo Insogna and the real-require contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,6 @@
function _class_check_private_static_field_descriptor(descriptor, action) {
if (descriptor === undefined) {
throw new TypeError("attempted to " + action + " private static field before its declaration");
}
}
export { _class_check_private_static_field_descriptor as _ };