@phill-component/icons 1.0.0 → 1.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.
@@ -0,0 +1,75 @@
1
+ <template>
2
+ <view class="icon" @tap="onTap" :hover-class="hoverClass" :style="rootStyle">
3
+ <!-- #ifdef H5 -->
4
+ <svg v-bind="$attrs" :style="iconStyle" :width="iconW" :height="iconH" fill="none" viewBox="0 0 48 48"><path stroke="currentColor" stroke-width="2" d="M42.354 40.854 36.01 34.51m0 0a9 9 0 0 1-15.364-6.364c0-5 4-9 9-9s9 4 9 9a8.97 8.97 0 0 1-2.636 6.364zm5.636-26.365h-36m10 16h-10m10 16h-10"/></svg>
5
+ <!-- #endif -->
6
+ <!-- #ifndef H5 -->
7
+ <image :src="imgSrc" :style="iconBoxStyle" />
8
+ <!-- #endif -->
9
+ <text v-if="label != ''" class="icon__label" :style="labelStyle">{{ label }}</text>
10
+ </view>
11
+ </template>
12
+ <script setup lang="uts">
13
+ import { computed } from 'vue'
14
+ import imgSrc from '@/uni_modules/@phill-component/icons/mobile/uvue/icon-find-replace/find-replace.png'
15
+ const props = defineProps({
16
+ size: { type: [String, Number], default: '1em' },
17
+ color: { type: String, default: 'var(--tsm-color-primary)' },
18
+ label: { type: [String, Number], default: '' },
19
+ labelPos: { type: String, default: 'right' },
20
+ labelSize: { type: [String, Number], default: '15px' },
21
+ labelColor: { type: String, default: '' },
22
+ space: { type: [String, Number], default: '3px' },
23
+ width: { type: [String, Number], default: '' },
24
+ height: { type: [String, Number], default: '' },
25
+ hoverClass: { type: String, default: '' },
26
+ index: { type: [String, Number], default: '' },
27
+ stop: { type: Boolean, default: false }
28
+ })
29
+ const emit = defineEmits(['click'])
30
+ function toPx(v: any): string {
31
+ if (typeof v === 'number') return (v as number).toString() + 'px'
32
+ const s = (v as string)
33
+ return s != '' ? s : ''
34
+ }
35
+ const iconW = computed((): string => (props.width != '' ? toPx(props.width) : toPx(props.size)))
36
+ const iconH = computed((): string => (props.height != '' ? toPx(props.height) : toPx(props.size)))
37
+ const iconStyle = computed((): UTSJSONObject => {
38
+ return { 'color': props.color != '' ? props.color : 'inherit' } as UTSJSONObject
39
+ })
40
+ const iconBoxStyle = computed((): UTSJSONObject => ({ width: iconW.value, height: iconH.value } as UTSJSONObject))
41
+ const wrapStyle = computed((): UTSJSONObject => {
42
+ const map = { right: 'row', left: 'row-reverse', top: 'column-reverse', bottom: 'column' } as UTSJSONObject
43
+ let dir: string = 'row'
44
+ const cand = map[props.labelPos] as string | null
45
+ if (cand != null && cand.length > 0) {
46
+ dir = cand
47
+ }
48
+ return { display: 'flex', alignItems: 'center', flexDirection: dir } as UTSJSONObject
49
+ })
50
+ const rootStyle = computed((): UTSJSONObject => {
51
+ const s = wrapStyle.value
52
+ const i = iconStyle.value
53
+ for (const key in i) {
54
+ s[key] = i[key]
55
+ }
56
+ return s
57
+ })
58
+ const labelStyle = computed((): UTSJSONObject => {
59
+ const out = {} as UTSJSONObject
60
+ if (props.labelColor != '') out['color'] = props.labelColor
61
+ out['fontSize'] = toPx(props.labelSize)
62
+ out['marginLeft'] = props.labelPos == 'right' ? toPx(props.space) : 0
63
+ out['marginTop'] = props.labelPos == 'bottom' ? toPx(props.space) : 0
64
+ out['marginRight'] = props.labelPos == 'left' ? toPx(props.space) : 0
65
+ out['marginBottom'] = props.labelPos == 'top' ? toPx(props.space) : 0
66
+ return out
67
+ })
68
+ function onTap(e: UniPointerEvent) {
69
+ emit('click', props.index)
70
+ if (props.stop) e.stopPropagation()
71
+ }
72
+ </script>
73
+ <style scoped>
74
+ .icon__label { line-height: 1; }
75
+ </style>
Binary file
@@ -0,0 +1,75 @@
1
+ <template>
2
+ <view class="icon" @tap="onTap" :hover-class="hoverClass" :style="rootStyle">
3
+ <!-- #ifdef H5 -->
4
+ <svg v-bind="$attrs" :style="iconStyle" :width="iconW" :height="iconH" fill="none" viewBox="0 0 48 48"><path stroke="currentColor" stroke-width="2" d="M6 6v18m0 0v18m0-18h20m0 0V6m0 18v18m6-7.5c0 3.038 2.239 5.5 5 5.5s5-2.462 5-5.5-2.239-5.5-5-5.5-5 2.462-5 5.5zm0 0v-5.73c0-4.444 3.867-7.677 8-7.263.437.044.736.08.952.115"/></svg>
5
+ <!-- #endif -->
6
+ <!-- #ifndef H5 -->
7
+ <image :src="imgSrc" :style="iconBoxStyle" />
8
+ <!-- #endif -->
9
+ <text v-if="label != ''" class="icon__label" :style="labelStyle">{{ label }}</text>
10
+ </view>
11
+ </template>
12
+ <script setup lang="uts">
13
+ import { computed } from 'vue'
14
+ import imgSrc from '@/uni_modules/@phill-component/icons/mobile/uvue/icon-h6/h6.png'
15
+ const props = defineProps({
16
+ size: { type: [String, Number], default: '1em' },
17
+ color: { type: String, default: 'var(--tsm-color-primary)' },
18
+ label: { type: [String, Number], default: '' },
19
+ labelPos: { type: String, default: 'right' },
20
+ labelSize: { type: [String, Number], default: '15px' },
21
+ labelColor: { type: String, default: '' },
22
+ space: { type: [String, Number], default: '3px' },
23
+ width: { type: [String, Number], default: '' },
24
+ height: { type: [String, Number], default: '' },
25
+ hoverClass: { type: String, default: '' },
26
+ index: { type: [String, Number], default: '' },
27
+ stop: { type: Boolean, default: false }
28
+ })
29
+ const emit = defineEmits(['click'])
30
+ function toPx(v: any): string {
31
+ if (typeof v === 'number') return (v as number).toString() + 'px'
32
+ const s = (v as string)
33
+ return s != '' ? s : ''
34
+ }
35
+ const iconW = computed((): string => (props.width != '' ? toPx(props.width) : toPx(props.size)))
36
+ const iconH = computed((): string => (props.height != '' ? toPx(props.height) : toPx(props.size)))
37
+ const iconStyle = computed((): UTSJSONObject => {
38
+ return { 'color': props.color != '' ? props.color : 'inherit' } as UTSJSONObject
39
+ })
40
+ const iconBoxStyle = computed((): UTSJSONObject => ({ width: iconW.value, height: iconH.value } as UTSJSONObject))
41
+ const wrapStyle = computed((): UTSJSONObject => {
42
+ const map = { right: 'row', left: 'row-reverse', top: 'column-reverse', bottom: 'column' } as UTSJSONObject
43
+ let dir: string = 'row'
44
+ const cand = map[props.labelPos] as string | null
45
+ if (cand != null && cand.length > 0) {
46
+ dir = cand
47
+ }
48
+ return { display: 'flex', alignItems: 'center', flexDirection: dir } as UTSJSONObject
49
+ })
50
+ const rootStyle = computed((): UTSJSONObject => {
51
+ const s = wrapStyle.value
52
+ const i = iconStyle.value
53
+ for (const key in i) {
54
+ s[key] = i[key]
55
+ }
56
+ return s
57
+ })
58
+ const labelStyle = computed((): UTSJSONObject => {
59
+ const out = {} as UTSJSONObject
60
+ if (props.labelColor != '') out['color'] = props.labelColor
61
+ out['fontSize'] = toPx(props.labelSize)
62
+ out['marginLeft'] = props.labelPos == 'right' ? toPx(props.space) : 0
63
+ out['marginTop'] = props.labelPos == 'bottom' ? toPx(props.space) : 0
64
+ out['marginRight'] = props.labelPos == 'left' ? toPx(props.space) : 0
65
+ out['marginBottom'] = props.labelPos == 'top' ? toPx(props.space) : 0
66
+ return out
67
+ })
68
+ function onTap(e: UniPointerEvent) {
69
+ emit('click', props.index)
70
+ if (props.stop) e.stopPropagation()
71
+ }
72
+ </script>
73
+ <style scoped>
74
+ .icon__label { line-height: 1; }
75
+ </style>
@@ -0,0 +1,58 @@
1
+ <template>
2
+ <view class="icon" @tap="onTap" :hover-class="hoverClass" :style="[wrapStyle, iconStyle]">
3
+ <!-- #ifdef H5 -->
4
+ <svg v-bind="$attrs" :style="iconStyle" :width="iconW" :height="iconH" fill="none" viewBox="0 0 48 48"><path stroke="currentColor" stroke-width="2" d="M42.354 40.854 36.01 34.51m0 0a9 9 0 0 1-15.364-6.364c0-5 4-9 9-9s9 4 9 9a8.97 8.97 0 0 1-2.636 6.364zm5.636-26.365h-36m10 16h-10m10 16h-10"/></svg>
5
+ <!-- #endif -->
6
+ <!-- #ifndef H5 -->
7
+ <image :src="imgSrc" :style="iconBoxStyle" />
8
+ <!-- #endif -->
9
+ <text v-if="label !== ''" class="icon__label" :style="labelStyle">{{ label }}</text>
10
+ </view>
11
+ </template>
12
+ <script setup>
13
+ import { computed } from 'vue'
14
+ import imgSrc from '@/uni_modules/@phill-component/icons/mobile/uvue/icon-find-replace/find-replace.png'
15
+ const props = defineProps({
16
+ size: { type: [String, Number], default: '1em' },
17
+ color: { type: String, default: 'var(--tsm-color-primary)' },
18
+ label: { type: [String, Number], default: '' },
19
+ labelPos: { type: String, default: 'right' },
20
+ labelSize: { type: [String, Number], default: '15px' },
21
+ labelColor: { type: String, default: '' },
22
+ space: { type: [String, Number], default: '3px' },
23
+ width: { type: [String, Number], default: '' },
24
+ height: { type: [String, Number], default: '' },
25
+ hoverClass: { type: String, default: '' },
26
+ index: { type: [String, Number], default: '' },
27
+ stop: { type: Boolean, default: false }
28
+ })
29
+ const emit = defineEmits(['click'])
30
+ const toPx = (v) => typeof v === 'number' ? (v + 'px') : (String(v||''));
31
+ const iconW = computed(() => props.width ? toPx(props.width) : toPx(props.size))
32
+ const iconH = computed(() => props.height ? toPx(props.height) : toPx(props.size))
33
+ const iconBoxStyle = computed(() => ({ width: iconW.value, height: iconH.value }))
34
+ const iconStyle = computed(() => {
35
+ return { color: props.color || 'inherit' }
36
+ })
37
+ const wrapStyle = computed(() => {
38
+ const dirMap = { right: 'row', left: 'row-reverse', top: 'column-reverse', bottom: 'column' }
39
+ return { display: 'flex', alignItems: 'center', flexDirection: dirMap[props.labelPos] || 'row' }
40
+ })
41
+ const labelStyle = computed(() => {
42
+ return {
43
+ color: props.labelColor || '',
44
+ fontSize: toPx(props.labelSize),
45
+ marginLeft: props.labelPos === 'right' ? toPx(props.space) : 0,
46
+ marginTop: props.labelPos === 'bottom' ? toPx(props.space) : 0,
47
+ marginRight: props.labelPos === 'left' ? toPx(props.space) : 0,
48
+ marginBottom: props.labelPos === 'top' ? toPx(props.space) : 0
49
+ }
50
+ })
51
+ function onTap(e) {
52
+ emit('click', props.index)
53
+ if (props.stop && e && e.stopPropagation) e.stopPropagation()
54
+ }
55
+ </script>
56
+ <style scoped>
57
+ .icon__label { line-height: 1; }
58
+ </style>
@@ -0,0 +1,58 @@
1
+ <template>
2
+ <view class="icon" @tap="onTap" :hover-class="hoverClass" :style="[wrapStyle, iconStyle]">
3
+ <!-- #ifdef H5 -->
4
+ <svg v-bind="$attrs" :style="iconStyle" :width="iconW" :height="iconH" fill="none" viewBox="0 0 48 48"><path stroke="currentColor" stroke-width="2" d="M6 6v18m0 0v18m0-18h20m0 0V6m0 18v18m6-7.5c0 3.038 2.239 5.5 5 5.5s5-2.462 5-5.5-2.239-5.5-5-5.5-5 2.462-5 5.5zm0 0v-5.73c0-4.444 3.867-7.677 8-7.263.437.044.736.08.952.115"/></svg>
5
+ <!-- #endif -->
6
+ <!-- #ifndef H5 -->
7
+ <image :src="imgSrc" :style="iconBoxStyle" />
8
+ <!-- #endif -->
9
+ <text v-if="label !== ''" class="icon__label" :style="labelStyle">{{ label }}</text>
10
+ </view>
11
+ </template>
12
+ <script setup>
13
+ import { computed } from 'vue'
14
+ import imgSrc from '@/uni_modules/@phill-component/icons/mobile/uvue/icon-h6/h6.png'
15
+ const props = defineProps({
16
+ size: { type: [String, Number], default: '1em' },
17
+ color: { type: String, default: 'var(--tsm-color-primary)' },
18
+ label: { type: [String, Number], default: '' },
19
+ labelPos: { type: String, default: 'right' },
20
+ labelSize: { type: [String, Number], default: '15px' },
21
+ labelColor: { type: String, default: '' },
22
+ space: { type: [String, Number], default: '3px' },
23
+ width: { type: [String, Number], default: '' },
24
+ height: { type: [String, Number], default: '' },
25
+ hoverClass: { type: String, default: '' },
26
+ index: { type: [String, Number], default: '' },
27
+ stop: { type: Boolean, default: false }
28
+ })
29
+ const emit = defineEmits(['click'])
30
+ const toPx = (v) => typeof v === 'number' ? (v + 'px') : (String(v||''));
31
+ const iconW = computed(() => props.width ? toPx(props.width) : toPx(props.size))
32
+ const iconH = computed(() => props.height ? toPx(props.height) : toPx(props.size))
33
+ const iconBoxStyle = computed(() => ({ width: iconW.value, height: iconH.value }))
34
+ const iconStyle = computed(() => {
35
+ return { color: props.color || 'inherit' }
36
+ })
37
+ const wrapStyle = computed(() => {
38
+ const dirMap = { right: 'row', left: 'row-reverse', top: 'column-reverse', bottom: 'column' }
39
+ return { display: 'flex', alignItems: 'center', flexDirection: dirMap[props.labelPos] || 'row' }
40
+ })
41
+ const labelStyle = computed(() => {
42
+ return {
43
+ color: props.labelColor || '',
44
+ fontSize: toPx(props.labelSize),
45
+ marginLeft: props.labelPos === 'right' ? toPx(props.space) : 0,
46
+ marginTop: props.labelPos === 'bottom' ? toPx(props.space) : 0,
47
+ marginRight: props.labelPos === 'left' ? toPx(props.space) : 0,
48
+ marginBottom: props.labelPos === 'top' ? toPx(props.space) : 0
49
+ }
50
+ })
51
+ function onTap(e) {
52
+ emit('click', props.index)
53
+ if (props.stop && e && e.stopPropagation) e.stopPropagation()
54
+ }
55
+ </script>
56
+ <style scoped>
57
+ .icon__label { line-height: 1; }
58
+ </style>
@@ -0,0 +1,53 @@
1
+ <template>
2
+ <view class="icon" @tap="onTap" :hover-class="hoverClass" :style="[wrapStyle, iconStyle]">
3
+ <svg v-bind="$attrs" :style="iconStyle" :width="iconW" :height="iconH" fill="none" viewBox="0 0 48 48"><path stroke="currentColor" stroke-width="2" d="M42.354 40.854 36.01 34.51m0 0a9 9 0 0 1-15.364-6.364c0-5 4-9 9-9s9 4 9 9a8.97 8.97 0 0 1-2.636 6.364zm5.636-26.365h-36m10 16h-10m10 16h-10"/></svg>
4
+ <text v-if="label !== ''" class="icon__label" :style="labelStyle">{{ label }}</text>
5
+ </view>
6
+ </template>
7
+ <script setup>
8
+ import { computed } from 'vue'
9
+ const props = defineProps({
10
+ size: { type: [String, Number], default: '1em' },
11
+ color: { type: String, default: 'inherit' },
12
+ label: { type: [String, Number], default: '' },
13
+ labelPos: { type: String, default: 'right' },
14
+ labelSize: { type: [String, Number], default: '15px' },
15
+ labelColor: { type: String, default: '' },
16
+ space: { type: [String, Number], default: '3px' },
17
+ width: { type: [String, Number], default: '' },
18
+ height: { type: [String, Number], default: '' },
19
+ hoverClass: { type: String, default: '' },
20
+ index: { type: [String, Number], default: '' },
21
+ stop: { type: Boolean, default: false }
22
+ })
23
+ const emit = defineEmits(['click'])
24
+ const toPx = (v) => typeof v === 'number' ? (v + 'px') : (String(v||''));
25
+ const iconW = computed(() => props.width ? toPx(props.width) : toPx(props.size))
26
+ const iconH = computed(() => props.height ? toPx(props.height) : toPx(props.size))
27
+ const iconBoxStyle = computed(() => ({ width: iconW.value, height: iconH.value }))
28
+ const iconStyle = computed(() => {
29
+ return { color: props.color || 'inherit' }
30
+ })
31
+ const wrapStyle = computed(() => {
32
+ const dirMap = { right: 'row', left: 'row-reverse', top: 'column-reverse', bottom: 'column' }
33
+ return { display: 'flex', alignItems: 'center', flexDirection: dirMap[props.labelPos] || 'row' }
34
+ })
35
+ const labelStyle = computed(() => {
36
+ return {
37
+ color: props.labelColor || '',
38
+ fontSize: toPx(props.labelSize),
39
+ marginLeft: props.labelPos === 'right' ? toPx(props.space) : 0,
40
+ marginTop: props.labelPos === 'bottom' ? toPx(props.space) : 0,
41
+ marginRight: props.labelPos === 'left' ? toPx(props.space) : 0,
42
+ marginBottom: props.labelPos === 'top' ? toPx(props.space) : 0
43
+ }
44
+ })
45
+ const imgSrc = '__IMG_SRC__'
46
+ function onTap(e) {
47
+ emit('click', props.index)
48
+ if (props.stop && e && e.stopPropagation) e.stopPropagation()
49
+ }
50
+ </script>
51
+ <style scoped>
52
+ .icon__label { line-height: 1; }
53
+ </style>
@@ -0,0 +1,53 @@
1
+ <template>
2
+ <view class="icon" @tap="onTap" :hover-class="hoverClass" :style="[wrapStyle, iconStyle]">
3
+ <svg v-bind="$attrs" :style="iconStyle" :width="iconW" :height="iconH" fill="none" viewBox="0 0 48 48"><path stroke="currentColor" stroke-width="2" d="M6 6v18m0 0v18m0-18h20m0 0V6m0 18v18m6-7.5c0 3.038 2.239 5.5 5 5.5s5-2.462 5-5.5-2.239-5.5-5-5.5-5 2.462-5 5.5zm0 0v-5.73c0-4.444 3.867-7.677 8-7.263.437.044.736.08.952.115"/></svg>
4
+ <text v-if="label !== ''" class="icon__label" :style="labelStyle">{{ label }}</text>
5
+ </view>
6
+ </template>
7
+ <script setup>
8
+ import { computed } from 'vue'
9
+ const props = defineProps({
10
+ size: { type: [String, Number], default: '1em' },
11
+ color: { type: String, default: 'inherit' },
12
+ label: { type: [String, Number], default: '' },
13
+ labelPos: { type: String, default: 'right' },
14
+ labelSize: { type: [String, Number], default: '15px' },
15
+ labelColor: { type: String, default: '' },
16
+ space: { type: [String, Number], default: '3px' },
17
+ width: { type: [String, Number], default: '' },
18
+ height: { type: [String, Number], default: '' },
19
+ hoverClass: { type: String, default: '' },
20
+ index: { type: [String, Number], default: '' },
21
+ stop: { type: Boolean, default: false }
22
+ })
23
+ const emit = defineEmits(['click'])
24
+ const toPx = (v) => typeof v === 'number' ? (v + 'px') : (String(v||''));
25
+ const iconW = computed(() => props.width ? toPx(props.width) : toPx(props.size))
26
+ const iconH = computed(() => props.height ? toPx(props.height) : toPx(props.size))
27
+ const iconBoxStyle = computed(() => ({ width: iconW.value, height: iconH.value }))
28
+ const iconStyle = computed(() => {
29
+ return { color: props.color || 'inherit' }
30
+ })
31
+ const wrapStyle = computed(() => {
32
+ const dirMap = { right: 'row', left: 'row-reverse', top: 'column-reverse', bottom: 'column' }
33
+ return { display: 'flex', alignItems: 'center', flexDirection: dirMap[props.labelPos] || 'row' }
34
+ })
35
+ const labelStyle = computed(() => {
36
+ return {
37
+ color: props.labelColor || '',
38
+ fontSize: toPx(props.labelSize),
39
+ marginLeft: props.labelPos === 'right' ? toPx(props.space) : 0,
40
+ marginTop: props.labelPos === 'bottom' ? toPx(props.space) : 0,
41
+ marginRight: props.labelPos === 'left' ? toPx(props.space) : 0,
42
+ marginBottom: props.labelPos === 'top' ? toPx(props.space) : 0
43
+ }
44
+ })
45
+ const imgSrc = '__IMG_SRC__'
46
+ function onTap(e) {
47
+ emit('click', props.index)
48
+ if (props.stop && e && e.stopPropagation) e.stopPropagation()
49
+ }
50
+ </script>
51
+ <style scoped>
52
+ .icon__label { line-height: 1; }
53
+ </style>
@@ -1,5 +1,7 @@
1
1
  export { default as IconCalendar } from './Calendar.vue';
