@sublime-ui/devkit 0.1.0 → 0.1.1

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/cli.js CHANGED
@@ -1,4 +1,12 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ FileExistsError,
4
+ initApp,
5
+ log,
6
+ run,
7
+ runInherit,
8
+ safeWrite
9
+ } from "./chunk-PPA6U663.js";
2
10
 
3
11
  // src/cli.ts
4
12
  import { Command } from "commander";
@@ -6,31 +14,6 @@ import { Command } from "commander";
6
14
  // src/lib/probe.ts
7
15
  import { join } from "path";
8
16
 
9
- // src/util/exec.ts
10
- import { execa } from "execa";
11
- async function run(file, args, opts = {}) {
12
- const result = await execa(file, args, {
13
- ...opts.cwd === void 0 ? {} : { cwd: opts.cwd },
14
- env: { ...process.env, ...opts.env },
15
- reject: false,
16
- all: false
17
- });
18
- return {
19
- stdout: result.stdout ?? "",
20
- stderr: result.stderr ?? "",
21
- exitCode: result.exitCode ?? 1
22
- };
23
- }
24
- async function runInherit(file, args, opts = {}) {
25
- const result = await execa(file, args, {
26
- ...opts.cwd === void 0 ? {} : { cwd: opts.cwd },
27
- env: { ...process.env, ...opts.env },
28
- stdio: "inherit",
29
- reject: false
30
- });
31
- return result.exitCode ?? 1;
32
- }
33
-
34
17
  // src/lib/detect.ts
35
18
  function parseJavaVersion(stderr) {
36
19
  const match = stderr.match(/version "([^"]+)"/);
@@ -155,23 +138,6 @@ function buildDoctorReport(probes) {
155
138
  return { rows, ok: rows.every((r) => r.ok) };
156
139
  }
157
140
 
158
- // src/util/log.ts
159
- import pc from "picocolors";
160
- var log = {
161
- info: (m) => console.log(m),
162
- step: (m) => console.log(pc.cyan(`\u2192 ${m}`)),
163
- success: (m) => console.log(pc.green(`\u2713 ${m}`)),
164
- warn: (m) => console.log(pc.yellow(`! ${m}`)),
165
- error: (m) => console.error(pc.red(`\u2717 ${m}`)),
166
- table: (rows) => {
167
- const width = rows.reduce((m, r) => Math.max(m, r.label.length), 0);
168
- for (const r of rows) {
169
- const mark = r.ok ? pc.green("\u2713") : pc.red("\u2717");
170
- console.log(`${mark} ${r.label.padEnd(width)} ${pc.dim(r.detail)}`);
171
- }
172
- }
173
- };
174
-
175
141
  // src/commands/doctor.ts
