@sublay/cli 7.0.0 → 7.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/LICENSE +202 -0
- package/NOTICE +9 -0
- package/README.md +118 -0
- package/dist/index.js +688 -145
- package/package.json +48 -19
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
+
import { createRequire } from "module";
|
|
4
5
|
import { Command } from "commander";
|
|
5
6
|
|
|
6
7
|
// src/commands/init.ts
|
|
@@ -18,12 +19,15 @@ async function detectProjectType() {
|
|
|
18
19
|
if (!await fs.pathExists(packageJsonPath)) {
|
|
19
20
|
return "unknown";
|
|
20
21
|
}
|
|
21
|
-
const
|
|
22
|
+
const packageJson2 = await fs.readJson(packageJsonPath);
|
|
22
23
|
const deps = {
|
|
23
|
-
...
|
|
24
|
-
...
|
|
24
|
+
...packageJson2.dependencies,
|
|
25
|
+
...packageJson2.devDependencies
|
|
25
26
|
};
|
|
26
|
-
if (deps["
|
|
27
|
+
if (deps["expo"]) {
|
|
28
|
+
return "expo";
|
|
29
|
+
}
|
|
30
|
+
if (deps["react-native"]) {
|
|
27
31
|
return "react-native";
|
|
28
32
|
}
|
|
29
33
|
if (deps["react"]) {
|
|
@@ -42,12 +46,12 @@ async function detectTypeScript() {
|
|
|
42
46
|
}
|
|
43
47
|
const packageJsonPath = path.join(process.cwd(), "package.json");
|
|
44
48
|
if (await fs.pathExists(packageJsonPath)) {
|
|
45
|
-
const
|
|
49
|
+
const packageJson2 = await fs.readJson(packageJsonPath);
|
|
46
50
|
const deps = {
|
|
47
|
-
...
|
|
48
|
-
...
|
|
51
|
+
...packageJson2.dependencies,
|
|
52
|
+
...packageJson2.devDependencies
|
|
49
53
|
};
|
|
50
|
-
if (deps["typescript"]
|
|
54
|
+
if (deps["typescript"]) {
|
|
51
55
|
return true;
|
|
52
56
|
}
|
|
53
57
|
}
|
|
@@ -62,6 +66,19 @@ async function init() {
|
|
|
62
66
|
console.log(chalk.bold("\n\u{1F680} Welcome to Sublay CLI\n"));
|
|
63
67
|
const projectType = await detectProjectType();
|
|
64
68
|
const hasTypeScript = await detectTypeScript();
|
|
69
|
+
if (!process.stdin.isTTY) {
|
|
70
|
+
console.error(
|
|
71
|
+
chalk.red(
|
|
72
|
+
"\n\u274C sublay init requires an interactive terminal \u2014 no TTY detected."
|
|
73
|
+
)
|
|
74
|
+
);
|
|
75
|
+
console.error(
|
|
76
|
+
chalk.dim(
|
|
77
|
+
" Run it directly in a terminal, not through a script or pipe.\n"
|
|
78
|
+
)
|
|
79
|
+
);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
65
82
|
const answers = await prompts([
|
|
66
83
|
{
|
|
67
84
|
type: "select",
|
|
@@ -72,7 +89,7 @@ async function init() {
|
|
|
72
89
|
{ title: "React Native", value: "react-native" },
|
|
73
90
|
{ title: "Expo", value: "expo" }
|
|
74
91
|
],
|
|
75
|
-
initial: projectType === "react-native" ? 1 : 0
|
|
92
|
+
initial: projectType === "expo" ? 2 : projectType === "react-native" ? 1 : 0
|
|
76
93
|
},
|
|
77
94
|
{
|
|
78
95
|
type: "select",
|
|
@@ -90,22 +107,26 @@ async function init() {
|
|
|
90
107
|
initial: "src/components"
|
|
91
108
|
}
|
|
92
109
|
]);
|
|
93
|
-
if (!answers.platform) {
|
|
110
|
+
if (!answers.platform || answers.style === void 0 || answers.componentsPath === void 0) {
|
|
94
111
|
console.log(chalk.yellow("\n\u26A0\uFE0F Setup cancelled"));
|
|
95
112
|
process.exit(0);
|
|
96
113
|
}
|
|
114
|
+
const componentsPath = answers.componentsPath || "src/components";
|
|
97
115
|
const config = {
|
|
98
116
|
platform: answers.platform,
|
|
99
117
|
style: answers.style || "styled",
|
|
100
118
|
typescript: hasTypeScript,
|
|
101
119
|
paths: {
|
|
102
|
-
components:
|
|
120
|
+
components: componentsPath
|
|
103
121
|
},
|
|
104
122
|
aliases: {
|
|
105
|
-
"@/components":
|
|
123
|
+
"@/components": `./${componentsPath}`
|
|
106
124
|
}
|
|
107
125
|
};
|
|
108
126
|
const configPath = path2.join(process.cwd(), "sublay.json");
|
|
127
|
+
if (await fs2.pathExists(configPath)) {
|
|
128
|
+
console.log(chalk.yellow("\n\u26A0\uFE0F Overwriting existing configuration: sublay.json"));
|
|
129
|
+
}
|
|
109
130
|
await fs2.writeJson(configPath, config, { spaces: 2 });
|
|
110
131
|
console.log(chalk.green("\n\u2705 Configuration saved to sublay.json"));
|
|
111
132
|
console.log(chalk.bold("\n\u{1F389} Sublay CLI initialized successfully!\n"));
|
|
@@ -129,92 +150,316 @@ import { fileURLToPath } from "url";
|
|
|
129
150
|
var __filename = fileURLToPath(import.meta.url);
|
|
130
151
|
var __dirname = path3.dirname(__filename);
|
|
131
152
|
var LOCAL_REGISTRY_PATH = path3.resolve(__dirname, "..", "..", "..", "registry");
|
|
132
|
-
|
|
153
|
+
var REMOTE_REGISTRY_BASE = "https://raw.githubusercontent.com/sublay-io/monorepo/main/registry";
|
|
154
|
+
var DEFAULT_REGISTRY_REF = "main";
|
|
155
|
+
var LOCAL_PATH_ENV = "SUBLAY_REGISTRY_LOCAL_PATH";
|
|
156
|
+
var REMOTE_BASE_ENV = "SUBLAY_REGISTRY_BASE_URL";
|
|
157
|
+
var RegistryError = class extends Error {
|
|
158
|
+
constructor(message) {
|
|
159
|
+
super(message);
|
|
160
|
+
this.name = "RegistryError";
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
function validateRef(ref) {
|
|
164
|
+
if (typeof ref !== "string" || ref.length === 0) {
|
|
165
|
+
return "A ref cannot be empty.";
|
|
166
|
+
}
|
|
167
|
+
if (!/^[A-Za-z0-9._\-/]+$/.test(ref)) {
|
|
168
|
+
return `Invalid ref "${ref}": a ref may only contain letters, digits, ".", "_", "-" and "/".`;
|
|
169
|
+
}
|
|
170
|
+
const segments = ref.split("/");
|
|
171
|
+
if (segments.some((segment) => segment === "..")) {
|
|
172
|
+
return `Invalid ref "${ref}": a ref cannot contain a ".." segment.`;
|
|
173
|
+
}
|
|
174
|
+
if (segments.some((segment) => segment.length === 0)) {
|
|
175
|
+
return `Invalid ref "${ref}": a ref cannot start or end with "/" or contain "//".`;
|
|
176
|
+
}
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
function validateComponentName(name) {
|
|
180
|
+
if (typeof name !== "string" || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name)) {
|
|
181
|
+
return `Invalid component name "${name}": a component name may only contain lowercase letters, digits and hyphens (for example "comments-threaded").`;
|
|
182
|
+
}
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
function getRemoteRegistryBase(ref = DEFAULT_REGISTRY_REF, env = process.env) {
|
|
186
|
+
const base = (env[REMOTE_BASE_ENV] || REMOTE_REGISTRY_BASE).replace(/\/+$/, "");
|
|
187
|
+
const suffix = `/${DEFAULT_REGISTRY_REF}/registry`;
|
|
188
|
+
if (!base.endsWith(suffix)) {
|
|
189
|
+
throw new RegistryError(
|
|
190
|
+
`Registry base URL "${base}" must end with "${suffix}".`
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
if (ref === DEFAULT_REGISTRY_REF) {
|
|
194
|
+
return base;
|
|
195
|
+
}
|
|
196
|
+
return `${base.slice(0, -suffix.length)}/${ref}/registry`;
|
|
197
|
+
}
|
|
198
|
+
function resolveRegistrySource(ref, env = process.env) {
|
|
199
|
+
if (ref === void 0) {
|
|
200
|
+
return {
|
|
201
|
+
ref: DEFAULT_REGISTRY_REF,
|
|
202
|
+
explicitRef: false,
|
|
203
|
+
localRoot: env[LOCAL_PATH_ENV] || LOCAL_REGISTRY_PATH,
|
|
204
|
+
remoteBase: getRemoteRegistryBase(DEFAULT_REGISTRY_REF, env)
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
const problem = validateRef(ref);
|
|
208
|
+
if (problem) {
|
|
209
|
+
throw new RegistryError(problem);
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
ref,
|
|
213
|
+
explicitRef: true,
|
|
214
|
+
localRoot: null,
|
|
215
|
+
remoteBase: getRemoteRegistryBase(ref, env)
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
function getRegistryPlatform(config) {
|
|
219
|
+
return config.platform === "expo" ? "react-native" : config.platform;
|
|
220
|
+
}
|
|
221
|
+
function getComponentLocation(componentName, config) {
|
|
222
|
+
const problem = validateComponentName(componentName);
|
|
223
|
+
if (problem) {
|
|
224
|
+
throw new RegistryError(problem);
|
|
225
|
+
}
|
|
226
|
+
return `${getRegistryPlatform(config)}/${componentName}/${config.style}`;
|
|
227
|
+
}
|
|
228
|
+
function getRemoteUrl(source, registryPath) {
|
|
229
|
+
return `${source.remoteBase}/${registryPath}`;
|
|
230
|
+
}
|
|
231
|
+
async function readRegistryPath(source, registryPath) {
|
|
232
|
+
if (source.localRoot) {
|
|
233
|
+
const localPath = path3.join(source.localRoot, ...registryPath.split("/"));
|
|
234
|
+
if (await fs3.pathExists(localPath)) {
|
|
235
|
+
return { ok: true, content: await fs3.readFile(localPath, "utf-8") };
|
|
236
|
+
}
|
|
237
|
+
if (source.localOnly) {
|
|
238
|
+
return {
|
|
239
|
+
ok: false,
|
|
240
|
+
url: localPath,
|
|
241
|
+
status: 404,
|
|
242
|
+
reason: "not found in the local registry"
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
const url = getRemoteUrl(source, registryPath);
|
|
133
247
|
try {
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const localExists = await fs3.pathExists(registryPath);
|
|
143
|
-
if (localExists) {
|
|
144
|
-
return await fs3.readJson(registryPath);
|
|
145
|
-
}
|
|
146
|
-
const url = `https://raw.githubusercontent.com/sublay-io/cli/main/registry/${registryPlatform}/${componentName}/${config.style}/registry.json`;
|
|
147
|
-
try {
|
|
148
|
-
const response = await fetch(url);
|
|
149
|
-
if (!response.ok) {
|
|
150
|
-
if (response.status === 404) {
|
|
151
|
-
console.error(`Component "${componentName}" with style "${config.style}" not found in registry.`);
|
|
152
|
-
} else {
|
|
153
|
-
console.error(`Failed to fetch registry from ${url}: ${response.status} ${response.statusText}`);
|
|
154
|
-
}
|
|
155
|
-
return null;
|
|
156
|
-
}
|
|
157
|
-
return await response.json();
|
|
158
|
-
} catch (fetchError) {
|
|
159
|
-
console.error(`Error fetching from GitHub (${url}):`, fetchError);
|
|
160
|
-
return null;
|
|
248
|
+
const response = await fetch(url);
|
|
249
|
+
if (!response.ok) {
|
|
250
|
+
return {
|
|
251
|
+
ok: false,
|
|
252
|
+
url,
|
|
253
|
+
status: response.status,
|
|
254
|
+
reason: `${response.status} ${response.statusText}`.trim()
|
|
255
|
+
};
|
|
161
256
|
}
|
|
257
|
+
return { ok: true, content: await response.text() };
|
|
162
258
|
} catch (error) {
|
|
163
|
-
|
|
164
|
-
|
|
259
|
+
return {
|
|
260
|
+
ok: false,
|
|
261
|
+
url,
|
|
262
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
263
|
+
};
|
|
165
264
|
}
|
|
166
265
|
}
|
|
167
|
-
async function
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
184
|
-
const fileUrl = `${registryUrl}/${filePath}`;
|
|
266
|
+
async function pinRegistrySource(source, componentName, config) {
|
|
267
|
+
if (!source.localRoot) {
|
|
268
|
+
return source;
|
|
269
|
+
}
|
|
270
|
+
const location = getComponentLocation(componentName, config);
|
|
271
|
+
const manifestPath = path3.join(
|
|
272
|
+
source.localRoot,
|
|
273
|
+
...location.split("/"),
|
|
274
|
+
"registry.json"
|
|
275
|
+
);
|
|
276
|
+
return await fs3.pathExists(manifestPath) ? { ...source, localOnly: true } : { ...source, localRoot: null };
|
|
277
|
+
}
|
|
278
|
+
async function fetchRegistry(componentName, config, source) {
|
|
279
|
+
const location = getComponentLocation(componentName, config);
|
|
280
|
+
const result = await readRegistryPath(source, `${location}/registry.json`);
|
|
281
|
+
if (result.ok) {
|
|
185
282
|
try {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
return await response.text();
|
|
192
|
-
} catch (fetchError) {
|
|
193
|
-
console.error(`Error fetching file from GitHub (${fileUrl}):`, fetchError);
|
|
194
|
-
return null;
|
|
283
|
+
return JSON.parse(result.content);
|
|
284
|
+
} catch {
|
|
285
|
+
throw new RegistryError(
|
|
286
|
+
`The registry manifest for "${componentName}" (${location}/registry.json) is not valid JSON.`
|
|
287
|
+
);
|
|
195
288
|
}
|
|
196
|
-
} catch (error) {
|
|
197
|
-
console.error(`Error fetching file ${filePath}:`, error);
|
|
198
|
-
return null;
|
|
199
289
|
}
|
|
290
|
+
if (result.status !== 404) {
|
|
291
|
+
throw new RegistryError(
|
|
292
|
+
`Failed to fetch registry from ${result.url}: ${result.reason}`
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
if (!source.explicitRef) {
|
|
296
|
+
throw new RegistryError(
|
|
297
|
+
`Component "${componentName}" with style "${config.style}" not found in registry.`
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
const probe = await readRegistryPath(source, "package.json");
|
|
301
|
+
if (probe.ok) {
|
|
302
|
+
throw new RegistryError(
|
|
303
|
+
`Component "${componentName}" with style "${config.style}" not found on branch "${source.ref}".`
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
if (probe.status === 404) {
|
|
307
|
+
throw new RegistryError(
|
|
308
|
+
`Branch "${source.ref}" not found: no registry is served from that branch.`
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
throw new RegistryError(
|
|
312
|
+
`Failed to fetch registry from ${probe.url}: ${probe.reason}`
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
async function fetchFile(source, location, filePath) {
|
|
316
|
+
const result = await readRegistryPath(source, `${location}/${filePath}`);
|
|
317
|
+
if (!result.ok) {
|
|
318
|
+
throw new RegistryError(
|
|
319
|
+
`Could not fetch ${filePath} from ${result.url}: ${result.reason}`
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
return result.content;
|
|
323
|
+
}
|
|
324
|
+
function getSharedManifestPath(platform) {
|
|
325
|
+
return `shared/${platform}/shared.json`;
|
|
326
|
+
}
|
|
327
|
+
function isStringArray(value) {
|
|
328
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
329
|
+
}
|
|
330
|
+
function parseSharedManifest(raw, label) {
|
|
331
|
+
const invalid = (detail) => new RegistryError(`The shared manifest ${label} is malformed: ${detail}.`);
|
|
332
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
|
|
333
|
+
throw invalid("it must be an object");
|
|
334
|
+
}
|
|
335
|
+
const { platform, pieces } = raw;
|
|
336
|
+
if (typeof pieces !== "object" || pieces === null || Array.isArray(pieces)) {
|
|
337
|
+
throw invalid('"pieces" must be an object keyed by piece name');
|
|
338
|
+
}
|
|
339
|
+
const parsed = {};
|
|
340
|
+
for (const [name, value] of Object.entries(pieces)) {
|
|
341
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
342
|
+
throw invalid(`piece "${name}" must be an object`);
|
|
343
|
+
}
|
|
344
|
+
const { files, requires = [], dependencies = [] } = value;
|
|
345
|
+
if (!isStringArray(files) || files.length === 0) {
|
|
346
|
+
throw invalid(`piece "${name}" must list at least one file`);
|
|
347
|
+
}
|
|
348
|
+
const outside = files.find(
|
|
349
|
+
(file) => !/^[A-Za-z0-9_][A-Za-z0-9._/-]*$/.test(file) || file.split("/").includes("..")
|
|
350
|
+
);
|
|
351
|
+
if (outside !== void 0) {
|
|
352
|
+
throw invalid(
|
|
353
|
+
`piece "${name}" lists "${outside}", which is not a path inside the shared folder`
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
if (!isStringArray(requires)) {
|
|
357
|
+
throw invalid(`piece "${name}" has a "requires" that is not a list of piece names`);
|
|
358
|
+
}
|
|
359
|
+
if (!isStringArray(dependencies)) {
|
|
360
|
+
throw invalid(`piece "${name}" has a "dependencies" that is not a list of packages`);
|
|
361
|
+
}
|
|
362
|
+
parsed[name] = { files, requires, dependencies };
|
|
363
|
+
}
|
|
364
|
+
return {
|
|
365
|
+
platform: typeof platform === "string" ? platform : "",
|
|
366
|
+
pieces: parsed
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
async function fetchSharedManifest(platform, source) {
|
|
370
|
+
const manifestPath = getSharedManifestPath(platform);
|
|
371
|
+
const result = await readRegistryPath(source, manifestPath);
|
|
372
|
+
if (!result.ok) {
|
|
373
|
+
throw new RegistryError(
|
|
374
|
+
`Could not fetch the shared manifest ${manifestPath} from ${result.url}: ${result.reason}`
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
let raw;
|
|
378
|
+
try {
|
|
379
|
+
raw = JSON.parse(result.content);
|
|
380
|
+
} catch {
|
|
381
|
+
throw new RegistryError(`The shared manifest ${manifestPath} is not valid JSON.`);
|
|
382
|
+
}
|
|
383
|
+
return parseSharedManifest(raw, manifestPath);
|
|
384
|
+
}
|
|
385
|
+
function resolveSharedPieces(names, manifest, labels) {
|
|
386
|
+
const resolved = [];
|
|
387
|
+
const visited = /* @__PURE__ */ new Set();
|
|
388
|
+
const visit = (name, requiredBy) => {
|
|
389
|
+
if (visited.has(name)) {
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
if (!Object.prototype.hasOwnProperty.call(manifest.pieces, name)) {
|
|
393
|
+
throw new RegistryError(
|
|
394
|
+
requiredBy === null ? `Shared piece "${name}", listed in ${labels.componentManifest}, is not defined in ${labels.sharedManifest}.` : `Shared piece "${requiredBy}" requires "${name}", which is not defined in ${labels.sharedManifest}.`
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
visited.add(name);
|
|
398
|
+
const piece = manifest.pieces[name];
|
|
399
|
+
for (const requirement of piece.requires) {
|
|
400
|
+
visit(requirement, name);
|
|
401
|
+
}
|
|
402
|
+
resolved.push({ name, ...piece });
|
|
403
|
+
};
|
|
404
|
+
for (const name of names) {
|
|
405
|
+
visit(name, null);
|
|
406
|
+
}
|
|
407
|
+
return resolved;
|
|
408
|
+
}
|
|
409
|
+
async function fetchSharedFile(source, platform, filePath) {
|
|
410
|
+
return fetchFile(source, `shared/${platform}`, filePath);
|
|
200
411
|
}
|
|
201
412
|
|
|
202
413
|
// src/utils/transform.ts
|
|
203
|
-
|
|
414
|
+
var SHARED_ALIAS_IMPORT = /\b((?:from|import|require)\s*\(?\s*)(['"])~shared\/([^'"/]+)\/([^'"]+)\2/g;
|
|
415
|
+
function findSharedAliasImports(content) {
|
|
416
|
+
return Array.from(content.matchAll(SHARED_ALIAS_IMPORT), (match) => ({
|
|
417
|
+
platform: match[3],
|
|
418
|
+
piece: match[4]
|
|
419
|
+
}));
|
|
420
|
+
}
|
|
421
|
+
function transformImports(content, config, options = {}) {
|
|
204
422
|
let transformed = content;
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
423
|
+
const { installedPath } = options;
|
|
424
|
+
if (installedPath !== void 0) {
|
|
425
|
+
const fromDir = posixDirname(installedPath);
|
|
426
|
+
transformed = transformed.replace(
|
|
427
|
+
SHARED_ALIAS_IMPORT,
|
|
428
|
+
(_match, lead, quote, _platform, piece) => `${lead}${quote}${relativeImportPath(fromDir, `shared/${piece}`)}${quote}`
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
transformed = transformed.replace(/from(\s+)(['"])\.\.\/files\//g, "from$1$2../components/");
|
|
432
|
+
transformed = transformed.replace(/import(\s+)(['"])\.\.\/files\//g, "import$1$2../components/");
|
|
433
|
+
transformed = transformed.replace(/import\((['"])\.\.\/files\//g, "import($1../components/");
|
|
208
434
|
if (config.platform === "expo") {
|
|
209
435
|
transformed = transformed.replace(
|
|
210
436
|
/from\s+["']@sublay\/react-native["']/g,
|
|
211
437
|
'from "@sublay/expo"'
|
|
212
438
|
);
|
|
439
|
+
transformed = transformed.replace(
|
|
440
|
+
/^(\s*\*\s+-\s+)@sublay\/react-native(?=\s|$)/gm,
|
|
441
|
+
"$1@sublay/expo"
|
|
442
|
+
);
|
|
213
443
|
}
|
|
214
444
|
if (config.aliases["@/components"]) {
|
|
215
445
|
}
|
|
216
446
|
return transformed;
|
|
217
447
|
}
|
|
448
|
+
function posixDirname(filePath) {
|
|
449
|
+
const index = filePath.lastIndexOf("/");
|
|
450
|
+
return index === -1 ? "" : filePath.slice(0, index);
|
|
451
|
+
}
|
|
452
|
+
function relativeImportPath(fromDir, target) {
|
|
453
|
+
const from = fromDir.split("/").filter(Boolean);
|
|
454
|
+
const to = target.split("/").filter(Boolean);
|
|
455
|
+
let common = 0;
|
|
456
|
+
while (common < from.length && common < to.length - 1 && from[common] === to[common]) {
|
|
457
|
+
common++;
|
|
458
|
+
}
|
|
459
|
+
const up = from.length - common;
|
|
460
|
+
const down = to.slice(common).join("/");
|
|
461
|
+
return up === 0 ? `./${down}` : `${"../".repeat(up)}${down}`;
|
|
462
|
+
}
|
|
218
463
|
|
|
219
464
|
// src/utils/dependencies.ts
|
|
220
465
|
import fs4 from "fs-extra";
|
|
@@ -222,6 +467,7 @@ import path4 from "path";
|
|
|
222
467
|
import chalk2 from "chalk";
|
|
223
468
|
import prompts2 from "prompts";
|
|
224
469
|
import { execa } from "execa";
|
|
470
|
+
import semver from "semver";
|
|
225
471
|
async function installDependencies(deps) {
|
|
226
472
|
try {
|
|
227
473
|
console.log(chalk2.blue(`
|
|
@@ -251,6 +497,62 @@ async function detectPackageManager() {
|
|
|
251
497
|
}
|
|
252
498
|
return "npm";
|
|
253
499
|
}
|
|
500
|
+
function parseDependencyName(dep) {
|
|
501
|
+
const parts = dep.split("@").filter(Boolean);
|
|
502
|
+
if (dep.startsWith("@")) {
|
|
503
|
+
return `@${parts[0]}`;
|
|
504
|
+
}
|
|
505
|
+
return parts[0];
|
|
506
|
+
}
|
|
507
|
+
function parseDependencySpec(dep) {
|
|
508
|
+
const name = parseDependencyName(dep);
|
|
509
|
+
const rest = dep.slice(name.length);
|
|
510
|
+
return { name, range: rest.startsWith("@") ? rest.slice(1) : "" };
|
|
511
|
+
}
|
|
512
|
+
function mergeDependencies(componentDependencies, sharedDependencies) {
|
|
513
|
+
const merged = [];
|
|
514
|
+
const seen = /* @__PURE__ */ new Set();
|
|
515
|
+
for (const dep of [...componentDependencies, ...sharedDependencies]) {
|
|
516
|
+
const name = parseDependencyName(dep);
|
|
517
|
+
if (seen.has(name)) {
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
520
|
+
seen.add(name);
|
|
521
|
+
merged.push(dep);
|
|
522
|
+
}
|
|
523
|
+
return merged;
|
|
524
|
+
}
|
|
525
|
+
function isDeclaredRangeBelow(declared, required) {
|
|
526
|
+
const options = { includePrerelease: true };
|
|
527
|
+
if (typeof declared !== "string" || !required || !semver.validRange(declared, options) || !semver.validRange(required, options)) {
|
|
528
|
+
return false;
|
|
529
|
+
}
|
|
530
|
+
if (semver.intersects(declared, required, options)) {
|
|
531
|
+
return false;
|
|
532
|
+
}
|
|
533
|
+
const declaredMin = semver.minVersion(declared);
|
|
534
|
+
const requiredMin = semver.minVersion(required);
|
|
535
|
+
if (!declaredMin || !requiredMin) {
|
|
536
|
+
return false;
|
|
537
|
+
}
|
|
538
|
+
return semver.lt(declaredMin, requiredMin);
|
|
539
|
+
}
|
|
540
|
+
function findDependencyStatus(dependencies, declaredDependencies) {
|
|
541
|
+
const missing = [];
|
|
542
|
+
const upgrades = [];
|
|
543
|
+
for (const spec of dependencies) {
|
|
544
|
+
const { name, range } = parseDependencySpec(spec);
|
|
545
|
+
const declared = declaredDependencies[name];
|
|
546
|
+
if (!declared) {
|
|
547
|
+
missing.push(spec);
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
if (name.startsWith("@sublay/") && typeof declared === "string" && isDeclaredRangeBelow(declared, range)) {
|
|
551
|
+
upgrades.push({ spec, declared });
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return { missing, upgrades };
|
|
555
|
+
}
|
|
254
556
|
async function checkComponentDependencies(dependencies) {
|
|
255
557
|
try {
|
|
256
558
|
const packageJsonPath = path4.join(process.cwd(), "package.json");
|
|
@@ -258,44 +560,44 @@ async function checkComponentDependencies(dependencies) {
|
|
|
258
560
|
console.log(chalk2.yellow("\n\u26A0\uFE0F No package.json found"));
|
|
259
561
|
return;
|
|
260
562
|
}
|
|
261
|
-
const
|
|
563
|
+
const packageJson2 = await fs4.readJson(packageJsonPath);
|
|
262
564
|
const allDeps = {
|
|
263
|
-
...
|
|
264
|
-
...
|
|
565
|
+
...packageJson2.dependencies,
|
|
566
|
+
...packageJson2.devDependencies
|
|
265
567
|
};
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
if (dep.startsWith("@")) {
|
|
269
|
-
return `@${parts[0]}`;
|
|
270
|
-
} else {
|
|
271
|
-
return parts[0];
|
|
272
|
-
}
|
|
273
|
-
});
|
|
274
|
-
const missingDeps = requiredDeps.filter((dep) => !allDeps[dep]);
|
|
275
|
-
if (missingDeps.length === 0) {
|
|
568
|
+
const { missing, upgrades } = findDependencyStatus(dependencies, allDeps);
|
|
569
|
+
if (missing.length === 0 && upgrades.length === 0) {
|
|
276
570
|
console.log(chalk2.green("\n\u2705 All required dependencies are installed"));
|
|
277
571
|
return;
|
|
278
572
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
573
|
+
if (missing.length > 0) {
|
|
574
|
+
console.log(chalk2.yellow("\n\u26A0\uFE0F Missing required dependencies:"));
|
|
575
|
+
missing.forEach((dep) => console.log(chalk2.dim(` - ${dep}`)));
|
|
576
|
+
}
|
|
577
|
+
if (upgrades.length > 0) {
|
|
578
|
+
console.log(chalk2.yellow("\n\u26A0\uFE0F Dependencies that need an upgrade:"));
|
|
579
|
+
upgrades.forEach(
|
|
580
|
+
({ spec, declared }) => console.log(chalk2.dim(` - ${spec} (your project has ${declared})`))
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
const toInstall = [...missing, ...upgrades.map((upgrade) => upgrade.spec)];
|
|
584
|
+
let install = false;
|
|
585
|
+
if (process.stdin.isTTY) {
|
|
586
|
+
const answer = await prompts2({
|
|
587
|
+
type: "confirm",
|
|
588
|
+
name: "install",
|
|
589
|
+
message: "Would you like to install them now?",
|
|
590
|
+
initial: true
|
|
591
|
+
});
|
|
592
|
+
install = Boolean(answer.install);
|
|
593
|
+
}
|
|
292
594
|
if (install) {
|
|
293
|
-
await installDependencies(
|
|
595
|
+
await installDependencies(toInstall);
|
|
294
596
|
} else {
|
|
295
597
|
console.log(chalk2.dim("\nYou can install them later with:"));
|
|
296
598
|
const packageManager = await detectPackageManager();
|
|
297
599
|
const installCmd = packageManager === "npm" ? "npm install" : packageManager === "yarn" ? "yarn add" : "pnpm add";
|
|
298
|
-
console.log(chalk2.dim(` ${installCmd} ${
|
|
600
|
+
console.log(chalk2.dim(` ${installCmd} ${toInstall.join(" ")}
|
|
299
601
|
`));
|
|
300
602
|
}
|
|
301
603
|
} catch (error) {
|
|
@@ -332,7 +634,7 @@ async function stripTypes(code, filePath) {
|
|
|
332
634
|
retainLines: true
|
|
333
635
|
// Try to keep line numbers similar
|
|
334
636
|
});
|
|
335
|
-
if (!result ||
|
|
637
|
+
if (!result || typeof result.code !== "string") {
|
|
336
638
|
throw new Error("Babel transformation returned no code");
|
|
337
639
|
}
|
|
338
640
|
return result.code;
|
|
@@ -349,49 +651,124 @@ function convertFileExtension(filePath) {
|
|
|
349
651
|
}
|
|
350
652
|
|
|
351
653
|
// src/commands/add.ts
|
|
352
|
-
|
|
654
|
+
var STRINGS_FILE = "utils/strings.ts";
|
|
655
|
+
var DEFAULT_LANGUAGE = "en";
|
|
656
|
+
var FETCH_CONCURRENCY = 8;
|
|
657
|
+
async function add(componentName, options = {}) {
|
|
353
658
|
const spinner = ora("Initializing...").start();
|
|
354
659
|
try {
|
|
660
|
+
let source;
|
|
661
|
+
try {
|
|
662
|
+
const nameProblem = validateComponentName(componentName);
|
|
663
|
+
if (nameProblem) {
|
|
664
|
+
throw new RegistryError(nameProblem);
|
|
665
|
+
}
|
|
666
|
+
source = resolveRegistrySource(options.ref);
|
|
667
|
+
} catch (error) {
|
|
668
|
+
if (error instanceof RegistryError) {
|
|
669
|
+
spinner.fail(error.message);
|
|
670
|
+
process.exit(1);
|
|
671
|
+
}
|
|
672
|
+
throw error;
|
|
673
|
+
}
|
|
355
674
|
const configPath = path5.join(process.cwd(), "sublay.json");
|
|
356
675
|
if (!await fs5.pathExists(configPath)) {
|
|
357
676
|
spinner.fail("No sublay.json found");
|
|
358
677
|
console.log(chalk3.yellow("\n\u26A0\uFE0F Please run: npx @sublay/cli init"));
|
|
359
678
|
process.exit(1);
|
|
360
679
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
680
|
+
let rawConfig;
|
|
681
|
+
try {
|
|
682
|
+
rawConfig = await fs5.readJson(configPath);
|
|
683
|
+
} catch {
|
|
684
|
+
rawConfig = null;
|
|
685
|
+
}
|
|
686
|
+
if (!isValidConfig(rawConfig)) {
|
|
687
|
+
spinner.fail("sublay.json is malformed");
|
|
688
|
+
console.error(
|
|
689
|
+
chalk3.red(
|
|
690
|
+
"\n\u274C sublay.json is missing or malformed \u2014 delete it and run `npx @sublay/cli init` again."
|
|
691
|
+
)
|
|
692
|
+
);
|
|
366
693
|
process.exit(1);
|
|
367
694
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
695
|
+
const config = rawConfig;
|
|
696
|
+
const componentDir = path5.join(
|
|
697
|
+
process.cwd(),
|
|
698
|
+
config.paths.components,
|
|
699
|
+
componentName
|
|
700
|
+
);
|
|
701
|
+
if (await fs5.pathExists(componentDir)) {
|
|
702
|
+
spinner.stop();
|
|
703
|
+
console.log(
|
|
704
|
+
chalk3.yellow(`\u26A0\uFE0F Overwriting existing component: ${componentName}`)
|
|
371
705
|
);
|
|
706
|
+
spinner.start(`Fetching ${componentName}...`);
|
|
372
707
|
}
|
|
373
|
-
spinner.text =
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
708
|
+
spinner.text = `Fetching ${componentName}...`;
|
|
709
|
+
source = await pinRegistrySource(source, componentName, config);
|
|
710
|
+
const registry = await fetchRegistry(componentName, config, source);
|
|
711
|
+
registry.dependencies = registry.dependencies.map(
|
|
712
|
+
(dep) => renameForPlatform(dep, config)
|
|
713
|
+
);
|
|
714
|
+
const languageFile = resolveLanguageFile(registry, options.lang, componentName);
|
|
715
|
+
const platform = getRegistryPlatform(config);
|
|
716
|
+
const location = getComponentLocation(componentName, config);
|
|
717
|
+
const componentManifestLabel = `${location}/registry.json`;
|
|
718
|
+
const sharedManifestLabel = getSharedManifestPath(platform);
|
|
719
|
+
const sharedNames = readSharedNames(registry, componentManifestLabel);
|
|
720
|
+
let sharedPieces = [];
|
|
721
|
+
if (sharedNames.length > 0) {
|
|
722
|
+
const sharedManifest = await fetchSharedManifest(platform, source);
|
|
723
|
+
sharedPieces = resolveSharedPieces(sharedNames, sharedManifest, {
|
|
724
|
+
componentManifest: componentManifestLabel,
|
|
725
|
+
sharedManifest: sharedManifestLabel
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
const dependencies = mergeDependencies(
|
|
729
|
+
registry.dependencies,
|
|
730
|
+
sharedPieces.flatMap((piece) => piece.dependencies).map((dep) => renameForPlatform(dep, config))
|
|
731
|
+
);
|
|
732
|
+
spinner.text = sharedPieces.length > 0 ? `Downloading files and ${sharedPieces.length} shared piece(s)...` : "Downloading files...";
|
|
733
|
+
const installedPieces = new Set(sharedPieces.map((piece) => piece.name));
|
|
734
|
+
const downloads = [
|
|
735
|
+
...registry.files.map((file) => ({ registryPath: file.path, shared: false })),
|
|
736
|
+
...sharedPieces.flatMap(
|
|
737
|
+
(piece) => piece.files.map((file) => ({ registryPath: file, shared: true }))
|
|
738
|
+
)
|
|
739
|
+
].filter(
|
|
740
|
+
// Skip development files
|
|
741
|
+
(download) => !shouldExcludeFile(download.registryPath)
|
|
742
|
+
);
|
|
743
|
+
const pendingFiles = await mapWithConcurrency(
|
|
744
|
+
downloads,
|
|
745
|
+
FETCH_CONCURRENCY,
|
|
746
|
+
async ({ registryPath, shared }) => {
|
|
747
|
+
const fileContent = shared ? await fetchSharedFile(source, platform, registryPath) : await fetchFile(
|
|
748
|
+
source,
|
|
749
|
+
location,
|
|
750
|
+
languageFile && registryPath === STRINGS_FILE ? languageFile : registryPath
|
|
751
|
+
);
|
|
752
|
+
for (const used of findSharedAliasImports(fileContent)) {
|
|
753
|
+
if (used.platform !== platform || !installedPieces.has(used.piece)) {
|
|
754
|
+
throw new RegistryError(
|
|
755
|
+
`${registryPath} imports the shared piece "${used.platform}/${used.piece}", which ${componentManifestLabel} does not list under "shared" for ${sharedManifestLabel}.`
|
|
756
|
+
);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
return prepareFile(
|
|
760
|
+
shared ? `shared/${registryPath}` : registryPath,
|
|
761
|
+
fileContent,
|
|
762
|
+
config,
|
|
763
|
+
componentName
|
|
764
|
+
);
|
|
390
765
|
}
|
|
391
|
-
|
|
392
|
-
|
|
766
|
+
);
|
|
767
|
+
let filesInstalled = 0;
|
|
768
|
+
for (const file of pendingFiles) {
|
|
769
|
+
const fullPath = path5.join(process.cwd(), file.targetPath);
|
|
393
770
|
await fs5.ensureDir(path5.dirname(fullPath));
|
|
394
|
-
await fs5.writeFile(fullPath,
|
|
771
|
+
await fs5.writeFile(fullPath, file.content, "utf-8");
|
|
395
772
|
filesInstalled++;
|
|
396
773
|
}
|
|
397
774
|
spinner.text = "Creating index file...";
|
|
@@ -411,31 +788,184 @@ async function add(componentName) {
|
|
|
411
788
|
)}`
|
|
412
789
|
)
|
|
413
790
|
);
|
|
414
|
-
|
|
791
|
+
if (sharedPieces.length > 0) {
|
|
792
|
+
console.log(
|
|
793
|
+
chalk3.dim(
|
|
794
|
+
`\u{1F9E9} Shared pieces in ${path5.join(
|
|
795
|
+
config.paths.components,
|
|
796
|
+
componentName,
|
|
797
|
+
"shared"
|
|
798
|
+
)}: ${sharedPieces.map((piece) => piece.name).join(", ")}`
|
|
799
|
+
)
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
await checkComponentDependencies(dependencies);
|
|
415
803
|
const componentInfo = getComponentInfo(registry);
|
|
416
|
-
const
|
|
804
|
+
const relativeImportPath2 = path5.relative(
|
|
417
805
|
process.cwd(),
|
|
418
806
|
path5.join(config.paths.components, componentName)
|
|
419
807
|
);
|
|
420
808
|
console.log(chalk3.bold("\n\u{1F4D6} Usage:"));
|
|
421
809
|
console.log(
|
|
422
810
|
chalk3.dim(
|
|
423
|
-
` import { ${componentInfo.mainComponent} } from './${
|
|
424
|
-
)
|
|
425
|
-
);
|
|
426
|
-
console.log(chalk3.dim(` // With types:`));
|
|
427
|
-
console.log(
|
|
428
|
-
chalk3.dim(
|
|
429
|
-
` import { ${componentInfo.mainComponent}, type ${componentInfo.typeExport} } from './${relativeImportPath}';`
|
|
811
|
+
` import { ${componentInfo.mainComponent} } from './${relativeImportPath2}';`
|
|
430
812
|
)
|
|
431
813
|
);
|
|
814
|
+
if (componentInfo.typeExport) {
|
|
815
|
+
console.log(chalk3.dim(` // With types:`));
|
|
816
|
+
console.log(
|
|
817
|
+
chalk3.dim(
|
|
818
|
+
` import { ${componentInfo.mainComponent}, type ${componentInfo.typeExport} } from './${relativeImportPath2}';`
|
|
819
|
+
)
|
|
820
|
+
);
|
|
821
|
+
}
|
|
822
|
+
printAppSetupNotes(dependencies);
|
|
432
823
|
console.log();
|
|
433
824
|
} catch (error) {
|
|
825
|
+
if (error instanceof RegistryError) {
|
|
826
|
+
spinner.fail(error.message);
|
|
827
|
+
console.error(chalk3.dim("\nNothing was written to your project."));
|
|
828
|
+
process.exit(1);
|
|
829
|
+
}
|
|
434
830
|
spinner.fail("Failed to add component");
|
|
435
831
|
console.error(chalk3.red("\n\u274C Error:"), error);
|
|
436
832
|
process.exit(1);
|
|
437
833
|
}
|
|
438
834
|
}
|
|
835
|
+
async function prepareFile(filePath, fileContent, config, componentName) {
|
|
836
|
+
const componentDir = path5.join(config.paths.components, componentName);
|
|
837
|
+
const needsTypeStrip = !config.typescript && (filePath.endsWith(".ts") || filePath.endsWith(".tsx"));
|
|
838
|
+
const finalFilePath = needsTypeStrip ? convertFileExtension(filePath) : filePath;
|
|
839
|
+
const targetPath = getTargetPath(finalFilePath, config, componentName);
|
|
840
|
+
const installedPath = path5.relative(componentDir, targetPath).split(path5.sep).join("/");
|
|
841
|
+
if (installedPath === ".." || installedPath.startsWith("../") || path5.isAbsolute(installedPath)) {
|
|
842
|
+
throw new RegistryError(
|
|
843
|
+
`${filePath} would be installed outside ${componentDir}; refusing to write it.`
|
|
844
|
+
);
|
|
845
|
+
}
|
|
846
|
+
let transformed = transformImports(fileContent, config, { installedPath });
|
|
847
|
+
if (needsTypeStrip) {
|
|
848
|
+
transformed = await stripTypes(transformed, filePath);
|
|
849
|
+
}
|
|
850
|
+
return { targetPath, content: transformed };
|
|
851
|
+
}
|
|
852
|
+
function renameForPlatform(dep, config) {
|
|
853
|
+
return config.platform === "expo" && dep.startsWith("@sublay/react-native") ? dep.replace("@sublay/react-native", "@sublay/expo") : dep;
|
|
854
|
+
}
|
|
855
|
+
function resolveLanguageFile(registry, lang, componentName) {
|
|
856
|
+
if (lang === void 0 || lang === DEFAULT_LANGUAGE) {
|
|
857
|
+
return null;
|
|
858
|
+
}
|
|
859
|
+
const languages = registry.languages && typeof registry.languages === "object" ? registry.languages : {};
|
|
860
|
+
const available = [DEFAULT_LANGUAGE, ...Object.keys(languages)].join(", ");
|
|
861
|
+
const languageFile = Object.prototype.hasOwnProperty.call(languages, lang) ? languages[lang] : void 0;
|
|
862
|
+
if (languageFile === void 0) {
|
|
863
|
+
throw new RegistryError(
|
|
864
|
+
`${componentName} is not available in "${lang}". Available languages: ${available}.`
|
|
865
|
+
);
|
|
866
|
+
}
|
|
867
|
+
if (typeof languageFile !== "string" || !/^[A-Za-z0-9_][A-Za-z0-9._/-]*$/.test(languageFile) || languageFile.split("/").includes("..")) {
|
|
868
|
+
throw new RegistryError(
|
|
869
|
+
`The "${lang}" entry under "languages" for ${componentName} is not a usable file path.`
|
|
870
|
+
);
|
|
871
|
+
}
|
|
872
|
+
if (!registry.files.some((file) => file.path === STRINGS_FILE)) {
|
|
873
|
+
throw new RegistryError(
|
|
874
|
+
`${componentName} lists languages but has no ${STRINGS_FILE} to put them in.`
|
|
875
|
+
);
|
|
876
|
+
}
|
|
877
|
+
return languageFile;
|
|
878
|
+
}
|
|
879
|
+
function readSharedNames(registry, manifestLabel) {
|
|
880
|
+
const { shared } = registry;
|
|
881
|
+
if (shared === void 0 || shared === null) {
|
|
882
|
+
return [];
|
|
883
|
+
}
|
|
884
|
+
if (!Array.isArray(shared) || shared.some((name) => typeof name !== "string")) {
|
|
885
|
+
throw new RegistryError(
|
|
886
|
+
`"shared" in ${manifestLabel} must be a list of shared piece names.`
|
|
887
|
+
);
|
|
888
|
+
}
|
|
889
|
+
return shared;
|
|
890
|
+
}
|
|
891
|
+
async function mapWithConcurrency(items, limit, task) {
|
|
892
|
+
const results = new Array(items.length);
|
|
893
|
+
let next = 0;
|
|
894
|
+
const worker = async () => {
|
|
895
|
+
while (next < items.length) {
|
|
896
|
+
const index = next++;
|
|
897
|
+
results[index] = await task(items[index]);
|
|
898
|
+
}
|
|
899
|
+
};
|
|
900
|
+
await Promise.all(
|
|
901
|
+
Array.from({ length: Math.min(limit, items.length) }, worker)
|
|
902
|
+
);
|
|
903
|
+
return results;
|
|
904
|
+
}
|
|
905
|
+
function isValidConfig(value) {
|
|
906
|
+
if (typeof value !== "object" || value === null) {
|
|
907
|
+
return false;
|
|
908
|
+
}
|
|
909
|
+
const config = value;
|
|
910
|
+
if (config.platform !== "react" && config.platform !== "react-native" && config.platform !== "expo") {
|
|
911
|
+
return false;
|
|
912
|
+
}
|
|
913
|
+
if (config.style !== "styled" && config.style !== "tailwind") {
|
|
914
|
+
return false;
|
|
915
|
+
}
|
|
916
|
+
if (typeof config.typescript !== "boolean") {
|
|
917
|
+
return false;
|
|
918
|
+
}
|
|
919
|
+
const paths = config.paths;
|
|
920
|
+
if (typeof paths !== "object" || paths === null) {
|
|
921
|
+
return false;
|
|
922
|
+
}
|
|
923
|
+
if (typeof paths.components !== "string") {
|
|
924
|
+
return false;
|
|
925
|
+
}
|
|
926
|
+
const aliases = config.aliases;
|
|
927
|
+
if (typeof aliases !== "object" || aliases === null) {
|
|
928
|
+
return false;
|
|
929
|
+
}
|
|
930
|
+
if (typeof aliases["@/components"] !== "string") {
|
|
931
|
+
return false;
|
|
932
|
+
}
|
|
933
|
+
return true;
|
|
934
|
+
}
|
|
935
|
+
function printAppSetupNotes(dependencies) {
|
|
936
|
+
const deps = dependencies.map((dep) => dep.toLowerCase());
|
|
937
|
+
const usesNativeWind = deps.some((dep) => dep.includes("nativewind"));
|
|
938
|
+
const usesBottomSheet = deps.some((dep) => dep.includes("@gorhom/bottom-sheet"));
|
|
939
|
+
if (!usesNativeWind && !usesBottomSheet) {
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
console.log(chalk3.bold("\n\u{1F527} App-level setup required:"));
|
|
943
|
+
if (usesNativeWind) {
|
|
944
|
+
console.log(
|
|
945
|
+
chalk3.yellow(" \u2022 NativeWind: these files use `className` props.")
|
|
946
|
+
);
|
|
947
|
+
console.log(
|
|
948
|
+
chalk3.dim(
|
|
949
|
+
" Wire up babel.config.js, metro.config.js, and add this component"
|
|
950
|
+
)
|
|
951
|
+
);
|
|
952
|
+
console.log(
|
|
953
|
+
chalk3.dim(" directory to your Tailwind config's `content` globs.")
|
|
954
|
+
);
|
|
955
|
+
}
|
|
956
|
+
if (usesBottomSheet) {
|
|
957
|
+
console.log(
|
|
958
|
+
chalk3.yellow(" \u2022 Bottom sheets need a gesture-handler root.")
|
|
959
|
+
);
|
|
960
|
+
console.log(
|
|
961
|
+
chalk3.dim(
|
|
962
|
+
" Wrap your app root in <GestureHandlerRootView style={{ flex: 1 }}>"
|
|
963
|
+
)
|
|
964
|
+
);
|
|
965
|
+
console.log(chalk3.dim(" from 'react-native-gesture-handler'."));
|
|
966
|
+
}
|
|
967
|
+
console.log(chalk3.dim("\n Setup guides: https://docs.sublay.io"));
|
|
968
|
+
}
|
|
439
969
|
function getTargetPath(filePath, config, componentName) {
|
|
440
970
|
const componentDir = path5.join(config.paths.components, componentName);
|
|
441
971
|
if (filePath.startsWith("hooks/")) {
|
|
@@ -447,6 +977,9 @@ function getTargetPath(filePath, config, componentName) {
|
|
|
447
977
|
if (filePath.startsWith("context/")) {
|
|
448
978
|
return path5.join(componentDir, "context", path5.basename(filePath));
|
|
449
979
|
}
|
|
980
|
+
if (filePath.startsWith("shared/")) {
|
|
981
|
+
return path5.join(componentDir, "shared", filePath.substring(7));
|
|
982
|
+
}
|
|
450
983
|
if (filePath.startsWith("files/")) {
|
|
451
984
|
const normalizedPath = filePath.substring(6);
|
|
452
985
|
return path5.join(componentDir, "components", normalizedPath);
|
|
@@ -499,8 +1032,18 @@ function shouldExcludeFile(filePath) {
|
|
|
499
1032
|
}
|
|
500
1033
|
|
|
501
1034
|
// src/index.ts
|
|
1035
|
+
var require2 = createRequire(import.meta.url);
|
|
1036
|
+
var packageJson = require2("../package.json");
|
|
502
1037
|
var program = new Command();
|
|
503
|
-
program.name("sublay").description("CLI for installing Sublay UI components").version(
|
|
1038
|
+
program.name("sublay").description("CLI for installing Sublay UI components").version(packageJson.version);
|
|
504
1039
|
program.command("init").description("Initialize Sublay configuration in your project").action(init);
|
|
505
|
-
program.command("add <component>").description("Add a component to your project").
|
|
1040
|
+
program.command("add <component>").description("Add a component to your project").option(
|
|
1041
|
+
"--ref <branch>",
|
|
1042
|
+
"read the component from this branch of the registry instead of the default"
|
|
1043
|
+
).option(
|
|
1044
|
+
"--lang <code>",
|
|
1045
|
+
"install the component's text in this language instead of English (for example: he)"
|
|
1046
|
+
).action(
|
|
1047
|
+
(component, options) => add(component, { ref: options.ref, lang: options.lang })
|
|
1048
|
+
);
|
|
506
1049
|
program.parse();
|