@swipt/cli 0.1.0-nightly.35 → 0.1.0-nightly.37
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/metro.mjs +251 -0
- package/package.json +9 -3
- package/project.mjs +16 -3
package/metro.mjs
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
import { transformAsync } from "@babel/core";
|
|
7
|
+
import { loadProjectEnv } from "@expo/env";
|
|
8
|
+
import { imageSize } from "image-size";
|
|
9
|
+
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
const cliDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
|
|
13
|
+
const sourceExtensions = ["js", "jsx", "json", "ts", "tsx", "cjs", "mjs"];
|
|
14
|
+
const assetExtensions = [
|
|
15
|
+
"bmp", "gif", "jpg", "jpeg", "png", "psd", "svg", "webp", "xml", "m4v", "mov", "mp4", "mpeg", "mpg", "webm", "aac", "aiff", "caf",
|
|
16
|
+
"m4a", "mp3", "wav", "html", "pdf", "yaml", "yml", "otf", "ttf", "zip",
|
|
17
|
+
];
|
|
18
|
+
const imageExtensions = new Set(["png", "jpg", "jpeg", "bmp", "gif", "webp", "psd", "svg", "tiff", "ktx"]);
|
|
19
|
+
const babelConfigNames = [
|
|
20
|
+
".babelrc", ".babelrc.js", ".babelrc.cjs", ".babelrc.mjs", ".babelrc.json", ".babelrc.cts",
|
|
21
|
+
"babel.config.js", "babel.config.cjs", "babel.config.mjs", "babel.config.json", "babel.config.cts", "babel.config.ts", "babel.config.mts",
|
|
22
|
+
];
|
|
23
|
+
const contextModes = ["sync", "eager", "lazy", "lazy-once"];
|
|
24
|
+
const scaleSuffixes = ["", "@1x", "@1.5x", "@2x", "@3x", "@4x"];
|
|
25
|
+
const platformSuffixes = ["", ".ios", ".native"];
|
|
26
|
+
const platformRank = { ios: 2, native: 1 };
|
|
27
|
+
|
|
28
|
+
const posix = file => file.split(path.sep).join("/");
|
|
29
|
+
const escape = text => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
30
|
+
|
|
31
|
+
export function loadEnvironment(root) {
|
|
32
|
+
loadProjectEnv(root, { mode: "production", silent: true });
|
|
33
|
+
process.env.BABEL_ENV ??= "production";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function defaultPreset(root) {
|
|
37
|
+
try {
|
|
38
|
+
return createRequire(path.join(root, "package.json")).resolve("expo/internal/babel-preset");
|
|
39
|
+
} catch {
|
|
40
|
+
return require.resolve("babel-preset-expo");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function contextFiles(directory, recursive) {
|
|
45
|
+
if (!fs.existsSync(directory)) {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
const extensions = new Set([...sourceExtensions, ...assetExtensions]);
|
|
49
|
+
return fs.readdirSync(directory, { recursive })
|
|
50
|
+
.map(posix)
|
|
51
|
+
.filter(file => extensions.has(path.extname(file).slice(1)) && fs.statSync(path.join(directory, file)).isFile())
|
|
52
|
+
.sort();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function requireContext({ types: t, template }) {
|
|
56
|
+
const argument = (call, index, fallback, expected, check) => {
|
|
57
|
+
if (call.node.arguments[index] === undefined) {
|
|
58
|
+
if (fallback !== undefined) {
|
|
59
|
+
return fallback;
|
|
60
|
+
}
|
|
61
|
+
} else {
|
|
62
|
+
const { confident, value } = call.get(`arguments.${index}`).evaluate();
|
|
63
|
+
if (confident && check(value)) {
|
|
64
|
+
return value;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
throw call.buildCodeFrameError(`argument ${index + 1} of require.context must be ${expected}`);
|
|
68
|
+
};
|
|
69
|
+
const pattern = (call, index) => {
|
|
70
|
+
const node = call.node.arguments[index];
|
|
71
|
+
if (node === undefined) {
|
|
72
|
+
return /^\.\/.*$/;
|
|
73
|
+
}
|
|
74
|
+
if (!t.isRegExpLiteral(node)) {
|
|
75
|
+
throw call.buildCodeFrameError(`argument ${index + 1} of require.context must be a regular expression literal`);
|
|
76
|
+
}
|
|
77
|
+
return new RegExp(node.pattern, node.flags.replace(/[gy]/g, ""));
|
|
78
|
+
};
|
|
79
|
+
return {
|
|
80
|
+
name: "swipt-require-context",
|
|
81
|
+
visitor: {
|
|
82
|
+
CallExpression(call, state) {
|
|
83
|
+
const { callee } = call.node;
|
|
84
|
+
if (
|
|
85
|
+
!t.isMemberExpression(callee) || !t.isIdentifier(callee.object, { name: "require" }) || call.scope.hasBinding("require")
|
|
86
|
+
|| !(callee.computed ? t.isStringLiteral(callee.property, { value: "context" }) : t.isIdentifier(callee.property, { name: "context" }))
|
|
87
|
+
) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const from = path.dirname(state.filename);
|
|
91
|
+
const directory = path.resolve(from, argument(call, 0, undefined, "a string", value => typeof value === "string"));
|
|
92
|
+
const recursive = argument(call, 1, true, "a boolean", value => typeof value === "boolean");
|
|
93
|
+
const filter = pattern(call, 2);
|
|
94
|
+
const mode = argument(call, 3, "sync", `one of ${contextModes.join(", ")}`, value => contextModes.includes(value));
|
|
95
|
+
const entries = contextFiles(directory, recursive)
|
|
96
|
+
.map(file => `./${file}`)
|
|
97
|
+
.filter(key => filter.test(key))
|
|
98
|
+
.map(key => {
|
|
99
|
+
const target = posix(path.relative(from, path.join(directory, key)));
|
|
100
|
+
return `${JSON.stringify(key)}: () => require(${JSON.stringify(target.startsWith(".") ? target : `./${target}`)})`;
|
|
101
|
+
});
|
|
102
|
+
call.replaceWith(template.expression.ast(`(() => {
|
|
103
|
+
const modules = { ${entries.join(", ")} };
|
|
104
|
+
const context = key => {
|
|
105
|
+
if (!Object.prototype.hasOwnProperty.call(modules, key)) {
|
|
106
|
+
throw Object.assign(new Error("Cannot find module '" + key + "'"), { code: "MODULE_NOT_FOUND" });
|
|
107
|
+
}
|
|
108
|
+
return ${mode === "sync" ? "modules[key]()" : "Promise.resolve().then(modules[key])"};
|
|
109
|
+
};
|
|
110
|
+
context.keys = () => Object.keys(modules);
|
|
111
|
+
context.resolve = key => key;
|
|
112
|
+
context.id = ${JSON.stringify(posix(path.relative(state.cwd, directory)) || ".")};
|
|
113
|
+
return context;
|
|
114
|
+
})()`));
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const loaderOf = file => {
|
|
121
|
+
const extension = path.extname(file);
|
|
122
|
+
return extension === ".tsx" ? "tsx" : [".ts", ".mts", ".cts"].includes(extension) ? "ts" : "jsx";
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
function archivedAsset(root, file) {
|
|
126
|
+
const relative = path.relative(root, file).split(path.sep);
|
|
127
|
+
return relative[0] === "assets" ? { path: relative.slice(1).join("/"), bundled: false } : { path: relative.map(part => (part === ".." ? "__" : part)).join("/"), bundled: true };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function assetVariants(resolved) {
|
|
131
|
+
const directory = path.dirname(resolved);
|
|
132
|
+
const extension = path.extname(resolved);
|
|
133
|
+
const stem = path.basename(resolved, extension).replace(/(\.(ios|native))$/, "").replace(/@[\d.]+x$/, "");
|
|
134
|
+
const pattern = new RegExp(`^${escape(stem)}(?:@([\\d.]+)x)?(?:\\.(ios|native))?${escape(extension)}$`);
|
|
135
|
+
const scales = new Map();
|
|
136
|
+
for (const name of fs.readdirSync(directory)) {
|
|
137
|
+
const match = pattern.exec(name);
|
|
138
|
+
if (match === null) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const scale = match[1] === undefined ? 1 : Number(match[1]);
|
|
142
|
+
const rank = platformRank[match[2]] ?? 0;
|
|
143
|
+
if (Number.isFinite(scale) && (scales.get(scale)?.rank ?? -1) < rank) {
|
|
144
|
+
scales.set(scale, { rank, file: path.join(directory, name) });
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return [...scales].sort(([a], [b]) => a - b).map(([scale, { file }]) => ({ scale, file }));
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function assetModule(root, variants, assets) {
|
|
151
|
+
const [first] = variants;
|
|
152
|
+
const extension = path.extname(first.file).slice(1);
|
|
153
|
+
const size = imageExtensions.has(extension) ? imageSize(fs.readFileSync(first.file)) : undefined;
|
|
154
|
+
const files = variants.map(({ scale, file }) => {
|
|
155
|
+
const archived = archivedAsset(root, file);
|
|
156
|
+
if (archived.bundled) {
|
|
157
|
+
assets.set(archived.path, file);
|
|
158
|
+
}
|
|
159
|
+
return [scale, archived.path];
|
|
160
|
+
});
|
|
161
|
+
return `const { PixelRatio } = require("react-native");
|
|
162
|
+
const { assets } = require("@swipt/api/file-system");
|
|
163
|
+
const files = ${JSON.stringify(files)};
|
|
164
|
+
const ratio = PixelRatio.get();
|
|
165
|
+
const [scale, file] = files.find(([scale]) => scale >= ratio) ?? files[files.length - 1];
|
|
166
|
+
module.exports = { uri: assets.file(file).uri, scale${size ? `, width: ${size.width / first.scale}, height: ${size.height / first.scale}` : ""} };
|
|
167
|
+
`;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function checkWorklets(root) {
|
|
171
|
+
let plugin;
|
|
172
|
+
try {
|
|
173
|
+
plugin = require.resolve("react-native-worklets/plugin", { paths: [root] });
|
|
174
|
+
} catch {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const { version } = JSON.parse(fs.readFileSync(path.join(path.dirname(plugin), "..", "package.json"), "utf8"));
|
|
178
|
+
const { version: expected } = require("react-native-worklets/package.json");
|
|
179
|
+
if (version !== expected) {
|
|
180
|
+
throw new Error(`the project has react-native-worklets ${version}, but Swipt runs ${expected}; install react-native-worklets@${expected} or remove it`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function metro(root, { assets, sourcemap }) {
|
|
185
|
+
checkWorklets(root);
|
|
186
|
+
const configName = babelConfigNames.find(name => fs.existsSync(path.join(root, name)));
|
|
187
|
+
const babel = {
|
|
188
|
+
cwd: root,
|
|
189
|
+
root,
|
|
190
|
+
babelrc: false,
|
|
191
|
+
configFile: false,
|
|
192
|
+
sourceType: "unambiguous",
|
|
193
|
+
envName: "production",
|
|
194
|
+
compact: false,
|
|
195
|
+
retainLines: true,
|
|
196
|
+
sourceMaps: sourcemap ? "inline" : false,
|
|
197
|
+
...(configName ? { extends: path.join(root, configName) } : { presets: [defaultPreset(root)] }),
|
|
198
|
+
plugins: [requireContext],
|
|
199
|
+
};
|
|
200
|
+
const caller = {
|
|
201
|
+
name: "metro", bundler: "metro", platform: "ios", engine: "hermes", isDev: false, isServer: false, isReactServer: false, baseUrl: "",
|
|
202
|
+
routerRoot: "app", projectRoot: root, isHMREnabled: true, metroSourceType: "module", supportsStaticESM: true,
|
|
203
|
+
};
|
|
204
|
+
const internal = Symbol("internal");
|
|
205
|
+
return {
|
|
206
|
+
name: "swiplet-metro",
|
|
207
|
+
setup(build) {
|
|
208
|
+
build.onResolve({ filter: /^@babel\/runtime\// }, async args => {
|
|
209
|
+
if (args.pluginData === internal) {
|
|
210
|
+
return undefined;
|
|
211
|
+
}
|
|
212
|
+
const options = { kind: args.kind, importer: args.importer, pluginData: internal };
|
|
213
|
+
const found = await build.resolve(args.path, { ...options, resolveDir: args.resolveDir });
|
|
214
|
+
return found.errors.length === 0 ? found : build.resolve(args.path, { ...options, resolveDir: cliDirectory });
|
|
215
|
+
});
|
|
216
|
+
if (assets) {
|
|
217
|
+
build.onResolve({ filter: new RegExp(`\\.(${assetExtensions.join("|")})$`) }, async args => {
|
|
218
|
+
if (args.pluginData === internal) {
|
|
219
|
+
return undefined;
|
|
220
|
+
}
|
|
221
|
+
const extension = path.extname(args.path);
|
|
222
|
+
const stem = args.path.slice(0, -extension.length);
|
|
223
|
+
let first;
|
|
224
|
+
for (const scale of scaleSuffixes) {
|
|
225
|
+
for (const platform of platformSuffixes) {
|
|
226
|
+
const found = await build.resolve(`${stem}${scale}${platform}${extension}`, { kind: args.kind, importer: args.importer, resolveDir: args.resolveDir, pluginData: internal });
|
|
227
|
+
if (found.errors.length === 0) {
|
|
228
|
+
return { path: found.path, namespace: "swiplet-asset", pluginData: assetVariants(found.path) };
|
|
229
|
+
}
|
|
230
|
+
first ??= found;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return { errors: first.errors };
|
|
234
|
+
});
|
|
235
|
+
build.onLoad({ filter: /.*/, namespace: "swiplet-asset" }, args => ({
|
|
236
|
+
contents: assetModule(root, args.pluginData, assets),
|
|
237
|
+
loader: "js",
|
|
238
|
+
resolveDir: root,
|
|
239
|
+
}));
|
|
240
|
+
}
|
|
241
|
+
build.onLoad({ filter: /\.[cm]?[jt]sx?$/, namespace: "file" }, async args => {
|
|
242
|
+
const result = await transformAsync(await fs.promises.readFile(args.path, "utf8"), {
|
|
243
|
+
...babel,
|
|
244
|
+
filename: args.path,
|
|
245
|
+
caller: { ...caller, isNodeModule: args.path.includes("node_modules") },
|
|
246
|
+
});
|
|
247
|
+
return result === null ? undefined : { contents: result.code, loader: loaderOf(args.path), resolveDir: path.dirname(args.path) };
|
|
248
|
+
});
|
|
249
|
+
},
|
|
250
|
+
};
|
|
251
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swipt/cli",
|
|
3
|
-
"version": "0.1.0-nightly.
|
|
3
|
+
"version": "0.1.0-nightly.37",
|
|
4
4
|
"license": "GPL-3.0-or-later",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -19,13 +19,19 @@
|
|
|
19
19
|
"access": "public"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
+
"@babel/core": "^7.29.7",
|
|
23
|
+
"@babel/runtime": "^7.29.10",
|
|
22
24
|
"@bufbuild/protobuf": "^2.16.0",
|
|
25
|
+
"@expo/env": "^2.5.3",
|
|
23
26
|
"@jridgewell/trace-mapping": "^0.3.31",
|
|
27
|
+
"babel-preset-expo": "~58.0.22",
|
|
24
28
|
"bonjour-service": "^1.4.4",
|
|
25
|
-
"esbuild": "^0.28.2"
|
|
29
|
+
"esbuild": "^0.28.2",
|
|
30
|
+
"image-size": "^2.0.4",
|
|
31
|
+
"react-native-worklets": "0.13.0"
|
|
26
32
|
},
|
|
27
33
|
"peerDependencies": {
|
|
28
|
-
"@swipt/api": "0.1.0-nightly.
|
|
34
|
+
"@swipt/api": "0.1.0-nightly.37"
|
|
29
35
|
},
|
|
30
36
|
"devDependencies": {
|
|
31
37
|
"@bufbuild/protoc-gen-es": "^2.16.0"
|
package/project.mjs
CHANGED
|
@@ -9,6 +9,8 @@ import vm from "node:vm";
|
|
|
9
9
|
|
|
10
10
|
import { build } from "esbuild";
|
|
11
11
|
|
|
12
|
+
import { loadEnvironment, metro } from "./metro.mjs";
|
|
13
|
+
|
|
12
14
|
const { version } = JSON.parse(fs.readFileSync(new URL("package.json", import.meta.url), "utf8"));
|
|
13
15
|
const { peerDependencies } = createRequire(import.meta.url)("@swipt/api/package.json");
|
|
14
16
|
|
|
@@ -16,6 +18,7 @@ const appModules = [
|
|
|
16
18
|
"react",
|
|
17
19
|
"react/jsx-runtime",
|
|
18
20
|
"react-native",
|
|
21
|
+
"react-native-worklets",
|
|
19
22
|
"expo",
|
|
20
23
|
"expo-localization",
|
|
21
24
|
"@expo/ui",
|
|
@@ -78,7 +81,8 @@ const typeImport = extension => ({
|
|
|
78
81
|
});
|
|
79
82
|
|
|
80
83
|
async function bundle(project) {
|
|
81
|
-
const out = { app: "", extension: "", extensionMap: "" };
|
|
84
|
+
const out = { app: "", extension: "", extensionMap: "", assets: new Map() };
|
|
85
|
+
loadEnvironment(project.root);
|
|
82
86
|
if (project.extension) {
|
|
83
87
|
const result = await build({
|
|
84
88
|
entryPoints: [project.extension],
|
|
@@ -93,6 +97,7 @@ async function bundle(project) {
|
|
|
93
97
|
sourcesContent: false,
|
|
94
98
|
write: false,
|
|
95
99
|
logLevel: "silent",
|
|
100
|
+
plugins: [metro(project.root, { sourcemap: true })],
|
|
96
101
|
});
|
|
97
102
|
out.extension = result.outputFiles.find(file => file.path.endsWith(".js")).text;
|
|
98
103
|
out.extensionMap = result.outputFiles.find(file => file.path.endsWith(".map")).text;
|
|
@@ -112,7 +117,7 @@ async function bundle(project) {
|
|
|
112
117
|
external: appModules,
|
|
113
118
|
write: false,
|
|
114
119
|
logLevel: "silent",
|
|
115
|
-
plugins: project.extension ? [typeImport(project.extension)] : [],
|
|
120
|
+
plugins: [...(project.extension ? [typeImport(project.extension)] : []), metro(project.root, { assets: out.assets })],
|
|
116
121
|
});
|
|
117
122
|
out.app = result.outputFiles[0].text;
|
|
118
123
|
}
|
|
@@ -256,10 +261,18 @@ export async function buildProject(root) {
|
|
|
256
261
|
if (fs.existsSync(path.join(root, "assets"))) {
|
|
257
262
|
fs.cpSync(path.join(root, "assets"), path.join(folder, "assets"), { recursive: true, filter: source => !path.basename(source).startsWith(".") });
|
|
258
263
|
}
|
|
264
|
+
for (const [name, source] of output.assets) {
|
|
265
|
+
const target = path.join(folder, "assets", name);
|
|
266
|
+
if (fs.existsSync(target)) {
|
|
267
|
+
throw new Error(`${path.relative(root, source)} and ${path.join("assets", name)} would both be assets/${name} in the swiplet`);
|
|
268
|
+
}
|
|
269
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
270
|
+
fs.copyFileSync(source, target);
|
|
271
|
+
}
|
|
259
272
|
const archive = path.join(dist, `${manifest.id}.swiplet`);
|
|
260
273
|
fs.rmSync(archive, { force: true });
|
|
261
274
|
fs.rmSync(path.join(dist, `${manifest.id}.zip`), { force: true });
|
|
262
|
-
execFileSync("zip", ["-qr", "-n", storedSuffixes, archive,
|
|
275
|
+
execFileSync("zip", ["-qr", "-n", storedSuffixes, archive, ...fs.readdirSync(folder), "-x", "*.map"], { cwd: folder });
|
|
263
276
|
return { id: manifest.id, archive, digest: folderDigest(folder) };
|
|
264
277
|
}
|
|
265
278
|
|