@libria/scaffold 0.2.1 → 0.3.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.
@@ -1,3 +1,176 @@
1
- import{createRequire as e}from"node:module";import t from"fs/promises";import n from"path";import r from"fs-extra";import{execFile as i}from"node:child_process";import{promisify as a}from"node:util";import{loadAllPlugins as o,loadPlugin as s}from"@libria/plugin-loader";const c=`scaffold-template`,l=`.lbscaffold`;async function u(e=process.cwd()){let r=e;for(;;){let e=n.join(r,l);try{return await t.access(e),e}catch{let e=n.dirname(r);if(e===r)return null;r=e}}}function d(){return n.join(process.cwd(),l)}async function f(e){let n=e??await u();if(!n)return{};try{let e=await t.readFile(n,`utf-8`);return JSON.parse(e)}catch(e){if(e.code===`ENOENT`)return{};throw Error(`Failed to load config from ${n}: ${e.message}`)}}async function p(e,n){let r=n??d();await t.writeFile(r,JSON.stringify(e,null,2)+`
2
- `,`utf-8`)}async function m(e){let n=e??d();try{throw await t.access(n),Error(`Config file already exists at ${n}`)}catch(e){if(e.code!==`ENOENT`)throw e}return await p({plugins:[`./plugins/**`],packages:[]},n),n}async function h(e,t){let n=await f(t);if(n.plugins||=[],n.plugins.includes(e))throw Error(`Plugin pattern '${e}' already exists in config`);n.plugins.push(e),await p(n,t??await u()??d())}async function g(e,t){let n=t??await u();if(!n)throw Error(`No config file found`);let r=await f(n);if(!r.plugins||!r.plugins.includes(e))throw Error(`Plugin pattern '${e}' not found in config`);r.plugins=r.plugins.filter(t=>t!==e),await p(r,n)}async function _(e){return(await f(e)).plugins??[]}async function v(e){let t=e??await u();if(!t)return[];let r=await f(t),i=n.dirname(t);return(r.plugins??[]).map(e=>n.isAbsolute(e)?e.replace(/\\/g,`/`):n.resolve(i,e).replace(/\\/g,`/`))}async function y(e,t){let n=await f(t);if(n.packages||=[],n.packages.includes(e))throw Error(`Package '${e}' already exists in config`);n.packages.push(e),await p(n,t??await u()??d())}async function b(e,t){let n=t??await u();if(!n)throw Error(`No config file found`);let r=await f(n);if(!r.packages||!r.packages.includes(e))throw Error(`Package '${e}' not found in config`);r.packages=r.packages.filter(t=>t!==e),await p(r,n)}async function x(e){return(await f(e)).packages??[]}async function S(e,t){async function i(e){let a=await r.readdir(e);for(let o of a){let a=n.join(e,o);if((await r.stat(a)).isDirectory())await i(a);else if(/\.(ts|js|json|md|txt)$/i.test(o)){let e=await r.readFile(a,`utf-8`);for(let[n,r]of Object.entries(t))e=e.replaceAll(n,r);await r.writeFile(a,e,`utf-8`)}}}await i(e)}const C=a(i);async function w(t){let r=e(n.join(process.cwd(),`package.json`));try{let e=r.resolve(`${t}/package.json`);return n.dirname(e)}catch{}let i=await T();if(i){let r=e(n.join(i,`_virtual.js`));try{let e=r.resolve(`${t}/package.json`);return n.dirname(e)}catch{}}throw Error(`Package '${t}' not found. Install it locally (npm install ${t}) or globally (npm install -g ${t}).`)}async function T(){try{let{stdout:e}=await C(`npm`,[`root`,`-g`],{encoding:`utf-8`}),n=e.trim();return await t.access(n),n}catch{return null}}async function E(e,r){let i=n.join(e,`plugin.json`);try{let n=await t.readFile(i,`utf-8`),a=JSON.parse(n);return r&&a.pluginType!==r?[]:[await s({...a,__dir:e})]}catch(e){if(e.code!==`ENOENT`)throw e}return o(e,r)}export{c as SCAFFOLD_TEMPLATE_PLUGIN_TYPE,y as addPackage,h as addPluginGlob,u as findConfigPath,d as getDefaultConfigPath,v as getPluginPaths,m as initConfig,x as listPackages,_ as listPluginGlobs,f as loadConfig,E as loadPackagePlugins,b as removePackage,g as removePluginGlob,S as replacePlaceholders,w as resolvePackageDir,p as saveConfig};
1
+ import fs from "fs/promises";
2
+ import path from "path";
3
+ import { execFile } from "node:child_process";
4
+ import { promisify } from "node:util";
5
+
6
+ //#region src/config.ts
7
+ const CONFIG_FILENAME = ".lbscaffold";
8
+ /**
9
+ * Find the config file by searching up the directory tree
10
+ */
11
+ async function findConfigPath(startDir = process.cwd()) {
12
+ let currentDir = startDir;
13
+ while (true) {
14
+ const configPath = path.join(currentDir, CONFIG_FILENAME);
15
+ try {
16
+ await fs.access(configPath);
17
+ return configPath;
18
+ } catch {
19
+ const parentDir = path.dirname(currentDir);
20
+ if (parentDir === currentDir) return null;
21
+ currentDir = parentDir;
22
+ }
23
+ }
24
+ }
25
+ /**
26
+ * Get the default config path in the current directory
27
+ */
28
+ function getDefaultConfigPath() {
29
+ return path.join(process.cwd(), CONFIG_FILENAME);
30
+ }
31
+ /**
32
+ * Load the config file
33
+ */
34
+ async function loadConfig(configPath) {
35
+ const resolvedPath = configPath ?? await findConfigPath();
36
+ if (!resolvedPath) return {};
37
+ try {
38
+ const content = await fs.readFile(resolvedPath, "utf-8");
39
+ return JSON.parse(content);
40
+ } catch (error) {
41
+ if (error.code === "ENOENT") return {};
42
+ throw new Error(`Failed to load config from ${resolvedPath}: ${error.message}`);
43
+ }
44
+ }
45
+ /**
46
+ * Save the config file
47
+ */
48
+ async function saveConfig(config, configPath) {
49
+ const resolvedPath = configPath ?? getDefaultConfigPath();
50
+ await fs.writeFile(resolvedPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
51
+ }
52
+ /**
53
+ * Initialize a new config file
54
+ */
55
+ async function initConfig(configPath) {
56
+ const resolvedPath = configPath ?? getDefaultConfigPath();
57
+ try {
58
+ await fs.access(resolvedPath);
59
+ throw new Error(`Config file already exists at ${resolvedPath}`);
60
+ } catch (error) {
61
+ if (error.code !== "ENOENT") throw error;
62
+ }
63
+ await saveConfig({
64
+ plugins: ["./plugins/**"],
65
+ packages: []
66
+ }, resolvedPath);
67
+ return resolvedPath;
68
+ }
69
+ /**
70
+ * Add a plugin glob pattern to the config
71
+ */
72
+ async function addPluginGlob(glob, configPath) {
73
+ const config = await loadConfig(configPath);
74
+ if (!config.plugins) config.plugins = [];
75
+ if (config.plugins.includes(glob)) throw new Error(`Plugin pattern '${glob}' already exists in config`);
76
+ config.plugins.push(glob);
77
+ await saveConfig(config, configPath ?? await findConfigPath() ?? getDefaultConfigPath());
78
+ }
79
+ /**
80
+ * Remove a plugin glob pattern from the config
81
+ */
82
+ async function removePluginGlob(glob, configPath) {
83
+ const resolvedPath = configPath ?? await findConfigPath();
84
+ if (!resolvedPath) throw new Error("No config file found");
85
+ const config = await loadConfig(resolvedPath);
86
+ if (!config.plugins || !config.plugins.includes(glob)) throw new Error(`Plugin pattern '${glob}' not found in config`);
87
+ config.plugins = config.plugins.filter((p) => p !== glob);
88
+ await saveConfig(config, resolvedPath);
89
+ }
90
+ /**
91
+ * List all plugin glob patterns in the config
92
+ */
93
+ async function listPluginGlobs(configPath) {
94
+ return (await loadConfig(configPath)).plugins ?? [];
95
+ }
96
+ /**
97
+ * Get absolute plugin paths from config globs
98
+ */
99
+ async function getPluginPaths(configPath) {
100
+ const resolvedConfigPath = configPath ?? await findConfigPath();
101
+ if (!resolvedConfigPath) return [];
102
+ const config = await loadConfig(resolvedConfigPath);
103
+ const configDir = path.dirname(resolvedConfigPath);
104
+ return (config.plugins ?? []).map((glob) => {
105
+ if (path.isAbsolute(glob)) return glob.replace(/\\/g, "/");
106
+ return path.resolve(configDir, glob).replace(/\\/g, "/");
107
+ });
108
+ }
109
+ /**
110
+ * Add an npm package name to the config
111
+ */
112
+ async function addPackage(packageName, configPath) {
113
+ const config = await loadConfig(configPath);
114
+ if (!config.packages) config.packages = [];
115
+ if (config.packages.includes(packageName)) throw new Error(`Package '${packageName}' already exists in config`);
116
+ config.packages.push(packageName);
117
+ await saveConfig(config, configPath ?? await findConfigPath() ?? getDefaultConfigPath());
118
+ }
119
+ /**
120
+ * Remove an npm package name from the config
121
+ */
122
+ async function removePackage(packageName, configPath) {
123
+ const resolvedPath = configPath ?? await findConfigPath();
124
+ if (!resolvedPath) throw new Error("No config file found");
125
+ const config = await loadConfig(resolvedPath);
126
+ if (!config.packages || !config.packages.includes(packageName)) throw new Error(`Package '${packageName}' not found in config`);
127
+ config.packages = config.packages.filter((p) => p !== packageName);
128
+ await saveConfig(config, resolvedPath);
129
+ }
130
+ /**
131
+ * List all npm package names in the config
132
+ */
133
+ async function listPackages(configPath) {
134
+ return (await loadConfig(configPath)).packages ?? [];
135
+ }
136
+
137
+ //#endregion
138
+ //#region src/utils/resolve-package.ts
139
+ const execFileAsync = promisify(execFile);
140
+ /**
141
+ * Resolve an npm package name to its installed root directory.
142
+ * Tries local node_modules first (relative to cwd), then global.
143
+ */
144
+ async function resolvePackageDir(packageName) {
145
+ const localCandidate = path.join(process.cwd(), "node_modules", packageName);
146
+ try {
147
+ await fs.access(path.join(localCandidate, "package.json"));
148
+ return localCandidate;
149
+ } catch {}
150
+ const globalDir = await getGlobalNodeModulesDir();
151
+ if (globalDir) {
152
+ const globalCandidate = path.join(globalDir, packageName);
153
+ try {
154
+ await fs.access(path.join(globalCandidate, "package.json"));
155
+ return globalCandidate;
156
+ } catch {}
157
+ }
158
+ throw new Error(`Package '${packageName}' not found. Install it locally (npm install ${packageName}) or globally (npm install -g ${packageName}).`);
159
+ }
160
+ /**
161
+ * Get the global node_modules directory using npm root -g.
162
+ */
163
+ async function getGlobalNodeModulesDir() {
164
+ try {
165
+ const { stdout } = await execFileAsync("npm", ["root", "-g"], { encoding: "utf-8" });
166
+ const dir = stdout.trim();
167
+ await fs.access(dir);
168
+ return dir;
169
+ } catch {
170
+ return null;
171
+ }
172
+ }
173
+
174
+ //#endregion
175
+ export { addPackage, addPluginGlob, findConfigPath, getDefaultConfigPath, getPluginPaths, initConfig, listPackages, listPluginGlobs, loadConfig, removePackage, removePluginGlob, resolvePackageDir, saveConfig };
3
176
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["fs"],"sources":["../../src/core/types.ts","../../src/config.ts","../../src/utils/replace-placeholders.ts","../../src/utils/resolve-package.ts","../../src/utils/load-package-plugins.ts"],"sourcesContent":["export const SCAFFOLD_TEMPLATE_PLUGIN_TYPE = 'scaffold-template';\n\nexport type ScaffoldTemplatePluginOption<TValue = string | boolean | number> = {\n readonly flags: string; // ex: --git-init\n readonly required?: boolean;\n readonly description: string;\n readonly defaultValue?: TValue | TValue[];\n readonly choices?: TValue[];\n};\n\nexport type ResolvedOptions<TOpt extends object> = {\n [k in keyof TOpt]: TOpt[k] extends ScaffoldTemplatePluginOption<infer TValue> ? TValue : never;\n};\n\nexport type ExecuteOptions<TOpt extends object> = ScaffoldTemplatePluginOptions & {\n [k in keyof TOpt]: TOpt[k] extends ScaffoldTemplatePluginOption<infer TValue> ? TValue : never;\n};\n\nexport interface ScaffoldTemplatePlugin<TOpt extends object = object> {\n readonly argument: string;\n\n getOptions(options: ScaffoldTemplatePluginOptions & Partial<ResolvedOptions<TOpt>>): Promise<\n Partial<{\n [k in keyof TOpt]: ScaffoldTemplatePluginOption;\n }>\n >;\n\n execute(options: ExecuteOptions<TOpt>): Promise<void>;\n}\n\nexport type ScaffoldTemplatePluginOptions = {\n name: string;\n dryRun?: boolean;\n force?: boolean;\n};\n","import fs from 'fs/promises';\nimport path from 'path';\n\nexport interface LbScaffoldConfig {\n plugins?: string[];\n packages?: string[];\n}\n\nconst CONFIG_FILENAME = '.lbscaffold';\n\n/**\n * Find the config file by searching up the directory tree\n */\nexport async function findConfigPath(startDir: string = process.cwd()): Promise<string | null> {\n let currentDir = startDir;\n\n while (true) {\n const configPath = path.join(currentDir, CONFIG_FILENAME);\n try {\n await fs.access(configPath);\n return configPath;\n } catch {\n const parentDir = path.dirname(currentDir);\n if (parentDir === currentDir) {\n // Reached root\n return null;\n }\n currentDir = parentDir;\n }\n }\n}\n\n/**\n * Get the default config path in the current directory\n */\nexport function getDefaultConfigPath(): string {\n return path.join(process.cwd(), CONFIG_FILENAME);\n}\n\n/**\n * Load the config file\n */\nexport async function loadConfig(configPath?: string): Promise<LbScaffoldConfig> {\n const resolvedPath = configPath ?? (await findConfigPath());\n\n if (!resolvedPath) {\n return {};\n }\n\n try {\n const content = await fs.readFile(resolvedPath, 'utf-8');\n return JSON.parse(content) as LbScaffoldConfig;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n return {};\n }\n throw new Error(`Failed to load config from ${resolvedPath}: ${(error as Error).message}`);\n }\n}\n\n/**\n * Save the config file\n */\nexport async function saveConfig(config: LbScaffoldConfig, configPath?: string): Promise<void> {\n const resolvedPath = configPath ?? getDefaultConfigPath();\n await fs.writeFile(resolvedPath, JSON.stringify(config, null, 2) + '\\n', 'utf-8');\n}\n\n/**\n * Initialize a new config file\n */\nexport async function initConfig(configPath?: string): Promise<string> {\n const resolvedPath = configPath ?? getDefaultConfigPath();\n\n try {\n await fs.access(resolvedPath);\n throw new Error(`Config file already exists at ${resolvedPath}`);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {\n throw error;\n }\n }\n\n const defaultConfig: LbScaffoldConfig = {\n plugins: ['./plugins/**'],\n packages: [],\n };\n\n await saveConfig(defaultConfig, resolvedPath);\n return resolvedPath;\n}\n\n/**\n * Add a plugin glob pattern to the config\n */\nexport async function addPluginGlob(glob: string, configPath?: string): Promise<void> {\n const config = await loadConfig(configPath);\n\n if (!config.plugins) {\n config.plugins = [];\n }\n\n if (config.plugins.includes(glob)) {\n throw new Error(`Plugin pattern '${glob}' already exists in config`);\n }\n\n config.plugins.push(glob);\n await saveConfig(config, configPath ?? (await findConfigPath()) ?? getDefaultConfigPath());\n}\n\n/**\n * Remove a plugin glob pattern from the config\n */\nexport async function removePluginGlob(glob: string, configPath?: string): Promise<void> {\n const resolvedPath = configPath ?? (await findConfigPath());\n\n if (!resolvedPath) {\n throw new Error('No config file found');\n }\n\n const config = await loadConfig(resolvedPath);\n\n if (!config.plugins || !config.plugins.includes(glob)) {\n throw new Error(`Plugin pattern '${glob}' not found in config`);\n }\n\n config.plugins = config.plugins.filter(p => p !== glob);\n await saveConfig(config, resolvedPath);\n}\n\n/**\n * List all plugin glob patterns in the config\n */\nexport async function listPluginGlobs(configPath?: string): Promise<string[]> {\n const config = await loadConfig(configPath);\n return config.plugins ?? [];\n}\n\n/**\n * Get absolute plugin paths from config globs\n */\nexport async function getPluginPaths(configPath?: string): Promise<string[]> {\n const resolvedConfigPath = configPath ?? (await findConfigPath());\n\n if (!resolvedConfigPath) {\n return [];\n }\n\n const config = await loadConfig(resolvedConfigPath);\n const configDir = path.dirname(resolvedConfigPath);\n\n return (config.plugins ?? []).map(glob => {\n // If glob is absolute, use as-is; otherwise resolve relative to config file\n if (path.isAbsolute(glob)) {\n return glob.replace(/\\\\/g, '/');\n }\n return path.resolve(configDir, glob).replace(/\\\\/g, '/');\n });\n}\n\n/**\n * Add an npm package name to the config\n */\nexport async function addPackage(packageName: string, configPath?: string): Promise<void> {\n const config = await loadConfig(configPath);\n\n if (!config.packages) {\n config.packages = [];\n }\n\n if (config.packages.includes(packageName)) {\n throw new Error(`Package '${packageName}' already exists in config`);\n }\n\n config.packages.push(packageName);\n await saveConfig(config, configPath ?? (await findConfigPath()) ?? getDefaultConfigPath());\n}\n\n/**\n * Remove an npm package name from the config\n */\nexport async function removePackage(packageName: string, configPath?: string): Promise<void> {\n const resolvedPath = configPath ?? (await findConfigPath());\n\n if (!resolvedPath) {\n throw new Error('No config file found');\n }\n\n const config = await loadConfig(resolvedPath);\n\n if (!config.packages || !config.packages.includes(packageName)) {\n throw new Error(`Package '${packageName}' not found in config`);\n }\n\n config.packages = config.packages.filter(p => p !== packageName);\n await saveConfig(config, resolvedPath);\n}\n\n/**\n * List all npm package names in the config\n */\nexport async function listPackages(configPath?: string): Promise<string[]> {\n const config = await loadConfig(configPath);\n return config.packages ?? [];\n}\n","import path from 'path';\n\nimport fs from 'fs-extra';\n\nexport async function replacePlaceholders<T extends Record<string, string>>(\n targetDir: string,\n replacements: T\n): Promise<void> {\n async function replaceInFiles(folder: string): Promise<void> {\n const entries = await fs.readdir(folder);\n for (const entry of entries) {\n const fullPath = path.join(folder, entry);\n const stat = await fs.stat(fullPath);\n if (stat.isDirectory()) {\n await replaceInFiles(fullPath);\n } else if (/\\.(ts|js|json|md|txt)$/i.test(entry)) {\n let content = await fs.readFile(fullPath, 'utf-8');\n for (const [placeholder, value] of Object.entries(replacements)) {\n content = content.replaceAll(placeholder, value);\n }\n await fs.writeFile(fullPath, content, 'utf-8');\n }\n }\n }\n\n await replaceInFiles(targetDir);\n}\n","import fs from 'fs/promises';\nimport { execFile } from 'node:child_process';\nimport { createRequire } from 'node:module';\nimport { promisify } from 'node:util';\nimport path from 'path';\n\nconst execFileAsync = promisify(execFile);\n\n/**\n * Resolve an npm package name to its installed directory.\n * Tries local node_modules first (relative to cwd), then global.\n */\nexport async function resolvePackageDir(packageName: string): Promise<string> {\n // Strategy 1: resolve from local node_modules via cwd\n const localRequire = createRequire(path.join(process.cwd(), 'package.json'));\n try {\n const packageJsonPath = localRequire.resolve(`${packageName}/package.json`);\n return path.dirname(packageJsonPath);\n } catch {\n // Not found locally, try global\n }\n\n // Strategy 2: resolve from global node_modules\n const globalDir = await getGlobalNodeModulesDir();\n if (globalDir) {\n const globalRequire = createRequire(path.join(globalDir, '_virtual.js'));\n try {\n const packageJsonPath = globalRequire.resolve(`${packageName}/package.json`);\n return path.dirname(packageJsonPath);\n } catch {\n // Not found globally either\n }\n }\n\n throw new Error(\n `Package '${packageName}' not found. ` +\n `Install it locally (npm install ${packageName}) or globally (npm install -g ${packageName}).`\n );\n}\n\n/**\n * Get the global node_modules directory using npm root -g.\n */\nasync function getGlobalNodeModulesDir(): Promise<string | null> {\n try {\n const { stdout } = await execFileAsync('npm', ['root', '-g'], { encoding: 'utf-8' });\n const dir = stdout.trim();\n await fs.access(dir);\n return dir;\n } catch {\n return null;\n }\n}\n","import fs from 'fs/promises';\nimport path from 'path';\n\nimport { LibriaPlugin, PluginManifest, loadPlugin, loadAllPlugins } from '@libria/plugin-loader';\n\n/**\n * Load plugins from an npm package directory.\n *\n * Handles two package structures:\n * 1. Single-template: plugin.json at package root -> use loadPlugin directly\n * 2. Multi-template: subdirectories with plugin.json -> use loadAllPlugins\n */\nexport async function loadPackagePlugins<T>(\n packageDir: string,\n pluginType?: string\n): Promise<LibriaPlugin<T>[]> {\n const rootPluginJson = path.join(packageDir, 'plugin.json');\n\n try {\n const content = await fs.readFile(rootPluginJson, 'utf-8');\n const rawManifest = JSON.parse(content);\n\n // Filter by pluginType if specified\n if (pluginType && rawManifest.pluginType !== pluginType) {\n return [];\n }\n\n const manifest: PluginManifest = {\n ...rawManifest,\n __dir: packageDir,\n };\n\n const plugin = await loadPlugin<T>(manifest);\n return [plugin];\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {\n // plugin.json exists but failed to load/parse -- rethrow\n throw error;\n }\n }\n\n // No root plugin.json -- try multi-template: scan subdirectories\n return loadAllPlugins<T>(packageDir, pluginType);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/config.ts","../../src/utils/resolve-package.ts"],"sourcesContent":["import fs from 'fs/promises';\nimport path from 'path';\n\nexport interface LbScaffoldConfig {\n plugins?: string[];\n packages?: string[];\n}\n\nconst CONFIG_FILENAME = '.lbscaffold';\n\n/**\n * Find the config file by searching up the directory tree\n */\nexport async function findConfigPath(startDir: string = process.cwd()): Promise<string | null> {\n let currentDir = startDir;\n\n while (true) {\n const configPath = path.join(currentDir, CONFIG_FILENAME);\n try {\n await fs.access(configPath);\n return configPath;\n } catch {\n const parentDir = path.dirname(currentDir);\n if (parentDir === currentDir) {\n // Reached root\n return null;\n }\n currentDir = parentDir;\n }\n }\n}\n\n/**\n * Get the default config path in the current directory\n */\nexport function getDefaultConfigPath(): string {\n return path.join(process.cwd(), CONFIG_FILENAME);\n}\n\n/**\n * Load the config file\n */\nexport async function loadConfig(configPath?: string): Promise<LbScaffoldConfig> {\n const resolvedPath = configPath ?? (await findConfigPath());\n\n if (!resolvedPath) {\n return {};\n }\n\n try {\n const content = await fs.readFile(resolvedPath, 'utf-8');\n return JSON.parse(content) as LbScaffoldConfig;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n return {};\n }\n throw new Error(`Failed to load config from ${resolvedPath}: ${(error as Error).message}`);\n }\n}\n\n/**\n * Save the config file\n */\nexport async function saveConfig(config: LbScaffoldConfig, configPath?: string): Promise<void> {\n const resolvedPath = configPath ?? getDefaultConfigPath();\n await fs.writeFile(resolvedPath, JSON.stringify(config, null, 2) + '\\n', 'utf-8');\n}\n\n/**\n * Initialize a new config file\n */\nexport async function initConfig(configPath?: string): Promise<string> {\n const resolvedPath = configPath ?? getDefaultConfigPath();\n\n try {\n await fs.access(resolvedPath);\n throw new Error(`Config file already exists at ${resolvedPath}`);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {\n throw error;\n }\n }\n\n const defaultConfig: LbScaffoldConfig = {\n plugins: ['./plugins/**'],\n packages: [],\n };\n\n await saveConfig(defaultConfig, resolvedPath);\n return resolvedPath;\n}\n\n/**\n * Add a plugin glob pattern to the config\n */\nexport async function addPluginGlob(glob: string, configPath?: string): Promise<void> {\n const config = await loadConfig(configPath);\n\n if (!config.plugins) {\n config.plugins = [];\n }\n\n if (config.plugins.includes(glob)) {\n throw new Error(`Plugin pattern '${glob}' already exists in config`);\n }\n\n config.plugins.push(glob);\n await saveConfig(config, configPath ?? (await findConfigPath()) ?? getDefaultConfigPath());\n}\n\n/**\n * Remove a plugin glob pattern from the config\n */\nexport async function removePluginGlob(glob: string, configPath?: string): Promise<void> {\n const resolvedPath = configPath ?? (await findConfigPath());\n\n if (!resolvedPath) {\n throw new Error('No config file found');\n }\n\n const config = await loadConfig(resolvedPath);\n\n if (!config.plugins || !config.plugins.includes(glob)) {\n throw new Error(`Plugin pattern '${glob}' not found in config`);\n }\n\n config.plugins = config.plugins.filter(p => p !== glob);\n await saveConfig(config, resolvedPath);\n}\n\n/**\n * List all plugin glob patterns in the config\n */\nexport async function listPluginGlobs(configPath?: string): Promise<string[]> {\n const config = await loadConfig(configPath);\n return config.plugins ?? [];\n}\n\n/**\n * Get absolute plugin paths from config globs\n */\nexport async function getPluginPaths(configPath?: string): Promise<string[]> {\n const resolvedConfigPath = configPath ?? (await findConfigPath());\n\n if (!resolvedConfigPath) {\n return [];\n }\n\n const config = await loadConfig(resolvedConfigPath);\n const configDir = path.dirname(resolvedConfigPath);\n\n return (config.plugins ?? []).map(glob => {\n // If glob is absolute, use as-is; otherwise resolve relative to config file\n if (path.isAbsolute(glob)) {\n return glob.replace(/\\\\/g, '/');\n }\n return path.resolve(configDir, glob).replace(/\\\\/g, '/');\n });\n}\n\n/**\n * Add an npm package name to the config\n */\nexport async function addPackage(packageName: string, configPath?: string): Promise<void> {\n const config = await loadConfig(configPath);\n\n if (!config.packages) {\n config.packages = [];\n }\n\n if (config.packages.includes(packageName)) {\n throw new Error(`Package '${packageName}' already exists in config`);\n }\n\n config.packages.push(packageName);\n await saveConfig(config, configPath ?? (await findConfigPath()) ?? getDefaultConfigPath());\n}\n\n/**\n * Remove an npm package name from the config\n */\nexport async function removePackage(packageName: string, configPath?: string): Promise<void> {\n const resolvedPath = configPath ?? (await findConfigPath());\n\n if (!resolvedPath) {\n throw new Error('No config file found');\n }\n\n const config = await loadConfig(resolvedPath);\n\n if (!config.packages || !config.packages.includes(packageName)) {\n throw new Error(`Package '${packageName}' not found in config`);\n }\n\n config.packages = config.packages.filter(p => p !== packageName);\n await saveConfig(config, resolvedPath);\n}\n\n/**\n * List all npm package names in the config\n */\nexport async function listPackages(configPath?: string): Promise<string[]> {\n const config = await loadConfig(configPath);\n return config.packages ?? [];\n}\n","import fs from 'fs/promises';\nimport { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport path from 'path';\n\nconst execFileAsync = promisify(execFile);\n\n/**\n * Resolve an npm package name to its installed root directory.\n * Tries local node_modules first (relative to cwd), then global.\n */\nexport async function resolvePackageDir(packageName: string): Promise<string> {\n // Strategy 1: resolve from local node_modules via cwd\n const localCandidate = path.join(process.cwd(), 'node_modules', packageName);\n try {\n await fs.access(path.join(localCandidate, 'package.json'));\n return localCandidate;\n } catch {\n // Not found locally, try global\n }\n\n // Strategy 2: resolve from global node_modules\n const globalDir = await getGlobalNodeModulesDir();\n if (globalDir) {\n const globalCandidate = path.join(globalDir, packageName);\n try {\n await fs.access(path.join(globalCandidate, 'package.json'));\n return globalCandidate;\n } catch {\n // Not found globally either\n }\n }\n\n throw new Error(\n `Package '${packageName}' not found. ` +\n `Install it locally (npm install ${packageName}) or globally (npm install -g ${packageName}).`\n );\n}\n\n/**\n * Get the global node_modules directory using npm root -g.\n */\nasync function getGlobalNodeModulesDir(): Promise<string | null> {\n try {\n const { stdout } = await execFileAsync('npm', ['root', '-g'], { encoding: 'utf-8' });\n const dir = stdout.trim();\n await fs.access(dir);\n return dir;\n } catch {\n return null;\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@libria/scaffold",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "A pluggable CLI that transforms blank directories into production-ready codebases in seconds",
5
5
  "main": "dist/cli/index.cjs",
6
6
  "module": "dist/cli/index.mjs",
@@ -36,13 +36,15 @@
36
36
  "homepage": "https://github.com/LibriaForge/scaffold#readme",
37
37
  "dependencies": {
38
38
  "@libria/plugin-loader": "^2.0.0-alpha",
39
+ "@libria/scaffold-core": "^0.3.0",
39
40
  "commander": "^14.0.3",
40
41
  "fast-glob": "^3.3.3",
41
42
  "fs-extra": "^11.3.3",
42
43
  "interactive-commander": "^0.6.0"
43
44
  },
44
45
  "optionalDependencies": {
45
- "@libria/scaffold-ts-lib": "^0.0.2",
46
- "@libria/scaffold-angular": "^0.0.2"
46
+ "@libria/scaffold-angular": "^0.0.4",
47
+ "@libria/scaffold-nestjs": "^0.0.3",
48
+ "@libria/scaffold-ts-lib": "^0.0.6"
47
49
  }
48
50
  }