@getdashi/cli 0.1.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.
- package/LICENSE +631 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +63 -0
- package/dist/commands/app-create.d.ts +6 -0
- package/dist/commands/app-create.js +89 -0
- package/dist/commands/app-dev.d.ts +6 -0
- package/dist/commands/app-dev.js +63 -0
- package/dist/entry.d.ts +2 -0
- package/dist/entry.js +13 -0
- package/dist/utils/serve-preview.d.ts +8 -0
- package/dist/utils/serve-preview.js +50 -0
- package/dist/utils/ui.d.ts +17 -0
- package/dist/utils/ui.js +56 -0
- package/dist/utils/wait-for-port.d.ts +6 -0
- package/dist/utils/wait-for-port.js +28 -0
- package/package.json +34 -0
- package/preview-dist/assets/index-77pJcSpc.css +1 -0
- package/preview-dist/assets/index-Bi2y3XyQ.js +11069 -0
- package/preview-dist/assets/index-TykmCjcq.js +56 -0
- package/preview-dist/index.html +18 -0
- package/templates/dashi-app/.prettierignore +4 -0
- package/templates/dashi-app/.prettierrc +9 -0
- package/templates/dashi-app/app/globals.css +29 -0
- package/templates/dashi-app/app/layout.tsx +22 -0
- package/templates/dashi-app/app/page.tsx +91 -0
- package/templates/dashi-app/app/preferences/page.tsx +95 -0
- package/templates/dashi-app/app/preferences.tsx +25 -0
- package/templates/dashi-app/app/providers.tsx +9 -0
- package/templates/dashi-app/eslint.config.js +23 -0
- package/templates/dashi-app/middleware.ts +46 -0
- package/templates/dashi-app/next.config.mjs +15 -0
- package/templates/dashi-app/package.json +36 -0
- package/templates/dashi-app/postcss.config.mjs +5 -0
- package/templates/dashi-app/public/manifest.json +10 -0
- package/templates/dashi-app/tsconfig.json +24 -0
package/dist/bin.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env -S node --no-warnings
|
|
2
|
+
// Patch BEFORE any imports are evaluated. Dynamic imports below are awaited
|
|
3
|
+
// so their top-level code (including Commander loading its package.json) runs
|
|
4
|
+
// only after this filter is in place.
|
|
5
|
+
process.removeAllListeners('warning');
|
|
6
|
+
process.on('warning', w => {
|
|
7
|
+
if (w.name === 'ExperimentalWarning')
|
|
8
|
+
return;
|
|
9
|
+
process.stderr.write(`${w.name}: ${w.message}\n`);
|
|
10
|
+
});
|
|
11
|
+
const { Command } = await import('commander');
|
|
12
|
+
const { appCreate } = await import('./commands/app-create.js');
|
|
13
|
+
const { appDev } = await import('./commands/app-dev.js');
|
|
14
|
+
const { brand, dim, logError } = await import('./utils/ui.js');
|
|
15
|
+
const { default: pkg } = await import('../package.json', { assert: { type: 'json' } });
|
|
16
|
+
// ─── Banner ───────────────────────────────────────────────────────────────────
|
|
17
|
+
const BANNER = `${brand.bold('◆ dashi')} ${dim(`v${pkg.version}`)}
|
|
18
|
+
`;
|
|
19
|
+
// ─── Program ─────────────────────────────────────────────────────────────────
|
|
20
|
+
const program = new Command();
|
|
21
|
+
program
|
|
22
|
+
.name('dashi')
|
|
23
|
+
.description('')
|
|
24
|
+
.version(pkg.version, '-v, --version', 'print the version number')
|
|
25
|
+
.addHelpText('beforeAll', BANNER)
|
|
26
|
+
.configureHelp({ sortSubcommands: true });
|
|
27
|
+
// ─── app ─────────────────────────────────────────────────────────────────────
|
|
28
|
+
const app = program.command('app').description('manage dashi apps');
|
|
29
|
+
app
|
|
30
|
+
.command('create')
|
|
31
|
+
.description('🚀 bootstrap a new dashi app from the template')
|
|
32
|
+
.argument('[name]', 'app name (prompted if omitted)')
|
|
33
|
+
.option('-p, --port <port>', 'dev server port', '3000')
|
|
34
|
+
.addHelpText('after', `
|
|
35
|
+
${dim('Examples:')}
|
|
36
|
+
${dim('$')} dashi app create
|
|
37
|
+
${dim('$')} dashi app create my-weather-app
|
|
38
|
+
${dim('$')} dashi app create my-app --port 4000
|
|
39
|
+
`)
|
|
40
|
+
.action(async (name, opts) => {
|
|
41
|
+
await appCreate({ name, port: parseInt(opts.port, 10) });
|
|
42
|
+
});
|
|
43
|
+
app
|
|
44
|
+
.command('dev')
|
|
45
|
+
.description('🎛 start the dev server + preview wrapper')
|
|
46
|
+
.option('-p, --port <port>', 'app dev server port', '3000')
|
|
47
|
+
.option('--preview-port <port>', 'preview server port', '3001')
|
|
48
|
+
.addHelpText('after', `
|
|
49
|
+
${dim('Examples:')}
|
|
50
|
+
${dim('$')} dashi app dev
|
|
51
|
+
${dim('$')} dashi app dev --port 4000 --preview-port 4001
|
|
52
|
+
`)
|
|
53
|
+
.action(async (opts) => {
|
|
54
|
+
await appDev({
|
|
55
|
+
appPort: parseInt(opts.port, 10),
|
|
56
|
+
previewPort: parseInt(opts.previewPort, 10),
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
60
|
+
logError(err instanceof Error ? err.message : String(err));
|
|
61
|
+
process.exit(1);
|
|
62
|
+
});
|
|
63
|
+
export {};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { cpSync, mkdirSync, readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { dirname } from 'node:path';
|
|
5
|
+
import { execSync } from 'node:child_process';
|
|
6
|
+
import prompts from 'prompts';
|
|
7
|
+
import { printBanner, printBox, spinner, logError, logSuccess, logStep, brand, dim, bold, url, } from '../utils/ui.js';
|
|
8
|
+
export async function appCreate({ name, port }) {
|
|
9
|
+
printBanner('app create');
|
|
10
|
+
const answers = await prompts([
|
|
11
|
+
{
|
|
12
|
+
type: name ? null : 'text',
|
|
13
|
+
name: 'name',
|
|
14
|
+
message: '📦 App name',
|
|
15
|
+
initial: 'my-dashi-app',
|
|
16
|
+
validate: (v) => (v.trim().length > 0 ? true : 'App name is required'),
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
type: port ? null : 'number',
|
|
20
|
+
name: 'port',
|
|
21
|
+
message: '🔌 Dev server port',
|
|
22
|
+
initial: 3000,
|
|
23
|
+
},
|
|
24
|
+
], {
|
|
25
|
+
onCancel: () => {
|
|
26
|
+
console.log(`\n ${dim('Cancelled.')}\n`);
|
|
27
|
+
process.exit(0);
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
const appName = (name ?? answers.name ?? 'my-dashi-app');
|
|
31
|
+
const appPort = (port ?? answers.port ?? 3000);
|
|
32
|
+
const targetDir = resolve(process.cwd(), appName);
|
|
33
|
+
if (existsSync(targetDir)) {
|
|
34
|
+
logError(`directory ${bold(appName)} already exists`);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
38
|
+
const templateDir = join(__dirname, '..', '..', 'templates', 'dashi-app');
|
|
39
|
+
console.log();
|
|
40
|
+
logStep(`Scaffolding ${brand.bold(appName)} at ${dim(targetDir)}`);
|
|
41
|
+
console.log();
|
|
42
|
+
mkdirSync(targetDir, { recursive: true });
|
|
43
|
+
cpSync(templateDir, targetDir, { recursive: true });
|
|
44
|
+
const pkgPath = join(targetDir, 'package.json');
|
|
45
|
+
const pkg = readFileSync(pkgPath, 'utf8')
|
|
46
|
+
.replace(/\{\{APP_NAME\}\}/g, appName)
|
|
47
|
+
.replace(/\{\{APP_PORT\}\}/g, String(appPort));
|
|
48
|
+
writeFileSync(pkgPath, pkg);
|
|
49
|
+
const manifestPath = join(targetDir, 'public', 'manifest.json');
|
|
50
|
+
const manifest = readFileSync(manifestPath, 'utf8').replace(/\{\{APP_NAME\}\}/g, appName);
|
|
51
|
+
writeFileSync(manifestPath, manifest);
|
|
52
|
+
const pm = detectPm();
|
|
53
|
+
const spin = spinner('Installing dependencies…');
|
|
54
|
+
spin.start();
|
|
55
|
+
try {
|
|
56
|
+
execSync(`${pm} install`, { cwd: targetDir, stdio: 'pipe' });
|
|
57
|
+
spin.succeed('Dependencies installed');
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
spin.fail('Install failed');
|
|
61
|
+
logError(err instanceof Error ? err.message : String(err));
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
logSuccess(`Created ${bold(appName)}`);
|
|
65
|
+
printBox([
|
|
66
|
+
`${brand('◆')} ${bold(appName)} ${dim(`→ ${targetDir}`)}`,
|
|
67
|
+
'',
|
|
68
|
+
` ${dim('cd')} ${appName}`,
|
|
69
|
+
` ${dim(pm + ' dev')} ${dim('start dev server + preview')}`,
|
|
70
|
+
` ${dim(pm + ' build')} ${dim('production build')}`,
|
|
71
|
+
` ${dim(pm + ' start')} ${dim('production server')}`,
|
|
72
|
+
]);
|
|
73
|
+
console.log(` Open ${url(`http://localhost:${appPort}`)} once the dev server starts.\n`);
|
|
74
|
+
}
|
|
75
|
+
function detectPm() {
|
|
76
|
+
try {
|
|
77
|
+
execSync('yarn --version', { stdio: 'ignore' });
|
|
78
|
+
return 'yarn';
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
try {
|
|
82
|
+
execSync('pnpm --version', { stdio: 'ignore' });
|
|
83
|
+
return 'pnpm';
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
return 'npm';
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import open from 'open';
|
|
5
|
+
import { waitForPort } from '../utils/wait-for-port.js';
|
|
6
|
+
import { servePreview } from '../utils/serve-preview.js';
|
|
7
|
+
import { printBanner, spinner, logSuccess, logUrl, logError, dim, brand } from '../utils/ui.js';
|
|
8
|
+
export async function appDev({ appPort, previewPort }) {
|
|
9
|
+
printBanner('app dev');
|
|
10
|
+
const cwd = process.cwd();
|
|
11
|
+
const pm = existsSync(join(cwd, 'yarn.lock'))
|
|
12
|
+
? 'yarn'
|
|
13
|
+
: existsSync(join(cwd, 'pnpm-lock.yaml'))
|
|
14
|
+
? 'pnpm'
|
|
15
|
+
: 'npm';
|
|
16
|
+
const appUrl = `http://localhost:${appPort}`;
|
|
17
|
+
const previewUrl = `http://localhost:${previewPort}?url=${encodeURIComponent(appUrl)}`;
|
|
18
|
+
console.log(` ${brand('📱')} ${dim('App ')} ${appUrl}`);
|
|
19
|
+
console.log(` ${brand('👁 ')} ${dim('Preview ')} ${previewUrl}`);
|
|
20
|
+
console.log();
|
|
21
|
+
// Start the Next.js dev server
|
|
22
|
+
const nextDev = spawn(pm, ['run', 'build:dev', '--', '--port', String(appPort)], {
|
|
23
|
+
cwd,
|
|
24
|
+
stdio: 'inherit',
|
|
25
|
+
shell: true,
|
|
26
|
+
env: { ...process.env, PORT: String(appPort) },
|
|
27
|
+
});
|
|
28
|
+
const cleanup = () => {
|
|
29
|
+
nextDev.kill();
|
|
30
|
+
};
|
|
31
|
+
process.on('SIGINT', cleanup);
|
|
32
|
+
process.on('SIGTERM', cleanup);
|
|
33
|
+
try {
|
|
34
|
+
const previewSpin = spinner('Starting preview server…');
|
|
35
|
+
previewSpin.start();
|
|
36
|
+
await servePreview(previewPort);
|
|
37
|
+
previewSpin.succeed(`Preview server listening on ${dim(`:${previewPort}`)}`);
|
|
38
|
+
const appSpin = spinner('Waiting for app server…');
|
|
39
|
+
appSpin.start();
|
|
40
|
+
await waitForPort(appPort);
|
|
41
|
+
appSpin.succeed('App server ready');
|
|
42
|
+
logSuccess('Opening preview in browser…');
|
|
43
|
+
console.log();
|
|
44
|
+
logUrl('App', appUrl);
|
|
45
|
+
logUrl('Preview', previewUrl);
|
|
46
|
+
console.log();
|
|
47
|
+
await open(previewUrl);
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
nextDev.kill();
|
|
51
|
+
logError(err instanceof Error ? err.message : String(err));
|
|
52
|
+
throw err;
|
|
53
|
+
}
|
|
54
|
+
// Keep alive until the app server exits
|
|
55
|
+
await new Promise((resolve, reject) => {
|
|
56
|
+
nextDev.on('close', code => {
|
|
57
|
+
if (code === 0 || code === null)
|
|
58
|
+
resolve();
|
|
59
|
+
else
|
|
60
|
+
reject(new Error(`App server exited with code ${code}`));
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
}
|
package/dist/entry.d.ts
ADDED
package/dist/entry.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env -S node --no-warnings
|
|
2
|
+
// This file has NO static imports so its top-level code executes before any
|
|
3
|
+
// dependencies are loaded. We patch the warning handler here so that
|
|
4
|
+
// ExperimentalWarnings emitted during module evaluation (e.g. Commander
|
|
5
|
+
// importing its own package.json) are silently dropped.
|
|
6
|
+
process.removeAllListeners('warning');
|
|
7
|
+
process.on('warning', (w) => {
|
|
8
|
+
if (w.name === 'ExperimentalWarning')
|
|
9
|
+
return;
|
|
10
|
+
process.stderr.write(`${w.name}: ${w.message}\n`);
|
|
11
|
+
});
|
|
12
|
+
await import('./bin.js');
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Starts a minimal static file server for the bundled preview SPA.
|
|
3
|
+
* All paths that don't match a file are served `index.html` so the
|
|
4
|
+
* React SPA can handle routing / query-string changes itself.
|
|
5
|
+
*
|
|
6
|
+
* @returns A promise that resolves once the server is listening.
|
|
7
|
+
*/
|
|
8
|
+
export declare function servePreview(port: number): Promise<void>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { createReadStream, statSync } from 'node:fs';
|
|
3
|
+
import { join, extname } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { dirname } from 'node:path';
|
|
6
|
+
const MIME = {
|
|
7
|
+
'.html': 'text/html; charset=utf-8',
|
|
8
|
+
'.js': 'application/javascript; charset=utf-8',
|
|
9
|
+
'.css': 'text/css; charset=utf-8',
|
|
10
|
+
'.svg': 'image/svg+xml',
|
|
11
|
+
'.png': 'image/png',
|
|
12
|
+
'.ico': 'image/x-icon',
|
|
13
|
+
'.json': 'application/json',
|
|
14
|
+
'.woff2': 'font/woff2',
|
|
15
|
+
'.woff': 'font/woff',
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Starts a minimal static file server for the bundled preview SPA.
|
|
19
|
+
* All paths that don't match a file are served `index.html` so the
|
|
20
|
+
* React SPA can handle routing / query-string changes itself.
|
|
21
|
+
*
|
|
22
|
+
* @returns A promise that resolves once the server is listening.
|
|
23
|
+
*/
|
|
24
|
+
export function servePreview(port) {
|
|
25
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
26
|
+
// In the published package this sits two levels up (dist/utils/ → preview-dist/)
|
|
27
|
+
const distDir = join(__dirname, '..', '..', 'preview-dist');
|
|
28
|
+
const server = createServer((req, res) => {
|
|
29
|
+
const rawPath = req.url?.split('?')[0] ?? '/';
|
|
30
|
+
const filePath = rawPath === '/' ? 'index.html' : rawPath.replace(/^\//, '');
|
|
31
|
+
const fullPath = join(distDir, filePath);
|
|
32
|
+
let exists = false;
|
|
33
|
+
try {
|
|
34
|
+
statSync(fullPath);
|
|
35
|
+
exists = true;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// fall through — serve index.html for SPA fallback
|
|
39
|
+
}
|
|
40
|
+
const servePath = exists ? fullPath : join(distDir, 'index.html');
|
|
41
|
+
const mime = MIME[extname(servePath)] ?? 'application/octet-stream';
|
|
42
|
+
res.setHeader('Content-Type', mime);
|
|
43
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
44
|
+
createReadStream(servePath).pipe(res);
|
|
45
|
+
});
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
server.on('error', reject);
|
|
48
|
+
server.listen(port, '127.0.0.1', () => resolve());
|
|
49
|
+
});
|
|
50
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type Ora } from 'ora';
|
|
2
|
+
export declare const brand: import("chalk").ChalkInstance;
|
|
3
|
+
export declare const dim: import("chalk").ChalkInstance;
|
|
4
|
+
export declare const bold: import("chalk").ChalkInstance;
|
|
5
|
+
export declare const url: import("chalk").ChalkInstance;
|
|
6
|
+
export declare const success: import("chalk").ChalkInstance;
|
|
7
|
+
export declare const error: import("chalk").ChalkInstance;
|
|
8
|
+
export declare const warn: import("chalk").ChalkInstance;
|
|
9
|
+
export declare const muted: import("chalk").ChalkInstance;
|
|
10
|
+
export declare function printBanner(subtitle?: string): void;
|
|
11
|
+
export declare function logSuccess(msg: string): void;
|
|
12
|
+
export declare function logError(msg: string): void;
|
|
13
|
+
export declare function logInfo(msg: string): void;
|
|
14
|
+
export declare function logStep(msg: string): void;
|
|
15
|
+
export declare function logUrl(label: string, href: string): void;
|
|
16
|
+
export declare function spinner(text: string): Ora;
|
|
17
|
+
export declare function printBox(lines: string[]): void;
|
package/dist/utils/ui.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
// ─── Palette ─────────────────────────────────────────────────────────────────
|
|
4
|
+
export const brand = chalk.hex('#7C3AED');
|
|
5
|
+
export const dim = chalk.dim;
|
|
6
|
+
export const bold = chalk.bold;
|
|
7
|
+
export const url = chalk.cyan.underline;
|
|
8
|
+
export const success = chalk.green;
|
|
9
|
+
export const error = chalk.red;
|
|
10
|
+
export const warn = chalk.yellow;
|
|
11
|
+
export const muted = chalk.gray;
|
|
12
|
+
// ─── Banner ───────────────────────────────────────────────────────────────────
|
|
13
|
+
export function printBanner(subtitle) {
|
|
14
|
+
console.log();
|
|
15
|
+
console.log(brand.bold(' ◆ dashi') + (subtitle ? dim(` ${subtitle}`) : ''));
|
|
16
|
+
console.log();
|
|
17
|
+
}
|
|
18
|
+
// ─── Log helpers ─────────────────────────────────────────────────────────────
|
|
19
|
+
export function logSuccess(msg) {
|
|
20
|
+
console.log(success(' ✔ ') + msg);
|
|
21
|
+
}
|
|
22
|
+
export function logError(msg) {
|
|
23
|
+
console.log(error(' ✖ ') + msg);
|
|
24
|
+
}
|
|
25
|
+
export function logInfo(msg) {
|
|
26
|
+
console.log(chalk.cyan(' ℹ ') + msg);
|
|
27
|
+
}
|
|
28
|
+
export function logStep(msg) {
|
|
29
|
+
console.log(dim(' › ') + msg);
|
|
30
|
+
}
|
|
31
|
+
export function logUrl(label, href) {
|
|
32
|
+
const paddedLabel = label.padEnd(10);
|
|
33
|
+
console.log(` ${dim('→')} ${muted(paddedLabel)} ${url(href)}`);
|
|
34
|
+
}
|
|
35
|
+
// ─── Spinner factory ──────────────────────────────────────────────────────────
|
|
36
|
+
export function spinner(text) {
|
|
37
|
+
return ora({ text, indent: 2 });
|
|
38
|
+
}
|
|
39
|
+
// ─── Box ─────────────────────────────────────────────────────────────────────
|
|
40
|
+
export function printBox(lines) {
|
|
41
|
+
const maxLen = Math.max(...lines.map(l => stripAnsi(l).length));
|
|
42
|
+
const hr = '─'.repeat(maxLen + 4);
|
|
43
|
+
console.log();
|
|
44
|
+
console.log(dim(` ╭${hr}╮`));
|
|
45
|
+
for (const line of lines) {
|
|
46
|
+
const pad = ' '.repeat(maxLen - stripAnsi(line).length);
|
|
47
|
+
console.log(dim(' │') + ` ${line}${pad} ` + dim('│'));
|
|
48
|
+
}
|
|
49
|
+
console.log(dim(` ╰${hr}╯`));
|
|
50
|
+
console.log();
|
|
51
|
+
}
|
|
52
|
+
// Very minimal ANSI stripper — good enough for our own formatted strings.
|
|
53
|
+
function stripAnsi(str) {
|
|
54
|
+
// eslint-disable-next-line no-control-regex
|
|
55
|
+
return str.replace(/\x1B\[[0-9;]*m/g, '');
|
|
56
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createConnection } from 'node:net';
|
|
2
|
+
/**
|
|
3
|
+
* Polls until a TCP connection to `localhost:<port>` succeeds, or
|
|
4
|
+
* until `timeoutMs` has elapsed. Useful for waiting on a Next.js dev
|
|
5
|
+
* server.
|
|
6
|
+
*/
|
|
7
|
+
export function waitForPort(port, timeoutMs = 60_000) {
|
|
8
|
+
return new Promise((resolve, reject) => {
|
|
9
|
+
const deadline = Date.now() + timeoutMs;
|
|
10
|
+
const attempt = () => {
|
|
11
|
+
const socket = createConnection({ host: '127.0.0.1', port });
|
|
12
|
+
socket.once('connect', () => {
|
|
13
|
+
socket.destroy();
|
|
14
|
+
resolve();
|
|
15
|
+
});
|
|
16
|
+
socket.once('error', () => {
|
|
17
|
+
socket.destroy();
|
|
18
|
+
if (Date.now() >= deadline) {
|
|
19
|
+
reject(new Error(`Timed out waiting for port ${port} to become available`));
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
setTimeout(attempt, 250);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
};
|
|
26
|
+
attempt();
|
|
27
|
+
});
|
|
28
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@getdashi/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "AGPL-3.0-only",
|
|
5
|
+
"description": "Dashi app development CLI",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"dashi": "./dist/bin.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"preview-dist",
|
|
13
|
+
"templates"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"prebuild": "node -e \"const{cpSync,mkdirSync}=require('fs');mkdirSync('preview-dist',{recursive:true});cpSync('../preview/dist','preview-dist',{recursive:true})\"",
|
|
17
|
+
"build": "tsc -p tsconfig.json",
|
|
18
|
+
"dev": "tsc -p tsconfig.json --watch",
|
|
19
|
+
"release": "bumpp --tag 'cli-v%s' --commit 'chore: release @getdashi/cli v%s' --push"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"chalk": "^5.6.2",
|
|
23
|
+
"commander": "^12.1.0",
|
|
24
|
+
"open": "^10.1.0",
|
|
25
|
+
"ora": "^9.3.0",
|
|
26
|
+
"prompts": "^2.4.2"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^20.11.19",
|
|
30
|
+
"@types/prompts": "^2.4.9",
|
|
31
|
+
"bumpp": "^9.0.0",
|
|
32
|
+
"typescript": "^5.3.3"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/*! tailwindcss v4.2.1 | MIT License | https://tailwindcss.com */@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-backdrop-blur:initial;--tw-backdrop-brightness:initial;--tw-backdrop-contrast:initial;--tw-backdrop-grayscale:initial;--tw-backdrop-hue-rotate:initial;--tw-backdrop-invert:initial;--tw-backdrop-opacity:initial;--tw-backdrop-saturate:initial;--tw-backdrop-sepia:initial;--tw-duration:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-400:oklch(70.4% .191 22.216);--color-green-500:oklch(72.3% .219 149.579);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-600:oklch(54.6% .245 262.881);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-black:#000;--color-white:#fff;--spacing:.25rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height: 1.5 ;--font-weight-thin:100;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--leading-relaxed:1.625;--radius-xl:.75rem;--radius-2xl:1rem;--ease-out:cubic-bezier(0, 0, .2, 1);--ease-in-out:cubic-bezier(.4, 0, .2, 1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::file-selector-button{-webkit-appearance:button;-moz-appearance:button;appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.inset-0{inset:calc(var(--spacing) * 0)}.top-0{top:calc(var(--spacing) * 0)}.top-0\.5{top:calc(var(--spacing) * .5)}.right-0{right:calc(var(--spacing) * 0)}.right-4{right:calc(var(--spacing) * 4)}.bottom-0{bottom:calc(var(--spacing) * 0)}.bottom-4{bottom:calc(var(--spacing) * 4)}.bottom-14{bottom:calc(var(--spacing) * 14)}.bottom-\[10px\]{bottom:10px}.bottom-\[calc\(100\%\+8px\)\]{bottom:calc(100% + 8px)}.left-0{left:calc(var(--spacing) * 0)}.left-0\.5{left:calc(var(--spacing) * .5)}.left-4{left:calc(var(--spacing) * 4)}.left-\[10px\]{left:10px}.z-1{z-index:1}.z-100{z-index:100}.z-9999{z-index:9999}.col-\[1\/3\]{grid-column:1/3}.col-start-1{grid-column-start:1}.col-start-2{grid-column-start:2}.col-start-3{grid-column-start:3}.row-\[1\/3\]{grid-row:1/3}.row-start-1{grid-row-start:1}.row-start-2{grid-row-start:2}.container{width:100%}@media(min-width:40rem){.container{max-width:40rem}}@media(min-width:48rem){.container{max-width:48rem}}@media(min-width:64rem){.container{max-width:64rem}}@media(min-width:80rem){.container{max-width:80rem}}@media(min-width:96rem){.container{max-width:96rem}}.m-0{margin:calc(var(--spacing) * 0)}.mx-2{margin-inline:calc(var(--spacing) * 2)}.mx-3{margin-inline:calc(var(--spacing) * 3)}.my-2{margin-block:calc(var(--spacing) * 2)}.mb-0\.5{margin-bottom:calc(var(--spacing) * .5)}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.h-2{height:calc(var(--spacing) * 2)}.h-4{height:calc(var(--spacing) * 4)}.h-8{height:calc(var(--spacing) * 8)}.h-9{height:calc(var(--spacing) * 9)}.h-10{height:calc(var(--spacing) * 10)}.h-\[14px\]{height:14px}.h-\[18px\]{height:18px}.h-\[22px\]{height:22px}.h-\[45px\]{height:45px}.h-px{height:1px}.h-screen{height:100vh}.max-h-\[70vh\]{max-height:70vh}.min-h-0{min-height:calc(var(--spacing) * 0)}.w-2{width:calc(var(--spacing) * 2)}.w-4{width:calc(var(--spacing) * 4)}.w-8{width:calc(var(--spacing) * 8)}.w-9{width:calc(var(--spacing) * 9)}.w-10{width:calc(var(--spacing) * 10)}.w-\[14px\]{width:14px}.w-\[420px\]{width:420px}.w-full{width:100%}.w-px{width:1px}.max-w-\[200px\]{max-width:200px}.min-w-0{min-width:calc(var(--spacing) * 0)}.min-w-\[140px\]{min-width:140px}.min-w-\[220px\]{min-width:220px}.min-w-\[230px\]{min-width:230px}.flex-1{flex:1}.shrink-0{flex-shrink:0}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.resize{resize:both}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-\[15px\]{gap:15px}.overflow-hidden{overflow:hidden}.overflow-visible{overflow:visible}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-\[10px\]{border-radius:10px}.rounded-full{border-radius:3.40282e38px}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-none{--tw-border-style:none;border-style:none}.border-\[\#2a2a2a\]{border-color:#2a2a2a}.border-\[\#333\]{border-color:#333}.border-white\/10{border-color:#ffffff1a}@supports (color:color-mix(in lab,red,red)){.border-white\/10{border-color:color-mix(in oklab,var(--color-white) 10%,transparent)}}.border-white\/20{border-color:#fff3}@supports (color:color-mix(in lab,red,red)){.border-white\/20{border-color:color-mix(in oklab,var(--color-white) 20%,transparent)}}.bg-\[\#1a1a1a\]{background-color:#1a1a1a}.bg-\[\#2a2a2a\]{background-color:#2a2a2a}.bg-\[\#6babff\]{background-color:#6babff}.bg-\[\#6babff\]\/12{background-color:#6babff1f}.bg-\[\#111\]{background-color:#111}.bg-\[\#080809\]{background-color:#080809}.bg-\[\#141414\]{background-color:#141414}.bg-black\/45{background-color:#00000073}@supports (color:color-mix(in lab,red,red)){.bg-black\/45{background-color:color-mix(in oklab,var(--color-black) 45%,transparent)}}.bg-green-500{background-color:var(--color-green-500)}.bg-transparent{background-color:#0000}.bg-white{background-color:var(--color-white)}.bg-white\/4{background-color:#ffffff0a}@supports (color:color-mix(in lab,red,red)){.bg-white\/4{background-color:color-mix(in oklab,var(--color-white) 4%,transparent)}}.bg-white\/8{background-color:#ffffff14}@supports (color:color-mix(in lab,red,red)){.bg-white\/8{background-color:color-mix(in oklab,var(--color-white) 8%,transparent)}}.bg-white\/9{background-color:#ffffff17}@supports (color:color-mix(in lab,red,red)){.bg-white\/9{background-color:color-mix(in oklab,var(--color-white) 9%,transparent)}}.bg-white\/12{background-color:#ffffff1f}@supports (color:color-mix(in lab,red,red)){.bg-white\/12{background-color:color-mix(in oklab,var(--color-white) 12%,transparent)}}.bg-white\/15{background-color:#ffffff26}@supports (color:color-mix(in lab,red,red)){.bg-white\/15{background-color:color-mix(in oklab,var(--color-white) 15%,transparent)}}.bg-white\/20{background-color:#fff3}@supports (color:color-mix(in lab,red,red)){.bg-white\/20{background-color:color-mix(in oklab,var(--color-white) 20%,transparent)}}.bg-linear-to-br{--tw-gradient-position:to bottom right}@supports (background-image:linear-gradient(in lab,red,red)){.bg-linear-to-br{--tw-gradient-position:to bottom right in oklab}}.bg-linear-to-br{background-image:linear-gradient(var(--tw-gradient-stops))}.from-blue-400{--tw-gradient-from:var(--color-blue-400);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-blue-600{--tw-gradient-to:var(--color-blue-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.p-8{padding:calc(var(--spacing) * 8)}.p-\[14px\]{padding:14px}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-\[10px\]{padding-inline:10px}.px-\[14px\]{padding-inline:14px}.px-\[18px\]{padding-inline:18px}.py-1{padding-block:calc(var(--spacing) * 1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-\[10px\]{padding-block:10px}.pt-1\.5{padding-top:calc(var(--spacing) * 1.5)}.pt-\[8px\]{padding-top:8px}.pb-0\.5{padding-bottom:calc(var(--spacing) * .5)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.text-left{text-align:left}.font-mono{font-family:var(--font-mono)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[10px\]{font-size:10px}.text-\[11px\]{font-size:11px}.text-\[18px\]{font-size:18px}.text-\[20px\]{font-size:20px}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.font-thin{--tw-font-weight:var(--font-weight-thin);font-weight:var(--font-weight-thin)}.tracking-\[0\.05em\]{--tw-tracking:.05em;letter-spacing:.05em}.break-all{word-break:break-all}.text-ellipsis{text-overflow:ellipsis}.whitespace-nowrap{white-space:nowrap}.text-\[\#6babff\]{color:#6babff}.text-\[\#9ca3af\]{color:#9ca3af}.text-\[\#d4d4d4\]{color:#d4d4d4}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-red-400{color:var(--color-red-400)}.text-white{color:var(--color-white)}.text-white\/40{color:#fff6}@supports (color:color-mix(in lab,red,red)){.text-white\/40{color:color-mix(in oklab,var(--color-white) 40%,transparent)}}.text-white\/70{color:#ffffffb3}@supports (color:color-mix(in lab,red,red)){.text-white\/70{color:color-mix(in oklab,var(--color-white) 70%,transparent)}}.text-white\/80{color:#fffc}@supports (color:color-mix(in lab,red,red)){.text-white\/80{color:color-mix(in oklab,var(--color-white) 80%,transparent)}}.uppercase{text-transform:uppercase}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.opacity-0{opacity:0}.opacity-20{opacity:.2}.opacity-100{opacity:1}.shadow-\[0_8px_24px_rgba\(0\,0\,0\,0\.5\)\]{--tw-shadow:0 8px 24px var(--tw-shadow-color,#00000080);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[inset_0_0_0_1px_rgba\(255\,255\,255\,0\.06\)\]{--tw-shadow:inset 0 0 0 1px var(--tw-shadow-color,#ffffff0f);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-\[inset_0_0_0_1px_rgba\(255\,255\,255\,0\.12\)\]{--tw-shadow:inset 0 0 0 1px var(--tw-shadow-color,#ffffff1f);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.backdrop-blur-\[8px\]{--tw-backdrop-blur:blur(8px);-webkit-backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,);backdrop-filter:var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-\[left\]{transition-property:left;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-150{--tw-duration:.15s;transition-duration:.15s}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-600{--tw-duration:.6s;transition-duration:.6s}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}}*,:before,:after{box-sizing:border-box}body{margin:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"<length-percentage>";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"<length-percentage>";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"<length-percentage>";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-backdrop-blur{syntax:"*";inherits:false}@property --tw-backdrop-brightness{syntax:"*";inherits:false}@property --tw-backdrop-contrast{syntax:"*";inherits:false}@property --tw-backdrop-grayscale{syntax:"*";inherits:false}@property --tw-backdrop-hue-rotate{syntax:"*";inherits:false}@property --tw-backdrop-invert{syntax:"*";inherits:false}@property --tw-backdrop-opacity{syntax:"*";inherits:false}@property --tw-backdrop-saturate{syntax:"*";inherits:false}@property --tw-backdrop-sepia{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}
|