@kywi-software/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.
Files changed (36) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +44 -0
  3. package/bin/kywi.js +22 -0
  4. package/dist/__tests__/command-wiring.test.d.ts +2 -0
  5. package/dist/__tests__/command-wiring.test.d.ts.map +1 -0
  6. package/dist/__tests__/command-wiring.test.js +55 -0
  7. package/dist/__tests__/command-wiring.test.js.map +1 -0
  8. package/dist/__tests__/migrate-generate.test.d.ts +2 -0
  9. package/dist/__tests__/migrate-generate.test.d.ts.map +1 -0
  10. package/dist/__tests__/migrate-generate.test.js +53 -0
  11. package/dist/__tests__/migrate-generate.test.js.map +1 -0
  12. package/dist/commands/migrate-generate.d.ts +2 -0
  13. package/dist/commands/migrate-generate.d.ts.map +1 -0
  14. package/dist/commands/migrate-generate.js +64 -0
  15. package/dist/commands/migrate-generate.js.map +1 -0
  16. package/dist/commands/migrate-status.d.ts +2 -0
  17. package/dist/commands/migrate-status.d.ts.map +1 -0
  18. package/dist/commands/migrate-status.js +22 -0
  19. package/dist/commands/migrate-status.js.map +1 -0
  20. package/dist/commands/migrate.d.ts +2 -0
  21. package/dist/commands/migrate.d.ts.map +1 -0
  22. package/dist/commands/migrate.js +72 -0
  23. package/dist/commands/migrate.js.map +1 -0
  24. package/dist/commands/seed-demo.d.ts +2 -0
  25. package/dist/commands/seed-demo.d.ts.map +1 -0
  26. package/dist/commands/seed-demo.js +451 -0
  27. package/dist/commands/seed-demo.js.map +1 -0
  28. package/dist/commands/seed.d.ts +2 -0
  29. package/dist/commands/seed.d.ts.map +1 -0
  30. package/dist/commands/seed.js +163 -0
  31. package/dist/commands/seed.js.map +1 -0
  32. package/dist/index.d.ts +6 -0
  33. package/dist/index.d.ts.map +1 -0
  34. package/dist/index.js +27 -0
  35. package/dist/index.js.map +1 -0
  36. package/package.json +52 -0
