create-acmekit-app 2.13.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.
Files changed (59) hide show
  1. package/.turbo/turbo-build.log +1 -0
  2. package/CHANGELOG.md +3 -0
  3. package/README.md +58 -0
  4. package/dist/commands/create.js +8 -0
  5. package/dist/index.js +31 -0
  6. package/dist/utils/clone-repo.js +77 -0
  7. package/dist/utils/create-abort-controller.js +11 -0
  8. package/dist/utils/create-db.js +172 -0
  9. package/dist/utils/execute.js +51 -0
  10. package/dist/utils/facts.js +87 -0
  11. package/dist/utils/format-connection-string.js +17 -0
  12. package/dist/utils/get-config-store.js +10 -0
  13. package/dist/utils/get-current-os.js +10 -0
  14. package/dist/utils/log-message.js +21 -0
  15. package/dist/utils/logger.js +8 -0
  16. package/dist/utils/nextjs-utils.js +85 -0
  17. package/dist/utils/node-version.js +5 -0
  18. package/dist/utils/package-manager.js +266 -0
  19. package/dist/utils/postgres-client.js +9 -0
  20. package/dist/utils/prepare-project.js +184 -0
  21. package/dist/utils/process-manager.js +50 -0
  22. package/dist/utils/project-creator/acmekit-plugin-creator.js +93 -0
  23. package/dist/utils/project-creator/acmekit-project-creator.js +204 -0
  24. package/dist/utils/project-creator/creator.js +46 -0
  25. package/dist/utils/project-creator/index.js +4 -0
  26. package/dist/utils/project-creator/project-creator-factory.js +83 -0
  27. package/dist/utils/start-acmekit.js +13 -0
  28. package/dist/utils/update-package-versions.js +28 -0
  29. package/jest.config.cjs +14 -0
  30. package/package.json +48 -0
  31. package/src/commands/create.ts +12 -0
  32. package/src/index.ts +65 -0
  33. package/src/types.d.ts +1 -0
  34. package/src/utils/__tests__/create-abort-controller.test.ts +166 -0
  35. package/src/utils/__tests__/package-manager.test.ts +637 -0
  36. package/src/utils/clone-repo.ts +117 -0
  37. package/src/utils/create-abort-controller.ts +16 -0
  38. package/src/utils/create-db.ts +245 -0
  39. package/src/utils/execute.ts +86 -0
  40. package/src/utils/facts.ts +148 -0
  41. package/src/utils/format-connection-string.ts +29 -0
  42. package/src/utils/get-config-store.ts +17 -0
  43. package/src/utils/get-current-os.ts +10 -0
  44. package/src/utils/log-message.ts +28 -0
  45. package/src/utils/logger.ts +10 -0
  46. package/src/utils/nextjs-utils.ts +139 -0
  47. package/src/utils/node-version.ts +7 -0
  48. package/src/utils/package-manager.ts +334 -0
  49. package/src/utils/postgres-client.ts +23 -0
  50. package/src/utils/prepare-project.ts +325 -0
  51. package/src/utils/process-manager.ts +60 -0
  52. package/src/utils/project-creator/acmekit-plugin-creator.ts +127 -0
  53. package/src/utils/project-creator/acmekit-project-creator.ts +272 -0
  54. package/src/utils/project-creator/creator.ts +77 -0
  55. package/src/utils/project-creator/index.ts +4 -0
  56. package/src/utils/project-creator/project-creator-factory.ts +119 -0
  57. package/src/utils/start-acmekit.ts +26 -0
  58. package/src/utils/update-package-versions.ts +37 -0
  59. package/tsconfig.json +25 -0
