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 +21 -0
- package/README.md +18 -0
- package/dist/generate.d.ts +10 -0
- package/dist/generate.js +221 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +81 -0
- package/package.json +30 -0
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;
|
package/dist/generate.js
ADDED
|
@@ -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
|
+
}
|
package/dist/index.d.ts
ADDED
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
|
+
}
|