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.
- package/.turbo/turbo-build.log +1 -0
- package/CHANGELOG.md +3 -0
- package/README.md +58 -0
- package/dist/commands/create.js +8 -0
- package/dist/index.js +31 -0
- package/dist/utils/clone-repo.js +77 -0
- package/dist/utils/create-abort-controller.js +11 -0
- package/dist/utils/create-db.js +172 -0
- package/dist/utils/execute.js +51 -0
- package/dist/utils/facts.js +87 -0
- package/dist/utils/format-connection-string.js +17 -0
- package/dist/utils/get-config-store.js +10 -0
- package/dist/utils/get-current-os.js +10 -0
- package/dist/utils/log-message.js +21 -0
- package/dist/utils/logger.js +8 -0
- package/dist/utils/nextjs-utils.js +85 -0
- package/dist/utils/node-version.js +5 -0
- package/dist/utils/package-manager.js +266 -0
- package/dist/utils/postgres-client.js +9 -0
- package/dist/utils/prepare-project.js +184 -0
- package/dist/utils/process-manager.js +50 -0
- package/dist/utils/project-creator/acmekit-plugin-creator.js +93 -0
- package/dist/utils/project-creator/acmekit-project-creator.js +204 -0
- package/dist/utils/project-creator/creator.js +46 -0
- package/dist/utils/project-creator/index.js +4 -0
- package/dist/utils/project-creator/project-creator-factory.js +83 -0
- package/dist/utils/start-acmekit.js +13 -0
- package/dist/utils/update-package-versions.js +28 -0
- package/jest.config.cjs +14 -0
- package/package.json +48 -0
- package/src/commands/create.ts +12 -0
- package/src/index.ts +65 -0
- package/src/types.d.ts +1 -0
- package/src/utils/__tests__/create-abort-controller.test.ts +166 -0
- package/src/utils/__tests__/package-manager.test.ts +637 -0
- package/src/utils/clone-repo.ts +117 -0
- package/src/utils/create-abort-controller.ts +16 -0
- package/src/utils/create-db.ts +245 -0
- package/src/utils/execute.ts +86 -0
- package/src/utils/facts.ts +148 -0
- package/src/utils/format-connection-string.ts +29 -0
- package/src/utils/get-config-store.ts +17 -0
- package/src/utils/get-current-os.ts +10 -0
- package/src/utils/log-message.ts +28 -0
- package/src/utils/logger.ts +10 -0
- package/src/utils/nextjs-utils.ts +139 -0
- package/src/utils/node-version.ts +7 -0
- package/src/utils/package-manager.ts +334 -0
- package/src/utils/postgres-client.ts +23 -0
- package/src/utils/prepare-project.ts +325 -0
- package/src/utils/process-manager.ts +60 -0
- package/src/utils/project-creator/acmekit-plugin-creator.ts +127 -0
- package/src/utils/project-creator/acmekit-project-creator.ts +272 -0
- package/src/utils/project-creator/creator.ts +77 -0
- package/src/utils/project-creator/index.ts +4 -0
- package/src/utils/project-creator/project-creator-factory.ts +119 -0
- package/src/utils/start-acmekit.ts +26 -0
- package/src/utils/update-package-versions.ts +37 -0
- 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,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)
|