create-trokky 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +138 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +95 -0
- package/dist/config.d.ts +24 -0
- package/dist/config.js +29 -0
- package/dist/emit.d.ts +21 -0
- package/dist/emit.js +489 -0
- package/dist/files-node.d.ts +8 -0
- package/dist/files-node.js +37 -0
- package/dist/files.d.ts +25 -0
- package/dist/files.js +48 -0
- package/dist/generate.d.ts +9 -0
- package/dist/generate.js +131 -0
- package/dist/generated/files-binary.d.ts +7 -0
- package/dist/generated/files-binary.js +7 -0
- package/dist/generated/files-text.d.ts +6 -0
- package/dist/generated/files-text.js +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/manifest.d.ts +89 -0
- package/dist/manifest.js +24 -0
- package/dist/node.d.ts +6 -0
- package/dist/node.js +6 -0
- package/dist/validate.d.ts +51 -0
- package/dist/validate.js +97 -0
- package/dist/write.d.ts +5 -0
- package/dist/write.js +15 -0
- package/files/base/gitignore +7 -0
- package/files/content/magazine/public/seed/README.txt +4 -0
- package/files/content/magazine/public/seed/annual-report-2026.pdf +0 -0
- package/files/content/magazine/public/seed/avatar-1.jpg +0 -0
- package/files/content/magazine/public/seed/avatar-2.jpg +0 -0
- package/files/content/magazine/public/seed/avatar-3.jpg +0 -0
- package/files/content/magazine/public/seed/cover-1.jpg +0 -0
- package/files/content/magazine/public/seed/cover-2.jpg +0 -0
- package/files/content/magazine/public/seed/cover-3.jpg +0 -0
- package/files/content/magazine/public/seed/cover-4.jpg +0 -0
- package/files/content/magazine/public/seed/cover-5.jpg +0 -0
- package/files/content/magazine/public/seed/cover-6.jpg +0 -0
- package/files/content/magazine/public/seed/editorial-guidelines.pdf +0 -0
- package/files/content/magazine/src/trokky/schemas.ts +116 -0
- package/files/content/magazine/src/trokky/seed.ts +143 -0
- package/files/content/magazine/src/trokky/structure.ts +19 -0
- package/files/site/magazine/src/layouts/Base.astro +55 -0
- package/files/site/magazine/src/pages/[slug].astro +11 -0
- package/files/site/magazine/src/pages/articles/[slug].astro +30 -0
- package/files/site/magazine/src/pages/articles/index.astro +21 -0
- package/files/site/magazine/src/pages/authors/[slug].astro +19 -0
- package/files/site/magazine/src/pages/categories/[slug].astro +19 -0
- package/files/site/magazine/src/pages/documents.astro +19 -0
- package/files/site/magazine/src/pages/index.astro +35 -0
- package/files/site/magazine/src/trokky/site.ts +81 -0
- package/files/workers/scripts/copy-studio.mjs +28 -0
- package/manifest.json +330 -0
- package/package.json +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Trokky
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# create-trokky
|
|
2
|
+
|
|
3
|
+
Compose a [Trokky](https://trokky.dev) project: pick where it runs, where content lives, and how
|
|
4
|
+
much of the stack you want.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
npm create trokky@latest
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
Not on npm yet? It runs straight from the repo, no install:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npx github:Trokky/generator my-site
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
Where will it run?
|
|
18
|
+
1) Cloudflare Workers — No server, no disk. D1 + R2.
|
|
19
|
+
2) Node / Express — A server you run: VPS, Docker, Railway.
|
|
20
|
+
|
|
21
|
+
What do you want?
|
|
22
|
+
1) API only — Headless. Bring your own frontend.
|
|
23
|
+
2) API + Studio — The admin UI, for editors.
|
|
24
|
+
3) API + Studio + a site — An Astro frontend as well.
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Non-interactive, for scripts and CI:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm create trokky@latest my-site -- --yes --target=workers --parts=full-site --content=magazine
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## What it will not let you build
|
|
34
|
+
|
|
35
|
+
The generator refuses impossible combinations and says why, instead of handing you a project that
|
|
36
|
+
compiles and dies on startup:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
$ npm create trokky@latest x -- --yes --target=workers --images=sharp
|
|
40
|
+
That combination cannot be built:
|
|
41
|
+
images: "sharp" is not available here.
|
|
42
|
+
sharp is a native libvips binding and Workers runs no native code.
|
|
43
|
+
Try: cloudflare-images, none
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Every such rule lives in [`manifest.json`](./manifest.json) as data — axes, options, constraints,
|
|
47
|
+
each with the reason it exists. **Nothing reimplements them.** `npm create trokky@latest`, the
|
|
48
|
+
trokky.build configurator and `trokky new` in the Go CLI all read that one file, so they cannot
|
|
49
|
+
drift apart.
|
|
50
|
+
|
|
51
|
+
## The same site, either runtime
|
|
52
|
+
|
|
53
|
+
`--parts=full-site` gives you an Astro frontend on **both** targets, and the pages are
|
|
54
|
+
byte-identical between them:
|
|
55
|
+
|
|
56
|
+
| | Workers | Node |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| Astro adapter | `@astrojs/cloudflare` | `@astrojs/node` in middleware mode, mounted on Express |
|
|
59
|
+
| How a page reaches Trokky | bindings from `cloudflare:workers` | the core, passed in as `Astro.locals` |
|
|
60
|
+
| Sample content appears | after the claim, in `waitUntil` | at boot, when the store is empty |
|
|
61
|
+
| Thumbnails | Cloudflare Images at upload | sharp at upload |
|
|
62
|
+
|
|
63
|
+
Only `src/trokky/page.ts` and `astro.config.mjs` differ. Every page, every layout and the query
|
|
64
|
+
helper are the same file — and on both runtimes a page reads content **in-process**, with no API
|
|
65
|
+
token and no HTTP round trip.
|
|
66
|
+
|
|
67
|
+
## Adding a content model
|
|
68
|
+
|
|
69
|
+
A content model is two directories and a manifest entry. Nothing in the generator learns its
|
|
70
|
+
name.
|
|
71
|
+
|
|
72
|
+
1. `files/content/<id>/` — `src/trokky/schemas.ts`, `structure.ts`, `seed.ts`, and any
|
|
73
|
+
`public/seed/` assets the seed uploads.
|
|
74
|
+
2. `files/site/<id>/` — the Astro pages, layouts and `src/trokky/site.ts` that render it.
|
|
75
|
+
**Optional**: leave it out and the model is headless-only, and the configurator will not offer
|
|
76
|
+
it a frontend. Pages are written against particular collections, which is why a site belongs
|
|
77
|
+
to its model rather than being shared.
|
|
78
|
+
3. `manifest.json` — add the option under the `content` axis. If it has a site, add its id to the
|
|
79
|
+
`content-needs-schemas` constraint's `allow` list.
|
|
80
|
+
|
|
81
|
+
`test/extensibility.test.ts` holds that contract: it fails if a model has pages the configurator
|
|
82
|
+
will not offer, or is offered a frontend it has no pages for.
|
|
83
|
+
|
|
84
|
+
## Using it as a library
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import { generate, validate, optionsFor, withDefaults } from 'create-trokky'
|
|
88
|
+
|
|
89
|
+
optionsFor('data', { target: 'workers' }) // ['cloudflare-d1']
|
|
90
|
+
validate({ target: 'workers', images: 'sharp' }) // { valid: false, problems: [...] }
|
|
91
|
+
|
|
92
|
+
const files = generate(withDefaults({ name: 'my-site', target: 'workers' }))
|
|
93
|
+
// Map<path, string | Uint8Array> — pure, nothing touches the disk
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`generate` is pure, and it takes its template files from a source you hand it — so the same
|
|
97
|
+
function serves a terminal, a browser and an API install:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
import { generate, inlineFileSource } from 'create-trokky'
|
|
101
|
+
import { text } from 'create-trokky/generated/files-text'
|
|
102
|
+
|
|
103
|
+
// No filesystem: this runs in a browser or a Worker.
|
|
104
|
+
const files = generate(config, inlineFileSource({ text, binary: {} }))
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`create-trokky/node` adds `fsFileSource()` and `writeTree()`, which the CLI uses. Nothing in the
|
|
108
|
+
main export touches `node:fs`, so a browser bundle never pulls it in.
|
|
109
|
+
|
|
110
|
+
## Releasing
|
|
111
|
+
|
|
112
|
+
Tag a version and the workflow publishes it, authenticating through npm **trusted publishing**:
|
|
113
|
+
GitHub Actions mints a short-lived OIDC token and npm exchanges it for publish rights, so no
|
|
114
|
+
long-lived token lives in this repo. npm attaches a provenance attestation automatically.
|
|
115
|
+
|
|
116
|
+
Requires npm CLI 11.5.1 or later, which is why the workflow upgrades npm before publishing —
|
|
117
|
+
Node 22 ships npm 10, and without the upgrade npm quietly falls back to looking for a token. Before it does, it installs the packed tarball into
|
|
118
|
+
an empty project and generates from *that* — because `files/` is most of this package, and npm's
|
|
119
|
+
own ignore rules can quietly drop parts of it. Both failures that shipped this way looked perfect
|
|
120
|
+
in the source tree: `.gitignore` is never packaged, and an empty directory survives neither git
|
|
121
|
+
nor npm.
|
|
122
|
+
|
|
123
|
+
## Development
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
npm install
|
|
127
|
+
npm test # the rules and the shape of generated trees
|
|
128
|
+
npm run smoke # generate → install → build → boot → claim, per composition
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
`npm test` proves a generated tree is coherent. **`npm run smoke` proves it runs**: it generates,
|
|
132
|
+
installs, builds, boots, claims the instance the way a first visitor would, checks the claim
|
|
133
|
+
cannot happen twice, and fetches a thumbnail — the far end of the whole media pipeline.
|
|
134
|
+
|
|
135
|
+
It has earned every step of that. It caught a Node project that typechecked, generated cleanly and died
|
|
136
|
+
on startup because nothing imported its adapters; two type errors in the emitted server; a
|
|
137
|
+
frontend that could not build on Node because a shared file imported a Workers-only one; and a
|
|
138
|
+
Node config with no image variants, where every page looked fine and every thumbnail was a 404.
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `npm create trokky@latest` — compose a project and write it out.
|
|
4
|
+
*
|
|
5
|
+
* Every rule it applies comes from the manifest, so this file knows nothing about what can go
|
|
6
|
+
* with what. It asks, it validates, it writes.
|
|
7
|
+
*/
|
|
8
|
+
import { createInterface } from 'node:readline/promises';
|
|
9
|
+
import { stdin, stdout, argv, exit } from 'node:process';
|
|
10
|
+
import { randomBytes } from 'node:crypto';
|
|
11
|
+
import { resolve } from 'node:path';
|
|
12
|
+
import { manifest, secretsFor } from './manifest.js';
|
|
13
|
+
import { withDefaults } from './config.js';
|
|
14
|
+
import { validate, optionsFor, describe } from './validate.js';
|
|
15
|
+
import { generate } from './generate.js';
|
|
16
|
+
import { fsFileSource } from './files-node.js';
|
|
17
|
+
import { writeTree } from './write.js';
|
|
18
|
+
function parseFlags(args) {
|
|
19
|
+
const flags = {};
|
|
20
|
+
for (const arg of args) {
|
|
21
|
+
const match = /^--([a-z-]+)(?:=(.*))?$/.exec(arg);
|
|
22
|
+
if (match)
|
|
23
|
+
flags[match[1]] = match[2] ?? 'true';
|
|
24
|
+
else if (!arg.startsWith('-'))
|
|
25
|
+
flags.name ??= arg;
|
|
26
|
+
}
|
|
27
|
+
return flags;
|
|
28
|
+
}
|
|
29
|
+
async function main() {
|
|
30
|
+
const flags = parseFlags(argv.slice(2));
|
|
31
|
+
const interactive = flags.yes !== 'true' && stdin.isTTY;
|
|
32
|
+
const rl = interactive ? createInterface({ input: stdin, output: stdout }) : null;
|
|
33
|
+
const ask = async (question, options, fallback) => {
|
|
34
|
+
if (!rl)
|
|
35
|
+
return fallback;
|
|
36
|
+
const axisOptions = manifest.axes.find(a => options.includes(a.options[0]?.id));
|
|
37
|
+
console.log(`\n${question}`);
|
|
38
|
+
options.forEach((id, i) => {
|
|
39
|
+
const option = manifest.axes.flatMap(a => a.options).find(o => o.id === id);
|
|
40
|
+
console.log(` ${i + 1}) ${option?.title ?? id}${option?.hint ? ` — ${option.hint}` : ''}`);
|
|
41
|
+
});
|
|
42
|
+
void axisOptions;
|
|
43
|
+
const answer = (await rl.question(`> [${options.indexOf(fallback) + 1}] `)).trim();
|
|
44
|
+
if (!answer)
|
|
45
|
+
return fallback;
|
|
46
|
+
const index = Number(answer) - 1;
|
|
47
|
+
return options[index] ?? (options.includes(answer) ? answer : fallback);
|
|
48
|
+
};
|
|
49
|
+
const name = flags.name ?? (rl ? (await rl.question('\nProject name > [my-trokky-site] ')).trim() || 'my-trokky-site' : 'my-trokky-site');
|
|
50
|
+
const target = (flags.target ?? (await ask('Where will it run?', ['workers', 'node'], 'workers')));
|
|
51
|
+
const chosen = { target };
|
|
52
|
+
for (const axisId of ['parts', 'content', 'data', 'media', 'images']) {
|
|
53
|
+
const available = optionsFor(axisId, chosen);
|
|
54
|
+
const preset = flags[axisId];
|
|
55
|
+
if (preset) {
|
|
56
|
+
chosen[axisId] = preset;
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const fallback = manifest.defaults[target]?.[axisId] ?? available[0];
|
|
60
|
+
// One option left is not a question; say what it is and move on.
|
|
61
|
+
chosen[axisId] = available.length <= 1
|
|
62
|
+
? available[0] ?? fallback
|
|
63
|
+
: await ask(manifest.axes.find(a => a.id === axisId).title, available, available.includes(fallback) ? fallback : available[0]);
|
|
64
|
+
}
|
|
65
|
+
await rl?.close();
|
|
66
|
+
const config = withDefaults({ name, ...chosen });
|
|
67
|
+
const result = validate(config);
|
|
68
|
+
if (!result.valid) {
|
|
69
|
+
console.error(`\nThat combination cannot be built:\n${describe(result.problems)}`);
|
|
70
|
+
exit(1);
|
|
71
|
+
}
|
|
72
|
+
const target_dir = resolve(flags.out ?? name);
|
|
73
|
+
writeTree(generate(config, fsFileSource()), target_dir);
|
|
74
|
+
const secrets = secretsFor(config);
|
|
75
|
+
console.log(`\nCreated ${config.name} in ${target_dir}\n`);
|
|
76
|
+
console.log(` ${config.parts === 'api' ? 'API' : config.parts === 'studio' ? 'API + Studio' : 'API + Studio + site'} · ${config.data} · ${config.media} · thumbnails: ${config.images}\n`);
|
|
77
|
+
console.log('Next:');
|
|
78
|
+
console.log(` cd ${config.name}`);
|
|
79
|
+
console.log(` cp ${config.target === 'workers' ? '.dev.vars.example .dev.vars' : '.env.example .env'}`);
|
|
80
|
+
for (const secret of secrets) {
|
|
81
|
+
// Only the generated ones get a value here; an endpoint or a bucket name is yours to fill in.
|
|
82
|
+
if (secret.generate) {
|
|
83
|
+
console.log(` # ${secret.name}=${randomBytes(secret.generate === 'hex32' ? 32 : 24).toString('hex')}`);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
console.log(` # ${secret.name}= (${secret.optional ? 'optional — ' : ''}${secret.why})`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
console.log(' npm install');
|
|
90
|
+
console.log(config.target === 'workers' ? ' npm run build && npm run preview' : ' npm run dev');
|
|
91
|
+
}
|
|
92
|
+
main().catch(error => {
|
|
93
|
+
console.error(error instanceof Error ? error.message : error);
|
|
94
|
+
exit(1);
|
|
95
|
+
});
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A composition, and the defaults that fill in what the caller did not say.
|
|
3
|
+
*/
|
|
4
|
+
export interface ProjectConfig {
|
|
5
|
+
/** Directory and package name; also the Worker name on Cloudflare. */
|
|
6
|
+
name: string;
|
|
7
|
+
target: 'workers' | 'node';
|
|
8
|
+
data: 'filesystem-data' | 'postgres-data' | 'cloudflare-d1';
|
|
9
|
+
media: 'filesystem-media' | 'cloudflare-r2' | 's3-media';
|
|
10
|
+
images: 'sharp' | 'cloudflare-images' | 'none';
|
|
11
|
+
parts: 'api' | 'studio' | 'full-site';
|
|
12
|
+
/** Open on purpose: a content model is a pair of directories, not a value this file knows. */
|
|
13
|
+
content: string;
|
|
14
|
+
/** Version range written into the generated package.json for @trokky/* . */
|
|
15
|
+
trokkyVersion: string;
|
|
16
|
+
}
|
|
17
|
+
export type PartialConfig = Partial<ProjectConfig> & {
|
|
18
|
+
name: string;
|
|
19
|
+
target: ProjectConfig['target'];
|
|
20
|
+
};
|
|
21
|
+
/** Fill a partial choice from the manifest's per-target defaults. */
|
|
22
|
+
export declare function withDefaults(input: PartialConfig): ProjectConfig;
|
|
23
|
+
export declare const hasStudio: (config: ProjectConfig) => boolean;
|
|
24
|
+
export declare const hasFrontend: (config: ProjectConfig) => boolean;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A composition, and the defaults that fill in what the caller did not say.
|
|
3
|
+
*/
|
|
4
|
+
import { manifest } from './manifest.js';
|
|
5
|
+
/** Fill a partial choice from the manifest's per-target defaults. */
|
|
6
|
+
export function withDefaults(input) {
|
|
7
|
+
const defaults = manifest.defaults[input.target] ?? {};
|
|
8
|
+
const pick = (key) => {
|
|
9
|
+
const chosen = input[key];
|
|
10
|
+
if (typeof chosen === 'string')
|
|
11
|
+
return chosen;
|
|
12
|
+
const fallback = defaults[key];
|
|
13
|
+
if (!fallback)
|
|
14
|
+
throw new Error(`No default for "${key}" on target "${input.target}"`);
|
|
15
|
+
return fallback;
|
|
16
|
+
};
|
|
17
|
+
return {
|
|
18
|
+
name: input.name,
|
|
19
|
+
target: input.target,
|
|
20
|
+
data: pick('data'),
|
|
21
|
+
media: pick('media'),
|
|
22
|
+
images: pick('images'),
|
|
23
|
+
parts: pick('parts'),
|
|
24
|
+
content: pick('content'),
|
|
25
|
+
trokkyVersion: input.trokkyVersion ?? '^3.4.1',
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export const hasStudio = (config) => config.parts !== 'api';
|
|
29
|
+
export const hasFrontend = (config) => config.parts === 'full-site';
|
package/dist/emit.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The files a composition produces that are not simply copied: anything whose *contents* depend
|
|
3
|
+
* on the choices. Copied files live under `files/`; computed ones are built here.
|
|
4
|
+
*/
|
|
5
|
+
import type { ProjectConfig } from './config.js';
|
|
6
|
+
export declare function packageJson(config: ProjectConfig): string;
|
|
7
|
+
/** Astro renders on request on both targets; only the adapter differs. */
|
|
8
|
+
export declare function astroConfig(config: ProjectConfig): string;
|
|
9
|
+
/**
|
|
10
|
+
* How a page gets hold of Trokky. The one file that cannot be shared between targets: a Worker
|
|
11
|
+
* takes its bindings from `cloudflare:workers`, a server is handed the core through Astro locals.
|
|
12
|
+
*/
|
|
13
|
+
export declare function pageHelper(config: ProjectConfig): string;
|
|
14
|
+
export declare function wranglerConfig(config: ProjectConfig): string;
|
|
15
|
+
/** The Worker's Trokky wiring: which adapters are imported is the composition, literally. */
|
|
16
|
+
export declare function workersCore(config: ProjectConfig): string;
|
|
17
|
+
export declare function workersEntry(config: ProjectConfig): string;
|
|
18
|
+
export declare function nodeEntry(config: ProjectConfig): string;
|
|
19
|
+
export declare function trokkyConfig(config: ProjectConfig): string;
|
|
20
|
+
export declare function envExample(config: ProjectConfig): string;
|
|
21
|
+
export declare function tsconfig(config: ProjectConfig): string;
|