startx 1.1.2 → 1.1.3
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.
|
@@ -223,4 +223,4 @@ export default extend(baseConfig);
|
|
|
223
223
|
`),l&&await o.writeFile(t.join(a,`vitest.config.ts`),`import vitestConfig from "vitest-config/node";
|
|
224
224
|
|
|
225
225
|
export default vitestConfig;
|
|
226
|
-
`),Z_.info(`Created package ${r} at ${t.relative(i.workspace,a)}`),Z_.info("Run `pnpm install` to link the new package.")}static async resolveEslintPreference(e){if(e.eslint===!1)return!1;let n=Iy.getDirectory(),r=await this.readRootPackage(n.workspace);return this.hasDependency(r,`eslint`)?e.eslint??!0:e.eslint===!0||await Xx.confirm({message:`ESLint is not installed in this monorepo. Install and enable it?`,default:!0})?(r.devDependencies={...r.devDependencies,eslint:await this.resolveDependencyVersion(n.workspace,`eslint`)},await this.writeJson(t.join(n.workspace,`package.json`),r),Z_.info(`Added eslint to the root devDependencies.`),e.install!==!1&&await this.installRootDependencies(n.workspace),!0):!1}static async getInstallTags(e){let t=new Set([`common`,`node`]),n=await this.readRootPackage(e.workspace);e.eslintEnabled&&t.add(`eslint`),this.hasDependency(n,`@biomejs/biome`)&&t.add(`biome`),this.hasDependency(n,`prettier`)&&t.add(`prettier`),this.hasDependency(n,`vitest`)&&t.add(`vitest`),this.hasDependency(n,`tsdown`)&&t.add(`tsdown`);for(let n of e.packages)n.packageJson?.startx?.tags?.forEach(e=>t.add(e)),n.packageJson?.startx?.iTags?.forEach(e=>t.add(e)),n.packageJson?.startx?.gTags?.forEach(e=>t.add(e)),(n.type===`apps`||n.packageJson?.startx?.mode===`standalone`)&&t.add(`runnable`);return Array.from(t)}static resolvePackageClosure(e){let t=new Map,n=[e.selectedPackage],r=r=>{let i=this.findPackage(e.packages,r);i&&!t.has(i.name)&&n.push(i)};for(e.includeEslintConfig&&r(`eslint-config`);n.length>0;){let e=n.shift();if(!t.has(e.name)){t.set(e.name,e);for(let t of[...e.packageJson?.startx?.requiredDeps??[],...e.packageJson?.startx?.requiredDevDeps??[]])r(t)}}return Array.from(t.values())}static async ensureTemplatePackage(e){let t=this.findPackage(e.packages,e.name);if(!t){Z_.warn(`Could not find template package ${e.name}; skipping.`);return}await this.installTemplatePackage({pkg:t,directory:e.directory,tags:e.tags})}static async installTemplatePackage(e){if(!e.pkg.packageJson)throw Error(`Missing package.json for ${e.pkg.name}`);let n=e.overrideRelativePath??e.pkg.relativePath,r=t.join(e.directory.workspace,n);if(await this.pathExists(t.join(r,`package.json`))&&!await Xx.confirm({message:`"${n}" already exists. Overwrite?`,default:!1})){Z_.info(`Skipping ${e.pkg.name}.`);return}let i=new Set([...e.tags,...e.pkg.packageJson.startx?.tags??[]]),a=e.pkg.packageJson.startx?.ignore??[];a.includes(`eslint-config`)&&i.delete(`eslint`),a.includes(`vitest-config`)&&i.delete(`vitest`);let{packageJson:o,isWorkspace:s}=By.handlePackageJson({app:e.pkg.packageJson,tags:Array.from(i),name:e.overrideName??e.pkg.packageJson.name??e.pkg.name});if(s)throw Error(`Cannot install workspace as a package: ${e.pkg.name}`);await this.syncDepsWithCatalog({workspace:e.directory.workspace,packageJson:o}),await Ny.writeJSONFile({dir:r,file:`package`,content:o}),await this.copyValidatedFilesFromFolder(e.pkg.path,r,i),await Ny.copyDirectory({from:t.join(e.pkg.path,`src`),to:t.join(r,`src`),exclude:i.has(`vitest`)?void 0:/\.test\.tsx?$/}),Z_.info(`Installed ${e.overrideName??e.pkg.name} at ${n}`)}static async copyValidatedFilesFromFolder(e,n,r){let i=await Ny.listFiles({dir:e}).catch(()=>[]);for(let a of i){let i=Py[a];i&&!i.tags.every(e=>r.has(e))||a!==`package.json`&&await Ny.copyFile({from:t.join(e,a),to:t.join(n,a)})}}static createPackageJson(e){let t={typecheck:`tsc --noEmit`,clean:`rimraf dist .turbo`},n={"typescript-config":`workspace:*`},r=[];return e.eslintEnabled?(t.lint=`eslint .`,t[`lint:fix`]=`eslint . --fix`,n[`eslint-config`]=`workspace:*`):r.push(`eslint-config`),e.vitestEnabled?(t.test=`vitest run`,n[`vitest-config`]=`workspace:*`):r.push(`vitest-config`),{name:e.name,version:`1.0.0`,type:`module`,scripts:t,exports:`./src/index.ts`,devDependencies:n,startx:{iTags:[`node`],requiredDevDeps:[`typescript-config`],...r.length>0?{ignore:r}:{}}}}static async checkAndInstallMissingDeps(e){let n=await this.readRootPackage(e.workspace),r=await Iy.parsePnpmWorkspace({dir:e.workspace}),i=[];for(let[t,
|
|
226
|
+
`),Z_.info(`Created package ${r} at ${t.relative(i.workspace,a)}`),Z_.info("Run `pnpm install` to link the new package.")}static async resolveEslintPreference(e){if(e.eslint===!1)return!1;let n=Iy.getDirectory(),r=await this.readRootPackage(n.workspace);return this.hasDependency(r,`eslint`)?e.eslint??!0:e.eslint===!0||await Xx.confirm({message:`ESLint is not installed in this monorepo. Install and enable it?`,default:!0})?(r.devDependencies={...r.devDependencies,eslint:await this.resolveDependencyVersion(n.workspace,`eslint`)},await this.writeJson(t.join(n.workspace,`package.json`),r),Z_.info(`Added eslint to the root devDependencies.`),e.install!==!1&&await this.installRootDependencies(n.workspace),!0):!1}static async getInstallTags(e){let t=new Set([`common`,`node`]),n=await this.readRootPackage(e.workspace);e.eslintEnabled&&t.add(`eslint`),this.hasDependency(n,`@biomejs/biome`)&&t.add(`biome`),this.hasDependency(n,`prettier`)&&t.add(`prettier`),this.hasDependency(n,`vitest`)&&t.add(`vitest`),this.hasDependency(n,`tsdown`)&&t.add(`tsdown`);for(let n of e.packages)n.packageJson?.startx?.tags?.forEach(e=>t.add(e)),n.packageJson?.startx?.iTags?.forEach(e=>t.add(e)),n.packageJson?.startx?.gTags?.forEach(e=>t.add(e)),(n.type===`apps`||n.packageJson?.startx?.mode===`standalone`)&&t.add(`runnable`);return Array.from(t)}static resolvePackageClosure(e){let t=new Map,n=[e.selectedPackage],r=r=>{let i=this.findPackage(e.packages,r);i&&!t.has(i.name)&&n.push(i)};for(e.includeEslintConfig&&r(`eslint-config`);n.length>0;){let e=n.shift();if(!t.has(e.name)){t.set(e.name,e);for(let t of[...e.packageJson?.startx?.requiredDeps??[],...e.packageJson?.startx?.requiredDevDeps??[]])r(t)}}return Array.from(t.values())}static async ensureTemplatePackage(e){let t=this.findPackage(e.packages,e.name);if(!t){Z_.warn(`Could not find template package ${e.name}; skipping.`);return}await this.installTemplatePackage({pkg:t,directory:e.directory,tags:e.tags})}static async installTemplatePackage(e){if(!e.pkg.packageJson)throw Error(`Missing package.json for ${e.pkg.name}`);let n=e.overrideRelativePath??e.pkg.relativePath,r=t.join(e.directory.workspace,n);if(await this.pathExists(t.join(r,`package.json`))&&!await Xx.confirm({message:`"${n}" already exists. Overwrite?`,default:!1})){Z_.info(`Skipping ${e.pkg.name}.`);return}let i=new Set([...e.tags,...e.pkg.packageJson.startx?.tags??[]]),a=e.pkg.packageJson.startx?.ignore??[];a.includes(`eslint-config`)&&i.delete(`eslint`),a.includes(`vitest-config`)&&i.delete(`vitest`);let{packageJson:o,isWorkspace:s}=By.handlePackageJson({app:e.pkg.packageJson,tags:Array.from(i),name:e.overrideName??e.pkg.packageJson.name??e.pkg.name});if(s)throw Error(`Cannot install workspace as a package: ${e.pkg.name}`);await this.syncDepsWithCatalog({workspace:e.directory.workspace,templateDir:e.directory.template,packageJson:o}),await Ny.writeJSONFile({dir:r,file:`package`,content:o}),await this.copyValidatedFilesFromFolder(e.pkg.path,r,i),await Ny.copyDirectory({from:t.join(e.pkg.path,`src`),to:t.join(r,`src`),exclude:i.has(`vitest`)?void 0:/\.test\.tsx?$/}),Z_.info(`Installed ${e.overrideName??e.pkg.name} at ${n}`)}static async copyValidatedFilesFromFolder(e,n,r){let i=await Ny.listFiles({dir:e}).catch(()=>[]);for(let a of i){let i=Py[a];i&&!i.tags.every(e=>r.has(e))||a!==`package.json`&&await Ny.copyFile({from:t.join(e,a),to:t.join(n,a)})}}static createPackageJson(e){let t={typecheck:`tsc --noEmit`,clean:`rimraf dist .turbo`},n={"typescript-config":`workspace:*`},r=[];return e.eslintEnabled?(t.lint=`eslint .`,t[`lint:fix`]=`eslint . --fix`,n[`eslint-config`]=`workspace:*`):r.push(`eslint-config`),e.vitestEnabled?(t.test=`vitest run`,n[`vitest-config`]=`workspace:*`):r.push(`vitest-config`),{name:e.name,version:`1.0.0`,type:`module`,scripts:t,exports:`./src/index.ts`,devDependencies:n,startx:{iTags:[`node`],requiredDevDeps:[`typescript-config`],...r.length>0?{ignore:r}:{}}}}static async checkAndInstallMissingDeps(e){let n=await this.readRootPackage(e.workspace),r=await Iy.parsePnpmWorkspace({dir:e.workspace}),i=[],a=[];for(let[t,o]of Object.entries(Ly))if(o.tags.every(t=>e.tags.includes(t))&&!o.tags.includes(`root`))if(o.version.startsWith(`workspace:`))await this.workspacePackageExists(e.workspace,t)||a.push(t);else{if(this.hasDependency(n,t))continue;let e=r?.catalog?.[t]?`catalog:`:o.version;i.push({name:t,version:e,isDev:o.isDevDependency??!0})}if(a.length>0){Z_.warn(`The following workspace packages are missing from this monorepo:`);for(let e of a)Z_.warn(` - ${e} → run: startx package add ${e}`)}if(i.length!==0){Z_.warn(`The following npm dependencies are required but not installed:`);for(let e of i)Z_.warn(` - ${e.name}`);if(!await Xx.confirm({message:`Add them to the workspace root package.json?`,default:!0})){Z_.warn(`Skipping. Some features may not work correctly without these dependencies.`);return}for(let e of i)e.isDev?n.devDependencies[e.name]=e.version:n.dependencies[e.name]=e.version;await this.writeJson(t.join(e.workspace,`package.json`),n),Z_.info(`Added missing dependencies to root package.json.`),e.install!==!1&&await this.installRootDependencies(e.workspace)}}static async workspacePackageExists(e,n){let r=n.startsWith(`@`)?t.join(...n.split(`/`)):n,i=[t.join(e,`configs`,r,`package.json`),t.join(e,`packages`,r,`package.json`),t.join(e,`apps`,r,`package.json`)];for(let e of i)if(await this.pathExists(e))return!0;return!1}static getDestinationPath(e,n){let r=t.dirname(e),i=n.includes(`/`)?n.split(`/`).pop():n;return t.join(r,i)}static getDefaultPackagePath(e){if(e.startsWith(`@`)){let[n,r]=e.split(`/`);return t.join(`packages`,n,r)}return t.join(`packages`,e)}static findPackage(e,t){return e.find(e=>e.name===t||e.packageJson?.name===t)}static hasDependency(e,t){return!!(e.dependencies?.[t]||e.devDependencies?.[t]||e.peerDependencies?.[t])}static async readRootPackage(e){let n=await o.readFile(t.join(e,`package.json`),`utf-8`);return JSON.parse(n)}static async resolveDependencyVersion(e,t){return(await Iy.parsePnpmWorkspace({dir:e}))?.catalog?.[t]?`catalog:`:t===`eslint`?`^9.0.0`:`latest`}static async installRootDependencies(e){let t=(await this.readRootPackage(e)).packageManager?.split(`@`)[0]||`pnpm`,r=t===`yarn`?`yarn`:t,i=[`install`];Z_.info(`Running ${r} ${i.join(` `)} to install ESLint...`),await new Promise((t,a)=>{let o=n(r,i,{cwd:e,stdio:`inherit`,shell:process.platform===`win32`});o.on(`error`,a),o.on(`close`,e=>{if(e===0){t();return}a(Error(`${r} ${i.join(` `)} exited with code ${e}`))})}).catch(t=>{Z_.warn(`Could not install dependencies automatically: ${t instanceof Error?t.message:t}`),Z_.warn(`Run "${r} ${i.join(` `)}" manually in ${e}.`)})}static async syncDepsWithCatalog(e){let n=t.join(e.workspace,`pnpm-workspace.yaml`),r;try{r=await o.readFile(n,`utf-8`)}catch{return}let i=jy.parseDocument(r),a=i.getIn([`catalog`]);if(!a)return;let s=await this.loadTemplateCatalog(e.templateDir),c=e.packageJson.dependencies,l=e.packageJson.devDependencies,u={},d=e=>{if(e){for(let[t,n]of Object.entries(e))if(!n.startsWith(`workspace:`))if(n===`catalog:`){if(!a[t]){let e=s[t];e&&(u[t]=e)}}else e[t]=`catalog:`,a[t]||(u[t]=n)}};if(d(c),d(l),Object.keys(u).length!==0){for(let[e,t]of Object.entries(u))i.setIn([`catalog`,e],t);await o.writeFile(n,i.toString()),Z_.info(`Added to pnpm-workspace.yaml catalog:`);for(let[e,t]of Object.entries(u))Z_.info(` + ${e}: ${t}`)}}static async loadTemplateCatalog(e){try{let n=await o.readFile(t.join(e,`pnpm-workspace.yaml`),`utf-8`);return jy.parseDocument(n).getIn([`catalog`])??{}}catch{return{}}}static async writeJson(e,t){await o.writeFile(e,`${JSON.stringify(t,null,2)}\n`)}static async pathExists(e){try{return await o.access(e),!0}catch{return!1}}},eS=`1.1.3`;const tS=new dv;tS.name(`startx`).description(`StartX CLI - Your all in one monorepo startup tool.`).version(eS),tS.command(`ping`).action(()=>{Z_.info(`pong`)}),tS.addCommand(Zx.command),tS.addCommand($x.command),tS.parse(process.argv);export{};
|
|
@@ -4,7 +4,7 @@ import { spawn } from "child_process";
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import fs from "fs/promises";
|
|
6
6
|
import path from "path";
|
|
7
|
-
import YAML from "yaml";
|
|
7
|
+
import * as YAML from "yaml";
|
|
8
8
|
import z from "zod";
|
|
9
9
|
|
|
10
10
|
import { DepCheck } from "../configs/deps";
|
|
@@ -376,6 +376,7 @@ export class PackageCommand {
|
|
|
376
376
|
|
|
377
377
|
await this.syncDepsWithCatalog({
|
|
378
378
|
workspace: props.directory.workspace,
|
|
379
|
+
templateDir: props.directory.template,
|
|
379
380
|
packageJson: packageJson as Record<string, unknown>,
|
|
380
381
|
});
|
|
381
382
|
|
|
@@ -448,19 +449,35 @@ export class PackageCommand {
|
|
|
448
449
|
const rootPackage = await this.readRootPackage(props.workspace);
|
|
449
450
|
const pnpmWorkspace = await CliUtils.parsePnpmWorkspace({ dir: props.workspace });
|
|
450
451
|
|
|
451
|
-
const
|
|
452
|
+
const missingNpm: Array<{ name: string; version: string; isDev: boolean }> = [];
|
|
453
|
+
const missingWorkspace: string[] = [];
|
|
454
|
+
|
|
452
455
|
for (const [dep, config] of Object.entries(DepCheck)) {
|
|
453
456
|
if (!config.tags.every(tag => props.tags.includes(tag))) continue;
|
|
454
457
|
if (config.tags.includes("root")) continue;
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
+
|
|
459
|
+
if (config.version.startsWith("workspace:")) {
|
|
460
|
+
// Workspace packages live in configs/, packages/, apps/ — not in root package.json
|
|
461
|
+
const exists = await this.workspacePackageExists(props.workspace, dep);
|
|
462
|
+
if (!exists) missingWorkspace.push(dep);
|
|
463
|
+
} else {
|
|
464
|
+
if (this.hasDependency(rootPackage, dep)) continue;
|
|
465
|
+
const version = pnpmWorkspace?.catalog?.[dep] ? "catalog:" : config.version;
|
|
466
|
+
missingNpm.push({ name: dep, version, isDev: config.isDevDependency ?? true });
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (missingWorkspace.length > 0) {
|
|
471
|
+
logger.warn("The following workspace packages are missing from this monorepo:");
|
|
472
|
+
for (const name of missingWorkspace) {
|
|
473
|
+
logger.warn(` - ${name} → run: startx package add ${name}`);
|
|
474
|
+
}
|
|
458
475
|
}
|
|
459
476
|
|
|
460
|
-
if (
|
|
477
|
+
if (missingNpm.length === 0) return;
|
|
461
478
|
|
|
462
|
-
logger.warn(
|
|
463
|
-
for (const dep of
|
|
479
|
+
logger.warn("The following npm dependencies are required but not installed:");
|
|
480
|
+
for (const dep of missingNpm) {
|
|
464
481
|
logger.warn(` - ${dep.name}`);
|
|
465
482
|
}
|
|
466
483
|
|
|
@@ -473,7 +490,7 @@ export class PackageCommand {
|
|
|
473
490
|
return;
|
|
474
491
|
}
|
|
475
492
|
|
|
476
|
-
for (const dep of
|
|
493
|
+
for (const dep of missingNpm) {
|
|
477
494
|
if (dep.isDev) {
|
|
478
495
|
(rootPackage.devDependencies as Record<string, string>)[dep.name] = dep.version;
|
|
479
496
|
} else {
|
|
@@ -489,6 +506,24 @@ export class PackageCommand {
|
|
|
489
506
|
}
|
|
490
507
|
}
|
|
491
508
|
|
|
509
|
+
private static async workspacePackageExists(workspace: string, packageName: string): Promise<boolean> {
|
|
510
|
+
// Resolve scoped names: @repo/lib → packages/@repo/lib
|
|
511
|
+
const subPath = packageName.startsWith("@")
|
|
512
|
+
? path.join(...packageName.split("/"))
|
|
513
|
+
: packageName;
|
|
514
|
+
|
|
515
|
+
const candidates = [
|
|
516
|
+
path.join(workspace, "configs", subPath, "package.json"),
|
|
517
|
+
path.join(workspace, "packages", subPath, "package.json"),
|
|
518
|
+
path.join(workspace, "apps", subPath, "package.json"),
|
|
519
|
+
];
|
|
520
|
+
|
|
521
|
+
for (const candidate of candidates) {
|
|
522
|
+
if (await this.pathExists(candidate)) return true;
|
|
523
|
+
}
|
|
524
|
+
return false;
|
|
525
|
+
}
|
|
526
|
+
|
|
492
527
|
private static getDestinationPath(templateRelativePath: string, newName: string): string {
|
|
493
528
|
const parentDir = path.dirname(templateRelativePath);
|
|
494
529
|
const leafName = newName.includes("/") ? newName.split("/").pop()! : newName;
|
|
@@ -559,6 +594,7 @@ export class PackageCommand {
|
|
|
559
594
|
|
|
560
595
|
private static async syncDepsWithCatalog(props: {
|
|
561
596
|
workspace: string;
|
|
597
|
+
templateDir: string;
|
|
562
598
|
packageJson: Record<string, unknown>;
|
|
563
599
|
}): Promise<void> {
|
|
564
600
|
const workspacePath = path.join(props.workspace, "pnpm-workspace.yaml");
|
|
@@ -566,13 +602,16 @@ export class PackageCommand {
|
|
|
566
602
|
try {
|
|
567
603
|
content = await fs.readFile(workspacePath, "utf-8");
|
|
568
604
|
} catch {
|
|
569
|
-
|
|
605
|
+
return;
|
|
570
606
|
}
|
|
571
607
|
|
|
572
608
|
const doc = YAML.parseDocument(content);
|
|
573
609
|
const catalog = doc.getIn(["catalog"]) as Record<string, string> | undefined;
|
|
574
610
|
if (!catalog) return;
|
|
575
611
|
|
|
612
|
+
// Load template's catalog to resolve "catalog:" entries to real versions
|
|
613
|
+
const templateCatalog = await this.loadTemplateCatalog(props.templateDir);
|
|
614
|
+
|
|
576
615
|
const deps = props.packageJson.dependencies as Record<string, string> | undefined;
|
|
577
616
|
const devDeps = props.packageJson.devDependencies as Record<string, string> | undefined;
|
|
578
617
|
const newEntries: Record<string, string> = {};
|
|
@@ -580,12 +619,19 @@ export class PackageCommand {
|
|
|
580
619
|
const processMap = (depMap: Record<string, string> | undefined) => {
|
|
581
620
|
if (!depMap) return;
|
|
582
621
|
for (const [name, version] of Object.entries(depMap)) {
|
|
583
|
-
if (version
|
|
584
|
-
|
|
585
|
-
|
|
622
|
+
if (version.startsWith("workspace:")) continue;
|
|
623
|
+
|
|
624
|
+
if (version === "catalog:") {
|
|
625
|
+
// Valid only if the user's catalog already has this entry.
|
|
626
|
+
// If not, resolve the real version from the template's catalog and add it.
|
|
627
|
+
if (!catalog[name]) {
|
|
628
|
+
const templateVersion = templateCatalog[name];
|
|
629
|
+
if (templateVersion) newEntries[name] = templateVersion;
|
|
630
|
+
}
|
|
586
631
|
} else {
|
|
587
|
-
|
|
632
|
+
// Hardcoded version — normalize to catalog:
|
|
588
633
|
depMap[name] = "catalog:";
|
|
634
|
+
if (!catalog[name]) newEntries[name] = version;
|
|
589
635
|
}
|
|
590
636
|
}
|
|
591
637
|
};
|
|
@@ -606,6 +652,16 @@ export class PackageCommand {
|
|
|
606
652
|
}
|
|
607
653
|
}
|
|
608
654
|
|
|
655
|
+
private static async loadTemplateCatalog(templateDir: string): Promise<Record<string, string>> {
|
|
656
|
+
try {
|
|
657
|
+
const raw = await fs.readFile(path.join(templateDir, "pnpm-workspace.yaml"), "utf-8");
|
|
658
|
+
const doc = YAML.parseDocument(raw);
|
|
659
|
+
return (doc.getIn(["catalog"]) as Record<string, string>) ?? {};
|
|
660
|
+
} catch {
|
|
661
|
+
return {};
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
609
665
|
private static async writeJson(file: string, content: object) {
|
|
610
666
|
await fs.writeFile(file, `${JSON.stringify(content, null, 2)}\n`);
|
|
611
667
|
}
|
package/package.json
CHANGED