@@ -0,0 +1,117 @@
1
+ import fs from "fs"
2
+ import { Ora } from "ora"
3
+ import path from "path"
4
+ import { isAbortError } from "./create-abort-controller.js"
5
+ import execute from "./execute.js"
6
+ import logMessage from "./log-message.js"
7
+ import { execFileSync } from "child_process"
8
+
9
+ type CloneRepoOptions = {
10
+ directoryName?: string
11
+ repoUrl?: string
12
+ abortController?: AbortController
13
+ verbose?: boolean
14
+ isPlugin?: boolean
15
+ }
16
+
17
+ const DEFAULT_REPO = "https://github.com/acmekit/acmekit-starter-default"
18
+ const DEFAULT_PLUGIN_REPO = "https://github.com/acmekit/acmekit-starter-plugin"
19
+ const BRANCH = "master"
20
+ const PLUGIN_BRANCH = "main"
21
+
22
+ export default async function cloneRepo({
23
+ directoryName = "",
24
+ repoUrl,
25
+ abortController,
26
+ verbose = false,
27
+ isPlugin = false,
28
+ }: CloneRepoOptions) {
29
+ const defaultRepo = isPlugin ? DEFAULT_PLUGIN_REPO : DEFAULT_REPO
30
+ const branch = isPlugin ? PLUGIN_BRANCH : BRANCH
31
+
32
+ await execute(
33
+ [
34
+ `git clone ${
35
+ repoUrl || defaultRepo
36
+ } -b ${branch} ${directoryName} --depth 1`,
37
+ {
38
+ signal: abortController?.signal,
39
+ },
40
+ ],
41
+ { verbose }
42
+ )
43
+ }
44
+
45
+ export async function runCloneRepo({
46
+ projectName,
47
+ repoUrl,
48
+ abortController,
49
+ spinner,
50
+ verbose = false,
51
+ isPlugin = false,
52
+ }: {
53
+ projectName: string
54
+ repoUrl: string
55
+ abortController: AbortController
56
+ spinner: Ora
57
+ verbose?: boolean
58
+ isPlugin?: boolean
59
+ }) {
60
+ try {
61
+ await cloneRepo({
62
+ directoryName: projectName,
63
+ repoUrl,
64
+ abortController,
65
+ verbose,
66
+ isPlugin,
67
+ })
68
+
69
+ deleteGitDirectory(projectName)
70
+ } catch (e) {
71
+ if (isAbortError(e)) {
72
+ process.exit()
73
+ }
74
+
75
+ spinner.stop()
76
+ logMessage({
77
+ message: `An error occurred while setting up your project: ${e}`,
78
+ type: "error",
79
+ })
80
+ }
81
+ }
82
+
83
+ function deleteGitDirectory(projectDirectory: string) {
84
+ try {
85
+ fs.rmSync(path.join(projectDirectory, ".git"), {
86
+ recursive: true,
87
+ force: true,
88
+ })
89
+ } catch (error) {
90
+ deleteWithCommand(projectDirectory, ".git")
91
+ }
92
+
93
+ try {
94
+ fs.rmSync(path.join(projectDirectory, ".github"), {
95
+ recursive: true,
96
+ force: true,
97
+ })
98
+ } catch (error) {
99
+ deleteWithCommand(projectDirectory, ".github")
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Useful for deleting directories when fs methods fail (e.g., with Yarn v3)
105
+ */
106
+ function deleteWithCommand(projectDirectory: string, dirName: string) {
107
+ const dirPath = path.normalize(path.join(projectDirectory, dirName))
108
+ if (!fs.existsSync(dirPath)) {
109
+ return
110
+ }
111
+
112
+ if (process.platform === "win32") {
113
+ execFileSync("cmd", ["/c", "rmdir", "/s", "/q", dirPath])
114
+ } else {
115
+ execFileSync("rm", ["-rf", dirPath])
116
+ }
117
+ }
@@ -0,0 +1,16 @@
1
+ import ProcessManager from "./process-manager.js"
2
+
3
+ export default (processManager: ProcessManager) => {
4
+ const abortController = new AbortController()
5
+ processManager.onTerminated(() => abortController.abort())
6
+ return abortController
7
+ }
8
+
9
+ export const isAbortError = (e: any) =>
10
+ e !== null && typeof e === "object" && "code" in e && e.code === "ABORT_ERR"
11
+
12
+ export const getAbortError = () => {
13
+ return {
14
+ code: "ABORT_ERR",
15
+ }
16
+ }
@@ -0,0 +1,245 @@
1
+ import { EOL } from "os"
2
+ import type pg from "@acmekit/deps/pg"
3
+ import postgresClient, {
4
+ DEFAULT_HOST,
5
+ DEFAULT_PORT,
6
+ } from "./postgres-client.js"
7
+ import inquirer from "inquirer"
8
+ import logMessage from "./log-message.js"
9
+ import formatConnectionString from "./format-connection-string.js"
10
+ import { Ora } from "ora"
11
+ import { getCurrentOs } from "./get-current-os.js"
12
+ import terminalLink from "terminal-link"
13
+
14
+ type CreateDbOptions = {
15
+ client: pg.Client
16
+ db: string
17
+ }
18
+
19
+ export default async function createDb({ client, db }: CreateDbOptions) {
20
+ await client.query(`CREATE DATABASE "${db}"`)
21
+ }
22
+
23
+ async function doesDbExist(
24
+ client: pg.Client,
25
+ dbName: string
26
+ ): Promise<boolean> {
27
+ const result = await client.query(
28
+ `SELECT datname FROM pg_catalog.pg_database WHERE datname='${dbName}';`
29
+ )
30
+
31
+ return !!result.rowCount
32
+ }
33
+
34
+ export async function runCreateDb({
35
+ client,
36
+ dbName,
37
+ spinner,
38
+ }: {
39
+ client: pg.Client
40
+ dbName: string
41
+ spinner: Ora
42
+ }): Promise<pg.Client> {
43
+ let newClient = client
44
+
45
+ try {
46
+ // create postgres database
47
+ await createDb({
48
+ client,
49
+ db: dbName,
50
+ })
51
+
52
+ // create a new connection with database selected
53
+ await client.end()
54
+ newClient = await postgresClient({
55
+ user: client.user,
56
+ password: client.password,
57
+ database: dbName,
58
+ })
59
+ } catch (e) {
60
+ spinner.stop()
61
+ logMessage({
62
+ message: `An error occurred while trying to create your database: ${e}`,
63
+ type: "error",
64
+ })
65
+ }
66
+
67
+ return newClient
68
+ }
69
+
70
+ async function getForDbName({
71
+ dbName,
72
+ verbose = false,
73
+ }: {
74
+ dbName: string
75
+ verbose?: boolean
76
+ }): Promise<{
77
+ client: pg.Client
78
+ dbConnectionString: string
79
+ dbName: string
80
+ }> {
81
+ let client!: pg.Client
82
+ let postgresUsername = "postgres"
83
+ let postgresPassword = ""
84
+
85
+ const defaultConnectionOptions = {
86
+ host: DEFAULT_HOST,
87
+ port: DEFAULT_PORT,
88
+ }
89
+
90
+ try {
91
+ client = await postgresClient({
92
+ user: postgresUsername,
93
+ password: postgresPassword,
94
+ ...defaultConnectionOptions,
95
+ })
96
+ } catch (e) {
97
+ if (verbose) {
98
+ logMessage({
99
+ message: `The following error occured when connecting to the database: ${e}`,
100
+ type: "verbose",
101
+ })
102
+ }
103
+ // ask for the user's postgres credentials
104
+ const answers = await inquirer.prompt([
105
+ {
106
+ type: "input",
107
+ name: "postgresUsername",
108
+ message: "Enter your Postgres username",
109
+ default: "postgres",
110
+ validate: (input) => {
111
+ return typeof input === "string" && input.length > 0
112
+ },
113
+ },
114
+ {
115
+ type: "password",
116
+ name: "postgresPassword",
117
+ message: "Enter your Postgres password",
118
+ },
119
+ ])
120
+
121
+ postgresUsername = answers.postgresUsername
122
+ postgresPassword = answers.postgresPassword
123
+
124
+ const { userDbName } = await inquirer.prompt([
125
+ {
126
+ type: "database",
127
+ name: "userDbName",
128
+ message: "Enter your Postgres user's database name",
129
+ default: answers.postgresUsername,
130
+ validate: (input) => {
131
+ return typeof input === "string" && input.length > 0
132
+ },
133
+ },
134
+ ])
135
+
136
+ try {
137
+ client = await postgresClient({
138
+ user: postgresUsername,
139
+ password: postgresPassword,
140
+ database: userDbName,
141
+ ...defaultConnectionOptions,
142
+ })
143
+ } catch (e) {
144
+ logMessage({
145
+ message: `Couldn't connect to PostgreSQL because of the following error: ${e}.${EOL}${EOL}Make sure you have PostgreSQL installed and the credentials you provided are correct.${EOL}${EOL}If you keep running into this issue despite having PostgreSQL installed, please check out our ${terminalLink(
146
+ "troubleshooting guides",
147
+ "https://docs.acmekit.com/resources/troubleshooting/database-errors"
148
+ )}.`,
149
+ type: "error",
150
+ })
151
+ }
152
+ }
153
+
154
+ // check if database exists
155
+ if (await doesDbExist(client, dbName)) {
156
+ const { newDbName } = await inquirer.prompt([
157
+ {
158
+ type: "input",
159
+ name: "newDbName",
160
+ message: `A database already exists with the name ${dbName}, please enter a name for the database:`,
161
+ default: dbName,
162
+ validate: (input) => {
163
+ return (
164
+ typeof input === "string" && input.length > 0 && input !== dbName
165
+ )
166
+ },
167
+ },
168
+ ])
169
+
170
+ dbName = newDbName
171
+ }
172
+
173
+ // format connection string
174
+ const dbConnectionString = formatConnectionString({
175
+ user: postgresUsername,
176
+ password: postgresPassword,
177
+ host: client!.host,
178
+ db: dbName,
179
+ })
180
+
181
+ return {
182
+ client,
183
+ dbConnectionString,
184
+ dbName,
185
+ }
186
+ }
187
+
188
+ async function getForDbUrl({
189
+ dbUrl,
190
+ verbose = false,
191
+ }: {
192
+ dbUrl: string
193
+ verbose?: boolean
194
+ }): Promise<{
195
+ client: pg.Client
196
+ dbConnectionString: string
197
+ }> {
198
+ let client!: pg.Client
199
+
200
+ try {
201
+ client = await postgresClient({
202
+ connectionString: dbUrl,
203
+ })
204
+ } catch (e) {
205
+ if (verbose) {
206
+ logMessage({
207
+ message: `The following error occured when connecting to the database: ${e}`,
208
+ type: "verbose",
209
+ })
210
+ }
211
+ logMessage({
212
+ message: `Couldn't connect to PostgreSQL using the database URL you passed. Make sure it's correct and try again.`,
213
+ type: "error",
214
+ })
215
+ }
216
+
217
+ return {
218
+ client,
219
+ dbConnectionString: dbUrl,
220
+ }
221
+ }
222
+
223
+ export async function getDbClientAndCredentials({
224
+ dbName = "",
225
+ dbUrl = "",
226
+ verbose = false,
227
+ }): Promise<{
228
+ client: pg.Client
229
+ dbConnectionString: string
230
+ verbose?: boolean
231
+ dbName?: string
232
+ }> {
233
+ // Check the db-url first, because the dbName is always defined in AcmeKitProjectCreator->create()->initializeProject()->setupDatabase()
234
+ if (dbUrl) {
235
+ return await getForDbUrl({
236
+ dbUrl,
237
+ verbose,
238
+ })
239
+ } else {
240
+ return await getForDbName({
241
+ dbName,
242
+ verbose,
243
+ })
244
+ }
245
+ }
@@ -0,0 +1,86 @@
1
+ import { exec, spawnSync, SpawnSyncOptions } from "child_process"
2
+ import util from "util"
3
+ import { getAbortError } from "./create-abort-controller.js"
4
+
5
+ const promiseExec = util.promisify(exec)
6
+
7
+ export type ExecuteResult = {
8
+ stdout?: string
9
+ stderr?: string
10
+ }
11
+
12
+ export type VerboseOptions = {
13
+ verbose?: boolean
14
+ // Since spawn doesn't allow us to both retrieve the
15
+ // output and output it live without using events,
16
+ // enabling this option, which is only useful if `verbose` is `true`,
17
+ // defers the output of the process until after the process is executed
18
+ // instead of outputting the log in realtime, which is the default.
19
+ // it prioritizes retrieving the output over outputting it in real-time.
20
+ needOutput?: boolean
21
+ }
22
+
23
+ type PromiseExecParams = Parameters<typeof promiseExec>
24
+ type SpawnParams = [string, SpawnSyncOptions]
25
+
26
+ const execute = async (
27
+ command: SpawnParams | PromiseExecParams,
28
+ { verbose = false, needOutput = false }: VerboseOptions
29
+ ): Promise<ExecuteResult> => {
30
+ if (verbose) {
31
+ const [commandStr, options] = command as SpawnParams
32
+ const childProcess = spawnSync(commandStr, {
33
+ ...options,
34
+ shell: true,
35
+ stdio: needOutput
36
+ ? "pipe"
37
+ : [process.stdin, process.stdout, process.stderr],
38
+ env: {
39
+ ...process.env,
40
+ ...(options.env || {}),
41
+ },
42
+ })
43
+
44
+ if (childProcess.error || childProcess.status !== 0) {
45
+ throw (
46
+ childProcess.error ||
47
+ childProcess.stderr?.toString() ||
48
+ `${commandStr} failed with status ${childProcess.status}`
49
+ )
50
+ }
51
+
52
+ if (
53
+ childProcess.signal &&
54
+ ["SIGINT", "SIGTERM"].includes(childProcess.signal)
55
+ ) {
56
+ throw getAbortError()
57
+ }
58
+
59
+ if (needOutput) {
60
+ console.log(
61
+ childProcess.stdout?.toString() || childProcess.stderr?.toString()
62
+ )
63
+ }
64
+
65
+ return {
66
+ stdout: childProcess.stdout?.toString() || "",
67
+ stderr: childProcess.stderr?.toString() || "",
68
+ }
69
+ } else {
70
+ const [commandStr, options] = command as PromiseExecParams
71
+ const childProcess = await promiseExec(commandStr, {
72
+ ...options,
73
+ env: {
74
+ ...process.env,
75
+ ...(options?.env || {}),
76
+ },
77
+ })
78
+
79
+ return {
80
+ stdout: childProcess.stdout as string,
81
+ stderr: childProcess.stderr as string,
82
+ }
83
+ }
84
+ }
85
+
86
+ export default execute
@@ -0,0 +1,148 @@
1
+ import boxen from "boxen"
2
+ import chalk from "chalk"
3
+ import { emojify } from "node-emoji"
4
+ import { Ora } from "ora"
5
+ import ProcessManager from "./process-manager.js"
6
+ import terminalLink from "terminal-link"
7
+
8
+ export type FactBoxOptions = {
9
+ interval: NodeJS.Timeout | null
10
+ spinner: Ora
11
+ processManager: ProcessManager
12
+ message?: string
13
+ title?: string
14
+ verbose?: boolean
15
+ }
16
+
17
+ const facts = [
18
+ "Specify a product's availability in one or more sales channels.",
19
+ "Payment providers can be configured per region.",
20
+ "Tax-inclusive pricing allows you to set prices for products and shipping options while delegating tax calculations to AcmeKit.",
21
+ "AcmeKit provides multi-currency and region support, with full control over prices for each currency and region.",
22
+ "Organize customers by customer groups and set special prices for them.",
23
+ "Specify the inventory of products per location and sales channel.",
24
+ "Publishable-API Keys allow you to send scoped requests to the server's store API routes.",
25
+ "API Routes expose business logic to clients, such as storefronts and admin customizations.",
26
+ "Subscribers are asynchronous functions that are executed when an event is emitted.",
27
+ "Data models represent tables in the database. They are created using AcmeKit's Data Modeling Language (DML).",
28
+ "AcmeKit's store API routes are prefixed by /store. The admin API routes are prefixed by /admin.",
29
+ "The JS SDK allows you to send requests to the AcmeKit server from your storefront or admin customizations.",
30
+ "Modules are reusable packages of functionalities related to a single commerce domain or integration.",
31
+ "Modules have a main service that provides data-management and integration functionalities.",
32
+ "Modules allow you to replace an entire functionality with your custom logic.",
33
+ "Infrastructure Modules are interchangeable modules that implement features and integrations related to the AcmeKit server's infrastructure.",
34
+ "Commerce Modules are built-in modules that provide core commerce logic specific to domains like Product, Cart and Order.",
35
+ "Workflows are a series of queries and actions, called steps, that complete a task.",
36
+ "A workflow's steps can be retried or rolled back in case of an error.",
37
+ `AcmeKit provides ${terminalLink(
38
+ "Claude Code plugins",
39
+ "https://github.com/acmekit/acmekit-claude-plugins"
40
+ )} to facilitate your development.`,
41
+ "AcmeKit provides an MCP server at https://docs.acmekit.com/mcp to support your learning and development experience with AI agents",
42
+ `AcmeKit is optimized to build custom commerce software with AI agents through its MCP server and ${terminalLink(
43
+ "Claude Code plugins",
44
+ "https://github.com/acmekit/acmekit-claude-plugins"
45
+ )}.`,
46
+ ]
47
+
48
+ export const getFact = () => {
49
+ const randIndex = Math.floor(Math.random() * facts.length)
50
+
51
+ return facts[randIndex]
52
+ }
53
+
54
+ export const showFact = ({
55
+ spinner,
56
+ title,
57
+ verbose,
58
+ }: Pick<FactBoxOptions, "spinner" | "verbose"> & {
59
+ title: string
60
+ }) => {
61
+ const fact = getFact()
62
+ if (verbose) {
63
+ spinner.stopAndPersist({
64
+ symbol: chalk.cyan("⠋"),
65
+ text: title,
66
+ })
67
+ } else {
68
+ spinner.text = `${title}\n${boxen(`${fact}`, {
69
+ title: chalk.cyan(`${emojify(":bulb:")} AcmeKit Tips`),
70
+ titleAlignment: "center",
71
+ textAlignment: "center",
72
+ padding: 1,
73
+ margin: 1,
74
+ })}`
75
+ }
76
+ }
77
+
78
+ export const createFactBox = ({
79
+ spinner,
80
+ title,
81
+ processManager,
82
+ verbose,
83
+ }: Pick<FactBoxOptions, "spinner" | "processManager" | "verbose"> & {
84
+ title: string
85
+ }): NodeJS.Timeout => {
86
+ showFact({ spinner, title, verbose })
87
+ const interval = setInterval(() => {
88
+ showFact({ spinner, title, verbose })
89
+ }, 10000)
90
+
91
+ processManager.addInterval(interval)
92
+
93
+ return interval
94
+ }
95
+
96
+ export const resetFactBox = ({
97
+ interval,
98
+ spinner,
99
+ successMessage,
100
+ processManager,
101
+ newTitle,
102
+ verbose,
103
+ }: Pick<
104
+ FactBoxOptions,
105
+ "interval" | "spinner" | "processManager" | "verbose"
106
+ > & {
107
+ successMessage: string
108
+ newTitle?: string
109
+ }): NodeJS.Timeout | null => {
110
+ if (interval) {
111
+ clearInterval(interval)
112
+ }
113
+
114
+ spinner.succeed(chalk.green(successMessage)).start()
115
+ let newInterval = null
116
+ if (newTitle) {
117
+ newInterval = createFactBox({
118
+ spinner,
119
+ title: newTitle,
120
+ processManager,
121
+ verbose,
122
+ })
123
+ }
124
+
125
+ return newInterval
126
+ }
127
+
128
+ export function displayFactBox({
129
+ interval,
130
+ spinner,
131
+ processManager,
132
+ title = "",
133
+ message = "",
134
+ verbose = false,
135
+ }: FactBoxOptions): NodeJS.Timeout | null {
136
+ if (!message) {
137
+ return createFactBox({ spinner, title, processManager, verbose })
138
+ }
139
+
140
+ return resetFactBox({
141
+ interval,
142
+ spinner,
143
+ successMessage: message,
144
+ processManager,
145
+ newTitle: title,
146
+ verbose,
147
+ })
148
+ }
@@ -0,0 +1,29 @@
1
+ type ConnectionStringOptions = {
2
+ user?: string
3
+ password?: string
4
+ host?: string
5
+ db: string
6
+ }
7
+
8
+ export function encodeDbValue(value: string): string {
9
+ return encodeURIComponent(value)
10
+ }
11
+
12
+ export default ({ user, password, host, db }: ConnectionStringOptions) => {
13
+ let connection = `postgres://`
14
+ if (user) {
15
+ connection += encodeDbValue(user)
16
+ }
17
+
18
+ if (password) {
19
+ connection += `:${encodeDbValue(password)}`
20
+ }
21
+
22
+ if (user || password) {
23
+ connection += "@"
24
+ }
25
+
26
+ connection += `${host}/${db}`
27
+
28
+ return connection
29
+ }
@@ -0,0 +1,17 @@
1
+ import Configstore from "configstore"
2
+
3
+ let config: Configstore
4
+
5
+ export const getConfigStore = (): Configstore => {
6
+ if (!config) {
7
+ config = new Configstore(
8
+ `acmekit`,
9
+ {},
10
+ {
11
+ globalConfigPath: true,
12
+ }
13
+ )
14
+ }
15
+
16
+ return config
17
+ }
@@ -0,0 +1,10 @@
1
+ export const getCurrentOs = (): string => {
2
+ switch (process.platform) {
3
+ case "darwin":
4
+ return "macos"
5
+ case "linux":
6
+ return "linux"
7
+ default:
8
+ return "windows"
9
+ }
10
+ }
@@ -0,0 +1,28 @@
1
+ import chalk from "chalk"
2
+ import { program } from "commander"
3
+ import { logger } from "./logger.js"
4
+
5
+ type LogOptions = {
6
+ message: string
7
+ type?: "error" | "success" | "info" | "warn" | "verbose"
8
+ stack?: string
9
+ }
10
+
11
+ export default ({ message, type = "info", stack }: LogOptions) => {
12
+ switch (type) {
13
+ case "info":
14
+ logger.info(chalk.white(message))
15
+ break
16
+ case "success":
17
+ logger.info(chalk.green(message))
18
+ break
19
+ case "warn":
20
+ logger.warn(chalk.yellow(message))
21
+ break
22
+ case "verbose":
23
+ logger.info(`${chalk.bgYellowBright("VERBOSE LOG:")} ${message}`)
24
+ break
25
+ case "error":
26
+ program.error(chalk.bold.red(message.trim() + (stack || "")))
27
+ }
28
+ }
@@ -0,0 +1,10 @@
1
+ import winston from "winston"
2
+
3
+ const consoleTransport = new winston.transports.Console({
4
+ format: winston.format.printf((log) => log.message as string),
5
+ })
6
+ const options = {
7
+ transports: [consoleTransport],
8
+ }
9
+
10
+ export const logger = winston.createLogger(options)