2
2
  export { default as IconFileFold } from './FileFold.vue';
3
+ export { default as IconFindReplace } from './FindReplace.vue';
4
+ export { default as IconH6 } from './H6.vue';
3
5
  export { default as IconHome } from './Home.vue';
4
6
  export { default as IconSetting } from './Setting.vue';
5
7
  export { default as IconTask } from './Task.vue';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phill-component/icons",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Unified icons for phillUI mobile and pc libraries",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -10,11 +10,11 @@
10
10
  ],
11
11
  "sideEffects": false,
12
12
  "bin": {
13
- "@phill-component/icons": "scripts/install.js"
13
+ "icons": "scripts/install.js"
14
14
  },
15
15
  "scripts": {
16
16
  "build": "node scripts/build.js",
17
- "release-core-icons": "node ../../../scripts/publish-icons.js"
17
+ "release": "node scripts/publish.js"
18
18
  },
19
19
  "keywords": [
20
20
  "icons",
@@ -32,4 +32,4 @@
32
32
  "sharp": "^0.34.5"
33
33
  },
34
34
  "dependencies": {}
35
- }
35
+ }
@@ -10,7 +10,7 @@ const path = require('path');
10
10
 
11
11
  function resolvePackageRoot() {
12
12
  // When executed from node_modules: __dirname -> <pkg>/scripts
13
- // In workspace/dev: __dirname -> repo/packages/core/icons/scripts
13
+ // In workspace/dev: __dirname -> repo/packages/icons/scripts
14
14
  const scriptsDir = __dirname;
15
15
  const candidate = path.resolve(scriptsDir, '..'); // <pkg>
16
16
  if (fs.existsSync(path.join(candidate, 'dist'))) return candidate;
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env node
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const cp = require('child_process');
5
+ const os = require('os');
6
+
7
+ function readJSON(p) {
8
+ return JSON.parse(fs.readFileSync(p, 'utf8'));
9
+ }
10
+
11
+ function normalizeNpmToken(input) {
12
+ let t = String(input || '').trim();
13
+ if (!t) return '';
14
+ if ((t.startsWith('"') && t.endsWith('"')) || (t.startsWith("'") && t.endsWith("'"))) {
15
+ t = t.slice(1, -1).trim();
16
+ }
17
+ const m = t.match(/_authToken\s*=\s*([^\s]+)/);
18
+ if (m && m[1]) return m[1].trim();
19
+ return t;
20
+ }
21
+
22
+ function normalizeNpmRegistry(input) {
23
+ let r = String(input || '').trim();
24
+ if (!r) r = 'https://registry.npmjs.org/';
25
+ if ((r.startsWith('"') && r.endsWith('"')) || (r.startsWith("'") && r.endsWith("'"))) {
26
+ r = r.slice(1, -1).trim();
27
+ }
28
+ if (!/^https?:\/\//.test(r)) r = `https://${r}`;
29
+ if (!r.endsWith('/')) r += '/';
30
+ return r;
31
+ }
32
+
33
+ function findProjectRoot(startDir) {
34
+ let dir = startDir;
35
+ while (true) {
36
+ const pkg = path.join(dir, 'package.json');
37
+ if (fs.existsSync(pkg)) {
38
+ try {
39
+ const json = JSON.parse(fs.readFileSync(pkg, 'utf8'));
40
+ if (json && json.private === true && json.name === 'phill-icon') return dir;
41
+ } catch {}
42
+ }
43
+ const parent = path.dirname(dir);
44
+ if (parent === dir) return null;
45
+ dir = parent;
46
+ }
47
+ }
48
+
49
+ function exec(command, args, options = {}) {
50
+ const res = cp.spawnSync(command, args, { encoding: 'utf8', ...options });
51
+ if (res.status !== 0) {
52
+ const err = new Error(res.stderr || res.stdout || `${command} failed`);
53
+ err.stdout = res.stdout;
54
+ err.stderr = res.stderr;
55
+ err.status = res.status;
56
+ throw err;
57
+ }
58
+ return res.stdout || '';
59
+ }
60
+
61
+ function main() {
62
+ const args = process.argv.slice(2);
63
+ const checkOnly = args.includes('--check');
64
+ const doPublish = process.env.PHILLUI_PUBLISH === '1';
65
+
66
+ const pkgDir = process.cwd();
67
+ const iconsPkgPath = path.join(pkgDir, 'package.json');
68
+ if (!fs.existsSync(iconsPkgPath)) {
69
+ console.error('[publish-icons] 未在 icons 包目录下执行。');
70
+ process.exit(1);
71
+ }
72
+ const iconsPkg = readJSON(iconsPkgPath);
73
+
74
+ if (checkOnly || !doPublish) {
75
+ console.log('[publish-icons] 校验通过。未执行发布(设置 PHILLUI_PUBLISH=1 才会发布)。');
76
+ process.exit(0);
77
+ }
78
+
79
+ const npmToken = normalizeNpmToken(process.env.NPM_TOKEN);
80
+ const npmRegistry = normalizeNpmRegistry(process.env.NPM_REGISTRY);
81
+
82
+ const pkgNpmrc = path.join(pkgDir, '.npmrc');
83
+ const root = findProjectRoot(pkgDir);
84
+ const rootNpmrc = root ? path.join(root, '.npmrc') : null;
85
+
86
+ let tmpDir = null;
87
+ let userconfig = null;
88
+
89
+ try {
90
+ // 预先检查版本
91
+ try {
92
+ const viewOut = exec('npm', ['view', iconsPkg.name, 'version', '--registry', npmRegistry], { env: process.env });
93
+ const remoteVersion = String(viewOut).trim();
94
+ if (remoteVersion === iconsPkg.version) {
95
+ console.log(`[publish-icons] ${iconsPkg.name}@${iconsPkg.version} 已存在,跳过。`);
96
+ return;
97
+ }
98
+ } catch (e) {}
99
+
100
+ if (npmToken) {
101
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'phill-icons-npmrc-'));
102
+ userconfig = path.join(tmpDir, '.npmrc');
103
+ const registryHost = new URL(npmRegistry).host;
104
+ fs.writeFileSync(
105
+ userconfig,
106
+ `registry=${npmRegistry}\n` +
107
+ `always-auth=true\n` +
108
+ `//${registryHost}/:_authToken=${npmToken}\n`
109
+ );
110
+ console.log(`[publish-icons] 使用临时 npmrc:${userconfig}`);
111
+ } else if (fs.existsSync(pkgNpmrc)) {
112
+ userconfig = pkgNpmrc;
113
+ console.log(`[publish-icons] 使用包内 npmrc:${userconfig}`);
114
+ } else if (rootNpmrc && fs.existsSync(rootNpmrc)) {
115
+ userconfig = rootNpmrc;
116
+ console.log(`[publish-icons] 使用根目录 npmrc:${userconfig}`);
117
+ } else {
118
+ console.error('[publish-icons] 缺少认证信息:请提供 NPM_TOKEN 或者提供可用的 .npmrc(包内/根目录)。');
119
+ process.exit(1);
120
+ }
121
+
122
+ const env = { ...process.env, NPM_CONFIG_USERCONFIG: userconfig, npm_config_userconfig: userconfig };
123
+
124
+ // 验证身份
125
+ const whoami = exec('npm', ['whoami', '--userconfig', userconfig], { env }).trim();
126
+ console.log(`[publish-icons] 当前登录用户:${whoami}`);
127
+
128
+ console.log(`[publish-icons] 执行:npm publish --access public --userconfig ${userconfig}`);
129
+ exec('npm', ['publish', '--access', 'public', '--userconfig', userconfig], { env });
130
+ console.log(`[publish-icons] ${iconsPkg.name}@${iconsPkg.version} 发布成功!`);
131
+ } catch (e) {
132
+ console.error('[publish-icons] 发布失败:', e.message);
133
+ if (e.stdout) console.error('STDOUT:', e.stdout.toString());
134
+ if (e.stderr) console.error('STDERR:', e.stderr.toString());
135
+ process.exit(e.status || 1);
136
+ } finally {
137
+ if (tmpDir) {
138
+ try {
139
+ fs.rmSync(tmpDir, { recursive: true, force: true });
140
+ } catch (e) {}
141
+ }
142
+ }
143
+ }
144
+
145
+ main();