@assistant-ui/metro 0.0.12 → 0.0.13
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.cjs +9 -2
- package/dist/index.cjs.map +1 -1
- package/dist/transformer.cjs +66 -18
- package/dist/transformer.cjs.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +12 -7
- package/src/transformer.test.ts +26 -2
package/dist/index.cjs
CHANGED
|
@@ -41,8 +41,15 @@ function withAui(config) {
|
|
|
41
41
|
const self = require$1.resolve("@assistant-ui/metro/transformer");
|
|
42
42
|
const { aui, ...baseConfig } = config;
|
|
43
43
|
const upstream = config.transformer?.babelTransformerPath;
|
|
44
|
-
|
|
45
|
-
if (
|
|
44
|
+
const isRewrap = upstream === self;
|
|
45
|
+
if (aui !== void 0 || !isRewrap) {
|
|
46
|
+
if (aui?.backendless) process.env[BACKENDLESS_ENV] = "1";
|
|
47
|
+
else delete process.env[BACKENDLESS_ENV];
|
|
48
|
+
}
|
|
49
|
+
if (!isRewrap) {
|
|
50
|
+
if (upstream) process.env[UPSTREAM_TRANSFORMER_ENV] = upstream;
|
|
51
|
+
else delete process.env[UPSTREAM_TRANSFORMER_ENV];
|
|
52
|
+
}
|
|
46
53
|
return {
|
|
47
54
|
...baseConfig,
|
|
48
55
|
transformer: {
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["require","createRequire"],"sources":["../src/index.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\n\nconst require = createRequire(import.meta.url);\n\n/**\n * The shape of a Metro / Expo config `withAui` accepts. Typed loosely so this\n * package needs no hard dependency on `metro` or `expo`; the input shape is\n * preserved and only `transformer.babelTransformerPath` is augmented.\n */\nexport type MetroConfigLike = {\n /** assistant-ui options, stripped from the config handed back to Metro. */\n aui?: WithAuiOptions | undefined;\n transformer?:\n | {\n babelTransformerPath?: string | undefined;\n [key: string]: unknown;\n }\n | undefined;\n [key: string]: unknown;\n};\n\n/**\n * Env var the transformer reads to delegate to the project's existing\n * (Expo / React Native) babel transformer. Metro forks its transform workers\n * after `metro.config` is evaluated, so they inherit this value.\n */\nexport const UPSTREAM_TRANSFORMER_ENV = \"AUI_METRO_UPSTREAM_TRANSFORMER\";\n\n/**\n * Env var carrying the `backendless` option to the transformer. Metro forks its\n * transform workers after `metro.config` is evaluated, so they inherit it.\n */\nexport const BACKENDLESS_ENV = \"AUI_METRO_BACKENDLESS\";\n\nexport interface WithAuiOptions {\n /**\n * The app has no backend importing the server builds of its `\"use generative\"`\n * modules (e.g. cloud-hosted runs). Frontend/human tool schemas then stay\n * uploadable from the client instead of being assumed backend-known.\n */\n backendless?: boolean;\n}\n\n/**\n * Wraps a Metro (or Expo) config so assistant-ui `\"use generative\"` modules are\n * compiled. Such a file colocates a tool's schema, its `execute`, and its\n * `render` via {@link https://www.assistant-ui.com/docs/tools/defining-tools | defineToolkit}.\n *\n * It points Metro's `babelTransformerPath` at this package's transformer, which\n * runs the `\"use generative\"` compiler and then delegates to your project's\n * existing transformer. The device (client) build keeps each tool's `render`\n * and any frontend `execute`; a server build (an Expo Router `+api` route)\n * keeps a server `execute` instead.\n *\n * ```js\n * // metro.config.js\n * const { getDefaultConfig } = require(\"expo/metro-config\");\n * const { withAui } = require(\"@assistant-ui/metro\");\n *\n * module.exports = withAui({\n * ...getDefaultConfig(__dirname),\n * aui: { backendless: true },\n * });\n * ```\n *\n * Authoring is identical to the web: a `\"use generative\"` file whose default\n * export is `defineToolkit({ ... })`, registered with `Tools({ toolkit })`.\n */\nexport function withAui<T extends MetroConfigLike>(config: T): T {\n const self = require.resolve(\"@assistant-ui/metro/transformer\");\n // Metro validates config keys it does not recognize, so `aui` must not\n // survive into the returned config.\n const { aui, ...baseConfig } = config;\n const upstream = config.transformer?.babelTransformerPath;\n\n
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["require","createRequire"],"sources":["../src/index.ts"],"sourcesContent":["import { createRequire } from \"node:module\";\n\nconst require = createRequire(import.meta.url);\n\n/**\n * The shape of a Metro / Expo config `withAui` accepts. Typed loosely so this\n * package needs no hard dependency on `metro` or `expo`; the input shape is\n * preserved and only `transformer.babelTransformerPath` is augmented.\n */\nexport type MetroConfigLike = {\n /** assistant-ui options, stripped from the config handed back to Metro. */\n aui?: WithAuiOptions | undefined;\n transformer?:\n | {\n babelTransformerPath?: string | undefined;\n [key: string]: unknown;\n }\n | undefined;\n [key: string]: unknown;\n};\n\n/**\n * Env var the transformer reads to delegate to the project's existing\n * (Expo / React Native) babel transformer. Metro forks its transform workers\n * after `metro.config` is evaluated, so they inherit this value.\n */\nexport const UPSTREAM_TRANSFORMER_ENV = \"AUI_METRO_UPSTREAM_TRANSFORMER\";\n\n/**\n * Env var carrying the `backendless` option to the transformer. Metro forks its\n * transform workers after `metro.config` is evaluated, so they inherit it.\n */\nexport const BACKENDLESS_ENV = \"AUI_METRO_BACKENDLESS\";\n\nexport interface WithAuiOptions {\n /**\n * The app has no backend importing the server builds of its `\"use generative\"`\n * modules (e.g. cloud-hosted runs). Frontend/human tool schemas then stay\n * uploadable from the client instead of being assumed backend-known.\n */\n backendless?: boolean;\n}\n\n/**\n * Wraps a Metro (or Expo) config so assistant-ui `\"use generative\"` modules are\n * compiled. Such a file colocates a tool's schema, its `execute`, and its\n * `render` via {@link https://www.assistant-ui.com/docs/tools/defining-tools | defineToolkit}.\n *\n * It points Metro's `babelTransformerPath` at this package's transformer, which\n * runs the `\"use generative\"` compiler and then delegates to your project's\n * existing transformer. The device (client) build keeps each tool's `render`\n * and any frontend `execute`; a server build (an Expo Router `+api` route)\n * keeps a server `execute` instead.\n *\n * ```js\n * // metro.config.js\n * const { getDefaultConfig } = require(\"expo/metro-config\");\n * const { withAui } = require(\"@assistant-ui/metro\");\n *\n * module.exports = withAui({\n * ...getDefaultConfig(__dirname),\n * aui: { backendless: true },\n * });\n * ```\n *\n * Authoring is identical to the web: a `\"use generative\"` file whose default\n * export is `defineToolkit({ ... })`, registered with `Tools({ toolkit })`.\n */\nexport function withAui<T extends MetroConfigLike>(config: T): T {\n const self = require.resolve(\"@assistant-ui/metro/transformer\");\n // Metro validates config keys it does not recognize, so `aui` must not\n // survive into the returned config.\n const { aui, ...baseConfig } = config;\n const upstream = config.transformer?.babelTransformerPath;\n\n // A double-wrap is a re-entry: the first call already captured the real\n // upstream, and replacing it with our transformer would cause recursion.\n // An explicit `aui` on a rewrap still replaces the backendless setting.\n const isRewrap = upstream === self;\n\n if (aui !== undefined || !isRewrap) {\n if (aui?.backendless) process.env[BACKENDLESS_ENV] = \"1\";\n else delete process.env[BACKENDLESS_ENV];\n }\n\n if (!isRewrap) {\n if (upstream) process.env[UPSTREAM_TRANSFORMER_ENV] = upstream;\n else delete process.env[UPSTREAM_TRANSFORMER_ENV];\n }\n\n return {\n ...baseConfig,\n transformer: {\n ...config.transformer,\n babelTransformerPath: self,\n },\n } as T;\n}\n"],"mappings":";;AAEA,MAAMA,aAAAA,wBAAUC,CAAAA,CAAAA,cAAAA,CAAAA,QAAAA,KAAAA,CAAAA,CAAAA,cAAAA,UAAAA,CAAAA,CAAAA,IAA6B;;;;;;AAwB7C,MAAa,2BAA2B;;;;;AAMxC,MAAa,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;AAoC/B,SAAgB,QAAmC,QAAc;CAC/D,MAAM,OAAOD,UAAQ,QAAQ,iCAAiC;CAG9D,MAAM,EAAE,KAAK,GAAG,eAAe;CAC/B,MAAM,WAAW,OAAO,aAAa;CAKrC,MAAM,WAAW,aAAa;CAE9B,IAAI,QAAQ,KAAA,KAAa,CAAC,UAAU;EAClC,IAAI,KAAK,aAAa,QAAQ,IAAI,mBAAmB;OAChD,OAAO,QAAQ,IAAI;CAC1B;CAEA,IAAI,CAAC,UAAU;EACb,IAAI,UAAU,QAAQ,IAAI,4BAA4B;OACjD,OAAO,QAAQ,IAAI;CAC1B;CAEA,OAAO;EACL,GAAG;EACH,aAAa;GACX,GAAG,OAAO;GACV,sBAAsB;EACxB;CACF;AACF"}
|
package/dist/transformer.cjs
CHANGED
|
@@ -26,6 +26,7 @@ const require_index = require("./index.cjs");
|
|
|
26
26
|
let node_module = require("node:module");
|
|
27
27
|
let node_fs = require("node:fs");
|
|
28
28
|
let node_url = require("node:url");
|
|
29
|
+
let node_crypto = require("node:crypto");
|
|
29
30
|
let node_path = require("node:path");
|
|
30
31
|
node_path = __toESM(node_path, 1);
|
|
31
32
|
let _babel_parser = require("@babel/parser");
|
|
@@ -40,7 +41,7 @@ _babel_types = __toESM(_babel_types, 1);
|
|
|
40
41
|
const DIRECTIVE = "use generative";
|
|
41
42
|
//#endregion
|
|
42
43
|
//#region ../x-generative-compiler/dist/package.js
|
|
43
|
-
var version = "0.0.
|
|
44
|
+
var version = "0.0.17";
|
|
44
45
|
//#endregion
|
|
45
46
|
//#region ../../node_modules/.pnpm/semver@7.8.5/node_modules/semver/internal/constants.js
|
|
46
47
|
var require_constants = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
@@ -1590,6 +1591,7 @@ function isLineTerminator(char) {
|
|
|
1590
1591
|
* regions that target needs and pruning the imports the dropped regions used.
|
|
1591
1592
|
*/
|
|
1592
1593
|
function compileGenerative(code, options) {
|
|
1594
|
+
currentCompilePass++;
|
|
1593
1595
|
const { target, filename } = options;
|
|
1594
1596
|
const backendless = options.backendless ?? false;
|
|
1595
1597
|
const ast = (0, _babel_parser.parse)(code, {
|
|
@@ -2015,39 +2017,78 @@ function matchAliasPattern(pattern, source) {
|
|
|
2015
2017
|
if (source.length >= prefix.length + suffix.length && source.startsWith(prefix) && source.endsWith(suffix)) return source.slice(prefix.length, source.length - suffix.length);
|
|
2016
2018
|
return null;
|
|
2017
2019
|
}
|
|
2018
|
-
/**
|
|
2019
|
-
* Memoizes resolved aliases per start directory. The compiler runs once per
|
|
2020
|
-
* file across a build, so without this every aliased spread re-walks and
|
|
2021
|
-
* re-parses the same `tsconfig.json`. Process-lifetime, like
|
|
2022
|
-
* `checkedCorePackageJsonPaths`.
|
|
2023
|
-
*/
|
|
2024
2020
|
const tsconfigAliasesByDir = /* @__PURE__ */ new Map();
|
|
2021
|
+
let currentCompilePass = 0;
|
|
2022
|
+
function tsconfigFileVersion(content) {
|
|
2023
|
+
return (0, node_crypto.createHash)("sha1").update(content).digest("hex");
|
|
2024
|
+
}
|
|
2025
|
+
function readTsconfigFile(state, path) {
|
|
2026
|
+
const cached = state.fileContents.get(path);
|
|
2027
|
+
if (cached !== void 0) return cached;
|
|
2028
|
+
let content;
|
|
2029
|
+
try {
|
|
2030
|
+
content = (0, node_fs.readFileSync)(path, "utf8");
|
|
2031
|
+
} catch {
|
|
2032
|
+
content = null;
|
|
2033
|
+
}
|
|
2034
|
+
state.fileContents.set(path, content);
|
|
2035
|
+
state.fileVersions.set(path, content === null ? null : tsconfigFileVersion(content));
|
|
2036
|
+
return content;
|
|
2037
|
+
}
|
|
2038
|
+
function currentFileVersion(path) {
|
|
2039
|
+
try {
|
|
2040
|
+
return tsconfigFileVersion((0, node_fs.readFileSync)(path, "utf8"));
|
|
2041
|
+
} catch {
|
|
2042
|
+
return null;
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
function isTsconfigCacheCurrent(entry) {
|
|
2046
|
+
for (const [path, version] of entry.fileVersions) if (currentFileVersion(path) !== version) return false;
|
|
2047
|
+
return true;
|
|
2048
|
+
}
|
|
2025
2049
|
/** Walks up from a directory to the nearest `tsconfig.json` that declares `paths`. */
|
|
2026
2050
|
function loadTsconfigAliases(fromDir) {
|
|
2027
2051
|
const cached = tsconfigAliasesByDir.get(fromDir);
|
|
2028
|
-
if (cached
|
|
2052
|
+
if (cached) {
|
|
2053
|
+
if (cached.validatedInPass === currentCompilePass || cached.reuseAcrossPasses && isTsconfigCacheCurrent(cached)) {
|
|
2054
|
+
cached.validatedInPass = currentCompilePass;
|
|
2055
|
+
return cached.aliases;
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2029
2058
|
let aliases = null;
|
|
2059
|
+
const state = {
|
|
2060
|
+
fileVersions: /* @__PURE__ */ new Map(),
|
|
2061
|
+
fileContents: /* @__PURE__ */ new Map(),
|
|
2062
|
+
cacheable: true
|
|
2063
|
+
};
|
|
2030
2064
|
let dir = fromDir;
|
|
2031
2065
|
for (;;) {
|
|
2032
2066
|
const tsconfigPath = node_path.join(dir, "tsconfig.json");
|
|
2033
|
-
if ((
|
|
2034
|
-
aliases = readTsconfigAliases(tsconfigPath, /* @__PURE__ */ new Set());
|
|
2067
|
+
if (readTsconfigFile(state, tsconfigPath) !== null) {
|
|
2068
|
+
aliases = readTsconfigAliases(tsconfigPath, /* @__PURE__ */ new Set(), state);
|
|
2035
2069
|
if (aliases) break;
|
|
2036
2070
|
}
|
|
2037
2071
|
const parent = node_path.dirname(dir);
|
|
2038
2072
|
if (parent === dir) break;
|
|
2039
2073
|
dir = parent;
|
|
2040
2074
|
}
|
|
2041
|
-
tsconfigAliasesByDir.set(fromDir,
|
|
2075
|
+
tsconfigAliasesByDir.set(fromDir, {
|
|
2076
|
+
aliases,
|
|
2077
|
+
fileVersions: state.fileVersions,
|
|
2078
|
+
validatedInPass: currentCompilePass,
|
|
2079
|
+
reuseAcrossPasses: state.cacheable
|
|
2080
|
+
});
|
|
2042
2081
|
return aliases;
|
|
2043
2082
|
}
|
|
2044
2083
|
/** Reads `baseUrl`/`paths` from a tsconfig, following a single `extends` chain. */
|
|
2045
|
-
function readTsconfigAliases(tsconfigPath, seen) {
|
|
2084
|
+
function readTsconfigAliases(tsconfigPath, seen, state) {
|
|
2046
2085
|
if (seen.has(tsconfigPath)) return null;
|
|
2047
2086
|
seen.add(tsconfigPath);
|
|
2087
|
+
const raw = readTsconfigFile(state, tsconfigPath);
|
|
2088
|
+
if (raw === null) return null;
|
|
2048
2089
|
let config;
|
|
2049
2090
|
try {
|
|
2050
|
-
config = parseJsonc(
|
|
2091
|
+
config = parseJsonc(raw);
|
|
2051
2092
|
} catch {
|
|
2052
2093
|
return null;
|
|
2053
2094
|
}
|
|
@@ -2062,23 +2103,30 @@ function readTsconfigAliases(tsconfigPath, seen) {
|
|
|
2062
2103
|
for (let i = extendsList.length - 1; i >= 0; i--) {
|
|
2063
2104
|
const entry = extendsList[i];
|
|
2064
2105
|
if (typeof entry !== "string") continue;
|
|
2065
|
-
const extended = resolveExtendedTsconfig(entry, configDir);
|
|
2106
|
+
const extended = resolveExtendedTsconfig(entry, configDir, state);
|
|
2066
2107
|
if (extended) {
|
|
2067
|
-
const aliases = readTsconfigAliases(extended, seen);
|
|
2108
|
+
const aliases = readTsconfigAliases(extended, seen, state);
|
|
2068
2109
|
if (aliases) return aliases;
|
|
2069
2110
|
}
|
|
2070
2111
|
}
|
|
2071
2112
|
return null;
|
|
2072
2113
|
}
|
|
2073
2114
|
/** Resolves a tsconfig `extends` value (relative path or installed package). */
|
|
2074
|
-
function resolveExtendedTsconfig(extendsValue, configDir) {
|
|
2115
|
+
function resolveExtendedTsconfig(extendsValue, configDir, state) {
|
|
2075
2116
|
if (extendsValue.startsWith(".")) {
|
|
2076
2117
|
const base = node_path.resolve(configDir, extendsValue);
|
|
2077
|
-
|
|
2118
|
+
const candidates = node_path.extname(base) === ".json" ? [base] : [`${base}.json`, base];
|
|
2119
|
+
for (const candidate of candidates) if (readTsconfigFile(state, candidate) !== null) return candidate;
|
|
2120
|
+
return null;
|
|
2078
2121
|
}
|
|
2122
|
+
const request = extendsValue.endsWith(".json") ? extendsValue : `${extendsValue}.json`;
|
|
2123
|
+
const requireFromConfig = (0, node_module.createRequire)(node_path.join(configDir, "package.json"));
|
|
2079
2124
|
try {
|
|
2080
|
-
|
|
2125
|
+
const resolved = requireFromConfig.resolve(request);
|
|
2126
|
+
readTsconfigFile(state, resolved);
|
|
2127
|
+
return resolved;
|
|
2081
2128
|
} catch {
|
|
2129
|
+
state.cacheable = false;
|
|
2082
2130
|
return null;
|
|
2083
2131
|
}
|
|
2084
2132
|
}
|