create-flow-os 0.0.47 → 0.0.51-dev.1772054587

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/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "create-flow-os",
3
- "version": "0.0.47",
3
+ "version": "0.0.51-dev.1772054587",
4
4
  "license": "PolyForm-Shield-1.0.0",
5
5
  "type": "module",
6
6
  "dependencies": {
7
- "@flow-os/client": "^0.0.47"
7
+ "@flow-os/client": ">=0.0.1-dev.0"
8
8
  },
9
9
  "bin": {
10
10
  "create-flow-os": "./src/index.ts"
@@ -13,7 +13,7 @@
13
13
  "create": "bun run src/create/index.ts",
14
14
  "dev": "flow-os dev",
15
15
  "build": "flow-os build",
16
- "start": "bun run server/start.ts"
16
+ "start": "bun ./node_modules/@flow-os/server/src/bin.ts"
17
17
  },
18
18
  "devDependencies": {
19
19
  "@types/node": "^25.3.0",
package/src/init/index.ts CHANGED
@@ -1,12 +1,10 @@
1
1
  #!/usr/bin/env bun
2
- // Clear subito per nascondere resolving/installing di Bun
3
- process.stdout.write("\x1b[2J\x1b[H");
4
2
 
5
3
  import * as readline from "readline";
6
4
  import { join, dirname } from "path";
7
5
  import { fileURLToPath } from "url";
8
6
  import { libsWithConfig, toShortName, toPkgName } from "./lib";
9
- import { initLib, fetchFlowPackageVersions } from "./scaffold";
7
+ import { initLib, fetchFlowPackageVersions, shouldUseWorkspace, findFlowOsRepoRoot } from "./scaffold";
10
8
  import { bannerBox, withLoading, colors } from "./ui";
11
9
 
12
10
  const { V, V_LIGHT, Y, E, R, B } = colors;
@@ -16,7 +14,8 @@ const { V, V_LIGHT, Y, E, R, B } = colors;
16
14
  // ───────────────────────────────────────────────────────────────────────────────
17
15
  const cwd = process.cwd();
18
16
  const cliRoot = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
19
- const available = libsWithConfig(cliRoot).map(toShortName);
17
+ const flowOsRepoRoot = findFlowOsRepoRoot(cwd);
18
+ const available = libsWithConfig(cliRoot, flowOsRepoRoot).map(toShortName);
20
19
 
21
20
  let libs = [...new Set(process.argv.slice(3))];
22
21
 
@@ -43,7 +42,7 @@ if (!toInit.length) {
43
42
  // ───────────────────────────────────────────────────────────────────────────────
44
43
  const pkgNames = toInit.map(toPkgName);
45
44
  await withLoading(async (onStep) => {
46
- const versions = await fetchFlowPackageVersions(pkgNames);
45
+ const versions = shouldUseWorkspace(cwd) ? new Map<string, string>() : await fetchFlowPackageVersions(pkgNames);
47
46
  await initLib(toInit, cwd, versions, onStep);
48
47
  });
49
48
 
package/src/init/lib.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { existsSync, readFileSync } from "fs";
1
+ import { existsSync, readFileSync, readdirSync } from "fs";
2
2
  import { join, dirname } from "path";
3
3
  import { fileURLToPath } from "url";
4
4
 
@@ -21,7 +21,27 @@ export function flowDeps(root: string): string[] {
21
21
  return Object.keys(deps).filter((k) => k.startsWith(FLOW_PREFIX));
22
22
  }
23
23
 
24
- export function libsWithConfig(cliRoot: string): string[] {
24
+ /** Scansiona packages/ per tutti gli @flow-os/* (con o senza config) */
25
+ function scanPackagesDir(packagesDir: string): string[] {
26
+ const found: string[] = [];
27
+ try {
28
+ for (const name of readdirSync(packagesDir, { withFileTypes: true })) {
29
+ if (!name.isDirectory()) continue;
30
+ const pkgPath = join(packagesDir, name.name, "package.json");
31
+ if (!existsSync(pkgPath)) continue;
32
+ try {
33
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8")) as { name?: string };
34
+ if (pkg?.name?.startsWith(FLOW_PREFIX)) found.push(pkg.name);
35
+ } catch {}
36
+ }
37
+ } catch {}
38
+ return found;
39
+ }
40
+
41
+ export function libsWithConfig(cliRoot: string, flowOsRepoRoot?: string | null): string[] {
42
+ const packagesDir = flowOsRepoRoot ? join(flowOsRepoRoot, "packages") : join(cliRoot, "..");
43
+ const fromScan = scanPackagesDir(packagesDir);
44
+ if (fromScan.length > 0) return fromScan;
25
45
  const pkg = JSON.parse(readFileSync(join(cliRoot, "package.json"), "utf-8"));
26
46
  const deps = Object.keys(pkg.dependencies ?? {}).filter((k) => k.startsWith(FLOW_PREFIX));
27
47
  return deps.filter((name) => existsSync(join(pkgRoot(name), "config")));
package/src/init/merge.ts CHANGED
@@ -1,12 +1,19 @@
1
1
  /**
2
- * Merge generico per qualsiasi file: additivo (aggiunge, non toglie).
3
- * In conflitto: chiede all'utente.
2
+ * Merge: strategia "never overwrite" (create-react-app, ecc.).
3
+ * File vuoto template. File con contenuto → mantieni utente. Mai corrompere.
4
+ * JSON: merge additivo strutturato.
4
5
  */
5
6
 
6
7
  import * as readline from "readline";
7
8
 
8
9
  export type Conflict = { key: string; userVal: unknown; templateVal: unknown; path: string };
9
10
 
11
+ /** Opzioni (compatibilità): non usate con never-overwrite */
12
+ export type Merge3WayOptions = {
13
+ baseContent?: string;
14
+ saveBase?: (content: string) => void;
15
+ };
16
+
10
17
  /** Deep merge di oggetti: aggiunge chiavi nuove, per esistenti chiede se diverso */
11
18
  function deepMergeAdditive(
12
19
  user: Record<string, unknown>,
@@ -30,71 +37,6 @@ function deepMergeAdditive(
30
37
  return out;
31
38
  }
32
39
 
33
- /** Trova la posizione della parentesi graffa di chiusura corrispondente */
34
- function findMatchingBrace(str: string, start: number): number {
35
- let depth = 1;
36
- for (let i = start; i < str.length; i++) {
37
- if (str[i] === "{") depth++;
38
- else if (str[i] === "}") {
39
- depth--;
40
- if (depth === 0) return i;
41
- }
42
- }
43
- return -1;
44
- }
45
-
46
- type Extracted = { obj: Record<string, unknown>; fullMatch: string; fn: string };
47
-
48
- /** Estrae oggetto da pattern fn({...}) o fn() nel contenuto - qualsiasi libreria */
49
- function extractObjectArg(content: string): Extracted | null {
50
- const re = /(\w+)\s*\(\s*(\{)?/g;
51
- let m: RegExpExecArray | null;
52
- while ((m = re.exec(content)) !== null) {
53
- const fn = m[1];
54
- const openBrace = m[2];
55
- const startIdx = m.index + m[0].length;
56
- let arg = "";
57
- let closeIdx = startIdx - 1;
58
- if (openBrace === "{") {
59
- closeIdx = findMatchingBrace(content, startIdx);
60
- if (closeIdx >= 0) arg = content.slice(startIdx, closeIdx);
61
- }
62
- const parenIdx = content.indexOf(")", closeIdx + 1);
63
- const fullMatch = parenIdx >= 0 ? content.slice(m.index, parenIdx + 1) : "";
64
- if (arg && arg.trim()) {
65
- try {
66
- const obj = new Function("return " + "{" + arg + "}")() as Record<string, unknown>;
67
- return { obj, fullMatch, fn };
68
- } catch {
69
- continue;
70
- }
71
- }
72
- return { obj: {}, fullMatch, fn };
73
- }
74
- return null;
75
- }
76
-
77
- /** Serializza oggetto in formato JS compatibile */
78
- function serializeObject(obj: Record<string, unknown>): string {
79
- const entries = Object.entries(obj)
80
- .map(([k, v]) => {
81
- const key = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(k) ? k : JSON.stringify(k);
82
- if (typeof v === "object" && v !== null && !Array.isArray(v)) {
83
- return `${key}: ${serializeObject(v as Record<string, unknown>)}`;
84
- }
85
- return `${key}: ${JSON.stringify(v)}`;
86
- })
87
- .join(", ");
88
- return `{ ${entries} }`;
89
- }
90
-
91
- /** Sostituisce l'argomento nella chiamata fn(...) con il nuovo oggetto */
92
- function replaceObjectArg(content: string, ext: Extracted, newObj: Record<string, unknown>): string {
93
- const newArg = Object.keys(newObj).length > 0 ? serializeObject(newObj) : "";
94
- const replacement = `${ext.fn}(${newArg})`;
95
- return content.replace(ext.fullMatch, replacement);
96
- }
97
-
98
40
  /** Merge template+template (senza conflitti: il secondo vince) - per pre-merge pacchetti */
99
41
  function mergeTemplatesOnly(a: Record<string, unknown>, b: Record<string, unknown>): Record<string, unknown> {
100
42
  const out = { ...a };
@@ -124,30 +66,9 @@ export function mergeJsonTemplates(a: string, b: string): string {
124
66
  return JSON.stringify(mergeTemplatesOnly(objA, objB), null, 2);
125
67
  }
126
68
 
127
- /** Merge template+template per code (sync, nessun conflitto) */
69
+ /** Merge template+template per code: nessun merge automatico (evita corruzione). Il secondo vince. */
128
70
  export function mergeCodeTemplates(a: string, b: string): string {
129
- const extA = extractObjectArg(a);
130
- const extB = extractObjectArg(b);
131
- if (!extB) return a || b;
132
- const objA = extA?.obj ?? {};
133
- const objB = extB.obj;
134
- const merged = mergeTemplatesOnly(objA, objB);
135
- const base = a || b;
136
- return replaceObjectArg(base, extB, merged);
137
- }
138
-
139
- /** Merge per file con pattern fn({...}) - config(), defineConfig(), ecc. */
140
- export function mergeCodeObject(userContent: string, templateContent: string, _path: string): { merged: string; conflicts: Conflict[]; ext: Extracted | null } {
141
- const conflicts: Conflict[] = [];
142
- const userExt = extractObjectArg(userContent);
143
- const templateExt = extractObjectArg(templateContent);
144
- if (!templateExt) return { merged: userContent || templateContent, conflicts: [], ext: null };
145
- const userObj = userExt?.obj ?? {};
146
- const templateObj = templateExt.obj;
147
- const mergedObj = deepMergeAdditive(userObj, templateObj, "", conflicts);
148
- const base = userContent || templateContent;
149
- const merged = replaceObjectArg(base, templateExt, mergedObj);
150
- return { merged, conflicts, ext: templateExt };
71
+ return b || a;
151
72
  }
152
73
 
153
74
  /** Chiede all'utente se applicare i valori del template in conflitto */
@@ -166,15 +87,15 @@ export async function resolveConflicts(conflicts: Conflict[], path: string): Pro
166
87
  return overrides;
167
88
  }
168
89
 
169
- /** Merge generico: sceglie strategia in base al file */
90
+ /** Never overwrite: file vuoto template, file con contenuto → mantieni. Mai corrompere. */
170
91
  export async function mergeFile(
171
92
  userContent: string | null,
172
93
  templateContent: string,
173
94
  relPath: string,
174
- promptConflict: (path: string, conflicts: Conflict[]) => Promise<Record<string, unknown>>
95
+ promptConflict: (path: string, conflicts: Conflict[]) => Promise<Record<string, unknown>>,
96
+ _options?: Merge3WayOptions
175
97
  ): Promise<string> {
176
98
  const isJson = relPath.toLowerCase().endsWith(".json");
177
- const isCode = /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(relPath);
178
99
 
179
100
  if (isJson) {
180
101
  const { merged, conflicts } = mergeJson(userContent ?? "{}", templateContent, relPath);
@@ -187,18 +108,13 @@ export async function mergeFile(
187
108
  return merged;
188
109
  }
189
110
 
190
- if (isCode) {
191
- const { merged, conflicts, ext } = mergeCodeObject(userContent ?? "", templateContent, relPath);
192
- if (conflicts.length > 0 && ext) {
193
- const overrides = await promptConflict(relPath, conflicts);
194
- const extCur = extractObjectArg(merged);
195
- if (extCur) {
196
- const obj = { ...extCur.obj, ...overrides };
197
- return replaceObjectArg(merged, extCur, obj);
198
- }
199
- }
200
- return merged;
111
+ // Never overwrite: vuoto → template. Con contenuto → mantieni. Eccezione: file incompleto (manca export che template ha)
112
+ const content = (userContent ?? "").trim();
113
+ if (!content) return templateContent;
114
+ const tHasExport = /\bexport\s+default\b/.test(templateContent);
115
+ const uHasExport = /\bexport\s+default\b/.test(userContent ?? "");
116
+ if (tHasExport && !uHasExport) {
117
+ return templateContent; // File troncato
201
118
  }
202
-
203
- return userContent ?? templateContent;
119
+ return userContent!;
204
120
  }
@@ -1,4 +1,4 @@
1
- import { existsSync, cpSync, readFileSync, writeFileSync, readdirSync } from "fs";
1
+ import { existsSync, cpSync, readFileSync, writeFileSync, readdirSync, mkdirSync } from "fs";
2
2
  import { join, basename, dirname } from "path";
3
3
  import { fileURLToPath } from "url";
4
4
  import { pkgRoot, flowDeps, toPkgName, toShortName } from "./lib";
@@ -48,7 +48,7 @@ function mergeTemplateInto(combined: Map<string, string>, pkgFiles: Map<string,
48
48
  }
49
49
  }
50
50
 
51
- /** Scrive combined template in cwd facendo merge con file utente (un solo pass) */
51
+ /** Scrive combined template in cwd. Never overwrite: file con contenuto mantieni. */
52
52
  async function writeMergedWithUser(
53
53
  combined: Map<string, string>,
54
54
  cwd: string,
@@ -58,7 +58,6 @@ async function writeMergedWithUser(
58
58
  const dest = join(cwd, relPath);
59
59
  const destDir = dirname(dest);
60
60
  if (!existsSync(destDir)) {
61
- const { mkdirSync } = await import("fs");
62
61
  mkdirSync(destDir, { recursive: true });
63
62
  }
64
63
  const userContent = existsSync(dest) ? readFileSync(dest, "utf-8") : null;
@@ -79,6 +78,29 @@ function isCreateFlowOsDev(): boolean {
79
78
  }
80
79
  }
81
80
 
81
+ /** Trova la root del repo flow-os salendo da cwd (contiene packages/client) */
82
+ export function findFlowOsRepoRoot(cwd: string): string | null {
83
+ let d = cwd;
84
+ while (true) {
85
+ const pkgClient = join(d, "packages", "client", "package.json");
86
+ if (existsSync(pkgClient)) {
87
+ try {
88
+ const pkg = JSON.parse(readFileSync(pkgClient, "utf-8")) as { name?: string };
89
+ if (pkg?.name === "@flow-os/client") return d;
90
+ } catch {}
91
+ }
92
+ const parent = dirname(d);
93
+ if (parent === d) break;
94
+ d = parent;
95
+ }
96
+ return null;
97
+ }
98
+
99
+ /** True se usare workspace:* (cwd dentro repo flow-os) */
100
+ export function shouldUseWorkspace(cwd: string): boolean {
101
+ return !!findFlowOsRepoRoot(cwd);
102
+ }
103
+
82
104
  /** Recupera versione di un pacchetto @flow-os/* dal registry npm (con cache 5 min) */
83
105
  async function fetchFlowPackageVersion(pkgName: string): Promise<string | undefined> {
84
106
  const tag = isCreateFlowOsDev() ? "dev" : "latest";
@@ -169,12 +191,19 @@ async function fetchConfigFromNpm(
169
191
  /** Sostituisce workspace:* e 0.0.1 con versione concreta (workspace va bene solo dentro flow-os) */
170
192
  function resolveFlowDeps(
171
193
  deps: Record<string, string> | undefined,
172
- configDir: string,
173
- versionsFromNpm: Map<string, string>
194
+ pkgRootDir: string,
195
+ versionsFromNpm: Map<string, string>,
196
+ useWorkspace: boolean
174
197
  ): Record<string, string> {
175
198
  if (!deps) return {};
176
199
  const resolved = { ...deps };
177
- const ownerPkgPath = join(configDir, "..", "package.json");
200
+ if (useWorkspace) {
201
+ for (const k of Object.keys(resolved)) {
202
+ if (k.startsWith("@flow-os/")) resolved[k] = "workspace:*";
203
+ }
204
+ return resolved;
205
+ }
206
+ const ownerPkgPath = join(pkgRootDir, "package.json");
178
207
  let ownerVersion: string | undefined;
179
208
  if (existsSync(ownerPkgPath)) {
180
209
  try {
@@ -205,7 +234,25 @@ function resolveFlowDeps(
205
234
  return resolved;
206
235
  }
207
236
 
208
- function mergePkg(configDir: string, cwd: string, versionsFromNpm: Map<string, string>): void {
237
+ const PKG_KEY_ORDER = ["name", "version", "private", "type", "scripts", "dependencies", "devDependencies"];
238
+
239
+ function sortPkgKeys(pkg: Record<string, unknown>): Record<string, unknown> {
240
+ const ordered: Record<string, unknown> = {};
241
+ for (const k of PKG_KEY_ORDER) {
242
+ if (k in pkg) {
243
+ const v = pkg[k];
244
+ ordered[k] = k === "scripts" && v && typeof v === "object" && !Array.isArray(v)
245
+ ? Object.fromEntries(Object.entries(v as Record<string, string>).sort(([a], [b]) => a.localeCompare(b)))
246
+ : v;
247
+ }
248
+ }
249
+ for (const k of Object.keys(pkg).sort()) {
250
+ if (!(k in ordered)) ordered[k] = pkg[k];
251
+ }
252
+ return ordered;
253
+ }
254
+
255
+ function mergePkg(configDir: string, cwd: string, versionsFromNpm: Map<string, string>, useWorkspace: boolean): void {
209
256
  const configPkg = join(configDir, "package.json");
210
257
  if (!existsSync(configPkg)) return;
211
258
  const targetPath = join(cwd, "package.json");
@@ -215,12 +262,75 @@ function mergePkg(configDir: string, cwd: string, versionsFromNpm: Map<string, s
215
262
  : { ...config, name: basename(cwd) || "flow-app" };
216
263
  target.dependencies = { ...target.dependencies, ...config.dependencies };
217
264
  target.devDependencies = { ...target.devDependencies, ...config.devDependencies };
218
- for (const [k, v] of Object.entries(resolveFlowDeps(config.dependencies, configDir, versionsFromNpm)))
265
+ const pkgRootDir = join(configDir, "..");
266
+ for (const [k, v] of Object.entries(resolveFlowDeps(config.dependencies, pkgRootDir, versionsFromNpm, useWorkspace)))
219
267
  target.dependencies[k] = v;
220
- for (const [k, v] of Object.entries(resolveFlowDeps(config.devDependencies, configDir, versionsFromNpm)))
268
+ for (const [k, v] of Object.entries(resolveFlowDeps(config.devDependencies, pkgRootDir, versionsFromNpm, useWorkspace)))
221
269
  target.devDependencies[k] = v;
222
270
  target.scripts = { ...target.scripts, ...config.scripts };
223
- writeFileSync(targetPath, JSON.stringify(target, null, 2));
271
+ writeFileSync(targetPath, JSON.stringify(sortPkgKeys(target), null, 2));
272
+ }
273
+
274
+ type FlowOsInit = { dependencies?: Record<string, string>; devDependencies?: Record<string, string>; scripts?: Record<string, string>; name?: string; version?: string; type?: string };
275
+
276
+ /** Merge da flow-os-init: root package.json (se in repo) oppure package (npm) */
277
+ function mergeFlowOsInit(
278
+ packageRoot: string,
279
+ pkgName: string,
280
+ cwd: string,
281
+ versionsFromNpm: Map<string, string>,
282
+ useWorkspace: boolean,
283
+ flowOsRepoRoot: string | null
284
+ ): void {
285
+ let init: FlowOsInit | undefined;
286
+ if (flowOsRepoRoot) {
287
+ const rootPkgPath = join(flowOsRepoRoot, "package.json");
288
+ if (existsSync(rootPkgPath)) {
289
+ try {
290
+ const rootPkg = JSON.parse(readFileSync(rootPkgPath, "utf-8")) as { "flow-os-init"?: Record<string, FlowOsInit> };
291
+ init = rootPkg["flow-os-init"]?.[pkgName];
292
+ } catch {}
293
+ }
294
+ }
295
+ if (!init) {
296
+ const pkgPath = join(packageRoot, "package.json");
297
+ if (!existsSync(pkgPath)) return;
298
+ try {
299
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8")) as { "flow-os-init"?: FlowOsInit };
300
+ init = pkg["flow-os-init"];
301
+ } catch {
302
+ return;
303
+ }
304
+ }
305
+ if (!init) return;
306
+ const targetPath = join(cwd, "package.json");
307
+ const target = existsSync(targetPath)
308
+ ? JSON.parse(readFileSync(targetPath, "utf-8"))
309
+ : { name: init.name ?? (basename(cwd) || "flow-app"), version: init.version ?? "0.0.1", type: init.type ?? "module" };
310
+ target.dependencies = { ...target.dependencies, ...init.dependencies };
311
+ target.devDependencies = { ...target.devDependencies, ...init.devDependencies };
312
+ for (const [k, v] of Object.entries(resolveFlowDeps(init.dependencies, packageRoot, versionsFromNpm, useWorkspace)))
313
+ target.dependencies[k] = v;
314
+ for (const [k, v] of Object.entries(resolveFlowDeps(init.devDependencies, packageRoot, versionsFromNpm, useWorkspace)))
315
+ target.devDependencies[k] = v;
316
+ target.scripts = { ...target.scripts, ...init.scripts };
317
+ writeFileSync(targetPath, JSON.stringify(sortPkgKeys(target), null, 2));
318
+ }
319
+
320
+ /** Aggiunge dipendenza per pacchetti senza config (solo install) */
321
+ function addDependencyForPackage(
322
+ pkgName: string,
323
+ cwd: string,
324
+ versionsFromNpm: Map<string, string>,
325
+ useWorkspace: boolean
326
+ ): void {
327
+ const targetPath = join(cwd, "package.json");
328
+ if (!existsSync(targetPath)) return;
329
+ const target = JSON.parse(readFileSync(targetPath, "utf-8"));
330
+ target.dependencies = target.dependencies ?? {};
331
+ const spec = useWorkspace ? "workspace:*" : (versionsFromNpm.get(pkgName) ? `^${versionsFromNpm.get(pkgName)}` : undefined);
332
+ if (spec) target.dependencies[pkgName] = spec;
333
+ writeFileSync(targetPath, JSON.stringify(sortPkgKeys(target), null, 2));
224
334
  }
225
335
 
226
336
  /** Assicura che versions abbia le versioni per pkgNames (fetch lazy) */
@@ -251,12 +361,17 @@ async function collectAllTemplates(
251
361
  done: Set<string>,
252
362
  order: string[],
253
363
  versionsFromNpm: Map<string, string>,
254
- tmpDirs: string[]
364
+ tmpDirs: string[],
365
+ flowOsRepoRoot: string | null
255
366
  ): Promise<void> {
256
367
  for (const lib of libs) {
257
368
  const pkgName = toPkgName(lib);
258
369
  if (done.has(pkgName)) continue;
259
370
 
371
+ const shortName = toShortName(pkgName);
372
+ const localFromRepo = flowOsRepoRoot ? join(flowOsRepoRoot, "packages", shortName) : "";
373
+ const localConfigFromRepo = localFromRepo ? join(localFromRepo, "config") : "";
374
+
260
375
  let root: string;
261
376
  let configDir: string;
262
377
  try {
@@ -266,12 +381,22 @@ async function collectAllTemplates(
266
381
  root = "";
267
382
  configDir = "";
268
383
  }
384
+ const hasLocalFromRepo = !!localConfigFromRepo && existsSync(localConfigFromRepo);
269
385
  const hasLocal = existsSync(configDir);
270
386
 
271
387
  let pkgFiles: Map<string, string>;
272
388
  let configDirForPkg: string;
389
+ const useLocalRepo = hasLocalFromRepo && flowOsRepoRoot;
273
390
  const npmVer = versionsFromNpm.get(pkgName);
274
- if (npmVer) {
391
+
392
+ if (useLocalRepo) {
393
+ configDirForPkg = localConfigFromRepo;
394
+ pkgFiles = collectConfigFiles(configDirForPkg);
395
+ const subDeps = flowDepsFromPkg(join(configDirForPkg, ".."));
396
+ for (const sub of subDeps) {
397
+ await collectAllTemplates([toShortName(sub)], combined, done, order, versionsFromNpm, tmpDirs, flowOsRepoRoot);
398
+ }
399
+ } else if (npmVer) {
275
400
  const fetched = await fetchConfigFromNpm(pkgName, npmVer, tmpDirs);
276
401
  if (fetched) {
277
402
  pkgFiles = fetched.files;
@@ -279,13 +404,13 @@ async function collectAllTemplates(
279
404
  const subDeps = flowDepsFromPkg(join(configDirForPkg, ".."));
280
405
  await ensureVersions(versionsFromNpm, subDeps);
281
406
  for (const sub of subDeps) {
282
- await collectAllTemplates([toShortName(sub)], combined, done, order, versionsFromNpm, tmpDirs);
407
+ await collectAllTemplates([toShortName(sub)], combined, done, order, versionsFromNpm, tmpDirs, flowOsRepoRoot);
283
408
  }
284
409
  } else if (hasLocal) {
285
410
  const subDeps = flowDeps(root);
286
411
  await ensureVersions(versionsFromNpm, subDeps);
287
412
  for (const sub of subDeps) {
288
- await collectAllTemplates([toShortName(sub)], combined, done, order, versionsFromNpm, tmpDirs);
413
+ await collectAllTemplates([toShortName(sub)], combined, done, order, versionsFromNpm, tmpDirs, flowOsRepoRoot);
289
414
  }
290
415
  pkgFiles = collectConfigFiles(configDir);
291
416
  configDirForPkg = configDir;
@@ -296,7 +421,7 @@ async function collectAllTemplates(
296
421
  const subDeps = flowDeps(root);
297
422
  await ensureVersions(versionsFromNpm, subDeps);
298
423
  for (const sub of subDeps) {
299
- await collectAllTemplates([toShortName(sub)], combined, done, order, versionsFromNpm, tmpDirs);
424
+ await collectAllTemplates([toShortName(sub)], combined, done, order, versionsFromNpm, tmpDirs, flowOsRepoRoot);
300
425
  }
301
426
  pkgFiles = collectConfigFiles(configDir);
302
427
  configDirForPkg = configDir;
@@ -319,28 +444,68 @@ export async function initLib(
319
444
  onProgress?: (step: InitProgressStep) => void
320
445
  ): Promise<void> {
321
446
  const pkgNames = libs.map(toPkgName);
447
+ const flowOsRepoRoot = findFlowOsRepoRoot(cwd);
448
+ const useWorkspace = !!flowOsRepoRoot;
322
449
  const versions = versionsFromNpm ?? new Map<string, string>();
323
450
 
324
451
  onProgress?.("fetch");
325
- const fetched = await fetchFlowPackageVersions(pkgNames);
326
- for (const [k, v] of fetched) versions.set(k, v);
452
+ if (!useWorkspace) {
453
+ const fetched = await fetchFlowPackageVersions(pkgNames);
454
+ for (const [k, v] of fetched) versions.set(k, v);
455
+ }
327
456
 
328
457
  onProgress?.("templates");
329
458
  const combined = new Map<string, string>();
330
459
  const done = new Set<string>();
331
460
  const order: string[] = [];
332
461
  const tmpDirs: string[] = [];
333
- await collectAllTemplates(libs, combined, done, order, versions, tmpDirs);
462
+ await collectAllTemplates(libs, combined, done, order, versions, tmpDirs, flowOsRepoRoot);
334
463
 
335
464
  for (const configDir of order) {
336
- mergePkg(configDir, cwd, versions);
465
+ const packageRoot = join(configDir, "..");
466
+ const pkgName = (() => {
467
+ try {
468
+ const p = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf-8")) as { name?: string };
469
+ return p?.name ?? "";
470
+ } catch {
471
+ return "";
472
+ }
473
+ })();
474
+ if (existsSync(join(configDir, "package.json"))) {
475
+ mergePkg(configDir, cwd, versions, useWorkspace);
476
+ } else {
477
+ mergeFlowOsInit(packageRoot, pkgName, cwd, versions, useWorkspace, flowOsRepoRoot);
478
+ }
479
+ }
480
+
481
+ const targetPath = join(cwd, "package.json");
482
+ if (!existsSync(targetPath)) {
483
+ writeFileSync(
484
+ targetPath,
485
+ JSON.stringify(sortPkgKeys({ name: basename(cwd) || "flow-app", version: "0.0.1", private: true, type: "module" }), null, 2)
486
+ );
487
+ }
488
+
489
+ const toAdd = new Set<string>(pkgNames);
490
+ for (const configDir of order) {
491
+ const packageRoot = join(configDir, "..");
492
+ for (const peer of flowDepsFromPkg(packageRoot)) toAdd.add(peer);
493
+ }
494
+ for (const pkgName of pkgNames) {
495
+ const packageRoot = flowOsRepoRoot ? join(flowOsRepoRoot, "packages", toShortName(pkgName)) : (() => { try { return pkgRoot(pkgName); } catch { return ""; } })();
496
+ if (packageRoot) for (const peer of flowDepsFromPkg(packageRoot)) toAdd.add(peer);
497
+ }
498
+ await ensureVersions(versions, [...toAdd]);
499
+ for (const pkgName of toAdd) {
500
+ addDependencyForPackage(pkgName, cwd, versions, useWorkspace);
337
501
  }
338
502
 
339
503
  onProgress?.("write");
340
504
  await writeMergedWithUser(combined, cwd, (path, conflicts) => resolveConflicts(conflicts, path));
341
505
 
342
506
  onProgress?.("install");
343
- const proc = Bun.spawn(["bun", "install"], { cwd, stdout: "inherit", stderr: "inherit" });
507
+ const installCwd = useWorkspace && flowOsRepoRoot ? flowOsRepoRoot : cwd;
508
+ const proc = Bun.spawn(["bun", "install"], { cwd: installCwd, stdout: "pipe", stderr: "pipe" });
344
509
  const exitCode = await proc.exited;
345
510
  if (exitCode !== 0) {
346
511
  throw new Error(`bun install exited with code ${exitCode}`);
package/src/init/ui.ts CHANGED
@@ -9,6 +9,7 @@ const E = "\x1b[91m";
9
9
  const R = "\x1b[0m";
10
10
  const B = "\x1b[1m";
11
11
  const DIM = "\x1b[2m";
12
+ const W = "\x1b[97m"; // bianco (solo per headerLogo)
12
13
 
13
14
  const FRAMES = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏";
14
15
 
@@ -25,19 +26,24 @@ export function bannerBox(title: string, lines: string[], color: string, w = 50)
25
26
  const c = color;
26
27
  const inner = " " + c + B + title + R + " ";
27
28
  const innerLen = stripAnsi(inner).length;
28
- const half = Math.max(0, Math.floor((w - 2 - innerLen) / 2));
29
- const top = c + "╭" + "─".repeat(half) + inner + "─".repeat(w - 2 - half - innerLen) + "╮" + R;
30
- const bottom = c + "╰" + "─".repeat(w - 2) + "╯" + R;
31
- const bodyLines = lines.map((l) => c + "" + R + " " + pad(l, w - 2) + c + "" + R);
32
- return "\n" + top + "\n" + bodyLines.join("\n") + "\n" + bottom + "\n";
29
+ const innerW = w - 4;
30
+ const half = Math.max(0, Math.floor((innerW - innerLen) / 2));
31
+ const rest = innerW - half - innerLen;
32
+ const top = c + "" + "".repeat(half) + inner + c + "─".repeat(rest) + "" + R;
33
+ const bottom = c + "" + "─".repeat(innerW) + "" + R;
34
+ const emptyLine = c + "│" + R + " " + " ".repeat(innerW - 2) + c + "│" + R;
35
+ const bodyLines = lines.map((l) => c + "│" + R + " " + pad(l, innerW - 2) + c + "│" + R);
36
+ return "\n" + top + "\n" + emptyLine + "\n" + bodyLines.join("\n") + "\n" + bottom + "\n";
33
37
  }
34
38
 
35
- export function box(lines: string[], color: string, w = 52): string {
39
+ export function box(lines: string[], color: string, w = 50): string {
36
40
  const c = color;
37
- const top = c + "╭" + "─".repeat(w) + "╮" + R;
38
- const bottom = c + "" + "─".repeat(w) + "" + R;
39
- const body = lines.map((l) => c + "" + R + " " + pad(l, w - 2) + c + "" + R).join("\n");
40
- return "\n" + top + "\n" + body + "\n" + bottom + "\n";
41
+ const innerW = w - 4;
42
+ const top = c + "" + "─".repeat(innerW) + "" + R;
43
+ const bottom = c + "" + "".repeat(innerW) + "" + R;
44
+ const emptyLine = c + "" + R + " " + " ".repeat(innerW - 2) + c + "" + R;
45
+ const body = lines.map((l) => c + "│" + R + " " + pad(l, innerW - 2) + c + "│" + R).join("\n");
46
+ return "\n" + top + "\n" + emptyLine + "\n" + body + "\n" + bottom + "\n";
41
47
  }
42
48
 
43
49
  import type { InitProgressStep } from "./scaffold";
@@ -87,8 +93,6 @@ export async function withLoading<T>(
87
93
  }
88
94
  }
89
95
 
90
- const W = "\x1b[97m"; // bianco
91
-
92
96
  export const colors = { V, V_LIGHT, Y, E, R, B, DIM, W };
93
97
 
94
98
  /** Icona layers: 3 quadrati arrotondati sovrapposti, vista isometrica con shading ▓▒░ */