tmc-cli 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +195 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin-loader.d.ts +10 -0
- package/dist/plugin-loader.d.ts.map +1 -0
- package/dist/types.d.ts +10 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/errors.d.ts +3 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/package-name.d.ts +2 -0
- package/dist/utils/package-name.d.ts.map +1 -0
- package/dist/utils/paths.d.ts +3 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/package.json +33 -28
- package/.DS_Store +0 -0
- package/LICENSE +0 -21
- package/README.md +0 -9
- package/bin/index.js +0 -12
- package/bin/tmc-init.js +0 -118
- package/index.js +0 -1
- package/lib/download.js +0 -24
- package/lib/generator.js +0 -32
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,wBAAsB,GAAG,CAAC,IAAI,WAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CA0B5D"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/plugin-loader.ts
|
|
7
|
+
import { existsSync as existsSync2 } from "fs";
|
|
8
|
+
import { readdir, readFile } from "fs/promises";
|
|
9
|
+
import { pathToFileURL } from "url";
|
|
10
|
+
import { dirname as dirname2, join as join2, resolve as resolve2 } from "path";
|
|
11
|
+
import { fileURLToPath } from "url";
|
|
12
|
+
|
|
13
|
+
// src/utils/errors.ts
|
|
14
|
+
function toError(error) {
|
|
15
|
+
if (error instanceof Error) {
|
|
16
|
+
return error;
|
|
17
|
+
}
|
|
18
|
+
return new Error(String(error));
|
|
19
|
+
}
|
|
20
|
+
function formatPluginError(packageName, error) {
|
|
21
|
+
return `[tmc-cli] Failed to load plugin "${packageName}": ${error.message}`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// src/utils/package-name.ts
|
|
25
|
+
var PLUGIN_NAME_PATTERN = /^tmc-cli-plugin-[a-z0-9][a-z0-9._-]*$/;
|
|
26
|
+
function isPluginPackageName(packageName) {
|
|
27
|
+
return PLUGIN_NAME_PATTERN.test(packageName);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// src/utils/paths.ts
|
|
31
|
+
import { existsSync } from "fs";
|
|
32
|
+
import { dirname, join, parse, resolve } from "path";
|
|
33
|
+
function getParentDirectories(startPath) {
|
|
34
|
+
const directories = [];
|
|
35
|
+
let current = resolve(startPath);
|
|
36
|
+
const root = parse(current).root;
|
|
37
|
+
while (true) {
|
|
38
|
+
directories.push(current);
|
|
39
|
+
if (current === root) {
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
current = dirname(current);
|
|
43
|
+
}
|
|
44
|
+
return directories;
|
|
45
|
+
}
|
|
46
|
+
function getNodeModulesPaths(searchRoots) {
|
|
47
|
+
const seen = /* @__PURE__ */ new Set();
|
|
48
|
+
const nodeModulesPaths = [];
|
|
49
|
+
for (const root of searchRoots) {
|
|
50
|
+
for (const directory of getParentDirectories(root)) {
|
|
51
|
+
const nodeModulesPath = join(directory, "node_modules");
|
|
52
|
+
if (!seen.has(nodeModulesPath) && existsSync(nodeModulesPath)) {
|
|
53
|
+
seen.add(nodeModulesPath);
|
|
54
|
+
nodeModulesPaths.push(nodeModulesPath);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return nodeModulesPaths;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// src/plugin-loader.ts
|
|
62
|
+
var DEFAULT_CLI_DIR = dirname2(fileURLToPath(import.meta.url));
|
|
63
|
+
var PluginLoader = class {
|
|
64
|
+
cwd;
|
|
65
|
+
cliDir;
|
|
66
|
+
constructor(options = {}) {
|
|
67
|
+
this.cwd = resolve2(options.cwd ?? process.cwd());
|
|
68
|
+
this.cliDir = resolve2(options.cliDir ?? DEFAULT_CLI_DIR);
|
|
69
|
+
}
|
|
70
|
+
async loadPlugins() {
|
|
71
|
+
const candidates = await this.discoverPlugins();
|
|
72
|
+
const loaded = [];
|
|
73
|
+
const failed = [];
|
|
74
|
+
for (const candidate of candidates) {
|
|
75
|
+
try {
|
|
76
|
+
const plugin = await this.loadPlugin(candidate);
|
|
77
|
+
loaded.push({ ...candidate, plugin });
|
|
78
|
+
} catch (error) {
|
|
79
|
+
failed.push({ ...candidate, error: toError(error) });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return { loaded, failed };
|
|
83
|
+
}
|
|
84
|
+
async discoverPlugins() {
|
|
85
|
+
const nodeModulesPaths = getNodeModulesPaths([this.cwd, this.cliDir]);
|
|
86
|
+
const candidates = /* @__PURE__ */ new Map();
|
|
87
|
+
for (const nodeModulesPath of nodeModulesPaths) {
|
|
88
|
+
const entries = await safeReadDir(nodeModulesPath);
|
|
89
|
+
for (const entry of entries) {
|
|
90
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink()) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
if (!isPluginPackageName(entry.name)) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const packagePath = join2(nodeModulesPath, entry.name);
|
|
97
|
+
const packageJson = await readPackageJson(packagePath);
|
|
98
|
+
const packageName = packageJson.name ?? entry.name;
|
|
99
|
+
if (!isPluginPackageName(packageName) || candidates.has(packageName)) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
candidates.set(packageName, {
|
|
103
|
+
packageName,
|
|
104
|
+
packagePath
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return [...candidates.values()].sort(
|
|
109
|
+
(left, right) => left.packageName.localeCompare(right.packageName)
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
async loadPlugin(candidate) {
|
|
113
|
+
const module = await importPluginModule(candidate);
|
|
114
|
+
const plugin = pickPluginExport(module);
|
|
115
|
+
if (!isCliPlugin(plugin)) {
|
|
116
|
+
throw new Error("plugin must export a CliPlugin as default or named export `plugin`");
|
|
117
|
+
}
|
|
118
|
+
return plugin;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
async function importPluginModule(candidate) {
|
|
122
|
+
try {
|
|
123
|
+
return await import(candidate.packageName);
|
|
124
|
+
} catch (packageImportError) {
|
|
125
|
+
const fallbackEntry = await resolveFallbackEntry(candidate.packagePath);
|
|
126
|
+
if (!fallbackEntry) {
|
|
127
|
+
throw packageImportError;
|
|
128
|
+
}
|
|
129
|
+
return await import(pathToFileURL(fallbackEntry).href);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
async function resolveFallbackEntry(packagePath) {
|
|
133
|
+
const packageJson = await readPackageJson(packagePath);
|
|
134
|
+
const entries = [
|
|
135
|
+
packageJson.module,
|
|
136
|
+
packageJson.main,
|
|
137
|
+
"dist/index.js",
|
|
138
|
+
"src/index.ts",
|
|
139
|
+
"src/index.js"
|
|
140
|
+
].filter((entry) => Boolean(entry));
|
|
141
|
+
for (const entry of entries) {
|
|
142
|
+
const entryPath = join2(packagePath, entry);
|
|
143
|
+
if (existsSync2(entryPath)) {
|
|
144
|
+
return entryPath;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return void 0;
|
|
148
|
+
}
|
|
149
|
+
function pickPluginExport(module) {
|
|
150
|
+
return module.default ?? module.plugin;
|
|
151
|
+
}
|
|
152
|
+
function isCliPlugin(value) {
|
|
153
|
+
return typeof value === "object" && value !== null && "name" in value && "register" in value && typeof value.name === "string" && typeof value.register === "function";
|
|
154
|
+
}
|
|
155
|
+
async function readPackageJson(packagePath) {
|
|
156
|
+
const packageJsonPath = join2(packagePath, "package.json");
|
|
157
|
+
const content = await readFile(packageJsonPath, "utf8");
|
|
158
|
+
return JSON.parse(content);
|
|
159
|
+
}
|
|
160
|
+
async function safeReadDir(path) {
|
|
161
|
+
try {
|
|
162
|
+
return await readdir(path, { withFileTypes: true });
|
|
163
|
+
} catch {
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// src/index.ts
|
|
169
|
+
async function run(argv = process.argv) {
|
|
170
|
+
const program = new Command();
|
|
171
|
+
program.name("tmc").description("An extensible CLI platform powered by tmc-cli plugins.").version("0.1.0");
|
|
172
|
+
const loader = new PluginLoader();
|
|
173
|
+
const result = await loader.loadPlugins();
|
|
174
|
+
for (const { plugin } of result.loaded) {
|
|
175
|
+
await plugin.register(program);
|
|
176
|
+
}
|
|
177
|
+
for (const failedPlugin of result.failed) {
|
|
178
|
+
program.error(formatPluginError(failedPlugin.packageName, failedPlugin.error), {
|
|
179
|
+
exitCode: 1
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
if (argv.length <= 2) {
|
|
183
|
+
program.help();
|
|
184
|
+
}
|
|
185
|
+
await program.parseAsync(argv);
|
|
186
|
+
}
|
|
187
|
+
run().catch((error) => {
|
|
188
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
189
|
+
console.error(`[tmc-cli] ${message}`);
|
|
190
|
+
process.exitCode = 1;
|
|
191
|
+
});
|
|
192
|
+
export {
|
|
193
|
+
run
|
|
194
|
+
};
|
|
195
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/plugin-loader.ts","../src/utils/errors.ts","../src/utils/package-name.ts","../src/utils/paths.ts"],"sourcesContent":["import { Command } from \"commander\";\n\nimport { PluginLoader } from \"./plugin-loader.js\";\nimport { formatPluginError } from \"./utils/errors.js\";\n\nexport async function run(argv = process.argv): Promise<void> {\n const program = new Command();\n\n program\n .name(\"tmc\")\n .description(\"An extensible CLI platform powered by tmc-cli plugins.\")\n .version(\"0.1.0\");\n\n const loader = new PluginLoader();\n const result = await loader.loadPlugins();\n\n for (const { plugin } of result.loaded) {\n await plugin.register(program);\n }\n\n for (const failedPlugin of result.failed) {\n program.error(formatPluginError(failedPlugin.packageName, failedPlugin.error), {\n exitCode: 1\n });\n }\n\n if (argv.length <= 2) {\n program.help();\n }\n\n await program.parseAsync(argv);\n}\n\nrun().catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n console.error(`[tmc-cli] ${message}`);\n process.exitCode = 1;\n});\n","import { existsSync } from \"node:fs\";\nimport { readdir, readFile } from \"node:fs/promises\";\nimport { pathToFileURL } from \"node:url\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport type {\n CliPlugin,\n PluginCandidate,\n PluginLoadResult,\n PluginLoaderOptions,\n PluginModule\n} from \"./types.js\";\nimport { toError } from \"./utils/errors.js\";\nimport { isPluginPackageName } from \"./utils/package-name.js\";\nimport { getNodeModulesPaths } from \"./utils/paths.js\";\n\ninterface PackageJson {\n name?: string;\n main?: string;\n module?: string;\n}\n\nconst DEFAULT_CLI_DIR = dirname(fileURLToPath(import.meta.url));\n\nexport class PluginLoader {\n private readonly cwd: string;\n private readonly cliDir: string;\n\n constructor(options: PluginLoaderOptions = {}) {\n this.cwd = resolve(options.cwd ?? process.cwd());\n this.cliDir = resolve(options.cliDir ?? DEFAULT_CLI_DIR);\n }\n\n async loadPlugins(): Promise<PluginLoadResult> {\n const candidates = await this.discoverPlugins();\n const loaded: PluginLoadResult[\"loaded\"] = [];\n const failed: PluginLoadResult[\"failed\"] = [];\n\n for (const candidate of candidates) {\n try {\n const plugin = await this.loadPlugin(candidate);\n loaded.push({ ...candidate, plugin });\n } catch (error) {\n failed.push({ ...candidate, error: toError(error) });\n }\n }\n\n return { loaded, failed };\n }\n\n async discoverPlugins(): Promise<PluginCandidate[]> {\n const nodeModulesPaths = getNodeModulesPaths([this.cwd, this.cliDir]);\n const candidates = new Map<string, PluginCandidate>();\n\n for (const nodeModulesPath of nodeModulesPaths) {\n const entries = await safeReadDir(nodeModulesPath);\n\n for (const entry of entries) {\n if (!entry.isDirectory() && !entry.isSymbolicLink()) {\n continue;\n }\n\n if (!isPluginPackageName(entry.name)) {\n continue;\n }\n\n const packagePath = join(nodeModulesPath, entry.name);\n const packageJson = await readPackageJson(packagePath);\n const packageName = packageJson.name ?? entry.name;\n\n if (!isPluginPackageName(packageName) || candidates.has(packageName)) {\n continue;\n }\n\n candidates.set(packageName, {\n packageName,\n packagePath\n });\n }\n }\n\n return [...candidates.values()].sort((left, right) =>\n left.packageName.localeCompare(right.packageName)\n );\n }\n\n private async loadPlugin(candidate: PluginCandidate): Promise<CliPlugin> {\n const module = await importPluginModule(candidate);\n const plugin = pickPluginExport(module);\n\n if (!isCliPlugin(plugin)) {\n throw new Error(\"plugin must export a CliPlugin as default or named export `plugin`\");\n }\n\n return plugin;\n }\n}\n\nasync function importPluginModule(candidate: PluginCandidate): Promise<PluginModule> {\n try {\n return (await import(candidate.packageName)) as PluginModule;\n } catch (packageImportError) {\n const fallbackEntry = await resolveFallbackEntry(candidate.packagePath);\n\n if (!fallbackEntry) {\n throw packageImportError;\n }\n\n return (await import(pathToFileURL(fallbackEntry).href)) as PluginModule;\n }\n}\n\nasync function resolveFallbackEntry(packagePath: string): Promise<string | undefined> {\n const packageJson = await readPackageJson(packagePath);\n const entries = [\n packageJson.module,\n packageJson.main,\n \"dist/index.js\",\n \"src/index.ts\",\n \"src/index.js\"\n ].filter((entry): entry is string => Boolean(entry));\n\n for (const entry of entries) {\n const entryPath = join(packagePath, entry);\n\n if (existsSync(entryPath)) {\n return entryPath;\n }\n }\n\n return undefined;\n}\n\nfunction pickPluginExport(module: PluginModule): unknown {\n return module.default ?? module.plugin;\n}\n\nfunction isCliPlugin(value: unknown): value is CliPlugin {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"name\" in value &&\n \"register\" in value &&\n typeof value.name === \"string\" &&\n typeof value.register === \"function\"\n );\n}\n\nasync function readPackageJson(packagePath: string): Promise<PackageJson> {\n const packageJsonPath = join(packagePath, \"package.json\");\n const content = await readFile(packageJsonPath, \"utf8\");\n return JSON.parse(content) as PackageJson;\n}\n\nasync function safeReadDir(path: string) {\n try {\n return await readdir(path, { withFileTypes: true });\n } catch {\n return [];\n }\n}\n","export function toError(error: unknown): Error {\n if (error instanceof Error) {\n return error;\n }\n\n return new Error(String(error));\n}\n\nexport function formatPluginError(packageName: string, error: Error): string {\n return `[tmc-cli] Failed to load plugin \"${packageName}\": ${error.message}`;\n}\n","const PLUGIN_NAME_PATTERN = /^tmc-cli-plugin-[a-z0-9][a-z0-9._-]*$/;\n\nexport function isPluginPackageName(packageName: string): boolean {\n return PLUGIN_NAME_PATTERN.test(packageName);\n}\n","import { existsSync } from \"node:fs\";\nimport { dirname, join, parse, resolve } from \"node:path\";\n\nexport function getParentDirectories(startPath: string): string[] {\n const directories: string[] = [];\n let current = resolve(startPath);\n const root = parse(current).root;\n\n while (true) {\n directories.push(current);\n\n if (current === root) {\n break;\n }\n\n current = dirname(current);\n }\n\n return directories;\n}\n\nexport function getNodeModulesPaths(searchRoots: string[]): string[] {\n const seen = new Set<string>();\n const nodeModulesPaths: string[] = [];\n\n for (const root of searchRoots) {\n for (const directory of getParentDirectories(root)) {\n const nodeModulesPath = join(directory, \"node_modules\");\n\n if (!seen.has(nodeModulesPath) && existsSync(nodeModulesPath)) {\n seen.add(nodeModulesPath);\n nodeModulesPaths.push(nodeModulesPath);\n }\n }\n }\n\n return nodeModulesPaths;\n}\n"],"mappings":"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","names":["existsSync","dirname","join","resolve","dirname","resolve","join","existsSync"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { PluginCandidate, PluginLoadResult, PluginLoaderOptions } from "./types.js";
|
|
2
|
+
export declare class PluginLoader {
|
|
3
|
+
private readonly cwd;
|
|
4
|
+
private readonly cliDir;
|
|
5
|
+
constructor(options?: PluginLoaderOptions);
|
|
6
|
+
loadPlugins(): Promise<PluginLoadResult>;
|
|
7
|
+
discoverPlugins(): Promise<PluginCandidate[]>;
|
|
8
|
+
private loadPlugin;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=plugin-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugin-loader.d.ts","sourceRoot":"","sources":["../src/plugin-loader.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAEV,eAAe,EACf,gBAAgB,EAChB,mBAAmB,EAEpB,MAAM,YAAY,CAAC;AAapB,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAEpB,OAAO,GAAE,mBAAwB;IAKvC,WAAW,IAAI,OAAO,CAAC,gBAAgB,CAAC;IAiBxC,eAAe,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;YAoCrC,UAAU;CAUzB"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type { CliPlugin, FailedPlugin, LoadedPlugin, PluginLoadResult, PluginModule } from "tmc-cli-shared";
|
|
2
|
+
export interface PluginCandidate {
|
|
3
|
+
packageName: string;
|
|
4
|
+
packagePath: string;
|
|
5
|
+
}
|
|
6
|
+
export interface PluginLoaderOptions {
|
|
7
|
+
cwd?: string;
|
|
8
|
+
cliDir?: string;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,SAAS,EACT,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACb,MAAM,gBAAgB,CAAC;AAExB,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,mBAAmB;IAClC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAAA,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAM7C;AAED,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,MAAM,CAE3E"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package-name.d.ts","sourceRoot":"","sources":["../../src/utils/package-name.ts"],"names":[],"mappings":"AAEA,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAEhE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/utils/paths.ts"],"names":[],"mappings":"AAGA,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAgBhE;AAED,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAgBnE"}
|
package/package.json
CHANGED
|
@@ -1,36 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tmc-cli",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"scripts": {
|
|
10
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "An extensible CLI platform powered by plugins.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=22"
|
|
11
9
|
},
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"url": "git+https://github.com/kunkuntang/tmc-cli.git"
|
|
10
|
+
"bin": {
|
|
11
|
+
"tmc": "./dist/index.js"
|
|
15
12
|
},
|
|
16
|
-
"
|
|
17
|
-
"
|
|
13
|
+
"files": [
|
|
14
|
+
"dist"
|
|
18
15
|
],
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js"
|
|
20
|
+
}
|
|
23
21
|
},
|
|
24
|
-
"
|
|
22
|
+
"main": "./dist/index.js",
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
25
24
|
"dependencies": {
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
"
|
|
25
|
+
"commander": "^14.0.3",
|
|
26
|
+
"tmc-cli-shared": "0.1.2"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"tmc-cli-plugin-cdn": "0.1.2",
|
|
30
|
+
"tmc-cli-plugin-upload": "0.1.2"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"dev": "tsx src/index.ts",
|
|
37
|
+
"build": "rm -f tsconfig.tsbuildinfo dist/.tsbuildinfo && tsup && tsc --emitDeclarationOnly && rm -f dist/.tsbuildinfo",
|
|
38
|
+
"clean": "rm -rf dist",
|
|
39
|
+
"typecheck": "tsc --noEmit"
|
|
35
40
|
}
|
|
36
|
-
}
|
|
41
|
+
}
|
package/.DS_Store
DELETED
|
Binary file
|
package/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2019 Kuntang Huang
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
package/README.md
DELETED
package/bin/index.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const program = require('commander') // npm i commander -D
|
|
4
|
-
const updateNotifier = require('update-notifier');
|
|
5
|
-
const pkg = require('../package.json');
|
|
6
|
-
|
|
7
|
-
updateNotifier({ pkg }).notify();
|
|
8
|
-
|
|
9
|
-
program.version('1.0.0')
|
|
10
|
-
.usage('<command> [项目名称]')
|
|
11
|
-
.command('init', '创建新项目')
|
|
12
|
-
.parse(process.argv)
|
package/bin/tmc-init.js
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
const program = require('commander')
|
|
4
|
-
const path = require('path')
|
|
5
|
-
const fs = require('fs')
|
|
6
|
-
const glob = require('glob')
|
|
7
|
-
const download = require('../lib/download')
|
|
8
|
-
const inquirer = require('inquirer')
|
|
9
|
-
const chalk = require('chalk')
|
|
10
|
-
const logSymbols = require('log-symbols')
|
|
11
|
-
const generator = require('../lib/generator')
|
|
12
|
-
|
|
13
|
-
program.usage('<project-name>').parse(process.argv)
|
|
14
|
-
|
|
15
|
-
// 获取新建项目名称
|
|
16
|
-
let projectName = program.args[0]
|
|
17
|
-
|
|
18
|
-
if (!projectName) { // project-name 必填
|
|
19
|
-
// 相当于执行命令的--help选项,显示help信息,这是commander内置的一个命令选项
|
|
20
|
-
program.help()
|
|
21
|
-
return
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const rootName = path.basename(process.cwd()) // 获取当前路径
|
|
25
|
-
const list = glob.sync('*') // 遍历当前目录
|
|
26
|
-
|
|
27
|
-
let next = undefined
|
|
28
|
-
|
|
29
|
-
const inquirerList = [{
|
|
30
|
-
name: 'projectVersion',
|
|
31
|
-
message: '项目的版本',
|
|
32
|
-
type: 'input',
|
|
33
|
-
default: '0.1.0',
|
|
34
|
-
}, {
|
|
35
|
-
name: 'projectDescription',
|
|
36
|
-
message: '项目的描述',
|
|
37
|
-
type: 'input',
|
|
38
|
-
default: 'test',
|
|
39
|
-
}, {
|
|
40
|
-
name: 'author',
|
|
41
|
-
message: '项目的作者',
|
|
42
|
-
type: 'input',
|
|
43
|
-
default: 'kuntang',
|
|
44
|
-
}]
|
|
45
|
-
|
|
46
|
-
if (list.length) { // 如果当前目录不为空
|
|
47
|
-
if (list.filter(name => {
|
|
48
|
-
const fileName = path.resolve(process.cwd(), path.join('.', name))
|
|
49
|
-
const isDir = fs.statSync(fileName).isDirectory()
|
|
50
|
-
return name.indexOf(projectName) !== -1 && isDir
|
|
51
|
-
}).length !== 0) {
|
|
52
|
-
console.log(`项目${projectName}已经存在`)
|
|
53
|
-
return
|
|
54
|
-
}
|
|
55
|
-
next = inquirer.prompt(inquirerList).then(answer => {
|
|
56
|
-
return Promise.resolve({
|
|
57
|
-
projectPath: projectName,
|
|
58
|
-
...answer
|
|
59
|
-
})
|
|
60
|
-
})
|
|
61
|
-
} else if (rootName === projectName) {
|
|
62
|
-
next = inquirer.prompt([{
|
|
63
|
-
name: 'projectPath',
|
|
64
|
-
message: '当前目录为空,且目录名称和项目名称相同,是否直接在当前目录下创建新项目?',
|
|
65
|
-
type: 'confirm',
|
|
66
|
-
default: true
|
|
67
|
-
}, ...inquirerList]).then(answer => {
|
|
68
|
-
return Promise.resolve({
|
|
69
|
-
projectPath: answer.buildInCurrent ? '.' : projectName,
|
|
70
|
-
...answer
|
|
71
|
-
})
|
|
72
|
-
})
|
|
73
|
-
} else {
|
|
74
|
-
next = inquirer.prompt(inquirerList).then(answer => {
|
|
75
|
-
return Promise.resolve({
|
|
76
|
-
projectPath: projectName,
|
|
77
|
-
...answer
|
|
78
|
-
})
|
|
79
|
-
})
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
next && initialization()
|
|
83
|
-
|
|
84
|
-
function initialization() {
|
|
85
|
-
next.then(answer => {
|
|
86
|
-
const {
|
|
87
|
-
projectPath,
|
|
88
|
-
projectVersion,
|
|
89
|
-
projectDescription,
|
|
90
|
-
author,
|
|
91
|
-
} = answer
|
|
92
|
-
if (projectPath !== '.') {
|
|
93
|
-
fs.mkdirSync(projectPath)
|
|
94
|
-
}
|
|
95
|
-
return download(projectPath).then(downloadTempPath => {
|
|
96
|
-
return {
|
|
97
|
-
projectName,
|
|
98
|
-
projectVersion,
|
|
99
|
-
projectDescription,
|
|
100
|
-
projectPath,
|
|
101
|
-
author,
|
|
102
|
-
downloadTemp: downloadTempPath,
|
|
103
|
-
}
|
|
104
|
-
})
|
|
105
|
-
}).then(context => {
|
|
106
|
-
const src = path.join(process.cwd(), context.downloadTemp)
|
|
107
|
-
const dest = path.join(process.cwd(), context.projectPath)
|
|
108
|
-
return generator(context, src, dest)
|
|
109
|
-
}).then(context => {
|
|
110
|
-
// 成功用绿色显示,给出积极的反馈
|
|
111
|
-
console.log(logSymbols.success, chalk.green('创建成功:)'))
|
|
112
|
-
console.log()
|
|
113
|
-
console.log(chalk.green('cd ' + context.root + '\nnpm install\nnpm run dev'))
|
|
114
|
-
}).catch(error => {
|
|
115
|
-
// 失败了用红色,增强提示
|
|
116
|
-
console.error(logSymbols.error, chalk.red(`创建失败:${error.message}`))
|
|
117
|
-
})
|
|
118
|
-
}
|
package/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
console.log('welcome using tmc CLI')
|
package/lib/download.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
const download = require('download-git-repo')
|
|
2
|
-
const ora = require('ora')
|
|
3
|
-
const path = require('path')
|
|
4
|
-
|
|
5
|
-
module.exports = function(dest) {
|
|
6
|
-
dest = path.join(dest || '.', '.download-temp')
|
|
7
|
-
return new Promise((resolve, reject) => {
|
|
8
|
-
const url = 'https://github.com:kunkuntang/react-typescript-scaffold#template'
|
|
9
|
-
const spinner = ora(`正在下载项目模板,源地址:${url}`)
|
|
10
|
-
spinner.start()
|
|
11
|
-
// 这里可以根据具体的模板地址设置下载的url,注意,如果是git,url后面的branch不能忽略
|
|
12
|
-
download(url,
|
|
13
|
-
dest, { clone: true }, (err) => {
|
|
14
|
-
if (err) {
|
|
15
|
-
spinner.fail() // wrong :(
|
|
16
|
-
reject(err)
|
|
17
|
-
} else {
|
|
18
|
-
spinner.succeed() // ok :)
|
|
19
|
-
// 下载的模板存放在一个临时路径中,下载完成后,可以向下通知这个临时路径,以便后续处理
|
|
20
|
-
resolve(dest)
|
|
21
|
-
}
|
|
22
|
-
})
|
|
23
|
-
})
|
|
24
|
-
}
|
package/lib/generator.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
const Metalsmith = require('metalsmith')
|
|
2
|
-
const Handlebars = require('handlebars')
|
|
3
|
-
const rm = require('rimraf').sync
|
|
4
|
-
|
|
5
|
-
module.exports = function(metadata = {}, src, dest = '.') {
|
|
6
|
-
if (!src) {
|
|
7
|
-
return Promise.reject(new Error(`无效的source:${src}`))
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
return new Promise((resolve, reject) => {
|
|
11
|
-
Metalsmith(process.cwd())
|
|
12
|
-
.metadata(metadata)
|
|
13
|
-
.clean(false)
|
|
14
|
-
.source(src)
|
|
15
|
-
.destination(dest)
|
|
16
|
-
.use((files, metalsmith, done) => {
|
|
17
|
-
const meta = metalsmith.metadata()
|
|
18
|
-
Object.keys(files).forEach(fileName => {
|
|
19
|
-
if (fileName.includes('package.json')) {
|
|
20
|
-
const t = files[fileName].contents.toString()
|
|
21
|
-
files[fileName].contents = new Buffer.from(Handlebars.compile(t)(meta))
|
|
22
|
-
}
|
|
23
|
-
})
|
|
24
|
-
done()
|
|
25
|
-
}).build(err => {
|
|
26
|
-
rm(src)
|
|
27
|
-
err ? reject(err) : resolve({
|
|
28
|
-
root: dest
|
|
29
|
-
})
|
|
30
|
-
})
|
|
31
|
-
})
|
|
32
|
-
}
|