create-absolutejs 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,37 +5,16 @@ import { green } from 'picocolors';
5
5
  import { absoluteAuthPlugin, availablePlugins, defaultDependencies, defaultPlugins, eslintAndPrettierDependencies, eslintReactDependencies } from '../../data';
6
6
  import { getPackageVersion } from '../../utils/getPackageVersion';
7
7
  import { versions } from '../../versions';
8
- import { initTemplates } from '../db/dockerInitTemplates';
9
8
  import { computeFlags } from '../project/computeFlags';
10
- const dbScripts = {
11
- cockroachdb: {
12
- clientCmd: 'cockroach sql --insecure --database=database',
13
- waitCmd: initTemplates.cockroachdb.wait
14
- },
15
- gel: {
16
- clientCmd: 'gel -H localhost -P 5656 -u admin --tls-security insecure -b main',
17
- waitCmd: initTemplates.gel.wait
18
- },
19
- mariadb: {
20
- clientCmd: 'MYSQL_PWD=userpassword mariadb -h127.0.0.1 -u user database',
21
- waitCmd: initTemplates.mariadb.wait
22
- },
23
- mssql: {
24
- clientCmd: '/opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P SApassword1',
25
- waitCmd: initTemplates.mssql.wait
26
- },
27
- mysql: {
28
- clientCmd: 'MYSQL_PWD=userpassword mysql -h127.0.0.1 -u user database',
29
- waitCmd: initTemplates.mysql.wait
30
- },
31
- postgresql: {
32
- clientCmd: 'psql -h localhost -U user -d database',
33
- waitCmd: initTemplates.postgresql.wait
34
- },
35
- singlestore: {
36
- clientCmd: 'singlestore -u root -ppassword -D database',
37
- waitCmd: initTemplates.singlestore.wait
38
- }
9
+ const dbClientCommands = {
10
+ cockroachdb: 'cockroach sql --insecure --database=database',
11
+ gel: 'gel -H localhost -P 5656 -u admin --tls-security insecure -b main',
12
+ mariadb: 'MYSQL_PWD=userpassword mariadb -h127.0.0.1 -u user database',
13
+ mongodb: 'mongosh -u user -p password --authenticationDatabase admin database',
14
+ mssql: '/opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P SApassword1',
15
+ mysql: 'MYSQL_PWD=userpassword mysql -h127.0.0.1 -u user database',
16
+ postgresql: 'psql -h localhost -U user -d database',
17
+ singlestore: 'singlestore -u root -ppassword -D database'
39
18
  };
40
19
  export const createPackageJson = ({ projectName, authOption, plugins, databaseEngine, orm, databaseHost, useTailwind, latest, frontendDirectories, codeQualityTool }) => {
41
20
  const s = spinner();
@@ -111,7 +90,7 @@ export const createPackageJson = ({ projectName, authOption, plugins, databaseEn
111
90
  if (latest)
112
91
  s.stop(green('Package versions resolved'));
113
92
  const scripts = {
114
- dev: 'bun run --watch src/backend/server.ts',
93
+ dev: 'absolutejs dev',
115
94
  format: `prettier --write "./**/*.{js,ts,css,json,mjs,md${flags.requiresReact ? ',jsx,tsx' : ''}${flags.requiresSvelte ? ',svelte' : ''}${flags.requiresVue ? ',vue' : ''}${flags.requiresHtml || flags.requiresHtmx ? ',html' : ''}}"`,
116
95
  lint: 'eslint ./src',
117
96
  test: 'echo "Error: no test specified" && exit 1',
@@ -121,20 +100,15 @@ export const createPackageJson = ({ projectName, authOption, plugins, databaseEn
121
100
  if (isLocal &&
122
101
  databaseEngine !== undefined &&
123
102
  databaseEngine !== 'none' &&
124
- databaseEngine !== 'sqlite' &&
125
- databaseEngine !== 'mongodb') {
126
- const config = dbScripts[databaseEngine];
103
+ databaseEngine !== 'sqlite') {
104
+ const clientCmd = dbClientCommands[databaseEngine];
127
105
  const dockerPrefix = `docker compose -p ${databaseEngine} -f db/docker-compose.db.yml`;
128
- scripts['db:up'] = `${dockerPrefix} up -d db`;
129
- scripts['postdb:up'] =
130
- `${dockerPrefix} exec db bash -lc '${config.waitCmd}'`;
106
+ scripts['db:up'] = `${dockerPrefix} up -d --wait db`;
131
107
  scripts['db:down'] = `${dockerPrefix} down`;
132
108
  scripts['db:reset'] = `${dockerPrefix} down -v`;
133
109
  scripts[`db:${databaseEngine}`] =
134
- `${dockerPrefix} exec -it db bash -lc '${config.clientCmd}'`;
135
- scripts['predev'] = 'bun db:up';
110
+ `${dockerPrefix} exec -it db bash -lc '${clientCmd}'`;
136
111
  scripts[`predb:${databaseEngine}`] = 'bun db:up';
137
- scripts['postdev'] = 'bun db:down';
138
112
  scripts[`postdb:${databaseEngine}`] = 'bun db:down';
139
113
  }
140
114
  if (isLocal &&
@@ -156,6 +130,9 @@ export const createPackageJson = ({ projectName, authOption, plugins, databaseEn
156
130
  if (isLocal && databaseEngine === 'gel') {
157
131
  dependencies['gel'] = resolveVersion('gel', versions['gel']);
158
132
  }
133
+ if (databaseEngine === 'mongodb') {
134
+ dependencies['mongodb'] = resolveVersion('mongodb', versions['mongodb']);
135
+ }
159
136
  if (isLocal && databaseEngine === 'sqlite') {
160
137
  scripts['db:sqlite'] = 'sqlite3 db/database.sqlite';
161
138
  scripts['db:init'] = 'sqlite3 db/database.sqlite < db/init.sql';
@@ -29,6 +29,10 @@ export declare const initTemplates: {
29
29
  readonly cli: "MYSQL_PWD=userpassword mariadb -h127.0.0.1 -u user database -e";
30
30
  readonly wait: "until mariadb-admin ping -h127.0.0.1 --silent; do sleep 1; done";
31
31
  };
32
+ readonly mongodb: {
33
+ readonly cli: "mongosh -u user -p password --authenticationDatabase admin database --eval";
34
+ readonly wait: "for i in $(seq 1 60); do mongosh -u user -p password --authenticationDatabase admin --eval \"db.adminCommand(\\\"ping\\\")\" --quiet 2>/dev/null && exit 0; sleep 1; done; exit 1";
35
+ };
32
36
  readonly mssql: {
33
37
  readonly cli: "/opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P SApassword1 -Q";
34
38
  readonly wait: "until /opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P SApassword1 -Q \"SELECT 1\" >/dev/null 2>&1; do sleep 1; done";
@@ -122,6 +122,10 @@ export const initTemplates = {
122
122
  cli: 'MYSQL_PWD=userpassword mariadb -h127.0.0.1 -u user database -e',
123
123
  wait: 'until mariadb-admin ping -h127.0.0.1 --silent; do sleep 1; done'
124
124
  },
125
+ mongodb: {
126
+ cli: 'mongosh -u user -p password --authenticationDatabase admin database --eval',
127
+ wait: 'for i in $(seq 1 60); do mongosh -u user -p password --authenticationDatabase admin --eval "db.adminCommand(\\"ping\\")" --quiet 2>/dev/null && exit 0; sleep 1; done; exit 1'
128
+ },
125
129
  mssql: {
126
130
  cli: '/opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P SApassword1 -Q',
127
131
  wait: 'until /opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P SApassword1 -Q "SELECT 1" >/dev/null 2>&1; do sleep 1; done'
@@ -4,6 +4,10 @@ const templates = {
4
4
  env: {
5
5
  COCKROACH_DATABASE: 'database'
6
6
  },
7
+ healthcheck: {
8
+ startPeriod: '5s',
9
+ test: 'cockroach sql --insecure -e "select 1" >/dev/null 2>&1'
10
+ },
7
11
  image: 'cockroachdb/cockroach:latest-v25.3',
8
12
  port: '26257:26257',
9
13
  volumePath: '/cockroach/cockroach-data'
@@ -12,6 +16,10 @@ const templates = {
12
16
  env: {
13
17
  GEL_SERVER_SECURITY: 'insecure_dev_mode'
14
18
  },
19
+ healthcheck: {
20
+ startPeriod: '30s',
21
+ test: 'gel query -H localhost -P 5656 -u admin --tls-security insecure "select 1" >/dev/null 2>&1'
22
+ },
15
23
  image: 'geldata/gel:latest',
16
24
  port: '5656:5656',
17
25
  volumePath: '/var/lib/gel/data'
@@ -23,6 +31,10 @@ const templates = {
23
31
  MYSQL_ROOT_PASSWORD: 'rootpassword',
24
32
  MYSQL_USER: 'user'
25
33
  },
34
+ healthcheck: {
35
+ startPeriod: '5s',
36
+ test: 'mariadb-admin ping -h127.0.0.1 --silent'
37
+ },
26
38
  image: 'mariadb:11.4',
27
39
  port: '3306:3306',
28
40
  volumePath: '/var/lib/mysql'
@@ -33,6 +45,10 @@ const templates = {
33
45
  MONGO_INITDB_ROOT_PASSWORD: 'password',
34
46
  MONGO_INITDB_ROOT_USERNAME: 'user'
35
47
  },
48
+ healthcheck: {
49
+ startPeriod: '5s',
50
+ test: 'mongosh -u user -p password --authenticationDatabase admin --eval "db.adminCommand(\'ping\')" --quiet'
51
+ },
36
52
  image: 'mongo:7.0',
37
53
  port: '27017:27017',
38
54
  volumePath: '/data/db'
@@ -42,6 +58,10 @@ const templates = {
42
58
  ACCEPT_EULA: 'Y',
43
59
  MSSQL_SA_PASSWORD: 'SApassword1'
44
60
  },
61
+ healthcheck: {
62
+ startPeriod: '30s',
63
+ test: '/opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa -P SApassword1 -Q "SELECT 1" >/dev/null 2>&1'
64
+ },
45
65
  image: 'mcr.microsoft.com/mssql/server:2022-latest',
46
66
  port: '1433:1433',
47
67
  volumePath: '/var/opt/mssql'
@@ -53,6 +73,10 @@ const templates = {
53
73
  MYSQL_ROOT_PASSWORD: 'rootpassword',
54
74
  MYSQL_USER: 'user'
55
75
  },
76
+ healthcheck: {
77
+ startPeriod: '5s',
78
+ test: 'mysqladmin ping -h127.0.0.1 --silent'
79
+ },
56
80
  image: 'mysql:8.0',
57
81
  port: '3306:3306',
58
82
  volumePath: '/var/lib/mysql'
@@ -63,6 +87,10 @@ const templates = {
63
87
  POSTGRES_PASSWORD: 'password',
64
88
  POSTGRES_USER: 'user'
65
89
  },
90
+ healthcheck: {
91
+ startPeriod: '5s',
92
+ test: 'pg_isready -U user -h localhost --quiet'
93
+ },
66
94
  image: 'postgres:15',
67
95
  port: '5432:5432',
68
96
  volumePath: '/var/lib/postgresql/data'
@@ -71,6 +99,10 @@ const templates = {
71
99
  env: {
72
100
  ROOT_PASSWORD: 'password'
73
101
  },
102
+ healthcheck: {
103
+ startPeriod: '30s',
104
+ test: 'singlestore -u root -ppassword -e "SELECT 1" >/dev/null 2>&1'
105
+ },
74
106
  image: 'ghcr.io/singlestore-labs/singlestoredb-dev', // NOTE: No tag specified due to data persistence
75
107
  port: '3306:3306',
76
108
  volumePath: '/data'
@@ -82,8 +114,8 @@ export const generateDockerContainer = (databaseEngine) => {
82
114
  databaseEngine === 'sqlite') {
83
115
  throw new Error('Internal type error: Expected a valid local database engine');
84
116
  }
85
- const { image, port, env, volumePath, command } = templates[databaseEngine];
86
- const commandLines = command ? ` command: ${command}` : '';
117
+ const { command, env, healthcheck, image, port, volumePath } = templates[databaseEngine];
118
+ const commandLine = command ? `\n command: ${command}` : '';
87
119
  const envLines = Object.entries(env)
88
120
  .map(([key, value]) => ` ${key}: ${value}`)
89
121
  .join('\n');
@@ -94,8 +126,13 @@ export const generateDockerContainer = (databaseEngine) => {
94
126
  environment:
95
127
  ${envLines}
96
128
  ports:
97
- - "${port}"
98
- ${commandLines}
129
+ - "${port}"${commandLine}
130
+ healthcheck:
131
+ test: ["CMD-SHELL", "${healthcheck.test.replaceAll('"', '\\"')}"]
132
+ interval: 2s
133
+ timeout: 5s
134
+ retries: 30
135
+ start_period: ${healthcheck.startPeriod}
99
136
  volumes:
100
137
  - db_data:${volumePath}
101
138
 
@@ -30,7 +30,7 @@ declare const driverConfigurations: {
30
30
  readonly importLines: "";
31
31
  readonly queries: QueryOperations;
32
32
  };
33
- readonly 'mongodb:native:local': {
33
+ readonly 'mongodb:sql:local': {
34
34
  readonly dbType: "Db";
35
35
  readonly importLines: "";
36
36
  readonly queries: QueryOperations;
@@ -348,7 +348,7 @@ import { schema } from '../../../db/schema'`,
348
348
  importLines: ``,
349
349
  queries: mysqlSqlQueryOperations
350
350
  },
351
- 'mongodb:native:local': {
351
+ 'mongodb:sql:local': {
352
352
  dbType: 'Db',
353
353
  importLines: ``,
354
354
  queries: mongodbQueryOperations
@@ -4,5 +4,7 @@ type ScaffoldDatabaseProps = Pick<CreateConfiguration, 'projectName' | 'database
4
4
  backendDirectory: string;
5
5
  typesDirectory: string;
6
6
  };
7
- export declare const scaffoldDatabase: ({ projectName, databaseEngine, databaseHost, databaseDirectory, backendDirectory, authOption, orm, typesDirectory }: ScaffoldDatabaseProps) => Promise<void>;
7
+ export declare const scaffoldDatabase: ({ projectName, databaseEngine, databaseHost, databaseDirectory, backendDirectory, authOption, orm, typesDirectory }: ScaffoldDatabaseProps) => Promise<{
8
+ dockerFreshInstall: boolean;
9
+ }>;
8
10
  export {};
@@ -37,12 +37,13 @@ export const scaffoldDatabase = async ({ projectName, databaseEngine, databaseHo
37
37
  databaseEngine !== 'sqlite' &&
38
38
  databaseEngine !== undefined &&
39
39
  databaseEngine !== 'none') {
40
- await scaffoldDocker({
40
+ const { dockerFreshInstall } = await scaffoldDocker({
41
41
  authOption,
42
42
  databaseEngine,
43
43
  projectDatabaseDirectory,
44
44
  projectName
45
45
  });
46
+ return { dockerFreshInstall };
46
47
  }
47
48
  if (orm === 'drizzle') {
48
49
  if (!isDrizzleDialect(databaseEngine)) {
@@ -60,9 +61,10 @@ export const scaffoldDatabase = async ({ projectName, databaseEngine, databaseHo
60
61
  databaseHost
61
62
  });
62
63
  writeFileSync(join(typesDirectory, 'databaseTypes.ts'), drizzleTypes);
63
- return;
64
+ return { dockerFreshInstall: false };
64
65
  }
65
66
  if (orm === 'prisma') {
66
67
  console.warn(`${dim('│')}\n${yellow('▲')} Prisma support is not implemented yet`);
67
68
  }
69
+ return { dockerFreshInstall: false };
68
70
  };
@@ -5,5 +5,7 @@ type ScaffoldDockerProps = {
5
5
  authOption: AuthOption;
6
6
  projectName: string;
7
7
  };
8
- export declare const scaffoldDocker: ({ databaseEngine, projectDatabaseDirectory, projectName, authOption }: ScaffoldDockerProps) => Promise<void>;
8
+ export declare const scaffoldDocker: ({ databaseEngine, projectDatabaseDirectory, projectName, authOption }: ScaffoldDockerProps) => Promise<{
9
+ dockerFreshInstall: boolean;
10
+ }>;
9
11
  export {};
@@ -1,7 +1,7 @@
1
1
  import { writeFileSync } from 'fs';
2
2
  import { join } from 'path';
3
3
  import { $ } from 'bun';
4
- import { checkDockerInstalled } from '../../utils/checkDockerInstalled';
4
+ import { checkDockerInstalled, ensureDockerDaemonRunning, resolveDockerExe, shutdownDockerDaemon } from '../../utils/checkDockerInstalled';
5
5
  import { countHistoryTables, initTemplates, userTables } from './dockerInitTemplates';
6
6
  import { generateDockerContainer } from './generateDockerContainer';
7
7
  export const scaffoldDocker = async ({ databaseEngine, projectDatabaseDirectory, projectName, authOption }) => {
@@ -10,20 +10,30 @@ export const scaffoldDocker = async ({ databaseEngine, projectDatabaseDirectory,
10
10
  databaseEngine === 'sqlite') {
11
11
  throw new Error('Internal type error: databaseEngine must be defined and not "none" or "sqlite"');
12
12
  }
13
- await checkDockerInstalled();
13
+ const { freshInstall } = await checkDockerInstalled(databaseEngine);
14
+ const { daemonWasStarted } = await ensureDockerDaemonRunning();
14
15
  const dbContainer = generateDockerContainer(databaseEngine);
15
16
  writeFileSync(join(projectDatabaseDirectory, 'docker-compose.db.yml'), dbContainer, 'utf-8');
16
- if (databaseEngine === 'mongodb') {
17
- }
18
- else {
19
- const { wait, cli } = initTemplates[databaseEngine];
17
+ const docker = resolveDockerExe();
18
+ const hasSchemaInit = databaseEngine in userTables;
19
+ if (hasSchemaInit) {
20
+ const dbKey = databaseEngine;
21
+ const { wait, cli } = initTemplates[dbKey];
20
22
  const usesAuth = authOption !== undefined && authOption !== 'none';
21
23
  const dbCommand = usesAuth
22
- ? userTables[databaseEngine]
23
- : countHistoryTables[databaseEngine];
24
- await $ `bun db:up`.cwd(projectName);
25
- await $ `docker compose -p ${databaseEngine} -f db/docker-compose.db.yml exec -T db \
24
+ ? userTables[dbKey]
25
+ : countHistoryTables[dbKey];
26
+ await $ `${docker} compose -p ${databaseEngine} -f db/docker-compose.db.yml up -d db`.cwd(projectName);
27
+ await $ `${docker} compose -p ${databaseEngine} -f db/docker-compose.db.yml exec -T db \
26
28
  bash -lc '${wait} && ${cli} "${dbCommand}"'`.cwd(projectName);
27
- await $ `bun db:down`.cwd(projectName);
29
+ await $ `${docker} compose -p ${databaseEngine} -f db/docker-compose.db.yml down`.cwd(projectName);
30
+ }
31
+ else {
32
+ await $ `${docker} compose -p ${databaseEngine} -f db/docker-compose.db.yml up -d --wait db`.cwd(projectName);
33
+ await $ `${docker} compose -p ${databaseEngine} -f db/docker-compose.db.yml down`.cwd(projectName);
34
+ }
35
+ if (daemonWasStarted) {
36
+ await shutdownDockerDaemon();
28
37
  }
38
+ return { dockerFreshInstall: freshInstall };
29
39
  };
@@ -115,7 +115,10 @@ export const generateImportsBlock = ({ backendDirectory, deps, flags, orm, authO
115
115
  `import { SQL } from 'bun'`,
116
116
  `import { getEnv } from '@absolutejs/absolute'`
117
117
  ],
118
- mongodb: [],
118
+ mongodb: [
119
+ `import { MongoClient } from 'mongodb'`,
120
+ `import { getEnv } from '@absolutejs/absolute'`
121
+ ],
119
122
  mssql: [
120
123
  `import { connect } from 'mssql'`,
121
124
  `import { getEnv } from '@absolutejs/absolute'`
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env bun
2
- import { exit } from 'process';
2
+ import { exit, platform } from 'process';
3
3
  import { outro } from '@clack/prompts';
4
+ import { cyan, yellow } from 'picocolors';
4
5
  import { getDebugMessage, getOutroMessage, helpMessage } from './messages';
5
6
  import { prompt } from './prompt';
6
7
  import { scaffold } from './scaffold';
@@ -13,7 +14,12 @@ if (help === true) {
13
14
  exit(0);
14
15
  }
15
16
  const response = await prompt(argumentConfiguration);
16
- await scaffold({ envVariables, latest, packageManager, response });
17
+ const { dockerFreshInstall } = await scaffold({
18
+ envVariables,
19
+ latest,
20
+ packageManager,
21
+ response
22
+ });
17
23
  const debugMessage = debug !== false
18
24
  ? getDebugMessage({
19
25
  packageManager,
@@ -26,3 +32,6 @@ const outroMessage = getOutroMessage({
26
32
  projectName: response.projectName
27
33
  });
28
34
  outro(outroMessage + debugMessage);
35
+ if (dockerFreshInstall && platform === 'win32') {
36
+ console.log(`\n${yellow('▲')} Docker was freshly installed. Restart your terminal for ${cyan('docker')} to be available in PATH.\n`);
37
+ }
@@ -5,5 +5,7 @@ type ScaffoldProps = {
5
5
  latest: boolean;
6
6
  envVariables: string[] | undefined;
7
7
  };
8
- export declare const scaffold: ({ response: { projectName, codeQualityTool, initializeGitNow, databaseEngine, databaseHost, useHTMLScripts, useTailwind, databaseDirectory, absProviders, orm, frontends, plugins, authOption, buildDirectory, assetsDirectory, tailwind, installDependenciesNow, frontendDirectories }, latest, envVariables, packageManager }: ScaffoldProps) => Promise<void>;
8
+ export declare const scaffold: ({ response: { projectName, codeQualityTool, initializeGitNow, databaseEngine, databaseHost, useHTMLScripts, useTailwind, databaseDirectory, absProviders, orm, frontends, plugins, authOption, buildDirectory, assetsDirectory, tailwind, installDependenciesNow, frontendDirectories }, latest, envVariables, packageManager }: ScaffoldProps) => Promise<{
9
+ dockerFreshInstall: boolean;
10
+ }>;
9
11
  export {};
package/dist/scaffold.js CHANGED
@@ -51,10 +51,11 @@ export const scaffold = async ({ response: { projectName, codeQualityTool, initi
51
51
  plugins,
52
52
  tailwind
53
53
  });
54
- void (databaseDirectory !== undefined &&
54
+ let dockerFreshInstall = false;
55
+ if (databaseDirectory !== undefined &&
55
56
  databaseEngine !== 'none' &&
56
- databaseEngine !== undefined &&
57
- (await scaffoldDatabase({
57
+ databaseEngine !== undefined) {
58
+ const result = await scaffoldDatabase({
58
59
  authOption,
59
60
  backendDirectory,
60
61
  databaseDirectory,
@@ -63,7 +64,9 @@ export const scaffold = async ({ response: { projectName, codeQualityTool, initi
63
64
  orm,
64
65
  projectName,
65
66
  typesDirectory
66
- })));
67
+ });
68
+ dockerFreshInstall = result.dockerFreshInstall;
69
+ }
67
70
  scaffoldFrontends({
68
71
  absProviders,
69
72
  assetsDirectory,
@@ -87,4 +90,5 @@ export const scaffold = async ({ response: { projectName, codeQualityTool, initi
87
90
  if (initializeGitNow) {
88
91
  await initializeGit(projectName);
89
92
  }
93
+ return { dockerFreshInstall };
90
94
  };
@@ -1,2 +1,10 @@
1
+ export declare const resolveDockerExe: () => string;
1
2
  export declare const hasDocker: () => Promise<boolean>;
2
- export declare const checkDockerInstalled: () => Promise<void>;
3
+ export declare const isDockerDaemonRunning: () => Promise<boolean>;
4
+ export declare const ensureDockerDaemonRunning: () => Promise<{
5
+ daemonWasStarted: boolean;
6
+ }>;
7
+ export declare const shutdownDockerDaemon: () => Promise<void>;
8
+ export declare const checkDockerInstalled: (databaseEngine?: string) => Promise<{
9
+ freshInstall: boolean;
10
+ }>;
@@ -1,9 +1,12 @@
1
+ import { existsSync } from 'fs';
1
2
  import os from 'os';
2
3
  import { env, platform } from 'process';
3
4
  import { confirm, spinner } from '@clack/prompts';
4
5
  import { $ } from 'bun';
5
6
  import { dim, yellow } from 'picocolors';
6
7
  const DOCKER_URL = 'https://www.docker.com/products/docker-desktop';
8
+ const DOCKER_WIN_INSTALLER_URL = 'https://desktop.docker.com/win/main/amd64/Docker%20Desktop%20Installer.exe';
9
+ const DOCKER_WIN_BIN_PATH = 'C:\\Program Files\\Docker\\Docker\\resources\\bin';
7
10
  const isWSL = () => env.WSL_DISTRO_NAME !== undefined || /microsoft/i.test(os.release());
8
11
  let hostEnv;
9
12
  if (platform === 'win32') {
@@ -59,45 +62,48 @@ const configureDocker = async () => {
59
62
  await $ `sudo systemctl enable --now docker`.quiet().nothrow();
60
63
  spin.stop('Docker daemon running & permissions configured');
61
64
  };
62
- const installWindows = async () => {
63
- if (await commandExists('winget')) {
64
- const spin = spinner();
65
- spin.start('Installing Docker Desktop with winget');
66
- const res = await $ `winget install -e --id Docker.DockerDesktop`
65
+ const installWindowsDirectDownload = async () => {
66
+ const spin = spinner();
67
+ spin.start('Downloading Docker Desktop installer…');
68
+ const tmpDir = await import('os').then((os) => os.tmpdir());
69
+ const installerPath = `${tmpDir}\\DockerDesktopInstaller.exe`;
70
+ try {
71
+ const res = await Bun.fetch(DOCKER_WIN_INSTALLER_URL);
72
+ if (!res.ok)
73
+ return false;
74
+ const buffer = await res.arrayBuffer();
75
+ await Bun.write(installerPath, buffer);
76
+ spin.stop('Docker Desktop installer downloaded');
77
+ spin.start('Running Docker Desktop installer…');
78
+ const runRes = await $ `powershell.exe -NoProfile -Command "Start-Process -FilePath '${installerPath}' -ArgumentList 'install','--quiet','--accept-license' -Wait -Verb RunAs"`
67
79
  .quiet()
68
80
  .nothrow();
69
- spin.stop(res.exitCode === 0
81
+ spin.stop(runRes.exitCode === 0
70
82
  ? 'Docker Desktop installed'
71
- : 'winget install failed');
72
- return res.exitCode === 0;
83
+ : 'Installer failed');
84
+ return runRes.exitCode === 0;
73
85
  }
74
- if (await commandExists('choco')) {
75
- const spin = spinner();
76
- spin.start('Installing Docker Desktop with Chocolatey');
77
- const res = await $ `choco install docker-desktop -y`.quiet().nothrow();
78
- spin.stop(res.exitCode === 0
79
- ? 'Docker Desktop installed'
80
- : 'Chocolatey install failed');
81
- return res.exitCode === 0;
86
+ catch {
87
+ spin.stop('Direct download failed');
88
+ return false;
82
89
  }
83
- console.log(`Automatic Windows install failed. Get Docker Desktop from ${DOCKER_URL}`);
90
+ };
91
+ const installWindowsOpenBrowser = async () => {
92
+ await $ `powershell.exe -NoProfile -Command "Start-Process '${DOCKER_URL}'"`
93
+ .quiet()
94
+ .nothrow();
95
+ console.log(`Opened Docker Desktop download page. Install it, then run this again.`);
96
+ return false;
97
+ };
98
+ const installWindows = async () => {
99
+ if (await installWindowsDirectDownload())
100
+ return true;
101
+ await installWindowsOpenBrowser();
84
102
  return false;
85
103
  };
86
104
  const installWSL = async () => {
87
105
  if ((await $ `docker.exe --version`.quiet().nothrow()).exitCode === 0)
88
106
  return true;
89
- if (await commandExists('powershell.exe')) {
90
- const spin = spinner();
91
- spin.start('Installing Docker Desktop on Windows via winget');
92
- const res = await $ `powershell.exe -NoProfile -Command winget install -e --id Docker.DockerDesktop`
93
- .quiet()
94
- .nothrow();
95
- spin.stop(res.exitCode === 0
96
- ? 'Docker Desktop installed'
97
- : 'winget install failed');
98
- if (res.exitCode === 0)
99
- return true;
100
- }
101
107
  if (await aptInstall())
102
108
  return true;
103
109
  return runGetDocker();
@@ -150,30 +156,122 @@ const installLinux = async () => {
150
156
  console.log(`Automatic Linux install failed. See ${DOCKER_URL}`);
151
157
  return false;
152
158
  };
153
- export const hasDocker = async () => (await $ `docker --version`.quiet().nothrow()).exitCode === 0 &&
154
- (await $ `docker compose version`.quiet().nothrow()).exitCode === 0;
155
- export const checkDockerInstalled = async () => {
156
- if (await hasDocker())
159
+ const DAEMON_WAIT_ATTEMPTS = 30;
160
+ const DAEMON_WAIT_INTERVAL_MS = 2000;
161
+ export const resolveDockerExe = () => {
162
+ if (platform === 'win32') {
163
+ const fullPath = `${DOCKER_WIN_BIN_PATH}\\docker.exe`;
164
+ if (existsSync(fullPath))
165
+ return fullPath;
166
+ }
167
+ return 'docker';
168
+ };
169
+ export const hasDocker = async () => {
170
+ const docker = resolveDockerExe();
171
+ return ((await $ `${docker} --version`.quiet().nothrow()).exitCode === 0 &&
172
+ (await $ `${docker} compose version`.quiet().nothrow()).exitCode === 0);
173
+ };
174
+ export const isDockerDaemonRunning = async () => {
175
+ const docker = resolveDockerExe();
176
+ return (await $ `${docker} info`.quiet().nothrow()).exitCode === 0;
177
+ };
178
+ const waitForDaemonReady = async () => {
179
+ for (let attempt = 0; attempt < DAEMON_WAIT_ATTEMPTS; attempt++) {
180
+ if (await isDockerDaemonRunning())
181
+ return true;
182
+ await new Promise((resolve) => setTimeout(resolve, DAEMON_WAIT_INTERVAL_MS));
183
+ }
184
+ return false;
185
+ };
186
+ const startDockerDaemon = async () => {
187
+ const spin = spinner();
188
+ spin.start('Starting Docker daemon…');
189
+ const docker = resolveDockerExe();
190
+ const desktopRes = await $ `${docker} desktop start`.quiet().nothrow();
191
+ if (desktopRes.exitCode === 0 && (await waitForDaemonReady())) {
192
+ spin.stop('Docker Desktop started');
193
+ return true;
194
+ }
195
+ if (platform === 'darwin') {
196
+ await $ `open -a Docker`.quiet().nothrow();
197
+ if (await waitForDaemonReady()) {
198
+ spin.stop('Docker Desktop started');
199
+ return true;
200
+ }
201
+ spin.stop('Docker daemon did not start');
202
+ throw new Error('Docker daemon did not start. Please start Docker Desktop manually.');
203
+ }
204
+ if (platform === 'win32') {
205
+ const dockerDesktopExe = 'C:\\Program Files\\Docker\\Docker\\Docker Desktop.exe';
206
+ if (existsSync(dockerDesktopExe)) {
207
+ await $ `powershell.exe -NoProfile -Command "Start-Process '${dockerDesktopExe}'"`
208
+ .quiet()
209
+ .nothrow();
210
+ if (await waitForDaemonReady()) {
211
+ spin.stop('Docker Desktop started');
212
+ return true;
213
+ }
214
+ }
215
+ spin.stop('Docker Desktop start failed');
216
+ throw new Error('Docker Desktop failed to start. Please start it manually.');
217
+ }
218
+ await ensureSudo();
219
+ const systemctlRes = await $ `sudo systemctl start docker`.quiet().nothrow();
220
+ if (systemctlRes.exitCode !== 0) {
221
+ await $ `sudo service docker start`.quiet().nothrow();
222
+ }
223
+ if (await waitForDaemonReady()) {
224
+ spin.stop('Docker daemon started');
225
+ return true;
226
+ }
227
+ spin.stop('Docker daemon did not start');
228
+ throw new Error('Docker daemon did not start. Please start it manually.');
229
+ };
230
+ export const ensureDockerDaemonRunning = async () => {
231
+ if (await isDockerDaemonRunning()) {
232
+ return { daemonWasStarted: false };
233
+ }
234
+ await startDockerDaemon();
235
+ return { daemonWasStarted: true };
236
+ };
237
+ export const shutdownDockerDaemon = async () => {
238
+ const docker = resolveDockerExe();
239
+ const desktopRes = await $ `${docker} desktop shutdown`.quiet().nothrow();
240
+ if (desktopRes.exitCode === 0)
157
241
  return;
242
+ if (platform !== 'win32') {
243
+ await ensureSudo();
244
+ await $ `sudo systemctl stop docker`.quiet().nothrow();
245
+ }
246
+ };
247
+ export const checkDockerInstalled = async (databaseEngine) => {
248
+ if (await hasDocker())
249
+ return { freshInstall: false };
250
+ const dbLabel = databaseEngine ?? 'database';
158
251
  const proceed = await confirm({
159
252
  initialValue: true,
160
- message: 'Docker Engine and Compose plugin are required for local Postgresql. Install them now?'
253
+ message: `Docker Engine and Compose plugin are required for your local ${dbLabel}. Install them now?`
161
254
  });
162
255
  if (!proceed)
163
- return;
256
+ return { freshInstall: false };
164
257
  switch (hostEnv) {
165
258
  case 'windows':
166
- if (await installWindows())
167
- return;
259
+ if (await installWindows()) {
260
+ if (!env.PATH?.includes(DOCKER_WIN_BIN_PATH)) {
261
+ env.PATH = `${DOCKER_WIN_BIN_PATH};${env.PATH}`;
262
+ }
263
+ return { freshInstall: true };
264
+ }
168
265
  break;
169
266
  case 'wsl':
170
267
  if (await installWSL())
171
- return;
268
+ return { freshInstall: true };
172
269
  break;
173
270
  case 'linux':
174
271
  if (await installLinux())
175
- return;
272
+ return { freshInstall: true };
176
273
  break;
177
274
  }
178
275
  console.log(`Couldn't install Docker automatically. Download it from ${DOCKER_URL}`);
276
+ return { freshInstall: false };
179
277
  };
@@ -57,24 +57,24 @@ const apkInstallSqlite = async () => {
57
57
  spin.stop(res.exitCode === 0 ? 'sqlite3 installed' : 'apk install failed');
58
58
  return res.exitCode === 0;
59
59
  };
60
+ const hasWinget = async () => (await $ `powershell.exe -NoProfile -Command "Get-Command winget"`
61
+ .quiet()
62
+ .nothrow()).exitCode === 0;
60
63
  const installWindowsSqlite = async () => {
61
- if (await commandExists('winget')) {
64
+ if (await hasWinget()) {
62
65
  const spin = spinner();
66
+ spin.start('Updating winget sources');
67
+ await $ `powershell.exe -NoProfile -Command winget source update`
68
+ .quiet()
69
+ .nothrow();
70
+ spin.stop('winget sources updated');
63
71
  spin.start('Installing sqlite3 with winget');
64
- const res = await $ `winget install -e --id SQLite.SQLite`
72
+ await $ `powershell.exe -NoProfile -Command "Start-Process winget -ArgumentList 'install','-e','--id','SQLite.SQLite','--accept-package-agreements','--accept-source-agreements' -Verb RunAs -Wait"`
65
73
  .quiet()
66
74
  .nothrow();
67
- spin.stop(res.exitCode === 0 ? 'sqlite3 installed' : 'winget install failed');
68
- return res.exitCode === 0;
69
- }
70
- if (await commandExists('choco')) {
71
- const spin = spinner();
72
- spin.start('Installing sqlite3 with Chocolatey');
73
- const res = await $ `choco install sqlite -y`.quiet().nothrow();
74
- spin.stop(res.exitCode === 0
75
- ? 'sqlite3 installed'
76
- : 'Chocolatey install failed');
77
- return res.exitCode === 0;
75
+ const installed = await hasSqlite();
76
+ spin.stop(installed ? 'sqlite3 installed' : 'winget install failed');
77
+ return installed;
78
78
  }
79
79
  console.log(`Automatic Windows install failed. Get sqlite3 from ${SQLITE_URL}`);
80
80
  return false;
@@ -4,7 +4,7 @@
4
4
  * Run `bun run check-versions` to compare against latest npm versions.
5
5
  */
6
6
  export declare const versions: {
7
- readonly '@absolutejs/absolute': "0.13.0";
7
+ readonly '@absolutejs/absolute': "0.13.5";
8
8
  readonly '@absolutejs/auth': "0.22.0";
9
9
  readonly '@elysiajs/static': "1.4.7";
10
10
  readonly elysia: "1.4.22";
@@ -43,6 +43,7 @@ export declare const versions: {
43
43
  readonly '@types/mssql': "9.1.9";
44
44
  readonly '@types/pg': "8.16.0";
45
45
  readonly gel: "2.2.0";
46
+ readonly mongodb: "6.12.0";
46
47
  readonly mssql: "12.2.0";
47
48
  readonly mysql2: "3.16.3";
48
49
  readonly pg: "8.18.0";
package/dist/versions.js CHANGED
@@ -5,7 +5,7 @@
5
5
  */
6
6
  export const versions = {
7
7
  /* ── Core ─────────────────────────────────────────────── */
8
- '@absolutejs/absolute': '0.13.0',
8
+ '@absolutejs/absolute': '0.13.5',
9
9
  '@absolutejs/auth': '0.22.0',
10
10
  '@elysiajs/static': '1.4.7',
11
11
  elysia: '1.4.22',
@@ -55,6 +55,7 @@ export const versions = {
55
55
  '@types/mssql': '9.1.9',
56
56
  '@types/pg': '8.16.0',
57
57
  gel: '2.2.0',
58
+ mongodb: '6.12.0',
58
59
  mssql: '12.2.0',
59
60
  mysql2: '3.16.3',
60
61
  pg: '8.18.0'
package/package.json CHANGED
@@ -49,5 +49,5 @@
49
49
  "typecheck": "bun run tsc --noEmit"
50
50
  },
51
51
  "type": "module",
52
- "version": "0.7.0"
52
+ "version": "0.8.0"
53
53
  }