176
142
  async function doctorCommand() {
177
143
  log.step("Checking environment for offline Android builds\u2026");
@@ -546,10 +512,10 @@ async function runCommand(opts) {
546
512
  }
547
513
 
548
514
  // src/cli.ts
549
- import { input } from "@inquirer/prompts";
515
+ import { input as input2 } from "@inquirer/prompts";
550
516
 
551
517
  // src/commands/make-model.ts
552
- import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
518
+ import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
553
519
  import { join as join7 } from "path";
554
520
 
555
521
  // src/lib/generators/config.ts
@@ -581,8 +547,8 @@ function loadConfig(cwd) {
581
547
  }
582
548
 
583
549
  // src/lib/generators/names.ts
584
- function pascalCase(input2) {
585
- return input2.split(/[^A-Za-z0-9]+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
550
+ function pascalCase(input3) {
551
+ return input3.split(/[^A-Za-z0-9]+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
586
552
  }
587
553
  function pluralize(word) {
588
554
  const w = word.toLowerCase();
@@ -603,10 +569,10 @@ function deriveNames(name) {
603
569
 
604
570
  // src/lib/generators/fields.ts
605
571
  var SCALARS = /* @__PURE__ */ new Set(["string", "number", "boolean"]);
606
- function parseFields(input2) {
572
+ function parseFields(input3) {
607
573
  const fields = [];
608
574
  const warnings = [];
609
- for (const part of input2.split(",")) {
575
+ for (const part of input3.split(",")) {
610
576
  const trimmed = part.trim();
611
577
  if (trimmed === "") continue;
612
578
  const [rawName, rawType] = trimmed.split(":").map((s) => s.trim());
@@ -647,24 +613,6 @@ function updateBarrel(existing, line) {
647
613
  `;
648
614
  }
649
615
 
650
- // src/lib/generators/write.ts
651
- import { existsSync as existsSync5, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
652
- import { dirname } from "path";
653
- var FileExistsError = class _FileExistsError extends Error {
654
- path;
655
- constructor(path) {
656
- super(`File already exists: ${path} (use --force to overwrite)`);
657
- this.name = "FileExistsError";
658
- this.path = path;
659
- Object.setPrototypeOf(this, _FileExistsError.prototype);
660
- }
661
- };
662
- function safeWrite(path, content, force) {
663
- if (existsSync5(path) && !force) throw new FileExistsError(path);
664
- mkdirSync2(dirname(path), { recursive: true });
665
- writeFileSync2(path, content);
666
- }
667
-
668
616
  // src/commands/make-model.ts
669
617
  async function makeModel(opts) {
670
618
  const cfg = loadConfig(opts.cwd);
@@ -682,7 +630,7 @@ async function makeModel(opts) {
682
630
  const barrelPath = join7(opts.cwd, cfg.modelsDir, "index.ts");
683
631
  try {
684
632
  safeWrite(modelPath, content, opts.force);
685
- const existing = existsSync6(barrelPath) ? readFileSync3(barrelPath, "utf8") : "";
633
+ const existing = existsSync5(barrelPath) ? readFileSync3(barrelPath, "utf8") : "";
686
634
  safeWrite(barrelPath, updateBarrel(existing, `export * from './${names.fileName}.js';`), true);
687
635
  log.success(`Created ${modelPath}`);
688
636
  return 0;
@@ -696,7 +644,7 @@ async function makeModel(opts) {
696
644
  }
697
645
 
698
646
  // src/commands/make-component.ts
699
- import { existsSync as existsSync7, readFileSync as readFileSync4 } from "fs";
647
+ import { existsSync as existsSync6, readFileSync as readFileSync4 } from "fs";
700
648
  import { join as join8 } from "path";
701
649
 
702
650
  // src/lib/generators/render-component.ts
@@ -774,7 +722,7 @@ async function makeComponent(opts) {
774
722
  safeWrite(join8(dir, `${opts.name}.native.tsx`), renderComponentNative(opts.name, cfg.importAlias), opts.force);
775
723
  safeWrite(join8(dir, "index.ts"), renderComponentIndex(opts.name), opts.force);
776
724
  const barrelPath = join8(opts.cwd, cfg.componentsDir, "index.ts");
777
- const existing = existsSync7(barrelPath) ? readFileSync4(barrelPath, "utf8") : "";
725
+ const existing = existsSync6(barrelPath) ? readFileSync4(barrelPath, "utf8") : "";
778
726
  safeWrite(barrelPath, updateBarrel(existing, `export * from './${opts.name}/index.js';`), true);
779
727
  log.success(`Created ${dir}`);
780
728
  return 0;
@@ -790,7 +738,7 @@ async function makeComponent(opts) {
790
738
  // src/commands/theme-init.ts
791
739
  import { findPackageJSON } from "module";
792
740
  import { pathToFileURL } from "url";
793
- import { dirname as dirname2, join as join9 } from "path";
741
+ import { dirname, join as join9 } from "path";
794
742
 
795
743
  // src/lib/generators/render-tokens.ts
796
744
  function renderTokensWrapper(importAlias) {
@@ -807,7 +755,7 @@ async function resolveDefaultTokens(cwd) {
807
755
  const base = pathToFileURL(join9(cwd, "noop.js")).href;
808
756
  const pkgJsonPath = findPackageJSON("@sublime-ui/library", base);
809
757
  if (!pkgJsonPath) throw new Error("@sublime-ui/library not found");
810
- const tokensJs = join9(dirname2(pkgJsonPath), "dist", "tokens", "tokens.js");
758
+ const tokensJs = join9(dirname(pkgJsonPath), "dist", "tokens", "tokens.js");
811
759
  const mod = await import(pathToFileURL(tokensJs).href);
812
760
  return mod.defaultTokens;
813
761
  }
@@ -838,54 +786,137 @@ async function themeInit(opts) {
838
786
 
839
787
  // src/commands/build-nav.ts
840
788
  import { join as join10 } from "path";
841
- import { readFileSync as readFileSync5, watch as fsWatch } from "fs";
842
-
843
- // src/lib/navigation/load-storybook.ts
844
- import { pathToFileURL as pathToFileURL2 } from "url";
845
- function componentName(component) {
846
- if (typeof component === "function" && component.name) return component.name;
847
- if (component != null && typeof component === "object" && "displayName" in component && typeof component.displayName === "string") {
848
- return component.displayName;
789
+ import { readFileSync as readFileSync6, watch as fsWatch } from "fs";
790
+
791
+ // src/lib/navigation/analyze-storybook.ts
792
+ import { readFileSync as readFileSync5 } from "fs";
793
+ import ts from "typescript";
794
+ function analyzeStorybook(absFile) {
795
+ const source = readFileSync5(absFile, "utf8");
796
+ const sf = ts.createSourceFile(absFile, source, ts.ScriptTarget.Latest, true);
797
+ const localBooks = collectLocalBooks(sf);
798
+ const rootCall = findDefaultBookCall(sf, localBooks);
799
+ if (rootCall === void 0) {
800
+ throw new Error(`Storybook ${absFile} must default-export a book().`);
849
801
  }
850
- return "Anonymous";
851
- }
852
- function isBookDef(value) {
853
- return value != null && typeof value === "object" && value.kind === "book" && typeof value.pages === "object";
802
+ return bookToNode(rootCall, "root", {}, sf, localBooks);
854
803
  }
855
- function bookToNode(def, key, options) {
804
+ function bookToNode(call, key, options, sf, localBooks) {
805
+ const arg = call.arguments[0];
806
+ const config = arg !== void 0 && ts.isObjectLiteralExpression(arg) ? arg : void 0;
807
+ const format = config ? stringProp(config, "format") : void 0;
808
+ const pages = config ? objectProp(config, "pages") : void 0;
856
809
  const children = [];
857
- for (const [childKey, entry] of Object.entries(def.pages)) {
858
- children.push(entryToNode(entry, childKey));
810
+ if (pages) {
811
+ for (const prop of pages.properties) {
812
+ if (!ts.isPropertyAssignment(prop)) continue;
813
+ const childKey = propertyName(prop.name);
814
+ if (childKey === void 0) continue;
815
+ if (!ts.isCallExpression(prop.initializer)) continue;
816
+ children.push(entryToNode(prop.initializer, childKey, sf, localBooks));
817
+ }
859
818
  }
860
- return { key, kind: "book", format: def.format, options: { ...options }, children };
861
- }
862
- function entryToNode(entry, key) {
863
- if (entry.kind === "link") {
864
- if (!isBookDef(entry.book)) {
819
+ const node = { key, kind: "book", options: { ...options }, children };
820
+ if (format !== void 0) node.format = format;
821
+ return node;
822
+ }
823
+ function entryToNode(call, key, sf, localBooks) {
824
+ const callee = ts.isIdentifier(call.expression) ? call.expression.text : void 0;
825
+ if (callee === "link") {
826
+ const options2 = optionsFromCall(call, 1);
827
+ const target = call.arguments[0];
828
+ const targetName = target !== void 0 && ts.isIdentifier(target) ? target.text : void 0;
829
+ const targetBook = targetName !== void 0 ? localBooks.get(targetName) : void 0;
830
+ if (targetBook === void 0) {
865
831
  return {
866
832
  key,
867
833
  kind: "book",
868
- options: { ...entry.options ?? {} },
834
+ options: options2,
869
835
  children: [],
870
836
  linkError: `link("${key}") does not reference a book().`
871
837
  };
872
838
  }
873
- return bookToNode(entry.book, key, entry.options ?? {});
839
+ return bookToNode(targetBook, key, options2, sf, localBooks);
874
840
  }
875
- return {
876
- key,
877
- kind: "page",
878
- component: componentName(entry.component),
879
- options: { ...entry.options ?? {} }
841
+ const options = optionsFromCall(call, 1);
842
+ const component = call.arguments[0];
843
+ const componentName = component !== void 0 && ts.isIdentifier(component) ? component.text : void 0;
844
+ const node = { key, kind: "page", options };
845
+ if (componentName !== void 0) node.component = componentName;
846
+ return node;
847
+ }
848
+ function optionsFromCall(call, argIndex) {
849
+ const arg = call.arguments[argIndex];
850
+ if (arg === void 0 || !ts.isObjectLiteralExpression(arg)) return {};
851
+ const options = {};
852
+ const title = stringProp(arg, "title");
853
+ if (title !== void 0) options.title = title;
854
+ const icon = stringProp(arg, "icon");
855
+ if (icon !== void 0) options.icon = icon;
856
+ const path = stringProp(arg, "path");
857
+ if (path !== void 0) options.path = path;
858
+ const initial = booleanProp(arg, "initial");
859
+ if (initial !== void 0) options.initial = initial;
860
+ return options;
861
+ }
862
+ function findDefaultBookCall(sf, localBooks) {
863
+ let found;
864
+ for (const stmt of sf.statements) {
865
+ if (ts.isExportAssignment(stmt) && !stmt.isExportEquals) {
866
+ const expr = stmt.expression;
867
+ if (ts.isCallExpression(expr) && isBookCall(expr)) {
868
+ found = expr;
869
+ } else if (ts.isIdentifier(expr)) {
870
+ const local = localBooks.get(expr.text);
871
+ if (local) found = local;
872
+ }
873
+ }
874
+ }
875
+ return found;
876
+ }
877
+ function isBookCall(call) {
878
+ return ts.isIdentifier(call.expression) && call.expression.text === "book";
879
+ }
880
+ function collectLocalBooks(sf) {
881
+ const books = /* @__PURE__ */ new Map();
882
+ const visit = (node) => {
883
+ if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer !== void 0 && ts.isCallExpression(node.initializer) && isBookCall(node.initializer)) {
884
+ books.set(node.name.text, node.initializer);
885
+ }
886
+ ts.forEachChild(node, visit);
880
887
  };
888
+ visit(sf);
889
+ return books;
881
890
  }
882
- async function loadStorybook(absFile) {
883
- const mod = await import(pathToFileURL2(absFile).href);
884
- const root = mod.default;
885
- if (!isBookDef(root)) {
886
- throw new Error(`Storybook ${absFile} must default-export a book().`);
891
+ function stringProp(obj, name) {
892
+ const init = propInitializer(obj, name);
893
+ if (init && ts.isStringLiteralLike(init)) return init.text;
894
+ return void 0;
895
+ }
896
+ function booleanProp(obj, name) {
897
+ const init = propInitializer(obj, name);
898
+ if (init?.kind === ts.SyntaxKind.TrueKeyword) return true;
899
+ if (init?.kind === ts.SyntaxKind.FalseKeyword) return false;
900
+ return void 0;
901
+ }
902
+ function objectProp(obj, name) {
903
+ const init = propInitializer(obj, name);
904
+ if (init && ts.isObjectLiteralExpression(init)) return init;
905
+ return void 0;
906
+ }
907
+ function propInitializer(obj, name) {
908
+ for (const prop of obj.properties) {
909
+ if (ts.isPropertyAssignment(prop) && propertyName(prop.name) === name) {
910
+ return prop.initializer;
911
+ }
887
912
  }
888
- return bookToNode(root, "root", {});
913
+ return void 0;
914
+ }
915
+ function propertyName(name) {
916
+ if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
917
+ return name.text;
918
+ }
919
+ return void 0;
889
920
  }
890
921
 
891
922
  // src/lib/navigation/validate.ts
@@ -981,35 +1012,35 @@ function flatten(root) {
981
1012
  }
982
1013
 
983
1014
  // src/lib/navigation/extract-params.ts
984
- import ts from "typescript";
1015
+ import ts2 from "typescript";
985
1016
  function extractParams(source) {
986
- const sf = ts.createSourceFile("storybook.ts", source, ts.ScriptTarget.Latest, true);
1017
+ const sf = ts2.createSourceFile("storybook.ts", source, ts2.ScriptTarget.Latest, true);
987
1018
  const params = /* @__PURE__ */ new Map();
988
1019
  const visit = (node) => {
989
- if (ts.isPropertyAssignment(node) && ts.isCallExpression(node.initializer) && isPageCall(node.initializer) && node.initializer.typeArguments && node.initializer.typeArguments.length > 0) {
990
- const key = propertyName(node.name);
1020
+ if (ts2.isPropertyAssignment(node) && ts2.isCallExpression(node.initializer) && isPageCall(node.initializer) && node.initializer.typeArguments && node.initializer.typeArguments.length > 0) {
1021
+ const key = propertyName2(node.name);
991
1022
  if (key !== void 0) {
992
1023
  params.set(key, node.initializer.typeArguments[0].getText(sf).trim());
993
1024
  }
994
1025
  }
995
- ts.forEachChild(node, visit);
1026
+ ts2.forEachChild(node, visit);
996
1027
  };
997
1028
  visit(sf);
998
1029
  return params;
999
1030
  }
1000
1031
  function isPageCall(call) {
1001
- return ts.isIdentifier(call.expression) && call.expression.text === "page";
1032
+ return ts2.isIdentifier(call.expression) && call.expression.text === "page";
1002
1033
  }
1003
- function propertyName(name) {
1004
- if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
1034
+ function propertyName2(name) {
1035
+ if (ts2.isIdentifier(name) || ts2.isStringLiteral(name) || ts2.isNumericLiteral(name)) {
1005
1036
  return name.text;
1006
1037
  }
1007
1038
  return void 0;
1008
1039
  }
1009
1040
 
1010
1041
  // src/lib/navigation/merge-route-params.ts
1011
- function mergeRouteParams(input2) {
1012
- const { nativeKeys, webKeys, nativeParams, webParams } = input2;
1042
+ function mergeRouteParams(input3) {
1043
+ const { nativeKeys, webKeys, nativeParams, webParams } = input3;
1013
1044
  const keys = [.../* @__PURE__ */ new Set([...nativeKeys, ...webKeys])].sort();
1014
1045
  const routes = [];
1015
1046
  const conflicts = [];
@@ -1062,7 +1093,7 @@ function renderNative(root, opts) {
1062
1093
  const renderBook = (book) => {
1063
1094
  const { factory, nav } = factoryFor(book.format);
1064
1095
  const navVar = navVarFor(factory, nav);
1065
- const componentName2 = book.key === "root" ? "RootNavigator" : `${pascal(book.key)}Navigator`;
1096
+ const componentName = book.key === "root" ? "RootNavigator" : `${pascal(book.key)}Navigator`;
1066
1097
  const supportsIcon = factory === "createBottomTabNavigator" || factory === "createDrawerNavigator";
1067
1098
  const children = book.children ?? [];
1068
1099
  const screens = [];
@@ -1079,7 +1110,7 @@ function renderNative(root, opts) {
1079
1110
  const initialChild = children.find((c) => c.options.initial === true);
1080
1111
  const navProps = initialChild ? ` initialRouteName="${initialChild.key}"` : "";
1081
1112
  navigatorBlocks.push(
1082
- `function ${componentName2}() {
1113
+ `function ${componentName}() {
1083
1114
  return (
1084
1115
  <${navVar}.Navigator${navProps}>
1085
1116
  ${screens.join("\n")}
@@ -1087,7 +1118,7 @@ ${screens.join("\n")}
1087
1118
  );
1088
1119
  }`
1089
1120
  );
1090
- return componentName2;
1121
+ return componentName;
1091
1122
  };
1092
1123
  const rootComponent = renderBook(root);
1093
1124
  const factoryImports = [...usedFactories.entries()].map(([factory, nav]) => {
@@ -1298,8 +1329,8 @@ async function buildOnce(navDir, nativeFile, webFile) {
1298
1329
  let nativeRoot;
1299
1330
  let webRoot;
1300
1331
  try {
1301
- nativeRoot = await loadStorybook(nativeFile);
1302
- webRoot = await loadStorybook(webFile);
1332
+ nativeRoot = analyzeStorybook(nativeFile);
1333
+ webRoot = analyzeStorybook(webFile);
1303
1334
  } catch (err) {
1304
1335
  log.error(err instanceof Error ? err.message : String(err));
1305
1336
  return 1;
@@ -1313,8 +1344,8 @@ async function buildOnce(navDir, nativeFile, webFile) {
1313
1344
  log.error(`build:nav failed with ${diagnostics.length} error(s); wrote nothing.`);
1314
1345
  return 1;
1315
1346
  }
1316
- const nativeParams = extractParams(readFileSync5(nativeFile, "utf8"));
1317
- const webParams = extractParams(readFileSync5(webFile, "utf8"));
1347
+ const nativeParams = extractParams(readFileSync6(nativeFile, "utf8"));
1348
+ const webParams = extractParams(readFileSync6(webFile, "utf8"));
1318
1349
  const { routes, conflicts } = mergeRouteParams({
1319
1350
  nativeKeys: flatten(nativeRoot).routes.map((r) => r.key),
1320
1351
  webKeys: flatten(webRoot).routes.map((r) => r.key),
@@ -1334,7 +1365,7 @@ async function buildOnce(navDir, nativeFile, webFile) {
1334
1365
  const webSrc = renderWeb(webRoot, { screensImport: SCREENS_IMPORT });
1335
1366
  const dtsSrc = renderRoutesDts(routes);
1336
1367
  safeWrite(join10(navDir, "navigation.native.tsx"), nativeSrc, true);
1337
- safeWrite(join10(navDir, "navigation.web.tsx"), webSrc, true);
1368
+ safeWrite(join10(navDir, "navigation.tsx"), webSrc, true);
1338
1369
  safeWrite(join10(navDir, "routes.d.ts"), dtsSrc, true);
1339
1370
  safeWrite(join10(navDir, "index.ts"), BARREL, true);
1340
1371
  log.success(`build:nav wrote 4 files to ${navDir}`);
@@ -1372,6 +1403,42 @@ async function desktopBuild(opts) {
1372
1403
  return runner("electron-forge", ["make"], desktopDir);
1373
1404
  }
1374
1405
 
1406
+ // src/commands/init.ts
1407
+ import { input, checkbox } from "@inquirer/prompts";
1408
+ import { basename } from "path";
1409
+ var ALL = ["web", "mobile", "desktop"];
1410
+ function parseTargets(spec) {
1411
+ if (!spec) return void 0;
1412
+ const parts = spec.split(",").map((s) => s.trim()).filter(Boolean);
1413
+ return parts.filter((p) => ALL.includes(p));
1414
+ }
1415
+ var inquirerPrompt = async ({ dir }) => {
1416
+ const name = await input({ message: "App name:", default: basename(dir) });
1417
+ const targets = await checkbox({
1418
+ message: "Targets:",
1419
+ choices: [
1420
+ { name: "web (Vite + MUI)", value: "web", checked: true },
1421
+ { name: "mobile (React Native + Paper)", value: "mobile", checked: true },
1422
+ { name: "desktop (Electron, wraps web)", value: "desktop", checked: true }
1423
+ ]
1424
+ });
1425
+ return { name, targets };
1426
+ };
1427
+ async function runInit(opts) {
1428
+ const targets = parseTargets(opts.targets);
1429
+ return initApp({
1430
+ dir: opts.dir,
1431
+ ...opts.name !== void 0 ? { name: opts.name } : {},
1432
+ ...targets !== void 0 ? { targets } : {},
1433
+ install: opts.install,
1434
+ git: opts.git,
1435
+ force: opts.force,
1436
+ yes: opts.yes,
1437
+ prompt: opts.prompt ?? inquirerPrompt,
1438
+ ...opts.runner !== void 0 ? { runner: opts.runner } : {}
1439
+ });
1440
+ }
1441
+
1375
1442
  // src/cli.ts
1376
1443
  var program = new Command();
1377
1444
  program.name("sublime").description("Sublime UI devkit \u2014 offline Android builds and tooling").version("0.0.0");
@@ -1381,6 +1448,17 @@ program.command("doctor").description("Check the environment for offline Android
1381
1448
  program.command("setup").description("Install/repair the build environment").action(async () => {
1382
1449
  process.exit(await setupCommand());
1383
1450
  });
1451
+ program.command("init [dir]").description("Scaffold a new Sublime app (web/mobile/desktop)").option("--name <name>", "app (npm package) name").option("--targets <list>", "comma-separated: web,mobile,desktop").option("--no-install", "skip npm install").option("--no-git", "skip git init").option("--force", "scaffold into a non-empty directory").option("-y, --yes", "accept defaults, no prompts").action(async (dir, opts) => {
1452
+ process.exit(await runInit({
1453
+ dir: dir ?? process.cwd(),
1454
+ ...opts.name !== void 0 ? { name: opts.name } : {},
1455
+ ...opts.targets !== void 0 ? { targets: opts.targets } : {},
1456
+ install: opts.install,
1457
+ git: opts.git,
1458
+ force: opts.force ?? false,
1459
+ yes: opts.yes ?? false
1460
+ }));
1461
+ });
1384
1462
  program.command("build").description("Build a standalone Android APK/AAB offline").option("--release", "release APK (default)", true).option("--debug", "debug APK (requires Metro)").option("--aab", "Android App Bundle (bundleRelease)").option("--project <path>", "project directory", process.cwd()).action(async (opts) => {
1385
1463
  const code = await buildCommand({
1386
1464
  project: opts.project,
@@ -1403,7 +1481,7 @@ program.command("make:model <name>").description("Scaffold a Model (+ registerMo
1403
1481
  force: opts.force ?? false,
1404
1482
  ...opts.fields ? { fields: opts.fields } : {},
1405
1483
  ...opts.resource ? { resource: opts.resource } : {},
1406
- promptFields: () => input({ message: "Fields (name:type, comma-separated; blank for id-only):", default: "" })
1484
+ promptFields: () => input2({ message: "Fields (name:type, comma-separated; blank for id-only):", default: "" })
1407
1485
  });
1408
1486
  process.exit(code);
1409
1487
  });
package/dist/index.d.ts CHANGED
@@ -1,3 +1,33 @@
1
+ type Target = 'web' | 'mobile' | 'desktop';
2
+ interface ScaffoldFile {
3
+ /** Path relative to the app root, POSIX-style. */
4
+ path: string;
5
+ contents: string;
6
+ }
7
+ interface ScaffoldOptions {
8
+ dir: string;
9
+ name?: string;
10
+ targets?: Target[];
11
+ force?: boolean;
12
+ install?: boolean;
13
+ git?: boolean;
14
+ yes?: boolean;
15
+ }
16
+
17
+ type Prompt = (ctx: {
18
+ dir: string;
19
+ }) => Promise<{
20
+ name: string;
21
+ targets: Target[];
22
+ }>;
23
+ type PostRunner = (cmd: string, args: string[], cwd: string) => Promise<number>;
24
+ /** npm package-name rules: lowercase, url-safe, no leading dot/underscore, <=214 chars. */
25
+ declare function isValidNpmName(s: string): boolean;
26
+ declare function initApp(opts: ScaffoldOptions & {
27
+ prompt?: Prompt;
28
+ runner?: PostRunner;
29
+ }): Promise<number>;
30
+
1
31
  declare const version = "0.0.0";
2
32
 
3
- export { version };
33
+ export { type PostRunner, type Prompt, type ScaffoldFile, type ScaffoldOptions, type Target, initApp, isValidNpmName, version };
package/dist/index.js CHANGED
@@ -1,7 +1,13 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ initApp,
4
+ isValidNpmName
5
+ } from "./chunk-PPA6U663.js";
2
6
 
3
7
  // src/index.ts
4
8
  var version = "0.0.0";
5
9
  export {
10
+ initApp,
11
+ isValidNpmName,
6
12
  version
7
13
  };
package/package.json CHANGED
@@ -1,43 +1,60 @@
1
- {
2
- "name": "@sublime-ui/devkit",
3
- "version": "0.1.0",
4
- "description": "The Sublime UI CLI (sublime) — offline Android builds, code generators, the navigation compiler, and desktop tooling.",
5
- "keywords": ["sublime-ui", "cli", "devkit", "codegen", "scaffolding", "electron-forge", "android", "react-native", "typescript"],
6
- "homepage": "https://sublime-ui.github.io/sublime-ui/",
7
- "bugs": "https://github.com/sublime-ui/sublime-ui/issues",
8
- "repository": { "type": "git", "url": "git+https://github.com/sublime-ui/sublime-ui.git", "directory": "devkit" },
9
- "license": "MIT",
10
- "author": "Aaron Mkandawire",
11
- "publishConfig": { "access": "public" },
12
- "type": "module",
13
- "bin": {
14
- "sublime": "./dist/cli.js",
15
- "sui": "./dist/cli.js"
16
- },
17
- "exports": {
18
- ".": {
19
- "types": "./dist/index.d.ts",
20
- "import": "./dist/index.js"
21
- }
22
- },
23
- "files": [
24
- "dist"
25
- ],
26
- "scripts": {
27
- "build": "tsup",
28
- "typecheck": "tsc --noEmit && npm run typecheck:fixture",
29
- "typecheck:fixture": "tsc -p test/fixtures/nav-app/tsconfig.json",
30
- "test": "vitest run",
31
- "lint": "eslint src"
32
- },
33
- "dependencies": {
34
- "@inquirer/prompts": "^7.0.0",
35
- "commander": "^12.1.0",
36
- "execa": "^9.4.0",
37
- "picocolors": "^1.1.0",
38
- "typescript": "^5.6.0"
39
- },
40
- "devDependencies": {
41
- "@types/node": "^22.19.21"
42
- }
43
- }
1
+ {
2
+ "name": "@sublime-ui/devkit",
3
+ "version": "0.1.1",
4
+ "description": "The Sublime UI CLI (sublime) — offline Android builds, code generators, the navigation compiler, and desktop tooling.",
5
+ "keywords": [
6
+ "sublime-ui",
7
+ "cli",
8
+ "devkit",
9
+ "codegen",
10
+ "scaffolding",
11
+ "electron-forge",
12
+ "android",
13
+ "react-native",
14
+ "typescript"
15
+ ],
16
+ "homepage": "https://sublime-ui.github.io/sublime-ui/",
17
+ "bugs": "https://github.com/sublime-ui/sublime-ui/issues",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/sublime-ui/sublime-ui.git",
21
+ "directory": "devkit"
22
+ },
23
+ "license": "MIT",
24
+ "author": "Aaron Mkandawire",
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "type": "module",
29
+ "bin": {
30
+ "sublime": "./dist/cli.js",
31
+ "sui": "./dist/cli.js"
32
+ },
33
+ "exports": {
34
+ ".": {
35
+ "types": "./dist/index.d.ts",
36
+ "import": "./dist/index.js"
37
+ }
38
+ },
39
+ "files": [
40
+ "dist"
41
+ ],
42
+ "scripts": {
43
+ "build": "tsup",
44
+ "typecheck": "tsc --noEmit && npm run typecheck:fixture",
45
+ "typecheck:fixture": "tsc -p test/fixtures/nav-app/tsconfig.json",
46
+ "test": "vitest run --exclude \"**/node_modules/**\" --exclude \"**/dist/**\" --exclude \"**/test/e2e/**\"",
47
+ "test:e2e": "vitest run test/e2e",
48
+ "lint": "eslint src"
49
+ },
50
+ "dependencies": {
51
+ "@inquirer/prompts": "^7.0.0",
52
+ "commander": "^12.1.0",
53
+ "execa": "^9.4.0",
54
+ "picocolors": "^1.1.0",
55
+ "typescript": "^5.6.0"
56
+ },
57
+ "devDependencies": {
58
+ "@types/node": "^22.19.21"
59
+ }
60
+ }