@timeax/scaffold 0.0.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/.gitattributes +2 -0
- package/dist/cli.cjs +1081 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.mjs +1070 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.cjs +785 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +439 -0
- package/dist/index.d.ts +439 -0
- package/dist/index.mjs +773 -0
- package/dist/index.mjs.map +1 -0
- package/docs/structure.txt +25 -0
- package/package.json +49 -0
- package/readme.md +424 -0
- package/src/cli/main.ts +244 -0
- package/src/core/apply-structure.ts +255 -0
- package/src/core/cache-manager.ts +99 -0
- package/src/core/config-loader.ts +184 -0
- package/src/core/hook-runner.ts +73 -0
- package/src/core/init-scaffold.ts +162 -0
- package/src/core/resolve-structure.ts +64 -0
- package/src/core/runner.ts +94 -0
- package/src/core/scan-structure.ts +214 -0
- package/src/core/structure-txt.ts +203 -0
- package/src/core/watcher.ts +106 -0
- package/src/index.ts +5 -0
- package/src/schema/config.ts +180 -0
- package/src/schema/hooks.ts +139 -0
- package/src/schema/index.ts +4 -0
- package/src/schema/structure.ts +77 -0
- package/src/util/fs-utils.ts +126 -0
- package/src/util/logger.ts +144 -0
- package/tsconfig.json +24 -0
- package/tsup.config.ts +48 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// src/schema/hooks.ts
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Lifecycle stages for non-stub (regular) hooks.
|
|
5
|
+
*
|
|
6
|
+
* These hooks are called around file operations (create/delete).
|
|
7
|
+
*/
|
|
8
|
+
export type RegularHookKind =
|
|
9
|
+
| 'preCreateFile'
|
|
10
|
+
| 'postCreateFile'
|
|
11
|
+
| 'preDeleteFile'
|
|
12
|
+
| 'postDeleteFile';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Lifecycle stages for stub-related hooks.
|
|
16
|
+
*
|
|
17
|
+
* These hooks are called around stub resolution for file content.
|
|
18
|
+
*/
|
|
19
|
+
export type StubHookKind = 'preStub' | 'postStub';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Context object passed to all hooks (both regular and stub).
|
|
23
|
+
*/
|
|
24
|
+
export interface HookContext {
|
|
25
|
+
/**
|
|
26
|
+
* Absolute path to the group root / project root that this
|
|
27
|
+
* scaffold run is targeting.
|
|
28
|
+
*/
|
|
29
|
+
projectRoot: string;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Path of the file or directory relative to the project root
|
|
33
|
+
* used for this run (or group root, if grouped).
|
|
34
|
+
*
|
|
35
|
+
* Example: "src/index.ts", "Http/Controllers/Controller.php".
|
|
36
|
+
*/
|
|
37
|
+
targetPath: string;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Absolute path to the file or directory on disk.
|
|
41
|
+
*/
|
|
42
|
+
absolutePath: string;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Whether the target is a directory.
|
|
46
|
+
* (For now, most hooks will be for files, but this is future-proofing.)
|
|
47
|
+
*/
|
|
48
|
+
isDirectory: boolean;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The stub name associated with the file (if any).
|
|
52
|
+
*
|
|
53
|
+
* For regular hooks, this can be used to detect which stub
|
|
54
|
+
* produced a given file.
|
|
55
|
+
*/
|
|
56
|
+
stubName?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Common filter options used by both regular and stub hooks.
|
|
61
|
+
*
|
|
62
|
+
* Filters are evaluated against the `targetPath`.
|
|
63
|
+
*/
|
|
64
|
+
export interface HookFilter {
|
|
65
|
+
/**
|
|
66
|
+
* Glob patterns which must match for the hook to run.
|
|
67
|
+
* If provided, at least one pattern must match.
|
|
68
|
+
*/
|
|
69
|
+
include?: string[];
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Glob patterns which, if any match, will prevent the hook
|
|
73
|
+
* from running.
|
|
74
|
+
*/
|
|
75
|
+
exclude?: string[];
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Additional patterns or explicit file paths, treated similarly
|
|
79
|
+
* to `include` — mainly a convenience alias.
|
|
80
|
+
*/
|
|
81
|
+
files?: string[];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Function signature for regular hooks.
|
|
86
|
+
*/
|
|
87
|
+
export type RegularHookFn = (ctx: HookContext) => void | Promise<void>;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Function signature for stub hooks.
|
|
91
|
+
*/
|
|
92
|
+
export type StubHookFn = (ctx: HookContext) => void | Promise<void>;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Configuration for a regular hook instance.
|
|
96
|
+
*
|
|
97
|
+
* Each hook category (e.g. `preCreateFile`) can have an array
|
|
98
|
+
* of these, each with its own filter.
|
|
99
|
+
*/
|
|
100
|
+
export interface RegularHookConfig extends HookFilter {
|
|
101
|
+
fn: RegularHookFn;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Configuration for a stub hook instance.
|
|
106
|
+
*
|
|
107
|
+
* Each stub can have its own `preStub` / `postStub` hook arrays,
|
|
108
|
+
* each with independent filters.
|
|
109
|
+
*/
|
|
110
|
+
export interface StubHookConfig extends HookFilter {
|
|
111
|
+
fn: StubHookFn;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Stub configuration, defining how file content is generated
|
|
116
|
+
* and which stub-specific hooks apply.
|
|
117
|
+
*/
|
|
118
|
+
export interface StubConfig {
|
|
119
|
+
/**
|
|
120
|
+
* Unique name of this stub within the config.
|
|
121
|
+
* This is referenced from structure entries via `stub: name`.
|
|
122
|
+
*/
|
|
123
|
+
name: string;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Content generator for files that use this stub.
|
|
127
|
+
*
|
|
128
|
+
* If omitted, the scaffold engine may default to an empty file.
|
|
129
|
+
*/
|
|
130
|
+
getContent?: (ctx: HookContext) => string | Promise<string>;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Stub-specific hooks called for files that reference this stub.
|
|
134
|
+
*/
|
|
135
|
+
hooks?: {
|
|
136
|
+
preStub?: StubHookConfig[];
|
|
137
|
+
postStub?: StubHookConfig[];
|
|
138
|
+
};
|
|
139
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// src/schema/structure.ts
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Common options that can be applied to both files and directories
|
|
5
|
+
* in the scaffold structure.
|
|
6
|
+
*
|
|
7
|
+
* These options are *declarative* — they do not enforce behavior by
|
|
8
|
+
* themselves; they are consumed by the core engine.
|
|
9
|
+
*/
|
|
10
|
+
export interface BaseEntryOptions {
|
|
11
|
+
/**
|
|
12
|
+
* Glob patterns relative to the group root or project root
|
|
13
|
+
* (depending on how the engine is called).
|
|
14
|
+
*
|
|
15
|
+
* If provided, at least one pattern must match the entry path
|
|
16
|
+
* for the entry to be considered.
|
|
17
|
+
*/
|
|
18
|
+
include?: string[];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Glob patterns relative to the group root or project root.
|
|
22
|
+
*
|
|
23
|
+
* If any pattern matches the entry path, the entry will be ignored.
|
|
24
|
+
*/
|
|
25
|
+
exclude?: string[];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Name of the stub to use when creating this file or directory’s
|
|
29
|
+
* content. For directories, this can act as an “inherited” stub
|
|
30
|
+
* for child files if the engine chooses to support that behavior.
|
|
31
|
+
*/
|
|
32
|
+
stub?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* A single file entry in the structure tree.
|
|
37
|
+
*
|
|
38
|
+
* Paths are always stored as POSIX-style forward-slash paths
|
|
39
|
+
* relative to the group root / project root.
|
|
40
|
+
*/
|
|
41
|
+
export interface FileEntry extends BaseEntryOptions {
|
|
42
|
+
type: 'file';
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* File path (e.g. "src/index.ts", "Models/User.php").
|
|
46
|
+
* Paths should never end with a trailing slash.
|
|
47
|
+
*/
|
|
48
|
+
path: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* A directory entry in the structure tree.
|
|
53
|
+
*
|
|
54
|
+
* Paths should *logically* represent directories and may end
|
|
55
|
+
* with a trailing slash for readability (the engine can normalize).
|
|
56
|
+
*/
|
|
57
|
+
export interface DirEntry extends BaseEntryOptions {
|
|
58
|
+
type: 'dir';
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Directory path (e.g. "src/", "src/schema/", "Models/").
|
|
62
|
+
* It is recommended (but not strictly required) that directory
|
|
63
|
+
* paths end with a trailing slash.
|
|
64
|
+
*/
|
|
65
|
+
path: string;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Nested structure entries for files and subdirectories.
|
|
69
|
+
*/
|
|
70
|
+
children?: StructureEntry[];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* A single node in the structure tree:
|
|
75
|
+
* either a file or a directory.
|
|
76
|
+
*/
|
|
77
|
+
export type StructureEntry = FileEntry | DirEntry;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// src/util/fs-utils.ts
|
|
2
|
+
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Convert any path to a POSIX-style path with forward slashes.
|
|
8
|
+
*/
|
|
9
|
+
export function toPosixPath(p: string): string {
|
|
10
|
+
return p.replace(/\\/g, '/');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Ensure a directory exists (like mkdir -p).
|
|
15
|
+
* Returns the absolute path of the directory.
|
|
16
|
+
*/
|
|
17
|
+
export function ensureDirSync(dirPath: string): string {
|
|
18
|
+
if (!fs.existsSync(dirPath)) {
|
|
19
|
+
fs.mkdirSync(dirPath, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
return dirPath;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Synchronous check for file or directory existence.
|
|
26
|
+
*/
|
|
27
|
+
export function existsSync(targetPath: string): boolean {
|
|
28
|
+
return fs.existsSync(targetPath);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Read a file as UTF-8, returning null if it doesn't exist
|
|
33
|
+
* or if an error occurs (no exceptions thrown).
|
|
34
|
+
*/
|
|
35
|
+
export function readFileSafeSync(filePath: string): string | null {
|
|
36
|
+
try {
|
|
37
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Write a UTF-8 file, creating parent directories if needed.
|
|
45
|
+
*/
|
|
46
|
+
export function writeFileSafeSync(filePath: string, contents: string): void {
|
|
47
|
+
const dir = path.dirname(filePath);
|
|
48
|
+
ensureDirSync(dir);
|
|
49
|
+
fs.writeFileSync(filePath, contents, 'utf8');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Remove a file if it exists. Does nothing on error.
|
|
54
|
+
*/
|
|
55
|
+
export function removeFileSafeSync(filePath: string): void {
|
|
56
|
+
try {
|
|
57
|
+
if (fs.existsSync(filePath)) {
|
|
58
|
+
fs.unlinkSync(filePath);
|
|
59
|
+
}
|
|
60
|
+
} catch {
|
|
61
|
+
// ignore
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Get file stats if they exist, otherwise null.
|
|
67
|
+
*/
|
|
68
|
+
export function statSafeSync(targetPath: string): fs.Stats | null {
|
|
69
|
+
try {
|
|
70
|
+
return fs.statSync(targetPath);
|
|
71
|
+
} catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Resolve an absolute path from projectRoot + relative path,
|
|
78
|
+
* and assert it stays within the project root.
|
|
79
|
+
*
|
|
80
|
+
* Throws if the resolved path escapes the project root.
|
|
81
|
+
*/
|
|
82
|
+
export function resolveProjectPath(projectRoot: string, relPath: string): string {
|
|
83
|
+
const absRoot = path.resolve(projectRoot);
|
|
84
|
+
const absTarget = path.resolve(absRoot, relPath);
|
|
85
|
+
|
|
86
|
+
// Normalise for safety check
|
|
87
|
+
const rootWithSep = absRoot.endsWith(path.sep) ? absRoot : absRoot + path.sep;
|
|
88
|
+
if (!absTarget.startsWith(rootWithSep) && absTarget !== absRoot) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
`Attempted to resolve path outside project root: ` +
|
|
91
|
+
`root="${absRoot}", target="${absTarget}"`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return absTarget;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Convert an absolute path back to a project-relative path.
|
|
100
|
+
* Throws if the path is not under projectRoot.
|
|
101
|
+
*/
|
|
102
|
+
export function toProjectRelativePath(projectRoot: string, absolutePath: string): string {
|
|
103
|
+
const absRoot = path.resolve(projectRoot);
|
|
104
|
+
const absTarget = path.resolve(absolutePath);
|
|
105
|
+
|
|
106
|
+
const rootWithSep = absRoot.endsWith(path.sep) ? absRoot : absRoot + path.sep;
|
|
107
|
+
if (!absTarget.startsWith(rootWithSep) && absTarget !== absRoot) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
`Path "${absTarget}" is not inside project root "${absRoot}".`,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const rel = path.relative(absRoot, absTarget);
|
|
114
|
+
return toPosixPath(rel);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Check if `target` is inside (or equal to) `base` directory.
|
|
119
|
+
*/
|
|
120
|
+
export function isSubPath(base: string, target: string): boolean {
|
|
121
|
+
const absBase = path.resolve(base);
|
|
122
|
+
const absTarget = path.resolve(target);
|
|
123
|
+
|
|
124
|
+
const baseWithSep = absBase.endsWith(path.sep) ? absBase : absBase + path.sep;
|
|
125
|
+
return absTarget === absBase || absTarget.startsWith(baseWithSep);
|
|
126
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
// src/util/logger.ts
|
|
2
|
+
|
|
3
|
+
export type LogLevel = 'silent' | 'error' | 'warn' | 'info' | 'debug';
|
|
4
|
+
|
|
5
|
+
export interface LoggerOptions {
|
|
6
|
+
level?: LogLevel;
|
|
7
|
+
/**
|
|
8
|
+
* Optional prefix string (e.g. "[scaffold]" or "[group:app]").
|
|
9
|
+
*/
|
|
10
|
+
prefix?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Minimal ANSI color helpers (no external deps).
|
|
15
|
+
*/
|
|
16
|
+
const supportsColor =
|
|
17
|
+
typeof process !== 'undefined' &&
|
|
18
|
+
process.stdout &&
|
|
19
|
+
process.stdout.isTTY &&
|
|
20
|
+
process.env.NO_COLOR !== '1';
|
|
21
|
+
|
|
22
|
+
type ColorFn = (text: string) => string;
|
|
23
|
+
|
|
24
|
+
function wrap(code: number): ColorFn {
|
|
25
|
+
const open = `\u001b[${code}m`;
|
|
26
|
+
const close = `\u001b[0m`;
|
|
27
|
+
return (text: string) => (supportsColor ? `${open}${text}${close}` : text);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const color = {
|
|
31
|
+
red: wrap(31),
|
|
32
|
+
yellow: wrap(33),
|
|
33
|
+
green: wrap(32),
|
|
34
|
+
cyan: wrap(36),
|
|
35
|
+
magenta: wrap(35),
|
|
36
|
+
dim: wrap(2),
|
|
37
|
+
bold: wrap(1),
|
|
38
|
+
gray: wrap(90),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
function colorForLevel(level: LogLevel): ColorFn {
|
|
42
|
+
switch (level) {
|
|
43
|
+
case 'error':
|
|
44
|
+
return color.red;
|
|
45
|
+
case 'warn':
|
|
46
|
+
return color.yellow;
|
|
47
|
+
case 'info':
|
|
48
|
+
return color.cyan;
|
|
49
|
+
case 'debug':
|
|
50
|
+
return color.gray;
|
|
51
|
+
default:
|
|
52
|
+
return (s) => s;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Minimal logger for @timeax/scaffold with colored output.
|
|
58
|
+
*/
|
|
59
|
+
export class Logger {
|
|
60
|
+
private level: LogLevel;
|
|
61
|
+
private prefix: string | undefined;
|
|
62
|
+
|
|
63
|
+
constructor(options: LoggerOptions = {}) {
|
|
64
|
+
this.level = options.level ?? 'info';
|
|
65
|
+
this.prefix = options.prefix;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
setLevel(level: LogLevel) {
|
|
69
|
+
this.level = level;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
getLevel(): LogLevel {
|
|
73
|
+
return this.level;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Create a child logger with an additional prefix.
|
|
78
|
+
*/
|
|
79
|
+
child(prefix: string): Logger {
|
|
80
|
+
const combined = this.prefix ? `${this.prefix}${prefix}` : prefix;
|
|
81
|
+
return new Logger({ level: this.level, prefix: combined });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private formatMessage(msg: unknown, lvl: LogLevel): string {
|
|
85
|
+
const text =
|
|
86
|
+
typeof msg === 'string'
|
|
87
|
+
? msg
|
|
88
|
+
: msg instanceof Error
|
|
89
|
+
? msg.message
|
|
90
|
+
: String(msg);
|
|
91
|
+
|
|
92
|
+
const levelColor = colorForLevel(lvl);
|
|
93
|
+
const prefixColored = this.prefix
|
|
94
|
+
? color.magenta(this.prefix)
|
|
95
|
+
: undefined;
|
|
96
|
+
|
|
97
|
+
const textColored =
|
|
98
|
+
lvl === 'debug' ? color.dim(text) : levelColor(text);
|
|
99
|
+
|
|
100
|
+
if (prefixColored) {
|
|
101
|
+
return `${prefixColored} ${textColored}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return textColored;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private shouldLog(targetLevel: LogLevel): boolean {
|
|
108
|
+
const order: LogLevel[] = ['silent', 'error', 'warn', 'info', 'debug'];
|
|
109
|
+
const currentIdx = order.indexOf(this.level);
|
|
110
|
+
const targetIdx = order.indexOf(targetLevel);
|
|
111
|
+
if (currentIdx === -1 || targetIdx === -1) return true;
|
|
112
|
+
if (this.level === 'silent') return false;
|
|
113
|
+
return targetIdx <= currentIdx || targetLevel === 'error';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
error(msg: unknown, ...rest: unknown[]) {
|
|
117
|
+
if (!this.shouldLog('error')) return;
|
|
118
|
+
console.error(this.formatMessage(msg, 'error'), ...rest);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
warn(msg: unknown, ...rest: unknown[]) {
|
|
122
|
+
if (!this.shouldLog('warn')) return;
|
|
123
|
+
console.warn(this.formatMessage(msg, 'warn'), ...rest);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
info(msg: unknown, ...rest: unknown[]) {
|
|
127
|
+
if (!this.shouldLog('info')) return;
|
|
128
|
+
console.log(this.formatMessage(msg, 'info'), ...rest);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
debug(msg: unknown, ...rest: unknown[]) {
|
|
132
|
+
if (!this.shouldLog('debug')) return;
|
|
133
|
+
console.debug(this.formatMessage(msg, 'debug'), ...rest);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Default process-wide logger used by CLI and core.
|
|
139
|
+
* Level can be controlled via SCAFFOLD_LOG_LEVEL env.
|
|
140
|
+
*/
|
|
141
|
+
export const defaultLogger = new Logger({
|
|
142
|
+
level: (process.env.SCAFFOLD_LOG_LEVEL as LogLevel | undefined) ?? 'info',
|
|
143
|
+
prefix: '[scaffold]',
|
|
144
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"allowSyntheticDefaultImports": true,
|
|
8
|
+
"strict": true,
|
|
9
|
+
"forceConsistentCasingInFileNames": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"resolveJsonModule": true,
|
|
12
|
+
"rootDir": "src",
|
|
13
|
+
"outDir": "dist",
|
|
14
|
+
"declaration": true,
|
|
15
|
+
"declarationMap": true,
|
|
16
|
+
"sourceMap": true,
|
|
17
|
+
"types": [
|
|
18
|
+
"node"
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
"include": [
|
|
22
|
+
"src"
|
|
23
|
+
]
|
|
24
|
+
}
|
package/tsup.config.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// tsup.config.ts
|
|
2
|
+
import { defineConfig } from 'tsup';
|
|
3
|
+
|
|
4
|
+
export default defineConfig([
|
|
5
|
+
// Library build (@timeax/scaffold import)
|
|
6
|
+
{
|
|
7
|
+
entry: ['src/index.ts'],
|
|
8
|
+
outDir: 'dist',
|
|
9
|
+
format: ['esm', 'cjs'],
|
|
10
|
+
dts: true,
|
|
11
|
+
sourcemap: true,
|
|
12
|
+
clean: true,
|
|
13
|
+
target: 'node18',
|
|
14
|
+
platform: 'node',
|
|
15
|
+
treeshake: true,
|
|
16
|
+
splitting: false, // small lib, keep it simple
|
|
17
|
+
outExtension({ format }) {
|
|
18
|
+
return {
|
|
19
|
+
// ESM → .mjs, CJS → .cjs
|
|
20
|
+
js: format === 'esm' ? '.mjs' : '.cjs',
|
|
21
|
+
};
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
// CLI build (scaffold command)
|
|
26
|
+
{
|
|
27
|
+
entry: {
|
|
28
|
+
cli: 'src/cli/main.ts',
|
|
29
|
+
},
|
|
30
|
+
outDir: 'dist',
|
|
31
|
+
format: ['esm', 'cjs'],
|
|
32
|
+
dts: false,
|
|
33
|
+
sourcemap: true,
|
|
34
|
+
clean: false, // don't blow away the lib build
|
|
35
|
+
target: 'node18',
|
|
36
|
+
platform: 'node',
|
|
37
|
+
treeshake: true,
|
|
38
|
+
splitting: false,
|
|
39
|
+
outExtension({ format }) {
|
|
40
|
+
return {
|
|
41
|
+
js: format === 'esm' ? '.mjs' : '.cjs',
|
|
42
|
+
};
|
|
43
|
+
},
|
|
44
|
+
banner: {
|
|
45
|
+
js: '#!/usr/bin/env node',
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
]);
|