@meddleware/token-deployer-ui 0.0.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/.claude/move-language-reference/SKILL.md +445 -0
- package/.claude/skills/move-language-reference/SKILL.md +445 -0
- package/.claude/skills/sui-move-bootcamp/SKILL.md +128 -0
- package/.claude/sui-move-bootcamp/SKILL.md +128 -0
- package/.editorconfig +8 -0
- package/.env.example +75 -0
- package/.env.production +7 -0
- package/.gitattributes +1 -0
- package/.github/workflows/ci.yml +34 -0
- package/.github/workflows/token-deployer-e2e.yml +64 -0
- package/.oxfmtrc.json +5 -0
- package/.oxlintrc.json +10 -0
- package/.vscode/extensions.json +9 -0
- package/AGENTS.md +39 -0
- package/CLAUDE.md +159 -0
- package/LICENSE +121 -0
- package/README.md +234 -0
- package/env.d.ts +13 -0
- package/index.html +13 -0
- package/package.json +67 -0
- package/playwright.config.ts +30 -0
- package/public/CNAME +1 -0
- package/public/_headers +12 -0
- package/public/_redirects +1 -0
- package/public/favicon.ico +0 -0
- package/public/favicon.svg +5 -0
- package/public/og-image.png +0 -0
- package/public/robots.txt +3 -0
- package/public/sitemap.xml +8 -0
- package/scripts/e2e-browser.mjs +15 -0
- package/scripts/e2e-deploy.mjs +222 -0
- package/scripts/e2e-localnet.mjs +65 -0
- package/scripts/e2e-walrus-browser.mjs +115 -0
- package/scripts/gen-og-image.mjs +31 -0
- package/scripts/publish-localnet.mjs +55 -0
- package/scripts/regen-template.sh +45 -0
- package/scripts/sync-template-src.sh +36 -0
- package/scripts/verify-bytecode.mjs +49 -0
- package/src/App.vue +41 -0
- package/src/__tests__/App.spec.js +11 -0
- package/src/components/AppNotice.vue +15 -0
- package/src/components/ColorModeControl.vue +81 -0
- package/src/components/ConfigForm.vue +192 -0
- package/src/components/DeployProgress.vue +46 -0
- package/src/components/FieldHint.vue +79 -0
- package/src/components/FormField.vue +28 -0
- package/src/components/FormSelect.vue +43 -0
- package/src/components/GithubPush.vue +100 -0
- package/src/components/IconPicker.vue +410 -0
- package/src/components/LicensePicker.vue +182 -0
- package/src/components/ResultPanel.vue +88 -0
- package/src/components/ReviewPanel.vue +69 -0
- package/src/components/TermsDialog.vue +81 -0
- package/src/components/TokenDeployerView.vue +115 -0
- package/src/components/WalletBar.vue +108 -0
- package/src/components/WalrusBlobBrowser.vue +188 -0
- package/src/components/WalrusIconUpload.vue +205 -0
- package/src/composables/useAccessGate.ts +122 -0
- package/src/composables/useColorMode.ts +76 -0
- package/src/composables/useWalrusRelay.ts +21 -0
- package/src/config.ts +185 -0
- package/src/index.ts +1 -0
- package/src/lib/accessGate.ts +141 -0
- package/src/lib/buildPublishTx.ts +192 -0
- package/src/lib/deploy.ts +168 -0
- package/src/lib/deployExecutor.ts +61 -0
- package/src/lib/errors.ts +21 -0
- package/src/lib/form.ts +75 -0
- package/src/lib/generatePackage.ts +312 -0
- package/src/lib/github.ts +146 -0
- package/src/lib/licenses.ts +122 -0
- package/src/lib/readClient.ts +17 -0
- package/src/lib/template.ts +131 -0
- package/src/lib/types.ts +69 -0
- package/src/lib/validation.ts +172 -0
- package/src/lib/walrus-constants.ts +15 -0
- package/src/lib/walrus.ts +144 -0
- package/src/main.ts +154 -0
- package/src/move-template/sui_token_template.mv +0 -0
- package/src/move-template/template.ts +31 -0
- package/src/styles.css +384 -0
- package/src/template-src/files.json +10 -0
- package/test-results/.last-run.json +4 -0
- package/tests/accessGate.test.ts +108 -0
- package/tests/buildPublishTx.test.ts +243 -0
- package/tests/deploy.test.ts +81 -0
- package/tests/errors.test.ts +32 -0
- package/tests/generatePackage.test.ts +182 -0
- package/tests/github.test.ts +61 -0
- package/tests/licenses.test.ts +78 -0
- package/tests/template.test.ts +93 -0
- package/tests/templateArtifact.test.ts +61 -0
- package/tests/templateParity.test.ts +55 -0
- package/tests/useAccessGate.test.ts +101 -0
- package/tests/useWallet.test.ts +74 -0
- package/tests/useWalrusRelayGating.test.ts +30 -0
- package/tests/validation.test.ts +117 -0
- package/tests/walrusUpload.test.ts +93 -0
- package/tests/walrus_url_patch.test.ts +26 -0
- package/tsconfig.json +19 -0
- package/typedoc.json +34 -0
- package/vite.config.ts +65 -0
- package/vitest.config.ts +10 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed, reactive } from 'vue'
|
|
3
|
+
import type { FormModel } from '../lib/form.js'
|
|
4
|
+
import type { FormErrors } from '../lib/validation.js'
|
|
5
|
+
import { deriveStructName } from '../lib/validation.js'
|
|
6
|
+
import type { Network } from '../lib/types.js'
|
|
7
|
+
import LicensePicker from './LicensePicker.vue'
|
|
8
|
+
import IconPicker from './IconPicker.vue'
|
|
9
|
+
import FormField from './FormField.vue'
|
|
10
|
+
import FormSelect from './FormSelect.vue'
|
|
11
|
+
import FieldHint from './FieldHint.vue'
|
|
12
|
+
|
|
13
|
+
const props = defineProps<{
|
|
14
|
+
form: FormModel
|
|
15
|
+
errors: FormErrors
|
|
16
|
+
canProceed: boolean
|
|
17
|
+
network: Network
|
|
18
|
+
connected: boolean
|
|
19
|
+
}>()
|
|
20
|
+
const emit = defineEmits<{ (e: 'submit'): void }>()
|
|
21
|
+
|
|
22
|
+
const structPreview = computed(() => {
|
|
23
|
+
const m = props.form.moduleName.trim()
|
|
24
|
+
return m ? deriveStructName(m) : '—'
|
|
25
|
+
})
|
|
26
|
+
const coinTypePreview = computed(
|
|
27
|
+
() => `<package>::${props.form.moduleName.trim() || 'module'}::${structPreview.value}`,
|
|
28
|
+
)
|
|
29
|
+
const identifierError = computed(() => visibleError('packageName') || visibleError('moduleName'))
|
|
30
|
+
|
|
31
|
+
const touched = reactive(new Set<string>())
|
|
32
|
+
function visibleError(key: string): string | undefined {
|
|
33
|
+
return touched.has(key) ? props.errors[key as keyof FormErrors] : undefined
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function onBlur(key: string): void {
|
|
37
|
+
touched.add(key)
|
|
38
|
+
const form = props.form as unknown as Record<string, unknown>
|
|
39
|
+
if (typeof form[key] === 'string') form[key] = (form[key] as string).trim()
|
|
40
|
+
}
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
<template>
|
|
44
|
+
<form novalidate @submit.prevent="emit('submit')">
|
|
45
|
+
<fieldset>
|
|
46
|
+
<legend>Package identity <span class="permanent">(permanent)</span></legend>
|
|
47
|
+
<div class="grid2">
|
|
48
|
+
<FormField id="package" label="Package name" :error="visibleError('packageName')" v-slot="{ attrs }">
|
|
49
|
+
<input v-bind="attrs" v-model="form.packageName" placeholder="my_token" autocomplete="off" @blur="onBlur('packageName')" />
|
|
50
|
+
</FormField>
|
|
51
|
+
<FormField id="module" label="Module name" :error="visibleError('moduleName')" v-slot="{ attrs }">
|
|
52
|
+
<input v-bind="attrs" v-model="form.moduleName" placeholder="mytoken" autocomplete="off" @blur="onBlur('moduleName')" />
|
|
53
|
+
</FormField>
|
|
54
|
+
</div>
|
|
55
|
+
<p class="hint" aria-live="polite">
|
|
56
|
+
<template v-if="identifierError">
|
|
57
|
+
Fix the package and module names to preview the coin type.
|
|
58
|
+
</template>
|
|
59
|
+
<template v-else>
|
|
60
|
+
Coin type will be <span class="mono">{{ coinTypePreview }}</span
|
|
61
|
+
>. The witness struct <span class="mono">{{ structPreview }}</span> is derived from
|
|
62
|
+
the module name (a Sui requirement).
|
|
63
|
+
</template>
|
|
64
|
+
</p>
|
|
65
|
+
</fieldset>
|
|
66
|
+
|
|
67
|
+
<fieldset>
|
|
68
|
+
<legend>Token details</legend>
|
|
69
|
+
|
|
70
|
+
<div class="grid2">
|
|
71
|
+
<FormField id="name" label="Token name" :error="visibleError('name')" v-slot="{ attrs }">
|
|
72
|
+
<input v-bind="attrs" v-model="form.name" autocomplete="off" @blur="onBlur('name')" />
|
|
73
|
+
</FormField>
|
|
74
|
+
<FormField id="symbol" label="Symbol" :error="visibleError('symbol')" v-slot="{ attrs }">
|
|
75
|
+
<input v-bind="attrs" v-model="form.symbol" autocomplete="off" @blur="onBlur('symbol')" />
|
|
76
|
+
</FormField>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<FormField id="description" label="Description" :error="visibleError('description')">
|
|
80
|
+
<template #label-suffix> <span class="hint">(optional)</span></template>
|
|
81
|
+
<template #default="{ attrs }">
|
|
82
|
+
<textarea v-bind="attrs" v-model="form.description" @blur="onBlur('description')" />
|
|
83
|
+
</template>
|
|
84
|
+
</FormField>
|
|
85
|
+
|
|
86
|
+
<FormField id="decimals" label="Decimals" :error="visibleError('decimals')">
|
|
87
|
+
<template #label-suffix> <span class="permanent">(permanent)</span></template>
|
|
88
|
+
<template #default="{ attrs }">
|
|
89
|
+
<input v-bind="attrs" v-model="form.decimals" inputmode="numeric" @blur="onBlur('decimals')" />
|
|
90
|
+
</template>
|
|
91
|
+
</FormField>
|
|
92
|
+
|
|
93
|
+
<IconPicker
|
|
94
|
+
v-model="form.iconUrl"
|
|
95
|
+
:network="network"
|
|
96
|
+
:connected="connected"
|
|
97
|
+
:error="visibleError('iconUrl')"
|
|
98
|
+
@blur="onBlur('iconUrl')"
|
|
99
|
+
/>
|
|
100
|
+
</fieldset>
|
|
101
|
+
|
|
102
|
+
<details>
|
|
103
|
+
<summary>Advanced options</summary>
|
|
104
|
+
<fieldset>
|
|
105
|
+
<legend class="visually-hidden">Advanced options</legend>
|
|
106
|
+
|
|
107
|
+
<FormField id="supply" label="Initial supply" :error="visibleError('initialSupply')">
|
|
108
|
+
<template #label-suffix>
|
|
109
|
+
<FieldHint field-id="supply">
|
|
110
|
+
The number of whole tokens to mint and send to the recipient at deployment.
|
|
111
|
+
Uses your chosen decimal precision — with 9 decimals, 1 token = 1,000,000,000 base units on-chain.
|
|
112
|
+
Enter <b>0</b> or leave blank for no initial supply.
|
|
113
|
+
You can mint more later if you choose the Mintable supply policy.
|
|
114
|
+
</FieldHint>
|
|
115
|
+
</template>
|
|
116
|
+
<template #default="{ attrs }">
|
|
117
|
+
<input v-bind="attrs" v-model="form.initialSupply" inputmode="numeric" placeholder="0" @blur="onBlur('initialSupply')" />
|
|
118
|
+
</template>
|
|
119
|
+
</FormField>
|
|
120
|
+
|
|
121
|
+
<div class="grid2">
|
|
122
|
+
<FormField id="supply-policy" label="Supply">
|
|
123
|
+
<template #label-suffix>
|
|
124
|
+
<FieldHint field-id="supply-policy">
|
|
125
|
+
<b>Mintable</b>: you keep the TreasuryCap and can mint more tokens at any time.<br />
|
|
126
|
+
<b>Fixed</b>: the TreasuryCap is frozen — the total supply is sealed at deployment.
|
|
127
|
+
</FieldHint>
|
|
128
|
+
</template>
|
|
129
|
+
<template #default="{ attrs }">
|
|
130
|
+
<FormSelect v-bind="attrs" v-model="form.supplyPolicy">
|
|
131
|
+
<option value="mintable">Mintable</option>
|
|
132
|
+
<option value="fixed">Fixed</option>
|
|
133
|
+
</FormSelect>
|
|
134
|
+
</template>
|
|
135
|
+
</FormField>
|
|
136
|
+
<FormField id="metadata-policy" label="Metadata">
|
|
137
|
+
<template #label-suffix>
|
|
138
|
+
<FieldHint field-id="metadata-policy">
|
|
139
|
+
<b>Updatable</b>: you keep the MetadataCap and can later change the name, symbol,
|
|
140
|
+
description, or icon.<br />
|
|
141
|
+
<b>Frozen</b>: the MetadataCap is discarded — all metadata is permanently locked.
|
|
142
|
+
</FieldHint>
|
|
143
|
+
</template>
|
|
144
|
+
<template #default="{ attrs }">
|
|
145
|
+
<FormSelect v-bind="attrs" v-model="form.metadataPolicy">
|
|
146
|
+
<option value="updatable">Updatable</option>
|
|
147
|
+
<option value="frozen">Frozen</option>
|
|
148
|
+
</FormSelect>
|
|
149
|
+
</template>
|
|
150
|
+
</FormField>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<FormField id="package-policy" label="Package code">
|
|
154
|
+
<template #label-suffix>
|
|
155
|
+
<FieldHint field-id="package-policy">
|
|
156
|
+
<b>Immutable</b>: the UpgradeCap is burned — the on-chain code is sealed.
|
|
157
|
+
Recommended for trust and auditability.<br />
|
|
158
|
+
<b>Upgradeable</b>: you keep the UpgradeCap and can push code changes to the package
|
|
159
|
+
after deployment.
|
|
160
|
+
</FieldHint>
|
|
161
|
+
</template>
|
|
162
|
+
<template #default="{ attrs }">
|
|
163
|
+
<FormSelect v-bind="attrs" v-model="form.packagePolicy">
|
|
164
|
+
<option value="immutable">Immutable</option>
|
|
165
|
+
<option value="upgradeable">Upgradeable</option>
|
|
166
|
+
</FormSelect>
|
|
167
|
+
</template>
|
|
168
|
+
</FormField>
|
|
169
|
+
|
|
170
|
+
<FormField id="recipient" label="Recipient" :error="visibleError('recipient')">
|
|
171
|
+
<template #label-suffix>
|
|
172
|
+
<FieldHint field-id="recipient">
|
|
173
|
+
The Sui address that receives the TreasuryCap, MetadataCap, UpgradeCap (if kept),
|
|
174
|
+
and any initial supply minted at deployment.
|
|
175
|
+
Leave blank to send everything to the connected wallet.
|
|
176
|
+
Enter a different address to separate the deployer wallet from the custody wallet.
|
|
177
|
+
</FieldHint>
|
|
178
|
+
</template>
|
|
179
|
+
<template #default="{ attrs }">
|
|
180
|
+
<input v-bind="attrs" v-model="form.recipient" placeholder="0x…" autocomplete="off" @blur="onBlur('recipient')" />
|
|
181
|
+
</template>
|
|
182
|
+
</FormField>
|
|
183
|
+
|
|
184
|
+
<LicensePicker v-model="form.license" @name="form.licenseName = $event" />
|
|
185
|
+
</fieldset>
|
|
186
|
+
</details>
|
|
187
|
+
|
|
188
|
+
<div class="row" style="margin-top: 1rem">
|
|
189
|
+
<button type="submit" class="primary" :disabled="!canProceed">Review & deploy</button>
|
|
190
|
+
</div>
|
|
191
|
+
</form>
|
|
192
|
+
</template>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { DeployStep } from '../lib/deploy.js'
|
|
3
|
+
|
|
4
|
+
const ORDER: { key: DeployStep; label: string }[] = [
|
|
5
|
+
{ key: 'patching', label: 'Preparing the module' },
|
|
6
|
+
{ key: 'publishing', label: 'Publishing (sign in your wallet)' },
|
|
7
|
+
{ key: 'confirming', label: 'Confirming on-chain' },
|
|
8
|
+
{ key: 'finalizing', label: 'Finalizing supply & caps (sign again)' },
|
|
9
|
+
{ key: 'confirming-finalize', label: 'Confirming finalize' },
|
|
10
|
+
{ key: 'done', label: 'Done' },
|
|
11
|
+
]
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<script setup lang="ts">
|
|
15
|
+
import { computed } from 'vue'
|
|
16
|
+
|
|
17
|
+
const props = defineProps<{ step: DeployStep | null }>()
|
|
18
|
+
|
|
19
|
+
const currentIndex = computed(() => ORDER.findIndex((s) => s.key === props.step))
|
|
20
|
+
|
|
21
|
+
function getState(i: number): 'done' | 'active' | 'todo' {
|
|
22
|
+
if (currentIndex.value < 0) return 'todo'
|
|
23
|
+
if (i < currentIndex.value) return 'done'
|
|
24
|
+
if (i === currentIndex.value) return 'active'
|
|
25
|
+
return 'todo'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const steps = computed(() =>
|
|
29
|
+
ORDER.map((s, i) => ({ ...s, state: getState(i) }))
|
|
30
|
+
)
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<template>
|
|
34
|
+
<section class="card" aria-labelledby="progress-h">
|
|
35
|
+
<h2 id="progress-h">Deploying…</h2>
|
|
36
|
+
<ol class="steps" aria-live="polite">
|
|
37
|
+
<li v-for="s in steps" :key="s.key" :data-state="s.state">
|
|
38
|
+
<span v-if="s.state === 'active'" class="spinner" aria-hidden="true"></span>
|
|
39
|
+
<span v-else-if="s.state === 'done'" aria-hidden="true">✓ </span>
|
|
40
|
+
{{ s.label }}
|
|
41
|
+
<span v-if="s.state === 'active'" class="visually-hidden">(in progress)</span>
|
|
42
|
+
</li>
|
|
43
|
+
</ol>
|
|
44
|
+
<p class="hint">Keep this tab open and approve the wallet prompts.</p>
|
|
45
|
+
</section>
|
|
46
|
+
</template>
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
defineProps<{ fieldId: string }>()
|
|
3
|
+
|
|
4
|
+
function onKeyDown(e: KeyboardEvent) {
|
|
5
|
+
if (e.key === 'Escape') (e.currentTarget as HTMLElement).blur()
|
|
6
|
+
}
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<template>
|
|
10
|
+
<span class="field-hint">
|
|
11
|
+
<button
|
|
12
|
+
type="button"
|
|
13
|
+
class="field-hint-btn"
|
|
14
|
+
:aria-describedby="`${fieldId}-hint`"
|
|
15
|
+
aria-label="Help"
|
|
16
|
+
@keydown="onKeyDown"
|
|
17
|
+
>?</button>
|
|
18
|
+
<span :id="`${fieldId}-hint`" role="tooltip" class="field-hint-tip">
|
|
19
|
+
<slot />
|
|
20
|
+
</span>
|
|
21
|
+
</span>
|
|
22
|
+
</template>
|
|
23
|
+
|
|
24
|
+
<style scoped>
|
|
25
|
+
.field-hint {
|
|
26
|
+
position: relative;
|
|
27
|
+
display: inline-block;
|
|
28
|
+
margin-left: 0.35rem;
|
|
29
|
+
vertical-align: middle;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.field-hint-btn {
|
|
33
|
+
display: inline-flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
justify-content: center;
|
|
36
|
+
width: 1.1em;
|
|
37
|
+
height: 1.1em;
|
|
38
|
+
padding: 0;
|
|
39
|
+
font-size: 0.75rem;
|
|
40
|
+
font-weight: 700;
|
|
41
|
+
line-height: 1;
|
|
42
|
+
border-radius: 50%;
|
|
43
|
+
border: 1px solid var(--muted);
|
|
44
|
+
color: var(--muted);
|
|
45
|
+
background: transparent;
|
|
46
|
+
cursor: help;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.field-hint-tip {
|
|
50
|
+
position: absolute;
|
|
51
|
+
top: calc(100% + 6px);
|
|
52
|
+
left: 0;
|
|
53
|
+
z-index: 10;
|
|
54
|
+
min-width: 200px;
|
|
55
|
+
max-width: min(300px, 90vw);
|
|
56
|
+
padding: 0.6rem 0.75rem;
|
|
57
|
+
background: var(--surface);
|
|
58
|
+
border: 1px solid var(--border);
|
|
59
|
+
border-radius: 8px;
|
|
60
|
+
font-size: 0.85rem;
|
|
61
|
+
font-weight: 400;
|
|
62
|
+
line-height: 1.5;
|
|
63
|
+
color: var(--text);
|
|
64
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.12);
|
|
65
|
+
visibility: hidden;
|
|
66
|
+
opacity: 0;
|
|
67
|
+
transition:
|
|
68
|
+
opacity 0.15s ease,
|
|
69
|
+
visibility 0.15s ease;
|
|
70
|
+
pointer-events: none;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.field-hint:hover .field-hint-tip,
|
|
74
|
+
.field-hint:focus-within .field-hint-tip {
|
|
75
|
+
visibility: visible;
|
|
76
|
+
opacity: 1;
|
|
77
|
+
pointer-events: auto;
|
|
78
|
+
}
|
|
79
|
+
</style>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
|
|
4
|
+
const props = defineProps<{
|
|
5
|
+
id: string
|
|
6
|
+
label: string
|
|
7
|
+
error?: string | null
|
|
8
|
+
hint?: string
|
|
9
|
+
}>()
|
|
10
|
+
|
|
11
|
+
const errorId = computed(() => `${props.id}-err`)
|
|
12
|
+
const slotAttrs = computed(() => ({
|
|
13
|
+
id: props.id,
|
|
14
|
+
'aria-invalid': props.error ? (true as const) : undefined,
|
|
15
|
+
'aria-describedby': props.error ? errorId.value : undefined,
|
|
16
|
+
}))
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<template>
|
|
20
|
+
<div class="form-field">
|
|
21
|
+
<label :for="id">
|
|
22
|
+
{{ label }}<slot name="label-suffix" />
|
|
23
|
+
</label>
|
|
24
|
+
<slot :attrs="slotAttrs" />
|
|
25
|
+
<p v-if="hint && !error" class="hint">{{ hint }}</p>
|
|
26
|
+
<p v-if="error" :id="errorId" class="field-error">{{ error }}</p>
|
|
27
|
+
</div>
|
|
28
|
+
</template>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
defineOptions({ inheritAttrs: false })
|
|
3
|
+
const model = defineModel<string>()
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<template>
|
|
7
|
+
<div class="select-wrapper">
|
|
8
|
+
<select v-bind="$attrs" v-model="model"><slot /></select>
|
|
9
|
+
<svg
|
|
10
|
+
class="select-chevron"
|
|
11
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
12
|
+
width="12"
|
|
13
|
+
height="8"
|
|
14
|
+
viewBox="0 0 12 8"
|
|
15
|
+
aria-hidden="true"
|
|
16
|
+
>
|
|
17
|
+
<path d="M1 1l5 5 5-5" />
|
|
18
|
+
</svg>
|
|
19
|
+
</div>
|
|
20
|
+
</template>
|
|
21
|
+
|
|
22
|
+
<style scoped>
|
|
23
|
+
.select-wrapper {
|
|
24
|
+
position: relative;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.select-wrapper select {
|
|
28
|
+
appearance: none;
|
|
29
|
+
padding-right: 2.5rem;
|
|
30
|
+
width: 100%;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.select-chevron {
|
|
34
|
+
position: absolute;
|
|
35
|
+
right: 0.75rem;
|
|
36
|
+
top: 50%;
|
|
37
|
+
transform: translateY(-50%);
|
|
38
|
+
pointer-events: none;
|
|
39
|
+
stroke: var(--muted);
|
|
40
|
+
stroke-width: 1.5;
|
|
41
|
+
fill: none;
|
|
42
|
+
}
|
|
43
|
+
</style>
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref } from 'vue'
|
|
3
|
+
import type { PublishResult, TokenConfig } from '../lib/types.js'
|
|
4
|
+
import AppNotice from './AppNotice.vue'
|
|
5
|
+
import { buildPackageFiles } from '../lib/generatePackage.js'
|
|
6
|
+
import { fetchLicenseText } from '../lib/licenses.js'
|
|
7
|
+
import { createRepoAndPush } from '../lib/github.js'
|
|
8
|
+
|
|
9
|
+
const props = defineProps<{ config: TokenConfig; result: PublishResult }>()
|
|
10
|
+
|
|
11
|
+
const open = ref(false)
|
|
12
|
+
const token = ref('')
|
|
13
|
+
const repoName = ref(props.config.packageName)
|
|
14
|
+
const isPrivate = ref(false)
|
|
15
|
+
const busy = ref(false)
|
|
16
|
+
const error = ref<string | null>(null)
|
|
17
|
+
const doneUrl = ref<string | null>(null)
|
|
18
|
+
const progress = ref('')
|
|
19
|
+
|
|
20
|
+
async function push(): Promise<void> {
|
|
21
|
+
busy.value = true
|
|
22
|
+
error.value = null
|
|
23
|
+
doneUrl.value = null
|
|
24
|
+
try {
|
|
25
|
+
const licenseText = await fetchLicenseText(props.config.license)
|
|
26
|
+
const files = buildPackageFiles({
|
|
27
|
+
config: props.config,
|
|
28
|
+
licenseText: licenseText ?? undefined,
|
|
29
|
+
result: props.result,
|
|
30
|
+
})
|
|
31
|
+
const res = await createRepoAndPush({
|
|
32
|
+
token: token.value.trim(),
|
|
33
|
+
repoName: repoName.value.trim(),
|
|
34
|
+
description: `${props.config.name} (${props.config.symbol}) — Sui coin package`,
|
|
35
|
+
isPrivate: isPrivate.value,
|
|
36
|
+
files,
|
|
37
|
+
commitMessage: 'Initial token package (generated by Token Deployer)',
|
|
38
|
+
onProgress: (d, t) => (progress.value = `Pushing files… ${d}/${t}`),
|
|
39
|
+
})
|
|
40
|
+
doneUrl.value = res.htmlUrl
|
|
41
|
+
} catch (e) {
|
|
42
|
+
error.value = e instanceof Error ? e.message : String(e)
|
|
43
|
+
} finally {
|
|
44
|
+
// Never retain the token beyond a single attempt — clear it whether the push
|
|
45
|
+
// succeeded or failed (a failed push previously left it in the input/memory).
|
|
46
|
+
token.value = ''
|
|
47
|
+
busy.value = false
|
|
48
|
+
progress.value = ''
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
</script>
|
|
52
|
+
|
|
53
|
+
<template>
|
|
54
|
+
<div>
|
|
55
|
+
<button type="button" :aria-expanded="open" @click="open = !open">
|
|
56
|
+
{{ open ? '▾' : '▸' }} Create a GitHub repo (optional)
|
|
57
|
+
</button>
|
|
58
|
+
|
|
59
|
+
<div v-if="open" class="card" style="margin-top: 0.5rem">
|
|
60
|
+
<p v-once class="hint">
|
|
61
|
+
Uses your <strong>own</strong> GitHub token — the repo is created in your account and this
|
|
62
|
+
site's servers are never involved. The token is sent only to
|
|
63
|
+
<span class="mono">api.github.com</span> over HTTPS, is never stored or logged, and is
|
|
64
|
+
cleared as soon as the push finishes. Use a fine-grained token with "Contents" and
|
|
65
|
+
"Administration" write access, or a classic token with the
|
|
66
|
+
<span class="mono">repo</span> scope.
|
|
67
|
+
</p>
|
|
68
|
+
|
|
69
|
+
<label for="gh-repo">Repository name</label>
|
|
70
|
+
<input id="gh-repo" v-model="repoName" autocomplete="off" />
|
|
71
|
+
|
|
72
|
+
<label for="gh-token">GitHub token</label>
|
|
73
|
+
<input
|
|
74
|
+
id="gh-token"
|
|
75
|
+
v-model="token"
|
|
76
|
+
type="password"
|
|
77
|
+
autocomplete="off"
|
|
78
|
+
spellcheck="false"
|
|
79
|
+
placeholder="github_pat_… or ghp_…"
|
|
80
|
+
/>
|
|
81
|
+
|
|
82
|
+
<label style="font-weight: 400; margin-top: 0.75rem">
|
|
83
|
+
<input type="checkbox" v-model="isPrivate" style="width: auto" /> Make the repository
|
|
84
|
+
private
|
|
85
|
+
</label>
|
|
86
|
+
|
|
87
|
+
<div class="row" style="margin-top: 0.75rem">
|
|
88
|
+
<button type="button" class="primary" :disabled="busy || !token || !repoName" @click="push">
|
|
89
|
+
<span v-if="busy" class="spinner" aria-hidden="true"></span> Create repo & push
|
|
90
|
+
</button>
|
|
91
|
+
<span v-if="progress" class="hint" aria-live="polite">{{ progress }}</span>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<AppNotice v-if="error" type="error">{{ error }}</AppNotice>
|
|
95
|
+
<AppNotice v-if="doneUrl" type="ok">
|
|
96
|
+
Pushed: <a :href="doneUrl" target="_blank" rel="noopener noreferrer">{{ doneUrl }}</a>
|
|
97
|
+
</AppNotice>
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
</template>
|