@savvy-web/templates 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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Savvy Web Systems
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,101 @@
1
+ # @savvy-web/templates
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@savvy-web/templates)](https://www.npmjs.com/package/@savvy-web/templates)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ Pure TypeScript templates for Silk Suite project scaffolding. Each template takes typed options (validated with [Effect](https://effect.website/) Schema) and returns `TemplateEntry[]` -- a list of `{ name, filename, content }` records. No filesystem calls; you decide where and how to write the output.
7
+
8
+ ## Features
9
+
10
+ - Generate config files for package.json, tsconfig, Biome, Turborepo, pnpm, changesets, VS Code, gitignore, and README
11
+ - Validate all inputs at runtime with Effect Schema
12
+ - Compose an entire workspace scaffold from a single `createWorkspace` call
13
+ - Zero side effects -- templates return plain strings, never touch disk
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ pnpm add @savvy-web/templates effect
19
+ ```
20
+
21
+ `effect` is a peer dependency.
22
+
23
+ ## Quick Start
24
+
25
+ ```typescript
26
+ import { createPackageJson } from "@savvy-web/templates";
27
+
28
+ const entries = createPackageJson({
29
+ name: "@my-org/my-lib",
30
+ description: "A useful library",
31
+ type: "module",
32
+ license: "MIT",
33
+ exports: { ".": "./src/index.ts" },
34
+ });
35
+
36
+ for (const entry of entries) {
37
+ console.log(entry.filename); // "package.json"
38
+ console.log(entry.content); // sorted, formatted JSON
39
+ }
40
+ ```
41
+
42
+ ## API
43
+
44
+ Every `create*` function accepts an options object (or `unknown` -- Schema validates at runtime) and returns `TemplateEntry[]`.
45
+
46
+ ### TemplateEntry
47
+
48
+ ```typescript
49
+ interface TemplateEntry {
50
+ readonly name: string; // logical name, e.g. "tsconfig"
51
+ readonly filename: string; // suggested path, e.g. "tsconfig.json"
52
+ readonly content: string; // generated file content
53
+ }
54
+ ```
55
+
56
+ ### Templates
57
+
58
+ | Function | Output file | Key options |
59
+ | --- | --- | --- |
60
+ | `createPackageJson` | `package.json` | `name`, `version`, `type`, `exports`, `scripts`, `dependencies`, `engines`, `publishConfig` |
61
+ | `createTsConfig` | `tsconfig.json` | `extends`, `composite`, `include`, `exclude`, `references` |
62
+ | `createBiome` | `biome.jsonc` | `version`, `extends`, `root` |
63
+ | `createTurboRoot` | `turbo.json` | `tasks`, `globalDependencies`, `globalEnv`, `ui`, `concurrency` |
64
+ | `createTurboWorkspace` | `turbo.json` | `tasks` (extends root with `["//"]`) |
65
+ | `createPnpmWorkspace` | `pnpm-workspace.yaml` | `packages`, `autoInstallPeers`, `catalogMode`, `catalog` |
66
+ | `createChangeset` | `.changeset/config.json` | `access`, `baseBranch`, `changelog`, `repo` |
67
+ | `createVsCode` | `.vscode/settings.json`, `.vscode/extensions.json` | `settings` (`biome`, `turbo`, `vitest`), `extensions` |
68
+ | `createGitignore` | `.gitignore` | `sections` (`node`, `build`, `env`, `os`, `silk`), `additional` |
69
+ | `createReadme` | `README.md` | `name`, `description` |
70
+
71
+ ### Workspace Compositor
72
+
73
+ `createWorkspace` composes multiple templates into a full monorepo scaffold:
74
+
75
+ ```typescript
76
+ import { createWorkspace } from "@savvy-web/templates";
77
+
78
+ const entries = createWorkspace({
79
+ name: "my-monorepo",
80
+ packageManager: "pnpm",
81
+ packageManagerVersion: "10.33.0",
82
+ nodeVersion: "24.11.0",
83
+ features: {
84
+ biome: true,
85
+ turbo: true,
86
+ changesets: true,
87
+ vscode: true,
88
+ vitest: true,
89
+ },
90
+ });
91
+
92
+ // entries contains: package.json, tsconfig.json, .gitignore, README.md,
93
+ // pnpm-workspace.yaml, biome.jsonc, turbo.json, .changeset/config.json,
94
+ // .vscode/settings.json, .vscode/extensions.json
95
+ ```
96
+
97
+ Each feature flag is optional and defaults to `false`. The compositor always generates package.json, tsconfig.json, .gitignore, and README.md. pnpm-workspace.yaml is added automatically when `packageManager` is `"pnpm"`.
98
+
99
+ ## License
100
+
101
+ [MIT](./LICENSE)
package/index.d.ts ADDED
@@ -0,0 +1,203 @@
1
+ import { Schema } from 'effect';
2
+
3
+ export declare const BiomeOptions: Schema.Struct<{
4
+ version: typeof Schema.String;
5
+ extends: Schema.optional<Schema.Array$<typeof Schema.String>>;
6
+ root: Schema.optional<typeof Schema.Boolean>;
7
+ }>;
8
+
9
+ export declare type BiomeOptionsType = typeof BiomeOptions.Type;
10
+
11
+ export declare const ChangesetOptions: Schema.Struct<{
12
+ access: Schema.optionalWith<Schema.Literal<["public", "restricted"]>, {
13
+ default: () => "restricted";
14
+ }>;
15
+ baseBranch: Schema.optionalWith<typeof Schema.String, {
16
+ default: () => string;
17
+ }>;
18
+ changelog: Schema.optionalWith<typeof Schema.String, {
19
+ default: () => string;
20
+ }>;
21
+ repo: Schema.optional<Schema.filter<typeof Schema.String>>;
22
+ }>;
23
+
24
+ export declare type ChangesetOptionsType = typeof ChangesetOptions.Type;
25
+
26
+ export declare function createBiome(options: unknown): TemplateEntry[];
27
+
28
+ export declare function createChangeset(options: unknown): TemplateEntry[];
29
+
30
+ export declare function createGitignore(options: unknown): TemplateEntry[];
31
+
32
+ export declare function createPackageJson(options: unknown): TemplateEntry[];
33
+
34
+ export declare function createPnpmWorkspace(options: unknown): TemplateEntry[];
35
+
36
+ export declare function createReadme(options: unknown): TemplateEntry[];
37
+
38
+ export declare function createTsConfig(options: unknown): TemplateEntry[];
39
+
40
+ export declare function createTurboRoot(options: unknown): TemplateEntry[];
41
+
42
+ export declare function createTurboWorkspace(options: unknown): TemplateEntry[];
43
+
44
+ export declare function createVsCode(options: unknown): TemplateEntry[];
45
+
46
+ export declare function createWorkspace(options: unknown): TemplateEntry[];
47
+
48
+ export declare const GitignoreOptions: Schema.Struct<{
49
+ sections: Schema.optional<Schema.Struct<{
50
+ node: Schema.optional<typeof Schema.Boolean>;
51
+ build: Schema.optional<typeof Schema.Boolean>;
52
+ env: Schema.optional<typeof Schema.Boolean>;
53
+ os: Schema.optional<typeof Schema.Boolean>;
54
+ silk: Schema.optional<typeof Schema.Boolean>;
55
+ }>>;
56
+ additional: Schema.optional<Schema.Array$<typeof Schema.String>>;
57
+ }>;
58
+
59
+ export declare type GitignoreOptionsType = typeof GitignoreOptions.Type;
60
+
61
+ export declare const PackageJsonOptions: Schema.Struct<{
62
+ name: typeof Schema.String;
63
+ version: Schema.optionalWith<typeof Schema.String, {
64
+ default: () => string;
65
+ }>;
66
+ private: Schema.optional<typeof Schema.Boolean>;
67
+ description: Schema.optional<typeof Schema.String>;
68
+ homepage: Schema.optional<typeof Schema.String>;
69
+ bugs: Schema.optional<Schema.Struct<{
70
+ url: typeof Schema.String;
71
+ }>>;
72
+ repository: Schema.optional<Schema.Struct<{
73
+ type: typeof Schema.String;
74
+ url: typeof Schema.String;
75
+ directory: Schema.optional<typeof Schema.String>;
76
+ }>>;
77
+ license: Schema.optional<typeof Schema.String>;
78
+ author: Schema.optional<Schema.Struct<{
79
+ name: typeof Schema.String;
80
+ email: Schema.optional<typeof Schema.String>;
81
+ url: Schema.optional<typeof Schema.String>;
82
+ }>>;
83
+ sideEffects: Schema.optional<typeof Schema.Boolean>;
84
+ type: Schema.optional<Schema.Literal<["module", "commonjs"]>>;
85
+ exports: Schema.optional<typeof Schema.Unknown>;
86
+ scripts: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.String>>;
87
+ dependencies: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.String>>;
88
+ devDependencies: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.String>>;
89
+ peerDependencies: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.String>>;
90
+ engines: Schema.optional<Schema.Struct<{
91
+ node: Schema.optional<typeof Schema.String>;
92
+ pnpm: Schema.optional<typeof Schema.String>;
93
+ }>>;
94
+ packageManager: Schema.optional<typeof Schema.String>;
95
+ devEngines: Schema.optional<typeof Schema.Unknown>;
96
+ publishConfig: Schema.optional<Schema.Struct<{
97
+ access: Schema.optional<typeof Schema.String>;
98
+ directory: Schema.optional<typeof Schema.String>;
99
+ linkDirectory: Schema.optional<typeof Schema.Boolean>;
100
+ targets: Schema.optional<typeof Schema.Unknown>;
101
+ }>>;
102
+ keywords: Schema.optional<Schema.Array$<typeof Schema.String>>;
103
+ }>;
104
+
105
+ export declare type PackageJsonOptionsType = typeof PackageJsonOptions.Type;
106
+
107
+ export declare const PnpmWorkspaceOptions: Schema.Struct<{
108
+ packages: Schema.Array$<typeof Schema.String>;
109
+ autoInstallPeers: Schema.optional<typeof Schema.Boolean>;
110
+ catalogMode: Schema.optional<Schema.Literal<["strict", "prefer", "manual"]>>;
111
+ catalog: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.String>>;
112
+ }>;
113
+
114
+ export declare type PnpmWorkspaceOptionsType = typeof PnpmWorkspaceOptions.Type;
115
+
116
+ export declare const ReadmeOptions: Schema.Struct<{
117
+ name: typeof Schema.String;
118
+ description: Schema.optional<typeof Schema.String>;
119
+ }>;
120
+
121
+ export declare type ReadmeOptionsType = typeof ReadmeOptions.Type;
122
+
123
+ /** A template: typed options in, content entries out. */
124
+ export declare type Template<O> = (options: O) => TemplateEntry[];
125
+
126
+ /**
127
+ * A generated content entry from a template.
128
+ *
129
+ * Templates produce content with a logical name and suggested filename.
130
+ * The consumer decides where (and whether) to write the content.
131
+ */
132
+ export declare interface TemplateEntry {
133
+ /** Logical name for this entry (e.g., "tsconfig", "biome", "vscode-settings") */
134
+ readonly name: string;
135
+ /** Suggested default filename (e.g., "tsconfig.json", ".vscode/settings.json") */
136
+ readonly filename: string;
137
+ /** The generated file content */
138
+ readonly content: string;
139
+ }
140
+
141
+ export declare const TsConfigOptions: Schema.Struct<{
142
+ extends: Schema.optional<Schema.Union<[typeof Schema.String, Schema.Array$<typeof Schema.String>]>>;
143
+ composite: Schema.optional<typeof Schema.Boolean>;
144
+ include: Schema.optional<Schema.Array$<typeof Schema.String>>;
145
+ exclude: Schema.optional<Schema.Array$<typeof Schema.String>>;
146
+ references: Schema.optional<Schema.Array$<Schema.Struct<{
147
+ path: typeof Schema.String;
148
+ }>>>;
149
+ }>;
150
+
151
+ export declare type TsConfigOptionsType = typeof TsConfigOptions.Type;
152
+
153
+ export declare const TurboRootOptions: Schema.Struct<{
154
+ tasks: Schema.Record$<typeof Schema.String, typeof Schema.Unknown>;
155
+ globalDependencies: Schema.optional<Schema.Array$<typeof Schema.String>>;
156
+ globalEnv: Schema.optional<Schema.Array$<typeof Schema.String>>;
157
+ globalPassThroughEnv: Schema.optional<Schema.Array$<typeof Schema.String>>;
158
+ ui: Schema.optional<Schema.Literal<["tui", "stream"]>>;
159
+ concurrency: Schema.optional<Schema.Union<[typeof Schema.String, typeof Schema.Number]>>;
160
+ }>;
161
+
162
+ export declare type TurboRootOptionsType = typeof TurboRootOptions.Type;
163
+
164
+ export declare const TurboWorkspaceOptions: Schema.Struct<{
165
+ tasks: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.Unknown>>;
166
+ }>;
167
+
168
+ export declare type TurboWorkspaceOptionsType = typeof TurboWorkspaceOptions.Type;
169
+
170
+ /** An update template: existing content + partial options in, content entries out. */
171
+ export declare type UpdateTemplate<O> = (existing: string, options: Partial<O>) => TemplateEntry[];
172
+
173
+ export declare const VsCodeOptions: Schema.Struct<{
174
+ settings: Schema.optional<Schema.Struct<{
175
+ biome: Schema.optional<typeof Schema.Boolean>;
176
+ turbo: Schema.optional<typeof Schema.Boolean>;
177
+ vitest: Schema.optional<typeof Schema.Boolean>;
178
+ }>>;
179
+ extensions: Schema.optional<Schema.Array$<typeof Schema.String>>;
180
+ }>;
181
+
182
+ export declare type VsCodeOptionsType = typeof VsCodeOptions.Type;
183
+
184
+ export declare const WorkspaceOptions: Schema.Struct<{
185
+ name: typeof Schema.String;
186
+ packageManager: Schema.Literal<["pnpm", "npm", "bun"]>;
187
+ packageManagerVersion: typeof Schema.String;
188
+ nodeVersion: typeof Schema.String;
189
+ biomeVersion: Schema.optionalWith<typeof Schema.String, {
190
+ default: () => string;
191
+ }>;
192
+ features: Schema.optional<Schema.Struct<{
193
+ biome: Schema.optional<typeof Schema.Boolean>;
194
+ vitest: Schema.optional<typeof Schema.Boolean>;
195
+ turbo: Schema.optional<typeof Schema.Boolean>;
196
+ changesets: Schema.optional<typeof Schema.Boolean>;
197
+ vscode: Schema.optional<typeof Schema.Boolean>;
198
+ }>>;
199
+ }>;
200
+
201
+ export declare type WorkspaceOptionsType = typeof WorkspaceOptions.Type;
202
+
203
+ export { }
package/index.js ADDED
@@ -0,0 +1,491 @@
1
+ import { Schema } from "effect";
2
+ import { sortPackageJson } from "sort-package-json";
3
+ import js_yaml from "js-yaml";
4
+ const BiomeOptions = Schema.Struct({
5
+ version: Schema.String,
6
+ extends: Schema.optional(Schema.Array(Schema.String)),
7
+ root: Schema.optional(Schema.Boolean)
8
+ });
9
+ function createBiome(options) {
10
+ const opts = Schema.decodeUnknownSync(BiomeOptions)(options);
11
+ const config = {
12
+ $schema: `https://biomejs.dev/schemas/${opts.version}/schema.json`
13
+ };
14
+ if (opts.extends && opts.extends.length > 0) config.extends = opts.extends;
15
+ if (void 0 !== opts.root) config.root = opts.root;
16
+ const content = JSON.stringify(config, null, "\t");
17
+ return [
18
+ {
19
+ name: "biome",
20
+ filename: "biome.jsonc",
21
+ content
22
+ }
23
+ ];
24
+ }
25
+ const RepoPattern = Schema.String.pipe(Schema.pattern(/^[^/\s]+\/[^/\s]+$/));
26
+ const ChangesetOptions = Schema.Struct({
27
+ access: Schema.optionalWith(Schema.Literal("public", "restricted"), {
28
+ default: ()=>"restricted"
29
+ }),
30
+ baseBranch: Schema.optionalWith(Schema.String, {
31
+ default: ()=>"main"
32
+ }),
33
+ changelog: Schema.optionalWith(Schema.String, {
34
+ default: ()=>"@savvy-web/changesets/changelog"
35
+ }),
36
+ repo: Schema.optional(RepoPattern)
37
+ });
38
+ function createChangeset(options) {
39
+ const opts = Schema.decodeUnknownSync(ChangesetOptions)(options);
40
+ const config = {
41
+ $schema: "https://unpkg.com/@changesets/config@3.1.1/schema.json",
42
+ changelog: opts.repo ? [
43
+ opts.changelog,
44
+ {
45
+ repo: opts.repo
46
+ }
47
+ ] : opts.changelog,
48
+ commit: false,
49
+ fixed: [],
50
+ linked: [],
51
+ access: opts.access,
52
+ baseBranch: opts.baseBranch,
53
+ updateInternalDependencies: "patch",
54
+ privatePackages: {
55
+ version: true,
56
+ tag: false
57
+ },
58
+ ignore: []
59
+ };
60
+ const content = JSON.stringify(config, null, "\t");
61
+ return [
62
+ {
63
+ name: "changeset",
64
+ filename: ".changeset/config.json",
65
+ content
66
+ }
67
+ ];
68
+ }
69
+ const GitignoreOptions = Schema.Struct({
70
+ sections: Schema.optional(Schema.Struct({
71
+ node: Schema.optional(Schema.Boolean),
72
+ build: Schema.optional(Schema.Boolean),
73
+ env: Schema.optional(Schema.Boolean),
74
+ os: Schema.optional(Schema.Boolean),
75
+ silk: Schema.optional(Schema.Boolean)
76
+ })),
77
+ additional: Schema.optional(Schema.Array(Schema.String))
78
+ });
79
+ function createGitignore(options) {
80
+ const opts = Schema.decodeUnknownSync(GitignoreOptions)(options);
81
+ const sections = {
82
+ node: opts.sections?.node ?? true,
83
+ build: opts.sections?.build ?? true,
84
+ env: opts.sections?.env ?? true,
85
+ os: opts.sections?.os ?? true,
86
+ silk: opts.sections?.silk ?? true
87
+ };
88
+ const lines = [];
89
+ if (sections.os) lines.push("# OS files", "**/.DS_Store", "");
90
+ if (sections.node) lines.push("# Node.js", "**/node_modules", "**/*.tsbuildinfo*", "");
91
+ if (sections.build) lines.push("# Build artifacts", "**/dist", "**/.rslib", "**/.turbo", "**/*.api.json", "", "# Test artifacts", "**/.coverage", "**/.vitest", "");
92
+ if (sections.silk) lines.push("# Release artifacts", "pnpm-publish-summary.json", "pnpm-release.json", ".changeset/status.json", "", "# Private keys", "**/*.asc", "", "# AI agent settings", ".mcp.json", "");
93
+ if (sections.env) lines.push("# Environment files", "**/.env", "**/.env.*", "");
94
+ if (opts.additional && opts.additional.length > 0) lines.push("# Additional", ...opts.additional, "");
95
+ const content = lines.join("\n");
96
+ return [
97
+ {
98
+ name: "gitignore",
99
+ filename: ".gitignore",
100
+ content
101
+ }
102
+ ];
103
+ }
104
+ const Author = Schema.Struct({
105
+ name: Schema.String,
106
+ email: Schema.optional(Schema.String),
107
+ url: Schema.optional(Schema.String)
108
+ });
109
+ const Bugs = Schema.Struct({
110
+ url: Schema.String
111
+ });
112
+ const Repository = Schema.Struct({
113
+ type: Schema.String,
114
+ url: Schema.String,
115
+ directory: Schema.optional(Schema.String)
116
+ });
117
+ const PublishConfig = Schema.Struct({
118
+ access: Schema.optional(Schema.String),
119
+ directory: Schema.optional(Schema.String),
120
+ linkDirectory: Schema.optional(Schema.Boolean),
121
+ targets: Schema.optional(Schema.Unknown)
122
+ });
123
+ const PackageJsonOptions = Schema.Struct({
124
+ name: Schema.String,
125
+ version: Schema.optionalWith(Schema.String, {
126
+ default: ()=>"0.0.0"
127
+ }),
128
+ private: Schema.optional(Schema.Boolean),
129
+ description: Schema.optional(Schema.String),
130
+ homepage: Schema.optional(Schema.String),
131
+ bugs: Schema.optional(Bugs),
132
+ repository: Schema.optional(Repository),
133
+ license: Schema.optional(Schema.String),
134
+ author: Schema.optional(Author),
135
+ sideEffects: Schema.optional(Schema.Boolean),
136
+ type: Schema.optional(Schema.Literal("module", "commonjs")),
137
+ exports: Schema.optional(Schema.Unknown),
138
+ scripts: Schema.optional(Schema.Record({
139
+ key: Schema.String,
140
+ value: Schema.String
141
+ })),
142
+ dependencies: Schema.optional(Schema.Record({
143
+ key: Schema.String,
144
+ value: Schema.String
145
+ })),
146
+ devDependencies: Schema.optional(Schema.Record({
147
+ key: Schema.String,
148
+ value: Schema.String
149
+ })),
150
+ peerDependencies: Schema.optional(Schema.Record({
151
+ key: Schema.String,
152
+ value: Schema.String
153
+ })),
154
+ engines: Schema.optional(Schema.Struct({
155
+ node: Schema.optional(Schema.String),
156
+ pnpm: Schema.optional(Schema.String)
157
+ })),
158
+ packageManager: Schema.optional(Schema.String),
159
+ devEngines: Schema.optional(Schema.Unknown),
160
+ publishConfig: Schema.optional(PublishConfig),
161
+ keywords: Schema.optional(Schema.Array(Schema.String))
162
+ });
163
+ function createPackageJson(options) {
164
+ const opts = Schema.decodeUnknownSync(PackageJsonOptions)(options);
165
+ const pkg = {
166
+ name: opts.name,
167
+ version: opts.version
168
+ };
169
+ if (void 0 !== opts.private) pkg.private = opts.private;
170
+ if (opts.description) pkg.description = opts.description;
171
+ if (opts.homepage) pkg.homepage = opts.homepage;
172
+ if (opts.bugs) pkg.bugs = opts.bugs;
173
+ if (opts.repository) pkg.repository = opts.repository;
174
+ if (opts.license) pkg.license = opts.license;
175
+ if (opts.author) pkg.author = opts.author;
176
+ if (void 0 !== opts.sideEffects) pkg.sideEffects = opts.sideEffects;
177
+ if (opts.type) pkg.type = opts.type;
178
+ if (opts.exports) pkg.exports = opts.exports;
179
+ if (opts.scripts && Object.keys(opts.scripts).length > 0) pkg.scripts = opts.scripts;
180
+ if (opts.dependencies && Object.keys(opts.dependencies).length > 0) pkg.dependencies = opts.dependencies;
181
+ if (opts.devDependencies && Object.keys(opts.devDependencies).length > 0) pkg.devDependencies = opts.devDependencies;
182
+ if (opts.peerDependencies && Object.keys(opts.peerDependencies).length > 0) pkg.peerDependencies = opts.peerDependencies;
183
+ if (opts.engines) pkg.engines = opts.engines;
184
+ if (opts.packageManager) pkg.packageManager = opts.packageManager;
185
+ if (opts.devEngines) pkg.devEngines = opts.devEngines;
186
+ if (opts.publishConfig) pkg.publishConfig = opts.publishConfig;
187
+ if (opts.keywords && opts.keywords.length > 0) pkg.keywords = opts.keywords;
188
+ const sorted = sortPackageJson(pkg);
189
+ const content = JSON.stringify(sorted, null, "\t");
190
+ return [
191
+ {
192
+ name: "package-json",
193
+ filename: "package.json",
194
+ content
195
+ }
196
+ ];
197
+ }
198
+ const PnpmWorkspaceOptions = Schema.Struct({
199
+ packages: Schema.Array(Schema.String),
200
+ autoInstallPeers: Schema.optional(Schema.Boolean),
201
+ catalogMode: Schema.optional(Schema.Literal("strict", "prefer", "manual")),
202
+ catalog: Schema.optional(Schema.Record({
203
+ key: Schema.String,
204
+ value: Schema.String
205
+ }))
206
+ });
207
+ function createPnpmWorkspace(options) {
208
+ const opts = Schema.decodeUnknownSync(PnpmWorkspaceOptions)(options);
209
+ const config = {
210
+ packages: opts.packages
211
+ };
212
+ if (void 0 !== opts.autoInstallPeers) config.autoInstallPeers = opts.autoInstallPeers;
213
+ if (opts.catalogMode) config.catalogMode = opts.catalogMode;
214
+ if (opts.catalog && Object.keys(opts.catalog).length > 0) config.catalog = opts.catalog;
215
+ const content = js_yaml.dump(config, {
216
+ indent: 2,
217
+ lineWidth: -1,
218
+ noRefs: true,
219
+ sortKeys: false
220
+ });
221
+ return [
222
+ {
223
+ name: "pnpm-workspace",
224
+ filename: "pnpm-workspace.yaml",
225
+ content
226
+ }
227
+ ];
228
+ }
229
+ const ReadmeOptions = Schema.Struct({
230
+ name: Schema.String,
231
+ description: Schema.optional(Schema.String)
232
+ });
233
+ function createReadme(options) {
234
+ const opts = Schema.decodeUnknownSync(ReadmeOptions)(options);
235
+ const lines = [
236
+ `# ${opts.name}`
237
+ ];
238
+ if (opts.description) lines.push("", opts.description);
239
+ lines.push("");
240
+ const content = lines.join("\n");
241
+ return [
242
+ {
243
+ name: "readme",
244
+ filename: "README.md",
245
+ content
246
+ }
247
+ ];
248
+ }
249
+ const TsConfigOptions = Schema.Struct({
250
+ extends: Schema.optional(Schema.Union(Schema.String, Schema.Array(Schema.String))),
251
+ composite: Schema.optional(Schema.Boolean),
252
+ include: Schema.optional(Schema.Array(Schema.String)),
253
+ exclude: Schema.optional(Schema.Array(Schema.String)),
254
+ references: Schema.optional(Schema.Array(Schema.Struct({
255
+ path: Schema.String
256
+ })))
257
+ });
258
+ function createTsConfig(options) {
259
+ const opts = Schema.decodeUnknownSync(TsConfigOptions)(options);
260
+ const config = {};
261
+ if (void 0 !== opts.extends) config.extends = "string" == typeof opts.extends ? [
262
+ opts.extends
263
+ ] : opts.extends;
264
+ if (void 0 !== opts.composite) config.composite = opts.composite;
265
+ if (opts.include) config.include = opts.include;
266
+ if (opts.exclude) config.exclude = opts.exclude;
267
+ if (opts.references) config.references = opts.references;
268
+ const content = JSON.stringify(config, null, "\t");
269
+ return [
270
+ {
271
+ name: "tsconfig",
272
+ filename: "tsconfig.json",
273
+ content
274
+ }
275
+ ];
276
+ }
277
+ const TurboRootOptions = Schema.Struct({
278
+ tasks: Schema.Record({
279
+ key: Schema.String,
280
+ value: Schema.Unknown
281
+ }),
282
+ globalDependencies: Schema.optional(Schema.Array(Schema.String)),
283
+ globalEnv: Schema.optional(Schema.Array(Schema.String)),
284
+ globalPassThroughEnv: Schema.optional(Schema.Array(Schema.String)),
285
+ ui: Schema.optional(Schema.Literal("tui", "stream")),
286
+ concurrency: Schema.optional(Schema.Union(Schema.String, Schema.Number))
287
+ });
288
+ const TurboWorkspaceOptions = Schema.Struct({
289
+ tasks: Schema.optional(Schema.Record({
290
+ key: Schema.String,
291
+ value: Schema.Unknown
292
+ }))
293
+ });
294
+ function createTurboRoot(options) {
295
+ const opts = Schema.decodeUnknownSync(TurboRootOptions)(options);
296
+ const config = {
297
+ $schema: "https://turborepo.com/schema.v2.json",
298
+ tasks: opts.tasks
299
+ };
300
+ if (opts.globalDependencies && opts.globalDependencies.length > 0) config.globalDependencies = opts.globalDependencies;
301
+ if (opts.globalEnv && opts.globalEnv.length > 0) config.globalEnv = opts.globalEnv;
302
+ if (opts.globalPassThroughEnv && opts.globalPassThroughEnv.length > 0) config.globalPassThroughEnv = opts.globalPassThroughEnv;
303
+ if (opts.ui) config.ui = opts.ui;
304
+ if (void 0 !== opts.concurrency) config.concurrency = opts.concurrency;
305
+ const content = JSON.stringify(config, null, "\t");
306
+ return [
307
+ {
308
+ name: "turbo-root",
309
+ filename: "turbo.json",
310
+ content
311
+ }
312
+ ];
313
+ }
314
+ function createTurboWorkspace(options) {
315
+ const opts = Schema.decodeUnknownSync(TurboWorkspaceOptions)(options);
316
+ const config = {
317
+ extends: [
318
+ "//"
319
+ ]
320
+ };
321
+ if (opts.tasks && Object.keys(opts.tasks).length > 0) config.tasks = opts.tasks;
322
+ const content = JSON.stringify(config, null, "\t");
323
+ return [
324
+ {
325
+ name: "turbo-workspace",
326
+ filename: "turbo.json",
327
+ content
328
+ }
329
+ ];
330
+ }
331
+ const Settings = Schema.Struct({
332
+ biome: Schema.optional(Schema.Boolean),
333
+ turbo: Schema.optional(Schema.Boolean),
334
+ vitest: Schema.optional(Schema.Boolean)
335
+ });
336
+ const VsCodeOptions = Schema.Struct({
337
+ settings: Schema.optional(Settings),
338
+ extensions: Schema.optional(Schema.Array(Schema.String))
339
+ });
340
+ function createVsCode(options) {
341
+ const opts = Schema.decodeUnknownSync(VsCodeOptions)(options);
342
+ const features = opts.settings ?? {};
343
+ const codeActions = {
344
+ "source.fixAll.ts": "explicit"
345
+ };
346
+ const searchExclude = {
347
+ "**/node_modules": true,
348
+ "**/dist": true,
349
+ "**/.rslib": true,
350
+ "**/*.tsbuildinfo": true
351
+ };
352
+ const filesExclude = {
353
+ "**/node_modules": true,
354
+ "**/dist": true,
355
+ "**/.rslib": true,
356
+ "**/*.tsbuildinfo": true
357
+ };
358
+ if (features.biome) {
359
+ codeActions["source.organizeImports.biome"] = "explicit";
360
+ codeActions["source.fixAll.biome"] = "explicit";
361
+ }
362
+ if (features.vitest) {
363
+ searchExclude["**/.vitest"] = true;
364
+ searchExclude["**/.coverage"] = true;
365
+ filesExclude["**/.vitest"] = true;
366
+ filesExclude["**/.coverage"] = true;
367
+ }
368
+ if (features.turbo) {
369
+ searchExclude["**/.turbo"] = true;
370
+ filesExclude["**/.turbo"] = true;
371
+ }
372
+ const settings = {
373
+ "editor.formatOnSave": true,
374
+ "eslint.enable": !features.biome,
375
+ "prettier.enable": !features.biome,
376
+ "biome.enabled": !!features.biome,
377
+ "biome.requireConfiguration": true,
378
+ "editor.codeActionsOnSave": codeActions,
379
+ "search.exclude": searchExclude,
380
+ "files.exclude": filesExclude
381
+ };
382
+ const recommendations = [
383
+ ...new Set(opts.extensions ?? [])
384
+ ].sort();
385
+ return [
386
+ {
387
+ name: "vscode-settings",
388
+ filename: ".vscode/settings.json",
389
+ content: JSON.stringify(settings, null, "\t")
390
+ },
391
+ {
392
+ name: "vscode-extensions",
393
+ filename: ".vscode/extensions.json",
394
+ content: JSON.stringify({
395
+ recommendations
396
+ }, null, "\t")
397
+ }
398
+ ];
399
+ }
400
+ const Features = Schema.Struct({
401
+ biome: Schema.optional(Schema.Boolean),
402
+ vitest: Schema.optional(Schema.Boolean),
403
+ turbo: Schema.optional(Schema.Boolean),
404
+ changesets: Schema.optional(Schema.Boolean),
405
+ vscode: Schema.optional(Schema.Boolean)
406
+ });
407
+ const WorkspaceOptions = Schema.Struct({
408
+ name: Schema.String,
409
+ packageManager: Schema.Literal("pnpm", "npm", "bun"),
410
+ packageManagerVersion: Schema.String,
411
+ nodeVersion: Schema.String,
412
+ biomeVersion: Schema.optionalWith(Schema.String, {
413
+ default: ()=>"2.3.3"
414
+ }),
415
+ features: Schema.optional(Features)
416
+ });
417
+ function createWorkspace(options) {
418
+ const opts = Schema.decodeUnknownSync(WorkspaceOptions)(options);
419
+ const features = opts.features ?? {};
420
+ const entries = [];
421
+ entries.push(...createPackageJson({
422
+ name: opts.name,
423
+ version: "0.0.0",
424
+ private: true,
425
+ type: "module",
426
+ packageManager: `${opts.packageManager}@${opts.packageManagerVersion}`,
427
+ engines: {
428
+ node: `>=${opts.nodeVersion}`
429
+ }
430
+ }));
431
+ entries.push(...createTsConfig({}));
432
+ entries.push(...createGitignore({
433
+ sections: {
434
+ os: true,
435
+ node: true,
436
+ build: true,
437
+ env: true,
438
+ silk: !!features.changesets
439
+ }
440
+ }));
441
+ entries.push(...createReadme({
442
+ name: opts.name
443
+ }));
444
+ if ("pnpm" === opts.packageManager) entries.push(...createPnpmWorkspace({
445
+ packages: [
446
+ "packages/*"
447
+ ],
448
+ autoInstallPeers: true
449
+ }));
450
+ if (features.biome) entries.push(...createBiome({
451
+ version: opts.biomeVersion,
452
+ root: true
453
+ }));
454
+ if (features.turbo) entries.push(...createTurboRoot({
455
+ tasks: {
456
+ build: {
457
+ dependsOn: [
458
+ "^build"
459
+ ],
460
+ outputs: [
461
+ "dist/**"
462
+ ]
463
+ },
464
+ test: {
465
+ dependsOn: [
466
+ "build"
467
+ ]
468
+ },
469
+ "types:check": {
470
+ cache: true,
471
+ outputLogs: "errors-only"
472
+ }
473
+ },
474
+ globalPassThroughEnv: [
475
+ "CI",
476
+ "GITHUB_ACTIONS"
477
+ ]
478
+ }));
479
+ if (features.changesets) entries.push(...createChangeset({
480
+ access: "restricted"
481
+ }));
482
+ if (features.vscode) entries.push(...createVsCode({
483
+ settings: {
484
+ biome: !!features.biome,
485
+ turbo: !!features.turbo,
486
+ vitest: !!features.vitest
487
+ }
488
+ }));
489
+ return entries;
490
+ }
491
+ export { BiomeOptions, ChangesetOptions, GitignoreOptions, PackageJsonOptions, PnpmWorkspaceOptions, ReadmeOptions, TsConfigOptions, TurboRootOptions, TurboWorkspaceOptions, VsCodeOptions, WorkspaceOptions, createBiome, createChangeset, createGitignore, createPackageJson, createPnpmWorkspace, createReadme, createTsConfig, createTurboRoot, createTurboWorkspace, createVsCode, createWorkspace };
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@savvy-web/templates",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "description": "Pure TypeScript templates for Silk Suite project scaffolding",
6
+ "homepage": "https://github.com/savvy-web/systems/tree/main/packages/templates",
7
+ "bugs": {
8
+ "url": "https://github.com/savvy-web/systems/issues"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/savvy-web/systems.git",
13
+ "directory": "packages/templates"
14
+ },
15
+ "license": "MIT",
16
+ "author": {
17
+ "name": "C. Spencer Beggs",
18
+ "email": "spencer@savvyweb.systems",
19
+ "url": "https://savvyweb.systems"
20
+ },
21
+ "sideEffects": false,
22
+ "type": "module",
23
+ "exports": {
24
+ ".": {
25
+ "types": "./index.d.ts",
26
+ "import": "./index.js"
27
+ }
28
+ },
29
+ "dependencies": {
30
+ "js-yaml": "^4.1.1",
31
+ "sort-package-json": "^3.6.1"
32
+ },
33
+ "peerDependencies": {
34
+ "effect": ">=3.21.0"
35
+ },
36
+ "files": [
37
+ "!templates.api.json",
38
+ "!tsconfig.json",
39
+ "!tsdoc.json",
40
+ "LICENSE",
41
+ "README.md",
42
+ "index.d.ts",
43
+ "index.js",
44
+ "package.json",
45
+ "tsdoc-metadata.json"
46
+ ]
47
+ }
@@ -0,0 +1,11 @@
1
+ // This file is read by tools that parse documentation comments conforming to the TSDoc standard.
2
+ // It should be published with your NPM package. It should not be tracked by Git.
3
+ {
4
+ "tsdocVersion": "0.12",
5
+ "toolPackages": [
6
+ {
7
+ "packageName": "@microsoft/api-extractor",
8
+ "packageVersion": "7.57.8"
9
+ }
10
+ ]
11
+ }