package/README.md ADDED
@@ -0,0 +1,44 @@
1
+ # @kywi-software/cli
2
+
3
+ The `kywi` command-line tool: applies the schema described by a project's
4
+ `kywi.config.ts` to its database, and seeds default/demo content. Built on
5
+ `@kywi-software/core`'s schema generator and Drizzle. See the
6
+ [root README](https://github.com/kywi-software/kywi#readme) for how it fits
7
+ into the rest of Kywi.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install @kywi-software/cli
13
+ ```
14
+
15
+ Projects scaffolded with `npx create-kywi-app` already have it as a
16
+ dependency and wire `migrate`/`seed` into their `package.json` scripts.
17
+
18
+ ## Commands
19
+
20
+ Run from a project directory containing `kywi.config.ts` (or point `-c/--config`
21
+ at a different path):
22
+
23
+ ```bash
24
+ kywi migrate --push # apply the schema (drizzle-kit push — no migration files, good for prototyping)
25
+ kywi migrate # apply the schema via generated Drizzle migration files
26
+ kywi migrate:generate # generate Drizzle schema files from kywi.config.ts (writes to build/schema/)
27
+ kywi migrate:status # show pending Drizzle migrations
28
+ kywi seed # create the default site + a superadmin user
29
+ kywi seed:demo # seed realistic demo content (pages, posts, event, contact form, taxonomy, media, feed)
30
+ ```
31
+
32
+ Every command accepts `-c, --config <path>` (default `kywi.config.ts`).
33
+
34
+ `DATABASE_URL` (or the `db.url` in `kywi.config.ts`) selects the target
35
+ database; `kywi.config.ts` itself is loaded dynamically at runtime (that's
36
+ why `tsx` is a runtime dependency of this package — the CLI's own code is
37
+ plain compiled JS, but the config file it reads lives in your project as
38
+ TypeScript).
39
+
40
+ ## What's in the package
41
+
42
+ `packages/cli/src/index.ts` registers each command
43
+ (`packages/cli/src/commands/*.ts`) on a `commander` program named `kywi`.
44
+ That file is the source of truth for exact flags and behavior.
package/bin/kywi.js ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * kywi CLI entry point.
4
+ * Runs the built CLI (dist/index.js) under Node with the `tsx` ESM loader
5
+ * registered (--import tsx), so commands can dynamically `import()` the
6
+ * user's kywi.config.ts — a TypeScript file living in their own project,
7
+ * not something this package builds. The CLI's own code is plain compiled
8
+ * JS; tsx is only needed for that one dynamic import of user source.
9
+ */
10
+ import { fileURLToPath } from 'node:url'
11
+ import { resolve, dirname } from 'node:path'
12
+ import { spawn } from 'node:child_process'
13
+
14
+ const __dirname = dirname(fileURLToPath(import.meta.url))
15
+ const entry = resolve(__dirname, '..', 'dist', 'index.js')
16
+
17
+ const child = spawn(process.execPath, ['--import', 'tsx', entry, ...process.argv.slice(2)], {
18
+ stdio: 'inherit',
19
+ env: process.env,
20
+ })
21
+
22
+ child.on('exit', code => process.exit(code ?? 0))
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=command-wiring.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-wiring.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/command-wiring.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,55 @@
1
+ import { describe, it, expect, beforeAll } from 'vitest';
2
+ import { program } from 'commander';
3
+ // Importing each command module registers its command on the shared commander
4
+ // `program` singleton as a side effect (the same way src/index.ts wires them).
5
+ // We import the modules — NOT index.ts, which would call program.parse(argv) —
6
+ // so nothing runs; we only inspect the registered command graph.
7
+ beforeAll(async () => {
8
+ await import('../commands/migrate-generate.js');
9
+ await import('../commands/migrate.js');
10
+ await import('../commands/migrate-status.js');
11
+ await import('../commands/seed.js');
12
+ await import('../commands/seed-demo.js');
13
+ });
14
+ function command(name) {
15
+ const cmd = program.commands.find((c) => c.name() === name);
16
+ if (!cmd)
17
+ throw new Error(`command "${name}" is not registered`);
18
+ return cmd;
19
+ }
20
+ function optionDefaults(cmd) {
21
+ const out = {};
22
+ for (const opt of cmd.options)
23
+ out[opt.attributeName()] = opt.defaultValue;
24
+ return out;
25
+ }
26
+ describe('CLI command wiring', () => {
27
+ it('registers every command index.ts imports', () => {
28
+ const names = program.commands.map((c) => c.name()).sort();
29
+ expect(names).toEqual(['migrate', 'migrate:generate', 'migrate:status', 'seed', 'seed:demo']);
30
+ });
31
+ it('every command has a description and an action handler', () => {
32
+ for (const name of ['migrate', 'migrate:generate', 'migrate:status', 'seed', 'seed:demo']) {
33
+ const cmd = command(name);
34
+ expect(cmd.description().length).toBeGreaterThan(0);
35
+ // commander stores the action callback on a private field; presence proves wiring.
36
+ expect(cmd._actionHandler).toBeTypeOf('function');
37
+ }
38
+ });
39
+ it('migrate:generate exposes --config and --out with their defaults', () => {
40
+ const defaults = optionDefaults(command('migrate:generate'));
41
+ expect(defaults).toMatchObject({ config: 'kywi.config.ts', out: 'kywi-generated/schema' });
42
+ });
43
+ it('migrate exposes --config and a --push flag defaulting to false', () => {
44
+ const defaults = optionDefaults(command('migrate'));
45
+ expect(defaults).toMatchObject({ config: 'kywi.config.ts', push: false });
46
+ });
47
+ it('migrate:status takes no options', () => {
48
+ expect(command('migrate:status').options).toHaveLength(0);
49
+ });
50
+ it('seed and seed:demo both accept --config', () => {
51
+ expect(optionDefaults(command('seed'))).toMatchObject({ config: 'kywi.config.ts' });
52
+ expect(optionDefaults(command('seed:demo'))).toMatchObject({ config: 'kywi.config.ts' });
53
+ });
54
+ });
55
+ //# sourceMappingURL=command-wiring.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-wiring.test.js","sourceRoot":"","sources":["../../src/__tests__/command-wiring.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import '../commands/migrate-generate.js';
2
+ //# sourceMappingURL=migrate-generate.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate-generate.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/migrate-generate.test.ts"],"names":[],"mappings":"AA0BA,OAAO,iCAAiC,CAAA"}
@@ -0,0 +1,53 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ // Capture every file the command would write, without touching the filesystem.
3
+ const writes = [];
4
+ vi.mock('node:fs', () => ({
5
+ mkdirSync: vi.fn(),
6
+ writeFileSync: vi.fn((path, content) => { writes.push({ path, content }); }),
7
+ }));
8
+ // Keep the real (pure) schema generators; only stub the config loader so the
9
+ // command runs without a real kywi.config.ts on disk.
10
+ vi.mock('@kywi/core', async (importOriginal) => {
11
+ const actual = await importOriginal();
12
+ return {
13
+ ...actual,
14
+ parseKywiConfig: vi.fn().mockResolvedValue({
15
+ contentTypes: [
16
+ { name: 'blogPost', label: 'Blog Post', baseType: 'content', fields: [{ name: 'excerpt', type: 'richText', label: 'Excerpt' }] },
17
+ ],
18
+ db: { provider: 'postgresql', url: 'postgres://localhost/test' },
19
+ }),
20
+ };
21
+ });
22
+ import { program } from 'commander';
23
+ import { parseKywiConfig } from '@kywi/core';
24
+ import '../commands/migrate-generate.js';
25
+ function fileEndingWith(suffix) {
26
+ return writes.find((w) => w.path.endsWith(suffix));
27
+ }
28
+ describe('migrate:generate command', () => {
29
+ beforeEach(() => {
30
+ writes.length = 0;
31
+ vi.clearAllMocks();
32
+ program.exitOverride(); // throw instead of process.exit on any commander error
33
+ });
34
+ it('loads the config and generates schema files via the core generators', async () => {
35
+ await program.parseAsync(['migrate:generate', '--out', 'build/schema'], { from: 'user' });
36
+ // The command loaded the config through the core loader.
37
+ expect(parseKywiConfig).toHaveBeenCalledTimes(1);
38
+ expect(parseKywiConfig.mock.calls[0][0])
39
+ .toMatch(/kywi\.config\.ts$/);
40
+ // One schema file per content type, built by generateContentTypeSchema.
41
+ const blogSchema = fileEndingWith('kywi_ct_blog_post.ts');
42
+ expect(blogSchema).toBeDefined();
43
+ expect(blogSchema.content).toContain("pgTable('kywi_ct_blog_post'");
44
+ expect(blogSchema.content).toContain('excerpt');
45
+ // Barrel index, base re-export, and the drizzle config are all emitted.
46
+ expect(fileEndingWith('schema/index.ts')).toBeDefined();
47
+ expect(fileEndingWith('base.ts')).toBeDefined();
48
+ const drizzle = fileEndingWith('drizzle.config.ts');
49
+ expect(drizzle).toBeDefined();
50
+ expect(drizzle.content.toLowerCase()).toContain('postgres');
51
+ });
52
+ });
53
+ //# sourceMappingURL=migrate-generate.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate-generate.test.js","sourceRoot":"","sources":["../../src/__tests__/migrate-generate.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=migrate-generate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate-generate.d.ts","sourceRoot":"","sources":["../../src/commands/migrate-generate.ts"],"names":[],"mappings":""}
@@ -0,0 +1,64 @@
1
+ import { program } from 'commander';
2
+ import { resolve, join } from 'node:path';
3
+ import { mkdirSync, writeFileSync } from 'node:fs';
4
+ import { parseKywiConfig } from '@kywi-software/core';
5
+ import { generateContentTypeSchema, generateSchemaIndex, generateBaseSchema, generateDrizzleConfig, contentTypeToTableName, } from '@kywi-software/core';
6
+ program
7
+ .command('migrate:generate')
8
+ .description('Generate Drizzle schema files from kywi.config.ts')
9
+ .option('-c, --config <path>', 'Path to kywi.config.ts', 'kywi.config.ts')
10
+ .option('-o, --out <dir>', 'Output directory for generated schema files', 'kywi-generated/schema')
11
+ .action(async (options) => {
12
+ const configPath = resolve(process.cwd(), options.config);
13
+ const outDir = resolve(process.cwd(), options.out);
14
+ const migrationsDir = resolve(process.cwd(), 'kywi-generated/migrations');
15
+ console.log(`Loading config from: ${configPath}`);
16
+ let config;
17
+ try {
18
+ config = await parseKywiConfig(configPath);
19
+ }
20
+ catch (err) {
21
+ console.error(String(err));
22
+ process.exit(1);
23
+ }
24
+ // Ensure output directories exist
25
+ mkdirSync(outDir, { recursive: true });
26
+ mkdirSync(migrationsDir, { recursive: true });
27
+ const { contentTypes, db } = config;
28
+ if (contentTypes.length === 0) {
29
+ console.log('No custom content types found — only base schema will be used.');
30
+ }
31
+ // Write one file per content type
32
+ for (const ct of contentTypes) {
33
+ const tableName = contentTypeToTableName(ct.name);
34
+ const fileName = `${tableName}.ts`;
35
+ const filePath = join(outDir, fileName);
36
+ const code = generateContentTypeSchema(ct);
37
+ writeFileSync(filePath, code, 'utf-8');
38
+ console.log(` ✓ ${fileName}`);
39
+ }
40
+ // Write the self-contained base re-export that the barrel index references
41
+ // as '../base.js'. Emitting it here means the generated output no longer
42
+ // depends on a hand-written shim to resolve the core schema.
43
+ const basePath = resolve(outDir, '..', 'base.ts');
44
+ writeFileSync(basePath, generateBaseSchema(), 'utf-8');
45
+ console.log(` ✓ base.ts`);
46
+ // Write the barrel index
47
+ const indexPath = join(outDir, 'index.ts');
48
+ writeFileSync(indexPath, generateSchemaIndex(contentTypes), 'utf-8');
49
+ console.log(` ✓ index.ts`);
50
+ // Write the drizzle.config.ts at the project root
51
+ const drizzleConfigPath = resolve(process.cwd(), 'drizzle.config.ts');
52
+ const schemaRelative = `./${join('kywi-generated/schema', 'index.ts')}`;
53
+ const migrationsRelative = './kywi-generated/migrations';
54
+ const drizzleConfig = generateDrizzleConfig({
55
+ dbProvider: db.provider,
56
+ schemaRelativePath: schemaRelative,
57
+ migrationsRelativePath: migrationsRelative,
58
+ });
59
+ writeFileSync(drizzleConfigPath, drizzleConfig, 'utf-8');
60
+ console.log(` ✓ drizzle.config.ts (project root)`);
61
+ console.log(`\nGenerated ${contentTypes.length} content type schema(s) to ${options.out}/`);
62
+ console.log(`Run 'kywi migrate' to apply the schema to your database.`);
63
+ });
64
+ //# sourceMappingURL=migrate-generate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate-generate.js","sourceRoot":"","sources":["../../src/commands/migrate-generate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=migrate-status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate-status.d.ts","sourceRoot":"","sources":["../../src/commands/migrate-status.ts"],"names":[],"mappings":""}
@@ -0,0 +1,22 @@
1
+ import { program } from 'commander';
2
+ import { resolve } from 'node:path';
3
+ import { existsSync } from 'node:fs';
4
+ import { spawnSync } from 'node:child_process';
5
+ program
6
+ .command('migrate:status')
7
+ .description('Show pending Drizzle migrations')
8
+ .action(() => {
9
+ const drizzleConfigPath = resolve(process.cwd(), 'drizzle.config.ts');
10
+ if (!existsSync(drizzleConfigPath)) {
11
+ console.error(`drizzle.config.ts not found at ${drizzleConfigPath}`);
12
+ console.error("Run 'kywi migrate:generate' first.");
13
+ process.exit(1);
14
+ }
15
+ const drizzleKitBin = resolve(process.cwd(), 'node_modules', '.bin', 'drizzle-kit');
16
+ const result = spawnSync(drizzleKitBin, ['status'], {
17
+ stdio: 'inherit',
18
+ env: process.env,
19
+ });
20
+ process.exit(result.status ?? 0);
21
+ });
22
+ //# sourceMappingURL=migrate-status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate-status.js","sourceRoot":"","sources":["../../src/commands/migrate-status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAE9C,OAAO;KACJ,OAAO,CAAC,gBAAgB,CAAC;KACzB,WAAW,CAAC,iCAAiC,CAAC;KAC9C,MAAM,CAAC,GAAG,EAAE;IACX,MAAM,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,mBAAmB,CAAC,CAAA;IAErE,IAAI,CAAC,UAAU,CAAC,iBAAiB,CAAC,EAAE,CAAC;QACnC,OAAO,CAAC,KAAK,CAAC,kCAAkC,iBAAiB,EAAE,CAAC,CAAA;QACpE,OAAO,CAAC,KAAK,CAAC,oCAAoC,CAAC,CAAA;QACnD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACjB,CAAC;IAED,MAAM,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,cAAc,EAAE,MAAM,EAAE,aAAa,CAAC,CAAA;IACnF,MAAM,MAAM,GAAG,SAAS,CAAC,aAAa,EAAE,CAAC,QAAQ,CAAC,EAAE;QAClD,KAAK,EAAE,SAAS;QAChB,GAAG,EAAE,OAAO,CAAC,GAAG;KACjB,CAAC,CAAA;IAEF,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC,CAAA;AAClC,CAAC,CAAC,CAAA"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=migrate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../src/commands/migrate.ts"],"names":[],"mappings":""}
@@ -0,0 +1,72 @@
1
+ import { program } from 'commander';
2
+ import { resolve } from 'node:path';
3
+ import { existsSync } from 'node:fs';
4
+ import { spawnSync } from 'node:child_process';
5
+ program
6
+ .command('migrate')
7
+ .description("Apply the generated Drizzle schema to the database. Runs 'migrate:generate' first, then drizzle-kit migrate.")
8
+ .option('-c, --config <path>', 'Path to kywi.config.ts', 'kywi.config.ts')
9
+ .option('--push', 'Use drizzle-kit push instead of migrate (for prototyping — no migration files)', false)
10
+ .action(async (options) => {
11
+ const drizzleConfigPath = resolve(process.cwd(), 'drizzle.config.ts');
12
+ // Step 1: regenerate schema files.
13
+ // Propagate this process's own execArgv (notably `--import tsx`, set by the
14
+ // bin) so the child keeps the tsx loader — without it the child runs plain
15
+ // `node dist/index.js` and crashes the moment migrate:generate dynamically
16
+ // imports the user's TypeScript kywi.config.ts.
17
+ console.log("Running 'kywi migrate:generate'...");
18
+ const generateResult = spawnSync(process.argv[0], [...process.execArgv, process.argv[1], 'migrate:generate', '--config', options.config], { stdio: 'inherit', env: process.env });
19
+ if (generateResult.status !== 0) {
20
+ process.exit(generateResult.status ?? 1);
21
+ }
22
+ if (!existsSync(drizzleConfigPath)) {
23
+ console.error(`drizzle.config.ts not found at ${drizzleConfigPath}`);
24
+ console.error("Run 'kywi migrate:generate' first.");
25
+ process.exit(1);
26
+ }
27
+ // Pre-flight the database connection so a missing DB / wrong DATABASE_URL /
28
+ // stopped server surfaces as one actionable line here, instead of drizzle-kit
29
+ // dumping a raw pg 3D000 stack (F1a-J01-G2).
30
+ try {
31
+ const configPath = resolve(process.cwd(), options.config);
32
+ const config = (await import(configPath)).default;
33
+ const { createDb, resolveDatabaseUrl, explainDbError } = await import('@kywi-software/core');
34
+ const { sql } = await import('drizzle-orm');
35
+ let dbUrl;
36
+ try {
37
+ dbUrl = resolveDatabaseUrl(config.db?.url, 'postgres://localhost:5432/kywi_dev').url;
38
+ }
39
+ catch (err) {
40
+ console.error(`\n✖ ${err.message}\n`);
41
+ process.exit(1);
42
+ }
43
+ const db = createDb(dbUrl, config.db?.provider);
44
+ try {
45
+ await db.execute(sql `select 1`);
46
+ }
47
+ catch (err) {
48
+ const friendly = explainDbError(err, dbUrl);
49
+ if (friendly) {
50
+ console.error(`\n✖ ${friendly}\n`);
51
+ process.exit(1);
52
+ }
53
+ throw err;
54
+ }
55
+ }
56
+ catch (err) {
57
+ // Only connection problems are converted above; anything else is a real
58
+ // bug — surface it rather than swallow.
59
+ if (err?.userFacing)
60
+ throw err;
61
+ }
62
+ // Step 2: invoke drizzle-kit
63
+ const drizzleKitBin = resolve(process.cwd(), 'node_modules', '.bin', 'drizzle-kit');
64
+ const subCommand = options.push ? 'push' : 'migrate';
65
+ console.log(`\nRunning drizzle-kit ${subCommand}...`);
66
+ const result = spawnSync(drizzleKitBin, [subCommand], {
67
+ stdio: 'inherit',
68
+ env: process.env,
69
+ });
70
+ process.exit(result.status ?? 0);
71
+ });
72
+ //# sourceMappingURL=migrate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate.js","sourceRoot":"","sources":["../../src/commands/migrate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=seed-demo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seed-demo.d.ts","sourceRoot":"","sources":["../../src/commands/seed-demo.ts"],"names":[],"mappings":""}