create-fate-mod 1.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) 2024 Stanislav Sonder
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,18 @@
1
+ # create-fate-mod
2
+
3
+ Scaffolder for [FATE: Core](https://github.com/Stanislavsonder/fate-core) mod
4
+ projects.
5
+
6
+ ```
7
+ pnpm create fate-mod
8
+ ```
9
+
10
+ Prompts for a mod id, display name, author info, and capabilities
11
+ (`sheetComponents`/`dice`/`theme`/`translations`), then generates a ready-to-
12
+ build project using `@fate-core/mod-build`'s Vite preset — `npm install &&
13
+ npm run dev` gets you live-reloading in the app's Developer Mode.
14
+
15
+ See [`docs/MOD_API.md`](https://github.com/Stanislavsonder/fate-core/blob/main/docs/MOD_API.md)
16
+ for the full authoring contract, and
17
+ [`fate-core-mods`](https://github.com/Stanislavsonder/fate-core-mods)'s
18
+ `SUBMITTING.md` for how to publish what you build.
@@ -0,0 +1,10 @@
1
+ export type Capability = 'sheetComponents' | 'dice' | 'theme' | 'translations';
2
+ export interface ScaffoldAnswers {
3
+ id: string;
4
+ displayName: string;
5
+ authorName: string;
6
+ authorGithub: string;
7
+ capabilities: Capability[];
8
+ languages: string[];
9
+ }
10
+ export declare function generateFiles(root: string, answers: ScaffoldAnswers): void;
@@ -0,0 +1,221 @@
1
+ import { mkdirSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ /**
4
+ * Tracks the FateSDK ABI (src/mods/sdk.ts's SDK_VERSION in the app repo) —
5
+ * same version-discipline rule as @fate-core/mod-types/@fate-core/mod-build's
6
+ * own READMEs. Bump this in the same PR that bumps SDK_VERSION.
7
+ */
8
+ const CURRENT_SDK_VERSION = '1.1.0';
9
+ export function generateFiles(root, answers) {
10
+ const hasSheet = answers.capabilities.includes('sheetComponents');
11
+ mkdirSync(join(root, 'translations'), { recursive: true });
12
+ if (hasSheet) {
13
+ mkdirSync(join(root, 'src', 'components'), { recursive: true });
14
+ }
15
+ writeFileSync(join(root, 'package.json'), packageJson(answers));
16
+ writeFileSync(join(root, 'manifest.json'), manifestJson(answers));
17
+ writeFileSync(join(root, 'bundle.ts'), bundleTs(answers));
18
+ writeFileSync(join(root, 'vite.config.ts'), `import { defineModConfig } from '@fate-core/mod-build'\n\nexport default defineModConfig()\n`);
19
+ writeFileSync(join(root, 'tsconfig.json'), tsconfigJson());
20
+ writeFileSync(join(root, '.gitignore'), 'node_modules\ndist\n');
21
+ writeFileSync(join(root, 'README.md'), readmeMd(answers));
22
+ writeFileSync(join(root, 'CHANGELOG.md'), '# Changelog\n\n## 1.0.0\n\nInitial release.\n');
23
+ writeFileSync(join(root, 'LICENSE'), licenseMit(answers.authorName));
24
+ for (const language of answers.languages) {
25
+ writeFileSync(join(root, 'translations', `${language}.json`), translationJson(answers));
26
+ }
27
+ if (hasSheet) {
28
+ writeFileSync(join(root, 'src', 'components', 'ExampleSection.vue'), exampleSectionVue());
29
+ writeFileSync(join(root, 'src', 'components', 'index.ts'), componentsIndexTs());
30
+ }
31
+ }
32
+ function packageJson(answers) {
33
+ const name = answers.id.split('@')[1] ?? answers.id;
34
+ const json = {
35
+ name,
36
+ private: true,
37
+ type: 'module',
38
+ scripts: {
39
+ dev: 'fate-mod-build dev',
40
+ build: 'fate-mod-build build'
41
+ },
42
+ dependencies: {
43
+ '@fate-core/mod-types': `^${CURRENT_SDK_VERSION}`
44
+ },
45
+ devDependencies: {
46
+ '@fate-core/mod-build': `^${CURRENT_SDK_VERSION}`,
47
+ '@ionic/vue': '8.8.15',
48
+ '@vitejs/plugin-vue': '6.0.8',
49
+ ionicons: '8.0.13',
50
+ vite: '8.1.5',
51
+ vue: '3.5.40',
52
+ 'vue-i18n': '11.4.7'
53
+ }
54
+ };
55
+ return JSON.stringify(json, null, '\t') + '\n';
56
+ }
57
+ function manifestJson(answers) {
58
+ const json = {
59
+ id: answers.id,
60
+ version: '1.0.0',
61
+ name: 't.name',
62
+ author: {
63
+ name: answers.authorName,
64
+ github: answers.authorGithub
65
+ },
66
+ description: {
67
+ short: 't.description.short',
68
+ full: 't.description.full'
69
+ },
70
+ languages: answers.languages,
71
+ tags: [],
72
+ loadPriority: 100,
73
+ sdk: `^${CURRENT_SDK_VERSION}`,
74
+ entry: 'bundle.mjs',
75
+ capabilities: answers.capabilities,
76
+ config: {
77
+ groups: [],
78
+ options: []
79
+ }
80
+ };
81
+ return JSON.stringify(json, null, '\t') + '\n';
82
+ }
83
+ function bundleTs(answers) {
84
+ const lines = ["import { defineFateMod } from '@fate-core/mod-types'"];
85
+ if (answers.capabilities.includes('sheetComponents')) {
86
+ lines.push("import components from './src/components'");
87
+ }
88
+ lines.push('', 'export default defineFateMod({');
89
+ if (answers.capabilities.includes('sheetComponents')) {
90
+ lines.push('\tcomponents,');
91
+ }
92
+ lines.push('\tonInstall() {},', '\tonUninstall() {},', '\tonReconfigure() {},');
93
+ if (answers.capabilities.includes('dice')) {
94
+ lines.push('\tdice: {', '\t\tshapes: [], // add your DiceConstructor exports here', '\t\tmaterials: []', '\t},');
95
+ }
96
+ if (answers.capabilities.includes('theme')) {
97
+ lines.push('\ttheme: {', '\t\tcss: `:root {\n\t\t\t/* --ion-color-primary: #your-color; */\n\t\t}`', '\t},');
98
+ }
99
+ lines.push('})', '');
100
+ return lines.join('\n');
101
+ }
102
+ function tsconfigJson() {
103
+ const json = {
104
+ compilerOptions: {
105
+ // mod-build's own relative imports use explicit .ts extensions (needed
106
+ // for Node's native ESM loader when Vite loads a *consuming* config
107
+ // file) — any project depending on it needs this too.
108
+ allowImportingTsExtensions: true,
109
+ esModuleInterop: true,
110
+ isolatedModules: true,
111
+ jsx: 'preserve',
112
+ lib: ['ESNext', 'DOM'],
113
+ module: 'ESNext',
114
+ moduleResolution: 'bundler',
115
+ noEmit: true,
116
+ resolveJsonModule: true,
117
+ skipLibCheck: true,
118
+ strict: true,
119
+ target: 'ESNext',
120
+ types: ['vite/client'],
121
+ useDefineForClassFields: true
122
+ },
123
+ exclude: ['dist'],
124
+ include: ['**/*.ts', '**/*.vue']
125
+ };
126
+ return JSON.stringify(json, null, '\t') + '\n';
127
+ }
128
+ function translationJson(answers) {
129
+ const json = {
130
+ name: answers.displayName,
131
+ description: {
132
+ full: 'Full description of the mod (plain text).',
133
+ short: 'Short description of the mod (plain text, ~140 characters).'
134
+ }
135
+ };
136
+ return JSON.stringify(json, null, '\t') + '\n';
137
+ }
138
+ function exampleSectionVue() {
139
+ return `<script setup lang="ts">
140
+ import { inject } from 'vue'
141
+ import type { Ref } from 'vue'
142
+ import type { Character, FateContext } from '@fate-core/mod-types'
143
+
144
+ const character = defineModel<Character>({ required: true })
145
+ const context = inject<Ref<FateContext>>('context')!
146
+ </script>
147
+
148
+ <template>
149
+ <div>
150
+ <!-- Your section's template here -->
151
+ </div>
152
+ </template>
153
+ `;
154
+ }
155
+ function componentsIndexTs() {
156
+ return `import ExampleSection from './ExampleSection.vue'
157
+ import type { FateModuleComponent } from '@fate-core/mod-types'
158
+
159
+ export default [
160
+ {
161
+ id: 'example-section',
162
+ component: ExampleSection,
163
+ order: 1000
164
+ }
165
+ ] as FateModuleComponent[]
166
+ `;
167
+ }
168
+ function readmeMd(answers) {
169
+ return `# ${answers.displayName}
170
+
171
+ A FATE: Core mod (\`${answers.id}\`), scaffolded by \`create-fate-mod\`.
172
+
173
+ ## Developing
174
+
175
+ \`\`\`
176
+ npm install
177
+ npm run dev
178
+ \`\`\`
179
+
180
+ Then, in the app: Settings → Developer Mode → enable it → connect to
181
+ \`http://localhost:5199\`. Changes to this project live-reload in the app.
182
+
183
+ ## Publishing
184
+
185
+ See [\`fate-core-mods\`](https://github.com/Stanislavsonder/fate-core-mods)'s
186
+ \`SUBMITTING.md\` for how to submit this mod to the public registry once it's
187
+ ready. In short: \`npm run build\`, then open a pull request against that repo
188
+ adding this folder under \`mods/${answers.id}/\`.
189
+
190
+ ## API reference
191
+
192
+ See [\`docs/MOD_API.md\`](https://github.com/Stanislavsonder/fate-core/blob/main/docs/MOD_API.md)
193
+ in the app repo for the full contract this mod is built against (manifest
194
+ shape, \`window.FateSDK\`, capabilities, lifecycle hooks).
195
+ `;
196
+ }
197
+ function licenseMit(authorName) {
198
+ const year = new Date().getFullYear();
199
+ return `MIT License
200
+
201
+ Copyright (c) ${year} ${authorName}
202
+
203
+ Permission is hereby granted, free of charge, to any person obtaining a copy
204
+ of this software and associated documentation files (the "Software"), to deal
205
+ in the Software without restriction, including without limitation the rights
206
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
207
+ copies of the Software, and to permit persons to whom the Software is
208
+ furnished to do so, subject to the following conditions:
209
+
210
+ The above copyright notice and this permission notice shall be included in all
211
+ copies or substantial portions of the Software.
212
+
213
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
214
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
215
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
216
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
217
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
218
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
219
+ SOFTWARE.
220
+ `;
221
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, mkdirSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import prompts from 'prompts';
5
+ import { generateFiles } from "./generate.js";
6
+ const ID_PATTERN = /^[a-z0-9-]+@[a-z0-9-]+$/;
7
+ const GITHUB_PATTERN = /^[A-Za-z0-9-]+$/;
8
+ const LANGUAGE_PATTERN = /^[a-z]{2}(-[A-Z]{2})?$/;
9
+ async function main() {
10
+ const response = await prompts([
11
+ {
12
+ type: 'text',
13
+ name: 'id',
14
+ message: 'Mod id (author@name, lowercase kebab-case)',
15
+ validate: (value) => ID_PATTERN.test(value) || 'Must match author@name, e.g. "jsmith@my-mod"'
16
+ },
17
+ {
18
+ type: 'text',
19
+ name: 'displayName',
20
+ message: 'Display name',
21
+ validate: (value) => value.trim().length > 0 || 'Required'
22
+ },
23
+ {
24
+ type: 'text',
25
+ name: 'authorName',
26
+ message: 'Your name',
27
+ validate: (value) => value.trim().length > 0 || 'Required'
28
+ },
29
+ {
30
+ type: 'text',
31
+ name: 'authorGithub',
32
+ message: 'Your GitHub handle (fate-core-mods CI verifies this against the PR author)',
33
+ validate: (value) => GITHUB_PATTERN.test(value) || 'Must be a valid GitHub username'
34
+ },
35
+ {
36
+ type: 'multiselect',
37
+ name: 'capabilities',
38
+ message: 'Capabilities (space to toggle, enter to confirm)',
39
+ choices: [
40
+ { title: 'Sheet components (a character sheet section)', value: 'sheetComponents', selected: true },
41
+ { title: 'Dice (custom roll shapes/materials) — experimental', value: 'dice' },
42
+ { title: 'Theme (an app skin)', value: 'theme' },
43
+ { title: 'Translations (a localization pack)', value: 'translations' }
44
+ ],
45
+ min: 1,
46
+ instructions: false
47
+ },
48
+ {
49
+ type: 'text',
50
+ name: 'languages',
51
+ message: 'Languages (comma-separated ISO codes)',
52
+ initial: 'en',
53
+ validate: (value) => value
54
+ .split(',')
55
+ .map(s => s.trim())
56
+ .every(s => LANGUAGE_PATTERN.test(s)) || 'Each language must look like "en" or "en-US"'
57
+ }
58
+ ], { onCancel: () => process.exit(1) });
59
+ const answers = {
60
+ id: response.id,
61
+ displayName: response.displayName,
62
+ authorName: response.authorName,
63
+ authorGithub: response.authorGithub,
64
+ capabilities: response.capabilities,
65
+ languages: response.languages.split(',').map(s => s.trim())
66
+ };
67
+ const dirName = answers.id;
68
+ const targetDir = join(process.cwd(), dirName);
69
+ if (existsSync(targetDir)) {
70
+ console.error(`"${dirName}" already exists in the current directory.`);
71
+ process.exit(1);
72
+ }
73
+ mkdirSync(targetDir, { recursive: true });
74
+ generateFiles(targetDir, answers);
75
+ console.log(`\nCreated ${dirName}/`);
76
+ console.log(`\nNext steps:\n cd ${dirName}\n npm install\n npm run dev\n`);
77
+ }
78
+ main().catch((e) => {
79
+ console.error(e);
80
+ process.exit(1);
81
+ });
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "create-fate-mod",
3
+ "version": "1.1.0",
4
+ "description": "Scaffolder for FATE: Core mod projects — run with `pnpm create fate-mod` (or `npm create fate-mod`).",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/Stanislavsonder/fate-core.git",
9
+ "directory": "packages/create-fate-mod"
10
+ },
11
+ "type": "module",
12
+ "bin": {
13
+ "create-fate-mod": "./dist/index.js"
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "dependencies": {
22
+ "prompts": "^2.4.2"
23
+ },
24
+ "devDependencies": {
25
+ "@types/prompts": "^2.4.9"
26
+ },
27
+ "scripts": {
28
+ "build": "tsc -p tsconfig.build.json"
29
+ }
30
+ }