@unbound-app/cli 0.9.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +18308 -1326
- package/dist/oxlint/plugin.d.mts +4240 -0
- package/dist/oxlint/plugin.mjs +154 -0
- package/dist/rollup/index.d.mts +418 -0
- package/dist/rollup/index.mjs +44024 -0
- package/dist/settings-typings-CnJ5F-rP.mjs +2482 -0
- package/package.json +39 -8
- package/templates/plugin/ADDON.md +9 -0
- package/templates/plugin/assets/icon.png +0 -0
- package/templates/plugin/bunfig.toml +3 -0
- package/templates/plugin/gitignore +2 -0
- package/templates/plugin/i18n/en-US.json +5 -0
- package/templates/plugin/media/banner.png +0 -0
- package/templates/plugin/oxlintrc.json +9 -0
- package/templates/plugin/package.json +11 -7
- package/templates/plugin/rollup.config.mjs +2 -42
- package/templates/plugin/settings.ts +9 -0
- package/templates/plugin/src/index.tsx +11 -0
- package/templates/plugin/src/unbound-i18n.d.ts +39 -0
- package/templates/plugin/src/unbound-settings.d.ts +174 -0
- package/templates/plugin/tests/index.test.ts +22 -0
- package/templates/plugin/tsconfig.json +18 -0
- package/templates/plugin/index.tsx +0 -21
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// src/oxlint/property-name.ts
|
|
2
|
+
/**
|
|
3
|
+
* @description Reads the static key of a member access, so `a.b` and `a['b']` both name `b`.
|
|
4
|
+
* @param node The member expression.
|
|
5
|
+
* @returns The key, or undefined when it is computed from a non-string expression.
|
|
6
|
+
*/
|
|
7
|
+
function propertyName(node) {
|
|
8
|
+
const { property } = node;
|
|
9
|
+
if (!node.computed && property.type === "Identifier") return property.name;
|
|
10
|
+
return property.type === "Literal" && typeof property.value === "string" ? property.value : void 0;
|
|
11
|
+
}
|
|
12
|
+
// src/oxlint/platform-check-only-in-platform-file.ts
|
|
13
|
+
const PLATFORM_FILE = /(^|\/)platform\.[cm]?[jt]sx?$/;
|
|
14
|
+
const PLATFORM_KEY_BY_OBJECT = /* @__PURE__ */ new Map([["Platform", "OS"], ["context", "platform"]]);
|
|
15
|
+
const platformCheckOnlyInPlatformFile = {
|
|
16
|
+
meta: {
|
|
17
|
+
type: "suggestion",
|
|
18
|
+
messages: { check: "`{{read}}` checks the platform outside `platform.ts`. Select the platform handler once in `platform.ts` with `definePlatform` and keep platform code in `ios/` and `android/`." }
|
|
19
|
+
},
|
|
20
|
+
create(context) {
|
|
21
|
+
if (PLATFORM_FILE.test(context.filename)) return {};
|
|
22
|
+
return { MemberExpression(node) {
|
|
23
|
+
if (node.object.type !== "Identifier") return;
|
|
24
|
+
const key = PLATFORM_KEY_BY_OBJECT.get(node.object.name);
|
|
25
|
+
if (!key || propertyName(node) !== key) return;
|
|
26
|
+
context.report({
|
|
27
|
+
node,
|
|
28
|
+
messageId: "check",
|
|
29
|
+
data: { read: `${node.object.name}.${key}` }
|
|
30
|
+
});
|
|
31
|
+
} };
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
// src/oxlint/no-module-scope-lookup.ts
|
|
35
|
+
const LOOKUPS_BY_NAMESPACE = /* @__PURE__ */ new Map([["metro", /* @__PURE__ */ new Set([
|
|
36
|
+
"find",
|
|
37
|
+
"findByProps",
|
|
38
|
+
"findByName",
|
|
39
|
+
"findByFilePath",
|
|
40
|
+
"findByPrototypes",
|
|
41
|
+
"findStore"
|
|
42
|
+
])], ["flux", /* @__PURE__ */ new Set(["getStore"])]]);
|
|
43
|
+
const SUBPATH_NAMESPACES = /* @__PURE__ */ new Map([["@unbound-app/api/metro", "metro"], ["@unbound-app/api/flux", "flux"]]);
|
|
44
|
+
const FUNCTIONS = /* @__PURE__ */ new Set([
|
|
45
|
+
"FunctionDeclaration",
|
|
46
|
+
"FunctionExpression",
|
|
47
|
+
"ArrowFunctionExpression"
|
|
48
|
+
]);
|
|
49
|
+
function isInFunction(call) {
|
|
50
|
+
for (let node = call.parent; node.type !== "Program"; node = node.parent) if (FUNCTIONS.has(node.type)) return true;
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
function isLookup(namespace, name) {
|
|
54
|
+
return Boolean(namespace && name && LOOKUPS_BY_NAMESPACE.get(namespace)?.has(name));
|
|
55
|
+
}
|
|
56
|
+
function identifierOrLastKey(node) {
|
|
57
|
+
if (node.type === "Identifier") return node.name;
|
|
58
|
+
return node.type === "MemberExpression" ? propertyName(node) : void 0;
|
|
59
|
+
}
|
|
60
|
+
function isLazy(call, lazyByDefault) {
|
|
61
|
+
const last = call.arguments.at(-1);
|
|
62
|
+
if (last?.type !== "ObjectExpression") return lazyByDefault;
|
|
63
|
+
const option = last.properties.find((property) => property.type === "Property" && !property.computed && property.key.type === "Identifier" && property.key.name === "lazy");
|
|
64
|
+
if (!option || option.type !== "Property") return lazyByDefault;
|
|
65
|
+
return option.value.type === "Literal" && option.value.value === true;
|
|
66
|
+
}
|
|
67
|
+
const noModuleScopeLookup = {
|
|
68
|
+
meta: {
|
|
69
|
+
type: "problem",
|
|
70
|
+
schema: [{
|
|
71
|
+
type: "object",
|
|
72
|
+
properties: { lazyByDefault: { type: "boolean" } },
|
|
73
|
+
additionalProperties: false
|
|
74
|
+
}],
|
|
75
|
+
messages: { eager: "`{{call}}` searches every Discord module while the addon loads. Pass `{ lazy: true }` or move the lookup into the function that uses it." }
|
|
76
|
+
},
|
|
77
|
+
create(context) {
|
|
78
|
+
const [options] = context.options;
|
|
79
|
+
const lazyByDefault = options?.lazyByDefault ?? false;
|
|
80
|
+
const importedLookups = /* @__PURE__ */ new Map();
|
|
81
|
+
return {
|
|
82
|
+
Program(program) {
|
|
83
|
+
for (const statement of program.body) {
|
|
84
|
+
if (statement.type !== "ImportDeclaration") continue;
|
|
85
|
+
const namespace = SUBPATH_NAMESPACES.get(statement.source.value);
|
|
86
|
+
for (const specifier of statement.specifiers) {
|
|
87
|
+
if (specifier.type !== "ImportSpecifier") continue;
|
|
88
|
+
const { imported: source } = specifier;
|
|
89
|
+
const name = source.type === "Identifier" ? source.name : source.value;
|
|
90
|
+
if (isLookup(namespace, name)) importedLookups.set(specifier.local.name, name);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
CallExpression(node) {
|
|
95
|
+
const { callee } = node;
|
|
96
|
+
let call;
|
|
97
|
+
if (callee.type === "Identifier" && importedLookups.has(callee.name)) call = callee.name;
|
|
98
|
+
else if (callee.type === "MemberExpression") {
|
|
99
|
+
const namespace = identifierOrLastKey(callee.object);
|
|
100
|
+
const name = propertyName(callee);
|
|
101
|
+
if (isLookup(namespace, name)) call = `${namespace}.${name}`;
|
|
102
|
+
}
|
|
103
|
+
if (!call || isLazy(node, lazyByDefault) || isInFunction(node)) return;
|
|
104
|
+
context.report({
|
|
105
|
+
node,
|
|
106
|
+
messageId: "eager",
|
|
107
|
+
data: { call }
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
// src/oxlint/no-window-unbound.ts
|
|
114
|
+
const GLOBAL_OBJECTS = /* @__PURE__ */ new Set(["window", "globalThis"]);
|
|
115
|
+
const STRING_GLOBAL_PATH = /^(window|globalThis)\.unbound(\.|$)/;
|
|
116
|
+
// src/oxlint/plugin.ts
|
|
117
|
+
const addonRules = {
|
|
118
|
+
meta: { name: "unbound" },
|
|
119
|
+
rules: {
|
|
120
|
+
"platform-check-only-in-platform-file": platformCheckOnlyInPlatformFile,
|
|
121
|
+
"no-module-scope-lookup": noModuleScopeLookup,
|
|
122
|
+
"no-window-unbound": {
|
|
123
|
+
meta: {
|
|
124
|
+
type: "problem",
|
|
125
|
+
messages: { global: "`window.unbound` is the unscoped API: patches, timers and listeners made through it outlive the plugin. Import from `@unbound-app/api` or use the scoped `unbound` global (ADR-0002)." }
|
|
126
|
+
},
|
|
127
|
+
create(context) {
|
|
128
|
+
return {
|
|
129
|
+
MemberExpression(node) {
|
|
130
|
+
if (node.object.type !== "Identifier" || !GLOBAL_OBJECTS.has(node.object.name)) return;
|
|
131
|
+
if (propertyName(node) === "unbound") context.report({
|
|
132
|
+
node,
|
|
133
|
+
messageId: "global"
|
|
134
|
+
});
|
|
135
|
+
},
|
|
136
|
+
Literal(node) {
|
|
137
|
+
if (typeof node.value === "string" && STRING_GLOBAL_PATH.test(node.value)) context.report({
|
|
138
|
+
node,
|
|
139
|
+
messageId: "global"
|
|
140
|
+
});
|
|
141
|
+
},
|
|
142
|
+
TemplateLiteral(node) {
|
|
143
|
+
const [head] = node.quasis;
|
|
144
|
+
if (head && STRING_GLOBAL_PATH.test(head.value.cooked ?? head.value.raw)) context.report({
|
|
145
|
+
node,
|
|
146
|
+
messageId: "global"
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
export { addonRules as default };
|
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
import { StaticDecode, Type } from "typebox";
|
|
2
|
+
import { ESTree } from "rolldown/utils";
|
|
3
|
+
import MagicString from "magic-string";
|
|
4
|
+
import { EmittedAsset, Plugin, PluginContext, RollupOptions } from "rollup";
|
|
5
|
+
/** One locale's messages, keyed by message key. */
|
|
6
|
+
type LocaleTable = Record<string, string>;
|
|
7
|
+
/** A translation as the build emits it: its minified JSON and that text's SHA-256. */
|
|
8
|
+
type EmittedLocale = {
|
|
9
|
+
text: string;
|
|
10
|
+
digest: string;
|
|
11
|
+
};
|
|
12
|
+
/** A plugin's `i18n/` folder, validated against its en-US file. */
|
|
13
|
+
type LocaleFolder = {
|
|
14
|
+
source: LocaleTable;
|
|
15
|
+
translations: Map<string, EmittedLocale>;
|
|
16
|
+
warnings: LocaleWarning[];
|
|
17
|
+
};
|
|
18
|
+
/** A translation problem the build keeps going past: the file, the key, and what is wrong. */
|
|
19
|
+
type LocaleWarning = {
|
|
20
|
+
file: string;
|
|
21
|
+
path: string;
|
|
22
|
+
message: string;
|
|
23
|
+
fix?: string;
|
|
24
|
+
};
|
|
25
|
+
/** The bare specifier a plugin imports its own messages from; the build resolves it to a generated module. */
|
|
26
|
+
export declare const LOCALE_MODULE = "@unbound-app/i18n";
|
|
27
|
+
/**
|
|
28
|
+
* Where `ubd build` and `ubd dev` write the ambient typings of {@link LOCALE_MODULE}, relative to the plugin.
|
|
29
|
+
* It sits in `src/`, which every plugin tsconfig includes, so the editor finds it with no setup.
|
|
30
|
+
*/
|
|
31
|
+
export declare const LOCALE_TYPINGS = "src/unbound-i18n.d.ts";
|
|
32
|
+
/**
|
|
33
|
+
* @description Reads and validates a plugin's `i18n/` folder: every file must be a JSON object of strings in
|
|
34
|
+
* valid ICU syntax, and en-US.json is required. Keys en-US lacks, and placeholders it disagrees on, are warnings.
|
|
35
|
+
* @param pluginRoot The plugin's directory.
|
|
36
|
+
* @returns The folder, or `undefined` when the plugin has none; throws naming the file that is invalid.
|
|
37
|
+
*/
|
|
38
|
+
export declare function readLocaleFolder(pluginRoot: string): LocaleFolder | undefined;
|
|
39
|
+
/**
|
|
40
|
+
* @description Rewrites `src/unbound-i18n.d.ts` from the plugin's en-US file, leaving the file alone when it is current.
|
|
41
|
+
* @param pluginRoot The plugin's directory.
|
|
42
|
+
* @returns Whether the file changed; `false` when the plugin has no en-US file. Throws when en-US is invalid.
|
|
43
|
+
*/
|
|
44
|
+
export declare function writeLocaleTypings(pluginRoot: string): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* @description Resolves {@link LOCALE_MODULE} to one generated module for the plugin being built, and refuses
|
|
47
|
+
* an import of the `i18n/` folder or its files by path: a JSON file would put that language in the bundle.
|
|
48
|
+
* @param pluginRoot The plugin's directory.
|
|
49
|
+
* @returns The rollup plugin.
|
|
50
|
+
*/
|
|
51
|
+
export declare function localeModule(pluginRoot: string): Plugin;
|
|
52
|
+
/**
|
|
53
|
+
* @description Validates the plugin's `i18n/` folder, writes the typings of {@link LOCALE_MODULE}, emits each
|
|
54
|
+
* locale but en-US beside the bundle as `i18n/<code>.json`, and lists their SHA-256 under `locales` in the dist manifest.
|
|
55
|
+
* @param pluginRoot The plugin's directory.
|
|
56
|
+
* @returns The rollup plugin.
|
|
57
|
+
*/
|
|
58
|
+
export declare function locales(pluginRoot: string): Plugin;
|
|
59
|
+
type Severity = 'error' | 'warning';
|
|
60
|
+
/** One problem a check found, at a place in one of the addon's files. */
|
|
61
|
+
type Diagnostic = {
|
|
62
|
+
/** The file, relative to the addon's directory, with `/` separators. */
|
|
63
|
+
file: string;
|
|
64
|
+
/** 1-based; 0 when the problem belongs to the whole file. */
|
|
65
|
+
line: number;
|
|
66
|
+
/** 1-based; 0 when the problem belongs to the whole file. */
|
|
67
|
+
column: number;
|
|
68
|
+
/** Where in the file's data the problem sits, such as a settings key or a manifest field. */
|
|
69
|
+
path?: string;
|
|
70
|
+
severity: Severity;
|
|
71
|
+
message: string;
|
|
72
|
+
fix?: string;
|
|
73
|
+
/** The check that found it; the runner fills in the check's id when a check leaves it out. */
|
|
74
|
+
check?: string;
|
|
75
|
+
};
|
|
76
|
+
/** Where and how bad a diagnostic is, for building one without spelling out every field. */
|
|
77
|
+
type DiagnosticInit = Omit<Diagnostic, 'line' | 'column'> & {
|
|
78
|
+
line?: number;
|
|
79
|
+
column?: number;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* @description Builds a diagnostic, placing it on the whole file when it has no line.
|
|
83
|
+
* @param init The diagnostic's fields.
|
|
84
|
+
* @returns The diagnostic.
|
|
85
|
+
*/
|
|
86
|
+
export declare function diagnostic({ line, column, ...rest }: DiagnosticInit): Diagnostic;
|
|
87
|
+
/**
|
|
88
|
+
* @description Formats a diagnostic as `file:L:C <path> <problem>. Fix: <fix>.`, the line every
|
|
89
|
+
* check, `ubd build` and `ubd check` print.
|
|
90
|
+
* @param diagnostic The diagnostic.
|
|
91
|
+
* @param file The file as the reader should see it; defaults to the diagnostic's own.
|
|
92
|
+
* @returns The line.
|
|
93
|
+
*/
|
|
94
|
+
export declare function formatDiagnostic(diagnostic: Diagnostic, file?: string): string;
|
|
95
|
+
/**
|
|
96
|
+
* @description Reports a diagnostic from a rollup plugin hook: an error stops the build, a warning
|
|
97
|
+
* does not. Rollup prints the formatted line, and `ubd check` reads the diagnostic back from the
|
|
98
|
+
* log's `meta`, so both show the same problem the same way.
|
|
99
|
+
* @param context The hook's `this`.
|
|
100
|
+
* @param found The diagnostic, with its file relative to the plugin's directory.
|
|
101
|
+
*/
|
|
102
|
+
export declare function reportDiagnostic(context: Pick<PluginContext, 'warn' | 'error'>, found: Diagnostic): void;
|
|
103
|
+
/** The image types a plugin ships: the formats React Native's `Image` decodes. An SVG is a component, not an asset. */
|
|
104
|
+
declare const ASSET_TYPES: readonly ['png', 'jpg', 'webp', 'gif'];
|
|
105
|
+
/** One of {@link ASSET_TYPES}. */
|
|
106
|
+
type AssetType = (typeof ASSET_TYPES)[number];
|
|
107
|
+
/** The scales an image ships at, as its `@2x` and `@3x` suffixes name them. */
|
|
108
|
+
type AssetScale = '1' | '2' | '3';
|
|
109
|
+
/** One image: its asset name, type, and its file at each scale found beside it. */
|
|
110
|
+
type AssetGroup = {
|
|
111
|
+
name: string;
|
|
112
|
+
type: AssetType;
|
|
113
|
+
files: Map<AssetScale, string>;
|
|
114
|
+
};
|
|
115
|
+
/** The images one build's modules imported, shared by the main build and its lazy modules' builds. */
|
|
116
|
+
type AssetCollector = {
|
|
117
|
+
imported: Map<string, AssetGroup>;
|
|
118
|
+
};
|
|
119
|
+
/**
|
|
120
|
+
* @description Creates the record of imported images that {@link assetImports} fills and {@link assets} ships.
|
|
121
|
+
* @returns An empty collector.
|
|
122
|
+
*/
|
|
123
|
+
export declare function createAssetCollector(): AssetCollector;
|
|
124
|
+
/**
|
|
125
|
+
* @description Reads an image file and its scale siblings (`star.png`, `star@2x.png`, `star@3x.png`) as one asset.
|
|
126
|
+
* The name is the path without extension or scale, relative to `assets/` inside it and to the plugin root outside it.
|
|
127
|
+
* @param pluginRoot The plugin's directory.
|
|
128
|
+
* @param path Any of the image's files.
|
|
129
|
+
* @returns The asset; throws when the file is not a supported image or its name is not path-safe.
|
|
130
|
+
*/
|
|
131
|
+
export declare function readAssetGroup(pluginRoot: string, path: string): AssetGroup;
|
|
132
|
+
/**
|
|
133
|
+
* @description Reads every image in a plugin's `assets/` folder, one group per name.
|
|
134
|
+
* @param pluginRoot The plugin's directory.
|
|
135
|
+
* @returns The assets by name; throws naming a file that is not a supported image, or two images sharing a name.
|
|
136
|
+
*/
|
|
137
|
+
export declare function readAssetFolder(pluginRoot: string): Map<string, AssetGroup>;
|
|
138
|
+
/**
|
|
139
|
+
* @description Resolves a relative or `~/` import of an image to a module whose default export is the image's
|
|
140
|
+
* registered asset id, and records the image so {@link assets} ships it. An unsupported image type fails the build.
|
|
141
|
+
* @param pluginRoot The plugin's directory.
|
|
142
|
+
* @param collector Where imported images are recorded.
|
|
143
|
+
* @returns The rollup plugin.
|
|
144
|
+
*/
|
|
145
|
+
export declare function assetImports(pluginRoot: string, collector: AssetCollector): Plugin;
|
|
146
|
+
/**
|
|
147
|
+
* @description Ships a plugin's images: every imported one, and each image in `assets/` whose name the bundle
|
|
148
|
+
* spells out as a whole string, as `assets.getIdByName('<name>')` does. Each file is emitted once beside the bundle as
|
|
149
|
+
* `assets/<sha256>.<type>`, and `assets` in the dist manifest lists each image's size and files.
|
|
150
|
+
* @param pluginRoot The plugin's directory.
|
|
151
|
+
* @param collector The images the build's modules imported.
|
|
152
|
+
* @returns The rollup plugin.
|
|
153
|
+
*/
|
|
154
|
+
export declare function assets(pluginRoot: string, collector: AssetCollector): Plugin;
|
|
155
|
+
type BytecodeOptions = {
|
|
156
|
+
versions: number[];
|
|
157
|
+
sourceName: string;
|
|
158
|
+
};
|
|
159
|
+
/**
|
|
160
|
+
* @description Wraps a plugin bundle the way the client evaluates its source, so the compiled program's completion value is the same factory.
|
|
161
|
+
* @param code The plugin's final bundle expression.
|
|
162
|
+
* @returns The wrapped source.
|
|
163
|
+
*/
|
|
164
|
+
export declare function bytecodeSource(code: string): string;
|
|
165
|
+
/**
|
|
166
|
+
* @description Names the compiled bundle beside a source bundle, matching the loader's lookup.
|
|
167
|
+
* @param fileName The source bundle's file name, such as `index.js`.
|
|
168
|
+
* @param version The Hermes bytecode version.
|
|
169
|
+
* @returns The bytecode file name, such as `index.98.hbc`.
|
|
170
|
+
*/
|
|
171
|
+
export declare function bytecodeFileName(fileName: string, version: number): string;
|
|
172
|
+
/**
|
|
173
|
+
* @description Counts how many of hermesc's newest versions to resolve so every wanted version is among them.
|
|
174
|
+
* @param latest hermesc's versions, newest first.
|
|
175
|
+
* @param wanted The bytecode versions to compile for.
|
|
176
|
+
* @returns The number of newest versions to resolve.
|
|
177
|
+
*/
|
|
178
|
+
export declare function hermescCount(latest: number[], wanted: number[]): number;
|
|
179
|
+
/**
|
|
180
|
+
* @description Compiles each plugin bundle to Hermes bytecode for exactly the bytecode versions the client ships.
|
|
181
|
+
* @param options The bytecode versions to compile for, and the name the bytecode's source locations use.
|
|
182
|
+
* @returns The rollup plugin.
|
|
183
|
+
*/
|
|
184
|
+
export declare function hermesBytecode({ versions, sourceName }: BytecodeOptions): Plugin;
|
|
185
|
+
declare const reportSectionSchema: Type.TUnion<[Type.TLiteral<"inlined">, Type.TLiteral<"shipped">, Type.TLiteral<"metro">]>;
|
|
186
|
+
/** What one feature plugin adds to the build report. Entries with the same section and label merge into one line. */
|
|
187
|
+
declare const reportEntrySchema: Type.TObject<{
|
|
188
|
+
section: Type.TUnion<[Type.TLiteral<"inlined">, Type.TLiteral<"shipped">, Type.TLiteral<"metro">]>;
|
|
189
|
+
label: Type.TString;
|
|
190
|
+
/** Show how many entries merged, as `(3 <unit>s)`, or `(3)` for `''`. */
|
|
191
|
+
unit: Type.TOptional<Type.TString>;
|
|
192
|
+
/** Bytes this entry adds; merged entries sum them. */
|
|
193
|
+
bytes: Type.TOptional<Type.TInteger>;
|
|
194
|
+
/** Counts this entry under a kind, such as `keyed` or `uncached`. */
|
|
195
|
+
tally: Type.TOptional<Type.TString>;
|
|
196
|
+
/** Where the entry comes from, such as `src/index.tsx:12`. */
|
|
197
|
+
location: Type.TOptional<Type.TString>;
|
|
198
|
+
/** Names this entry in the `ubd dev` summary, such as `uncached filter`. */
|
|
199
|
+
issue: Type.TOptional<Type.TString>;
|
|
200
|
+
}>;
|
|
201
|
+
declare const budgetNameSchema: Type.TUnion<[Type.TLiteral<"bundle">, Type.TLiteral<"assets">]>;
|
|
202
|
+
/** What `ubd build` reports for one plugin, written by the preset's `build-report` plugin. */
|
|
203
|
+
declare const buildReportSchema: Type.TObject<{
|
|
204
|
+
name: Type.TString;
|
|
205
|
+
version: Type.TString;
|
|
206
|
+
bundle: Type.TObject<{
|
|
207
|
+
file: Type.TString;
|
|
208
|
+
bytes: Type.TInteger;
|
|
209
|
+
gzip: Type.TInteger;
|
|
210
|
+
bytecode: Type.TArray<Type.TObject<{
|
|
211
|
+
version: Type.TInteger;
|
|
212
|
+
bytes: Type.TInteger;
|
|
213
|
+
}>>;
|
|
214
|
+
}>;
|
|
215
|
+
inlined: Type.TArray<Type.TObject<{
|
|
216
|
+
label: Type.TString;
|
|
217
|
+
/** When set, the line shows how many contributions it merged, as `(3 <unit>s)`, or `(3)` for `''`. */
|
|
218
|
+
unit: Type.TOptional<Type.TString>;
|
|
219
|
+
items: Type.TInteger;
|
|
220
|
+
bytes: Type.TInteger;
|
|
221
|
+
/** Contributions counted by kind, such as `{ keyed: 9, uncached: 1 }`. */
|
|
222
|
+
tallies: Type.TRecord<"^.*$", Type.TInteger>;
|
|
223
|
+
/** Where each located contribution came from, such as `src/index.tsx:12`. */
|
|
224
|
+
locations: Type.TArray<Type.TString>;
|
|
225
|
+
/** Contributions `ubd dev` names in its one-line summary, counted by noun, such as `uncached filter`. */
|
|
226
|
+
issues: Type.TRecord<"^.*$", Type.TInteger>;
|
|
227
|
+
}>>;
|
|
228
|
+
shipped: Type.TArray<Type.TObject<{
|
|
229
|
+
label: Type.TString;
|
|
230
|
+
/** When set, the line shows how many contributions it merged, as `(3 <unit>s)`, or `(3)` for `''`. */
|
|
231
|
+
unit: Type.TOptional<Type.TString>;
|
|
232
|
+
items: Type.TInteger;
|
|
233
|
+
bytes: Type.TInteger;
|
|
234
|
+
/** Contributions counted by kind, such as `{ keyed: 9, uncached: 1 }`. */
|
|
235
|
+
tallies: Type.TRecord<"^.*$", Type.TInteger>;
|
|
236
|
+
/** Where each located contribution came from, such as `src/index.tsx:12`. */
|
|
237
|
+
locations: Type.TArray<Type.TString>;
|
|
238
|
+
/** Contributions `ubd dev` names in its one-line summary, counted by noun, such as `uncached filter`. */
|
|
239
|
+
issues: Type.TRecord<"^.*$", Type.TInteger>;
|
|
240
|
+
}>>;
|
|
241
|
+
metro: Type.TArray<Type.TObject<{
|
|
242
|
+
label: Type.TString;
|
|
243
|
+
/** When set, the line shows how many contributions it merged, as `(3 <unit>s)`, or `(3)` for `''`. */
|
|
244
|
+
unit: Type.TOptional<Type.TString>;
|
|
245
|
+
items: Type.TInteger;
|
|
246
|
+
bytes: Type.TInteger;
|
|
247
|
+
/** Contributions counted by kind, such as `{ keyed: 9, uncached: 1 }`. */
|
|
248
|
+
tallies: Type.TRecord<"^.*$", Type.TInteger>;
|
|
249
|
+
/** Where each located contribution came from, such as `src/index.tsx:12`. */
|
|
250
|
+
locations: Type.TArray<Type.TString>;
|
|
251
|
+
/** Contributions `ubd dev` names in its one-line summary, counted by noun, such as `uncached filter`. */
|
|
252
|
+
issues: Type.TRecord<"^.*$", Type.TInteger>;
|
|
253
|
+
}>>;
|
|
254
|
+
/** The largest source modules by rendered size, before minification. */
|
|
255
|
+
modules: Type.TArray<Type.TObject<{
|
|
256
|
+
id: Type.TString;
|
|
257
|
+
bytes: Type.TInteger;
|
|
258
|
+
}>>;
|
|
259
|
+
/** Each budget's limit in bytes. */
|
|
260
|
+
budgets: Type.TObject<{
|
|
261
|
+
bundle: Type.TInteger;
|
|
262
|
+
assets: Type.TInteger;
|
|
263
|
+
}>;
|
|
264
|
+
warnings: Type.TArray<Type.TUnion<[Type.TObject<{
|
|
265
|
+
kind: Type.TLiteral<"budget">;
|
|
266
|
+
budget: Type.TUnion<[Type.TLiteral<"bundle">, Type.TLiteral<"assets">]>;
|
|
267
|
+
bytes: Type.TInteger;
|
|
268
|
+
limit: Type.TInteger;
|
|
269
|
+
}>, Type.TObject<{
|
|
270
|
+
kind: Type.TLiteral<"shim">;
|
|
271
|
+
library: Type.TString;
|
|
272
|
+
modules: Type.TArray<Type.TString>;
|
|
273
|
+
}>]>>;
|
|
274
|
+
}>;
|
|
275
|
+
type ReportSection = StaticDecode<typeof reportSectionSchema>;
|
|
276
|
+
type ReportEntry = StaticDecode<typeof reportEntrySchema>;
|
|
277
|
+
type BudgetName = StaticDecode<typeof budgetNameSchema>;
|
|
278
|
+
type BuildReport = StaticDecode<typeof buildReportSchema>;
|
|
279
|
+
/**
|
|
280
|
+
* @description Wraps a report entry as an asset to pass to `this.emitFile`, from any hook up to `generateBundle`.
|
|
281
|
+
* @param entry The line to add.
|
|
282
|
+
* @returns The asset carrying the entry.
|
|
283
|
+
* @example this.emitFile(reportLine({ section: 'inlined', label: 'svg', unit: '', bytes: xml.length }));
|
|
284
|
+
*/
|
|
285
|
+
export declare function reportLine(entry: ReportEntry): EmittedAsset;
|
|
286
|
+
/** A size in bytes, or a string such as `50kb`, `1.5mb` or `800b`. Units are binary: 1 KB is 1024 bytes. */
|
|
287
|
+
type ByteSize = number | `${number}${'b' | 'kb' | 'mb'}`;
|
|
288
|
+
/**
|
|
289
|
+
* @description Reads a budget size such as `50kb` as bytes.
|
|
290
|
+
* @param size The size, in bytes or with a `b`, `kb` or `mb` unit.
|
|
291
|
+
* @returns The size in bytes.
|
|
292
|
+
*/
|
|
293
|
+
export declare function parseByteSize(size: ByteSize): number;
|
|
294
|
+
type BudgetOptions = Partial<Record<BudgetName, ByteSize>>;
|
|
295
|
+
type BuildReportOptions = {
|
|
296
|
+
pluginRoot: string;
|
|
297
|
+
budget?: BudgetOptions;
|
|
298
|
+
/** Where to write the report as JSON; `ubd` sets it through {@link BUILD_REPORT_ENV}. Nothing is written without it. */
|
|
299
|
+
reportFile?: string;
|
|
300
|
+
};
|
|
301
|
+
export declare const DEFAULT_BUDGET: {
|
|
302
|
+
bundle: "50kb";
|
|
303
|
+
assets: "500kb";
|
|
304
|
+
};
|
|
305
|
+
/**
|
|
306
|
+
* @description Collects the entries feature plugins add with `reportLine`, keeps them out of `dist`, and writes the build report after the bundle is written.
|
|
307
|
+
* @param options The plugin's directory, its budgets, and where to write the report.
|
|
308
|
+
* @returns The rollup plugin. Its `generateBundle` runs last, so it sees the bytecode and every emitted file.
|
|
309
|
+
*/
|
|
310
|
+
export declare function buildReport({ pluginRoot, budget, reportFile }: BuildReportOptions): Plugin;
|
|
311
|
+
type Globals = (id: string) => string;
|
|
312
|
+
/**
|
|
313
|
+
* @description Maps an import the client provides at runtime to the global a plugin bundle reads instead.
|
|
314
|
+
* @param id The import specifier, such as `@unbound-app/api/metro/components`.
|
|
315
|
+
* @returns The global expression, such as `unbound.metro.components`, or an empty string to bundle the import.
|
|
316
|
+
*/
|
|
317
|
+
export declare function unboundGlobals(id: string): string;
|
|
318
|
+
/**
|
|
319
|
+
* @description Builds rollup's `external` option from a globals function.
|
|
320
|
+
* @param globals Maps an import to its runtime global.
|
|
321
|
+
* @returns A predicate that is true for every import the client provides at runtime.
|
|
322
|
+
*/
|
|
323
|
+
export declare function externalFor(globals: Globals): (id: string) => boolean;
|
|
324
|
+
type PluginConfigOptions = {
|
|
325
|
+
lazyModules?: string[];
|
|
326
|
+
bytecode?: boolean;
|
|
327
|
+
budget?: BudgetOptions;
|
|
328
|
+
};
|
|
329
|
+
/**
|
|
330
|
+
* @description Builds a plugin's rollup config with the given runtime globals.
|
|
331
|
+
* @param pluginUrl The `import.meta.url` of the plugin's `rollup.config.mjs`.
|
|
332
|
+
* @param globals Maps each import the client provides at runtime to its global.
|
|
333
|
+
* @param options List `lazyModules` to run only on first use, set `bytecode: false` to skip Hermes bytecode, and `budget` to change the build report's size limits.
|
|
334
|
+
* @returns The rollup config.
|
|
335
|
+
*/
|
|
336
|
+
export declare function pluginConfig(pluginUrl: string, globals: Globals, options?: PluginConfigOptions): RollupOptions;
|
|
337
|
+
/**
|
|
338
|
+
* @description The Unbound plugin preset: bundles `src/index`, reads `unbound`, `React` and the other shared libraries from the bare globals, and emits the manifest and the Hermes expression entrypoint.
|
|
339
|
+
* @param pluginUrl The `import.meta.url` of the plugin's `rollup.config.mjs`.
|
|
340
|
+
* @param options List `lazyModules` to run only on first use, set `bytecode: false` to skip Hermes bytecode, and `budget` to change the build report's size limits.
|
|
341
|
+
* @returns The rollup config.
|
|
342
|
+
*/
|
|
343
|
+
export declare function unbound(pluginUrl: string, options?: PluginConfigOptions): RollupOptions;
|
|
344
|
+
/** A component or hook that reads the plugin's strings, and the line it starts on. */
|
|
345
|
+
type LocaleReader = {
|
|
346
|
+
name: string;
|
|
347
|
+
start: number;
|
|
348
|
+
};
|
|
349
|
+
/** What {@link injectLocaleHooks} did to one module. */
|
|
350
|
+
type LocaleHookResult = {
|
|
351
|
+
/** The source with `useMessages()` added, or `undefined` when nothing changed. */
|
|
352
|
+
output: MagicString | undefined;
|
|
353
|
+
injected: LocaleReader[];
|
|
354
|
+
/** Class components that read strings; they cannot call hooks. */
|
|
355
|
+
classReaders: LocaleReader[];
|
|
356
|
+
};
|
|
357
|
+
/**
|
|
358
|
+
* @description Adds `useMessages()` at the top of each component and hook that reads `messages` or `format` from
|
|
359
|
+
* {@link LOCALE_MODULE}, directly, in a nested callback or through a same-file helper, unless it already calls it.
|
|
360
|
+
* @param code The module's source.
|
|
361
|
+
* @param program The module's program, as `rolldown/utils` parses it.
|
|
362
|
+
* @returns The edited source, the components and hooks that got the call, and the class components that read strings.
|
|
363
|
+
*/
|
|
364
|
+
export declare function injectLocaleHooks(code: string, program: ESTree.Program): LocaleHookResult;
|
|
365
|
+
/**
|
|
366
|
+
* @description Adds `useMessages()` to every plugin component and hook that reads its strings, so each re-renders on
|
|
367
|
+
* a locale change. Runs before swc, so React Refresh signatures include the added hook. Warns on class components
|
|
368
|
+
* that read strings, and logs each addition when `UNBOUND_VERBOSE=1`.
|
|
369
|
+
* @param pluginRoot The plugin's directory; only its own sources are transformed.
|
|
370
|
+
* @returns The rollup plugin.
|
|
371
|
+
*/
|
|
372
|
+
export declare function localeHooks(pluginRoot: string): Plugin;
|
|
373
|
+
type LazyModuleBuild = {
|
|
374
|
+
plugins: () => Plugin[];
|
|
375
|
+
external: (id: string) => boolean;
|
|
376
|
+
globals: Globals;
|
|
377
|
+
};
|
|
378
|
+
/**
|
|
379
|
+
* @description Generates a module whose exports load the wrapped bundle the first time one is used.
|
|
380
|
+
* @param code The wrapped module, built as an IIFE assigned to `__unboundLazyModule`.
|
|
381
|
+
* @param exportNames The wrapped module's export names.
|
|
382
|
+
* @returns The generated module's source.
|
|
383
|
+
*/
|
|
384
|
+
export declare function lazyModuleSource(code: string, exportNames: string[]): string;
|
|
385
|
+
/**
|
|
386
|
+
* @description Builds the listed modules apart and evaluates each only when the plugin first uses one of its exports.
|
|
387
|
+
* @param specifiers Import specifiers to defer, such as `highlight.js/lib/common`.
|
|
388
|
+
* @param build How to build each deferred module.
|
|
389
|
+
* @returns The rollup plugin.
|
|
390
|
+
*/
|
|
391
|
+
export declare function lazyModules(specifiers: string[], build: LazyModuleBuild): Plugin;
|
|
392
|
+
/**
|
|
393
|
+
* @description Rewrites each chunk into the object expression the client evaluates: `__load()` runs the plugin's
|
|
394
|
+
* module once and returns its named exports, which the client reads as `start`, `stop` and `SettingsPanel`.
|
|
395
|
+
* @returns The rollup plugin.
|
|
396
|
+
*/
|
|
397
|
+
export declare function hermesExpressionEntrypoint(): Plugin;
|
|
398
|
+
/**
|
|
399
|
+
* @description Resolves a module path the way a bundler would, trying source extensions and then index files.
|
|
400
|
+
* @param candidate An absolute path, with or without its extension.
|
|
401
|
+
* @returns The file it resolves to, or `null` when none exists.
|
|
402
|
+
*/
|
|
403
|
+
export declare function findFile(candidate: string): string | null;
|
|
404
|
+
/**
|
|
405
|
+
* @description Resolves `~/` imports to a plugin's `src` directory, mirroring the `paths` in its tsconfig.
|
|
406
|
+
* @param sourceRoot Absolute path of the plugin's `src` directory.
|
|
407
|
+
* @returns The rollup plugin.
|
|
408
|
+
*/
|
|
409
|
+
export declare function sourceAlias(sourceRoot: string): Plugin;
|
|
410
|
+
/**
|
|
411
|
+
* @description Gives every filter a plugin passes to a metro search a cache key derived from its source, so equal filters in any plugin share one persisted cache entry.
|
|
412
|
+
* A predicate equal to built-in filters becomes those built-ins; one that reads an outside variable fails the build.
|
|
413
|
+
* Runs on the bundled chunk, where an imported or aliased predicate resolves to its definition, before minify renames anything.
|
|
414
|
+
* @param pluginRoot The plugin's directory, which diagnostics name files relative to.
|
|
415
|
+
* @returns The rollup plugin.
|
|
416
|
+
*/
|
|
417
|
+
export declare function filterKeys(pluginRoot: string): Plugin;
|
|
418
|
+
export type { BudgetOptions, BuildReport, ByteSize, Diagnostic, Globals, PluginConfigOptions, ReportEntry, ReportSection, Severity };
|