blog: Shein IPO blog post
This commit is contained in:
parent
98bc188722
commit
d3c5b2cec3
11 changed files with 1458 additions and 1300 deletions
19
package.json
19
package.json
|
|
@ -47,16 +47,17 @@
|
||||||
"mongodb": "^4.17.2",
|
"mongodb": "^4.17.2",
|
||||||
"nanoid": "3.3.4",
|
"nanoid": "3.3.4",
|
||||||
"node-fetch": "^3.3.2",
|
"node-fetch": "^3.3.2",
|
||||||
"rehype-stringify": "^9.0.4",
|
"rehype-stringify": "^10.0.1",
|
||||||
"remark": "^14.0.3",
|
"remark": "^15.0.1",
|
||||||
"remark-frontmatter": "^4.0.1",
|
"remark-frontmatter": "^5.0.0",
|
||||||
"remark-parse": "^10.0.2",
|
"remark-gfm": "^4.0.0",
|
||||||
"remark-rehype": "^10.1.0",
|
"remark-parse": "^11.0.0",
|
||||||
"remark-stringify": "^10.0.3",
|
"remark-rehype": "^11.1.1",
|
||||||
|
"remark-stringify": "^11.0.0",
|
||||||
"sanitize-html": "^2.14.0",
|
"sanitize-html": "^2.14.0",
|
||||||
"strip-markdown": "^5.0.1",
|
"strip-markdown": "^6.0.0",
|
||||||
"to-vfile": "^7.2.4",
|
"to-vfile": "^8.0.0",
|
||||||
"unified": "^10.1.2",
|
"unified": "^11.0.5",
|
||||||
"zod": "^3.24.1"
|
"zod": "^3.24.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
title: 'Vibe Check #21'
|
title: 'Vibe Check #21'
|
||||||
date: 1970-01-01T00:00:00.000Z
|
date: 2025-01-09T00:00:00.000Z
|
||||||
slug: 2025-01-09-vibe-check-21
|
slug: 2025-01-09-vibe-check-21
|
||||||
author: Thomas Wilson
|
author: Thomas Wilson
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
---
|
||||||
|
title: Letter to MP against support of Shein IPO
|
||||||
|
date: 2025-01-16T00:00:00.000Z
|
||||||
|
slug: 2025-01-16-letter-to-mp-against-support-of-shein-ipo
|
||||||
|
author: Thomas Wilson
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
The following is an extract from a letter I sent to my MP in January 2025 to express my concerns about Shein's attempts to make an initial public offering (IPO), while continuing to source cotton from slave labour.
|
||||||
|
|
||||||
|
I post it because this year I want to take seriously my involvement in democracy, and part of that is expressing our opinions clearly to the people who
|
||||||
|
represent us in government.
|
||||||
|
|
||||||
|
The body of the letter follows below:
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
I am sure you are aware that on January 7, Shein, the global fast-fashion company founded in China and headquartered in Singapore, attended a parliamentary hearing with the Business Select Committee[^parliament] (BSC).
|
||||||
|
|
||||||
|
[^parliament]: [BTC questions Shein, Temu, McDonalds, Tesco, Anti-Slavery Commissioner on labour rights and supply chain integrity (parliament.uk)](https://committees.parliament.uk/committee/365/business-and-trade-committee/news/204582/btc-questions-shein-temu-mcdonalds-tesco-antislavery-commissioner-on-labour-rights-and-supply-chain-integrity/)
|
||||||
|
|
||||||
|
Shein seems to be seeking an Initial Public Offering (IPO) on the London Stock Exchange, after their recent (2024) bid for the same on the New York Stock Exchange, which appears to have been withdrawn[^bbc-ipo]
|
||||||
|
|
||||||
|
[^bbc-ipo]: [Fashion giant Shein closer to London listing (bbc.co.uk)](https://www.bbc.co.uk/news/articles/c9xx8l600z6o)
|
||||||
|
|
||||||
|
It is likely that their failure to list in New York was due to concerns about Shein using cotton sourced from the Xinjiang region of China in garments they produce and sell. The Xinjiang region has been associated with sustained "systemic policies of mass detention, torture, and cultural persecution"[^hrw] against Uyghurs, which Human Rights Watch have called crimes against humanity. In 2022 independent testing of cotton from a variety of garments bought from Shein were found to contain cotton, at least partially, from the Xinjiang region of China[^bloomberg]. Shein did not provide any comment on these test results at the time.
|
||||||
|
|
||||||
|
[^hrw]: [China: Crimes Against Humanity in Xinjiang (hrw.org)](https://www.hrw.org/news/2021/04/19/china-crimes-against-humanity-xinjiang)
|
||||||
|
|
||||||
|
[^bloomberg]: [Shein’s Cotton Tied to Chinese Region Accused of Forced Labor (bloomberg.com; paywall)](https://www.bloomberg.com/news/features/2022-11-21/shein-s-cotton-clothes-tied-to-xinjiang-china-region-accused-of-forced-labor)
|
||||||
|
|
||||||
|
Due, in part, to human rights concerns the United States government signed the Uyghur Forced Labor Prevention Act in 2021, which prohibits the import of materials manufactured in whole or in part within the region. It seems likely that this legal framework contributed to a New York IPO being non-viable.
|
||||||
|
|
||||||
|
In the context of a London IPO, I was delighted to watch the BSC chair question Shein's General Counsel with the clear intention of addressing the core issues. I was frustrated and unimpressed when it was clear that Shein's General Counsel was unwilling or unable to answer this line of questioning with any specific facts or company policies about their sourcing and labour standards in their supply chain.
|
||||||
|
|
||||||
|
In 2024 Shein's Executive Chairman Donald Tang visited Paris and London, stating: "we are going public to embrace scrutiny and public diligence, putting ourselves in the public square and the large fish tank for people to examine, asking questions"[^politico]
|
||||||
|
|
||||||
|
[^politico]: [China’s Shein sweet-talks EU to stave off hurdles on ‘fast fashion’ goods (politico.eu)](https://www.politico.eu/article/china-shein-european-regulation-dumping-forced-labor-pollution-donald-tang-geopolitics/)
|
||||||
|
|
||||||
|
This recent hearing with the BSC was an opportunity for Shein to demonstrate, to the public and government, their commitment to scrutiny and diligence. In reality, their response "bordered on contempt", to quote the meeting chair.
|
||||||
|
|
||||||
|
Shein are clearly a well-resourced company who have consistently prioritised optimisation-via-data into a very favourable market valuation. Over the last few years, this same company faced sustained, justified, and frankly extremely worrying concerns about their supply chain. They have been questioned by organisations from the United States, European Union, and now Great Britain. Their public actions suggest Shein is a company uninterested in answering scrutiny, but instead interested in lip service and a dominant market position to the exclusion of competition, even if that involves slave labour.
|
||||||
|
|
||||||
|
We have yet to see Shein make tangible improvements to its supply chain. At the very least they should be able to articulate and defend policies regarding its sourcing and labour practices. Moreover, we should expect them to enact and enforce them.
|
||||||
|
|
||||||
|
It is extremely clear to you and I both that it is never acceptable to violate human rights. Shein, and other companies, appear to see their involvement in modern slavery to be a justifiable or non-noteworthy cost of doing business.
|
||||||
|
|
||||||
|
I do not want business to be this way. I do not wish for London's Stock Exchange to offer any kind of endorsement to these practices.
|
||||||
|
|
||||||
|
The fact is that many clothing companies in Great Britain exist today that make the lives of their workers *better*. That take great care to source materials fairly and sustainably, investing in the lives of their labour force.
|
||||||
|
|
||||||
|
This government has clearly articulated, many times, that it wishes to support working people. This involves setting a global example of a developed economy protecting the lives and labour which support it. I do not think anybody wants a country where violation of human rights becomes an acceptable or ignorable practice at certain scales of operation.
|
||||||
|
|
||||||
|
I want to have conversations about things more visionary than a life free from slavery. I want certainty that the clothes that fill our wardrobes (and increasingly, our landfills) didn't necessitate slavery. But while it is possible for individuals and companies to accumulate immense wealth by doing exactly this, we simply cannot have those discussions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
@ -56,14 +56,14 @@ export class BlogController {
|
||||||
resolvedFileName,
|
resolvedFileName,
|
||||||
markdownContent
|
markdownContent
|
||||||
);
|
);
|
||||||
this._markdownRepository = await MarkdownRepository.singleton();
|
this._markdownRepository = await MarkdownRepository.singleton(true);
|
||||||
return createdBlogPost;
|
return createdBlogPost;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllBlogPosts(
|
async getAllBlogPosts(
|
||||||
pageSize?: number
|
pageSize?: number
|
||||||
): Promise<Array<BlogPostListItem | BookReviewListItem | SnoutStreetStudiosPostListItem>> {
|
): Promise<Array<BlogPostListItem | BookReviewListItem | SnoutStreetStudiosPostListItem>> {
|
||||||
console.log('getAllBlogPosts')
|
console.log('getAllBlogPosts');
|
||||||
console.log({ pageSize });
|
console.log({ pageSize });
|
||||||
const blogPosts = await this._markdownRepository.blogPosts;
|
const blogPosts = await this._markdownRepository.blogPosts;
|
||||||
|
|
||||||
|
|
@ -83,12 +83,12 @@ export class BlogController {
|
||||||
(post) => this.snoutStreetStudiosPostToSnoutStreetStudiosPostListItem(post)
|
(post) => this.snoutStreetStudiosPostToSnoutStreetStudiosPostListItem(post)
|
||||||
);
|
);
|
||||||
|
|
||||||
const allBlogPosts = [...blogPostListItems, ...bookReviewListItems, ...snoutStreetStudiosPostListItems].sort((a, b) =>
|
const allBlogPosts = [...blogPostListItems, ...bookReviewListItems, ...snoutStreetStudiosPostListItems].sort(
|
||||||
a.date > b.date ? -1 : 1
|
(a, b) => (a.date > b.date ? -1 : 1)
|
||||||
)
|
);
|
||||||
|
|
||||||
if (pageSize === undefined) {
|
if (pageSize === undefined) {
|
||||||
console.log('returning all blog posts')
|
console.log('returning all blog posts');
|
||||||
return allBlogPosts;
|
return allBlogPosts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -56,8 +56,8 @@ export class MarkdownRepository {
|
||||||
this.snoutStreetStudiosPosts = new SnoutStreetStudiosPostSet(snoutStreetStudiosPosts);
|
this.snoutStreetStudiosPosts = new SnoutStreetStudiosPostSet(snoutStreetStudiosPosts);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async singleton(): Promise<MarkdownRepository> {
|
public static async singleton(forceRefresh = false): Promise<MarkdownRepository> {
|
||||||
if (!this._singleton) {
|
if (forceRefresh || !this._singleton) {
|
||||||
this._singleton = await MarkdownRepository.fromViteGlobImport(
|
this._singleton = await MarkdownRepository.fromViteGlobImport(
|
||||||
blogPostMetaGlobImport,
|
blogPostMetaGlobImport,
|
||||||
bookReviewsMetaGlobImport,
|
bookReviewsMetaGlobImport,
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,9 @@ import remarkRehype from 'remark-rehype';
|
||||||
import rehypeStringify from 'rehype-stringify';
|
import rehypeStringify from 'rehype-stringify';
|
||||||
import stripMarkdown from 'strip-markdown';
|
import stripMarkdown from 'strip-markdown';
|
||||||
import type { Parent, Literal } from 'unist';
|
import type { Parent, Literal } from 'unist';
|
||||||
import { load as loadYaml } from 'js-yaml';
|
import remarkGfm from 'remark-gfm';
|
||||||
|
|
||||||
type MarkdownDocumentType = 'body' | 'excerpt';
|
import { load as loadYaml } from 'js-yaml';
|
||||||
|
|
||||||
export class MarkdownBuilder {
|
export class MarkdownBuilder {
|
||||||
static async getHtml(markdownContent: string): Promise<string> {
|
static async getHtml(markdownContent: string): Promise<string> {
|
||||||
|
|
@ -20,7 +20,7 @@ export class MarkdownBuilder {
|
||||||
|
|
||||||
static getFrontmatter<T extends Record<string, any>>(markdownContent: string, fileName: string): T | null {
|
static getFrontmatter<T extends Record<string, any>>(markdownContent: string, fileName: string): T | null {
|
||||||
const processor = this.getFrontmatterProcessor();
|
const processor = this.getFrontmatterProcessor();
|
||||||
const parsedMarkdown: Parent<Literal> = processor.parse(markdownContent) as Parent<Literal>;
|
const parsedMarkdown = processor.parse(markdownContent);
|
||||||
|
|
||||||
const frontmatterNode: Literal | undefined = parsedMarkdown.children.find((node) => node.type === 'yaml');
|
const frontmatterNode: Literal | undefined = parsedMarkdown.children.find((node) => node.type === 'yaml');
|
||||||
|
|
||||||
|
|
@ -47,14 +47,14 @@ export class MarkdownBuilder {
|
||||||
.join(' ');
|
.join(' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
private static getFrontmatterProcessor(): Processor {
|
private static getFrontmatterProcessor() {
|
||||||
return unified() //
|
return unified() //
|
||||||
.use(remarkParse)
|
.use(remarkParse)
|
||||||
.use(remarkFrontmatter)
|
.use(remarkFrontmatter)
|
||||||
.use(remarkStringify);
|
.use(remarkStringify);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static getExcerptMarkdownProcessor(): Processor {
|
private static getExcerptMarkdownProcessor() {
|
||||||
return unified()
|
return unified()
|
||||||
.use(remarkParse)
|
.use(remarkParse)
|
||||||
.use(remarkStringify)
|
.use(remarkStringify)
|
||||||
|
|
@ -62,12 +62,13 @@ export class MarkdownBuilder {
|
||||||
.use(stripMarkdown);
|
.use(stripMarkdown);
|
||||||
}
|
}
|
||||||
|
|
||||||
static getDocumentProcessor(): Processor {
|
static getDocumentProcessor() {
|
||||||
return unified() //
|
return unified() //
|
||||||
.use(remarkParse)
|
.use(remarkParse)
|
||||||
|
.use(remarkGfm)
|
||||||
.use(remarkFrontmatter)
|
.use(remarkFrontmatter)
|
||||||
.use(remarkStringify)
|
.use(remarkStringify)
|
||||||
.use(remarkRehype, { allowDangerousHtml: true })
|
.use(remarkRehype, { allowDangerousHtml: true, footnoteLabel: 'notes', footnoteLabelTagName: 'sup' })
|
||||||
.use(rehypeStringify, {
|
.use(rehypeStringify, {
|
||||||
allowDangerousHtml: true,
|
allowDangerousHtml: true,
|
||||||
allowDangerousCharacters: true,
|
allowDangerousCharacters: true,
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ export const POST: RequestHandler = async ({ getClientAddress, request }) => {
|
||||||
let fileName: string;
|
let fileName: string;
|
||||||
let markdownContent: string;
|
let markdownContent: string;
|
||||||
let title: string;
|
let title: string;
|
||||||
let date: string;
|
|
||||||
let slug: string;
|
let slug: string;
|
||||||
let author: string;
|
let author: string;
|
||||||
|
|
||||||
|
|
@ -26,7 +25,6 @@ export const POST: RequestHandler = async ({ getClientAddress, request }) => {
|
||||||
fileName = requestBody.fileName;
|
fileName = requestBody.fileName;
|
||||||
markdownContent = requestBody.markdownContent;
|
markdownContent = requestBody.markdownContent;
|
||||||
title = requestBody.title;
|
title = requestBody.title;
|
||||||
date = requestBody.date;
|
|
||||||
slug = requestBody.slug;
|
slug = requestBody.slug;
|
||||||
author = requestBody.author;
|
author = requestBody.author;
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
|
@ -35,16 +33,7 @@ export const POST: RequestHandler = async ({ getClientAddress, request }) => {
|
||||||
error(400, 'Error in request body.');
|
error(400, 'Error in request body.');
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log({
|
if ([fileName, markdownContent, title, slug, author].includes(undefined)) {
|
||||||
fileName,
|
|
||||||
markdownContent,
|
|
||||||
title,
|
|
||||||
date,
|
|
||||||
slug,
|
|
||||||
author,
|
|
||||||
});
|
|
||||||
|
|
||||||
if ([fileName, markdownContent, title, date, slug, author].includes(undefined)) {
|
|
||||||
error(400, `Missing parameters.`);
|
error(400, `Missing parameters.`);
|
||||||
} else if (!['127.0.0.1', '::1'].includes(address)) {
|
} else if (!['127.0.0.1', '::1'].includes(address)) {
|
||||||
console.log(address);
|
console.log(address);
|
||||||
|
|
@ -53,9 +42,7 @@ export const POST: RequestHandler = async ({ getClientAddress, request }) => {
|
||||||
|
|
||||||
const controller = await BlogController.singleton();
|
const controller = await BlogController.singleton();
|
||||||
|
|
||||||
const worryinglyManualFrontMatter = [`---`, dumpYaml({ title, date: new Date(date), slug, author }), `---`].join(
|
const worryinglyManualFrontMatter = [`---`, dumpYaml({ title, date: new Date(), slug, author }), `---`].join(`\n`);
|
||||||
`\n`
|
|
||||||
);
|
|
||||||
const escapedMarkdown = markdownContent.replaceAll(/\\n/g, '\n');
|
const escapedMarkdown = markdownContent.replaceAll(/\\n/g, '\n');
|
||||||
|
|
||||||
const contentWithFrontmatter = [worryinglyManualFrontMatter, escapedMarkdown].join(`\n`);
|
const contentWithFrontmatter = [worryinglyManualFrontMatter, escapedMarkdown].join(`\n`);
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,6 @@ const thisDirectory = import.meta.url
|
||||||
.filter((part) => part !== '+server.ts')
|
.filter((part) => part !== '+server.ts')
|
||||||
.join('/');
|
.join('/');
|
||||||
|
|
||||||
export const prerender = true;
|
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
default: async ({ getClientAddress, request }) => {
|
default: async ({ getClientAddress, request }) => {
|
||||||
console.log(`Received request to create new blog post.`);
|
console.log(`Received request to create new blog post.`);
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import type { LoadEvent } from '@sveltejs/kit';
|
import type { LoadEvent } from '@sveltejs/kit';
|
||||||
import { differenceInCalendarDays, getYear } from 'date-fns';
|
import { differenceInCalendarDays, getYear } from 'date-fns';
|
||||||
export const prerender = true;
|
export const prerender = false;
|
||||||
|
|
||||||
interface BlogPostListItem {
|
interface BlogPostListItem {
|
||||||
title: string;
|
title: string;
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(#article p, a) {
|
:global(#article p, a) {
|
||||||
line-height: 160%;
|
line-height: 165%;
|
||||||
font-size: var(--font-size);
|
font-size: var(--font-size);
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
letter-spacing: -0.5px;
|
letter-spacing: -0.5px;
|
||||||
|
|
@ -129,6 +129,22 @@
|
||||||
font-size: var(--font-size);
|
font-size: var(--font-size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:global(sup > a) {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
vertical-align: super;
|
||||||
|
color: var(--brand-orange);
|
||||||
|
padding: 2px;
|
||||||
|
transition:
|
||||||
|
background-color 0.2s,
|
||||||
|
color 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
background-color: var(--brand-orange);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#article {
|
#article {
|
||||||
--padding: 4px;
|
--padding: 4px;
|
||||||
--font-size: var(--font-size-base);
|
--font-size: var(--font-size-base);
|
||||||
|
|
@ -137,6 +153,7 @@
|
||||||
width: var(--width);
|
width: var(--width);
|
||||||
max-width: var(--max-width);
|
max-width: var(--max-width);
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
|
line-height: 150%;
|
||||||
|
|
||||||
@media screen and (max-width: 700px) {
|
@media screen and (max-width: 700px) {
|
||||||
--font-size: var(--font-size-sm);
|
--font-size: var(--font-size-sm);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue