Compare commits
30 commits
experiment
...
main
Author | SHA1 | Date | |
---|---|---|---|
bd56e44585 | |||
|
9cb1984c8f | ||
02a980eb79 | |||
|
a238acf0db | ||
|
2595c53f83 | ||
|
155b82cbea | ||
|
12e3cf2f85 | ||
|
e59c210ef4 | ||
|
1fec60d46e | ||
|
8401ac9593 | ||
|
d78e75e9c7 | ||
|
28f272b75b | ||
|
7a7ceceead | ||
|
253ad2e1bc | ||
|
2a238bb835 | ||
|
1c31ef575a | ||
f9c65835fd | |||
|
8ae594d1f5 | ||
bda74f01f9 | |||
|
ddcd451b49 | ||
|
ce5b962e10 | ||
|
c58b597318 | ||
e7c0a762eb | |||
|
01086b6e9b | ||
34eee3d2c1 | |||
|
01b65d337b | ||
c5f9fec9e9 | |||
2b749e4c63 | |||
|
24da21e722 | ||
|
db04ee25e4 |
8 changed files with 723 additions and 870 deletions
32
.github/workflows/app.yml
vendored
32
.github/workflows/app.yml
vendored
|
@ -27,25 +27,23 @@ jobs:
|
||||||
semver: ${{ steps.gitversion.outputs.SemVer }}
|
semver: ${{ steps.gitversion.outputs.SemVer }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Install GitVersion
|
- name: Install GitVersion
|
||||||
uses: gittools/actions/gitversion/setup@v3.2.1
|
uses: gittools/actions/gitversion/setup@v4.1.0
|
||||||
with:
|
with:
|
||||||
versionSpec: "5.x"
|
versionSpec: "6.x"
|
||||||
- name: Determine Version
|
- name: Determine Version
|
||||||
id: gitversion
|
id: gitversion
|
||||||
uses: gittools/actions/gitversion/execute@v3.2.1
|
uses: gittools/actions/gitversion/execute@v4.1.0
|
||||||
with:
|
|
||||||
useConfigFile: true
|
|
||||||
|
|
||||||
build_and_publish:
|
build_and_publish:
|
||||||
name: Build & Publish
|
name: Build & Publish
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: versionize
|
needs: versionize
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Build container images
|
- name: Build container images
|
||||||
run: |
|
run: |
|
||||||
|
@ -73,7 +71,7 @@ jobs:
|
||||||
matrix:
|
matrix:
|
||||||
environment: [ 'prd' ]
|
environment: [ 'prd' ]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
with:
|
with:
|
||||||
sparse-checkout: |
|
sparse-checkout: |
|
||||||
infrastructure
|
infrastructure
|
||||||
|
@ -86,13 +84,13 @@ jobs:
|
||||||
subscription-id: ${{ secrets.CALQUE_PRD_SUBSCRIPTION_ID }}
|
subscription-id: ${{ secrets.CALQUE_PRD_SUBSCRIPTION_ID }}
|
||||||
|
|
||||||
- name: Deploy bicep
|
- name: Deploy bicep
|
||||||
uses: Azure/cli@v2
|
uses: azure/cli@v2
|
||||||
with:
|
with:
|
||||||
azcliversion: 2.66.0
|
azcliversion: 2.75.0
|
||||||
inlineScript: |
|
inlineScript: >-
|
||||||
az deployment sub create \
|
az deployment sub create
|
||||||
--location westeurope \
|
--location westeurope
|
||||||
--template-file infrastructure/main.bicep \
|
--template-file infrastructure/main.bicep
|
||||||
--parameters infrastructure/params/${{ matrix.environment }}.bicepparam \
|
--parameters infrastructure/params/${{ matrix.environment }}.bicepparam
|
||||||
--parameters version=${{needs.versionize.outputs.semver}} \
|
--parameters version=${{needs.versionize.outputs.semver}}
|
||||||
--parameters registryUrl=${{ secrets.ACR_LOGIN_SERVER }}
|
--parameters registryUrl=${{ secrets.ACR_LOGIN_SERVER }}
|
|
@ -4,16 +4,17 @@ WORKDIR /usr/src/app
|
||||||
FROM base AS install
|
FROM base AS install
|
||||||
RUN mkdir -p /temp/dev
|
RUN mkdir -p /temp/dev
|
||||||
COPY package.json bun.lock /temp/dev
|
COPY package.json bun.lock /temp/dev
|
||||||
|
COPY patches/ /temp/dev/patches/
|
||||||
RUN cd /temp/dev && bun install --frozen-lockfile
|
RUN cd /temp/dev && bun install --frozen-lockfile
|
||||||
|
|
||||||
RUN mkdir -p /temp/prod
|
RUN mkdir -p /temp/prod
|
||||||
COPY package.json bun.lock /temp/prod/
|
COPY package.json bun.lock /temp/prod/
|
||||||
|
COPY patches/ /temp/prod/patches/
|
||||||
RUN cd /temp/prod && bun install --frozen-lockfile --production
|
RUN cd /temp/prod && bun install --frozen-lockfile --production
|
||||||
|
|
||||||
FROM base AS prerelease
|
FROM base AS prerelease
|
||||||
COPY --from=install /temp/dev/node_modules node_modules
|
COPY --from=install /temp/dev/node_modules node_modules
|
||||||
COPY . .
|
COPY . .
|
||||||
# RUN echo "SESSION_SECRET=$(head -c 64 /dev/random | base64)" > .env
|
|
||||||
|
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
ENV SERVER_PRESET=bun
|
ENV SERVER_PRESET=bun
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
assembly-versioning-scheme: MajorMinorPatch
|
|
||||||
assembly-file-versioning-scheme: MajorMinorPatchTag
|
|
||||||
assembly-informational-format: "{InformationalVersion}"
|
|
||||||
mode: Mainline
|
|
||||||
tag-prefix: "[vV]"
|
|
||||||
continuous-delivery-fallback-tag: ci
|
|
||||||
major-version-bump-message: '\+semver:\s?(breaking|major)'
|
|
||||||
minor-version-bump-message: '\+semver:\s?(feature|minor)'
|
|
||||||
patch-version-bump-message: '\+semver:\s?(fix|patch)'
|
|
||||||
no-bump-message: '\+semver:\s?(none|skip)'
|
|
||||||
legacy-semver-padding: 4
|
|
||||||
build-metadata-padding: 4
|
|
||||||
commits-since-version-source-padding: 4
|
|
||||||
commit-message-incrementing: Enabled
|
|
||||||
branches: {}
|
|
||||||
ignore:
|
|
||||||
sha: []
|
|
||||||
increment: Inherit
|
|
||||||
commit-date-format: yyyy-MM-dd
|
|
||||||
merge-message-formats: {}
|
|
|
@ -19,7 +19,7 @@ var context = {
|
||||||
deployedAt: deployedAt
|
deployedAt: deployedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
resource calqueResourceGroup 'Microsoft.Resources/resourceGroups@2025-03-01' = {
|
resource calqueResourceGroup 'Microsoft.Resources/resourceGroups@2025-04-01' = {
|
||||||
name: 'rg-${locationAbbreviation}-${environment}-${projectName}'
|
name: 'rg-${locationAbbreviation}-${environment}-${projectName}'
|
||||||
location: location
|
location: location
|
||||||
}
|
}
|
||||||
|
|
46
package.json
46
package.json
|
@ -6,50 +6,50 @@
|
||||||
"bun": ">=1"
|
"bun": ">=1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@solid-primitives/clipboard": "^1.6.0",
|
"@solid-primitives/clipboard": "^1.6.2",
|
||||||
"@solid-primitives/destructure": "^0.2.0",
|
"@solid-primitives/destructure": "^0.2.2",
|
||||||
"@solid-primitives/i18n": "^2.2.0",
|
"@solid-primitives/i18n": "^2.2.1",
|
||||||
"@solid-primitives/scheduled": "^1.5.0",
|
"@solid-primitives/scheduled": "^1.5.2",
|
||||||
"@solid-primitives/selection": "^0.1.0",
|
"@solid-primitives/selection": "^0.1.3",
|
||||||
"@solid-primitives/storage": "^4.3.1",
|
"@solid-primitives/storage": "^4.3.3",
|
||||||
"@solid-primitives/timer": "^1.4.0",
|
"@solid-primitives/timer": "^1.4.2",
|
||||||
"@solidjs/meta": "^0.29.4",
|
"@solidjs/meta": "^0.29.4",
|
||||||
"@solidjs/router": "^0.15.3",
|
"@solidjs/router": "^0.15.3",
|
||||||
"@solidjs/start": "^1.1.0",
|
"@solidjs/start": "^1.1.7",
|
||||||
"dexie": "^4.0.11",
|
"dexie": "^4.0.11",
|
||||||
"flag-icons": "^7.3.2",
|
"flag-icons": "^7.5.0",
|
||||||
"iterator-helpers-polyfill": "^3.0.1",
|
"iterator-helpers-polyfill": "^3.0.1",
|
||||||
"rehype-parse": "^9.0.1",
|
"rehype-parse": "^9.0.1",
|
||||||
"rehype-remark": "^10.0.0",
|
"rehype-remark": "^10.0.1",
|
||||||
"rehype-stringify": "^10.0.1",
|
"rehype-stringify": "^10.0.1",
|
||||||
"remark-parse": "^11.0.0",
|
"remark-parse": "^11.0.0",
|
||||||
"remark-rehype": "^11.1.1",
|
"remark-rehype": "^11.1.2",
|
||||||
"remark-stringify": "^11.0.0",
|
"remark-stringify": "^11.0.0",
|
||||||
"sitemap": "^8.0.0",
|
"sitemap": "^8.0.0",
|
||||||
"solid-icons": "^1.1.0",
|
"solid-icons": "^1.1.0",
|
||||||
"solid-js": "^1.9.4",
|
"solid-js": "^1.9.7",
|
||||||
"ts-pattern": "^5.6.2",
|
"ts-pattern": "^5.7.1",
|
||||||
"unified": "^11.0.5",
|
"unified": "^11.0.5",
|
||||||
"unist-util-find": "^3.0.0",
|
"unist-util-find": "^3.0.0",
|
||||||
"unist-util-visit": "^5.0.0",
|
"unist-util-visit": "^5.0.0",
|
||||||
"vinxi": "^0.5.3"
|
"vinxi": "^0.5.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@happy-dom/global-registrator": "^17.0.3",
|
"@happy-dom/global-registrator": "^18.0.1",
|
||||||
"@sinonjs/fake-timers": "^14.0.0",
|
"@sinonjs/fake-timers": "^14.0.0",
|
||||||
"@solidjs/testing-library": "^0.8.10",
|
"@solidjs/testing-library": "^0.8.10",
|
||||||
"@testing-library/jest-dom": "^6.6.3",
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
"@testing-library/user-event": "^14.6.1",
|
"@testing-library/user-event": "^14.6.1",
|
||||||
"@types/sinonjs__fake-timers": "^8.1.5",
|
"@types/sinonjs__fake-timers": "^8.1.5",
|
||||||
"@types/wicg-file-system-access": "^2023.10.5",
|
"@types/wicg-file-system-access": "^2023.10.6",
|
||||||
"@vitest/coverage-istanbul": "3.1.4",
|
"@vitest/coverage-istanbul": "3.2.4",
|
||||||
"@vitest/coverage-v8": "3.1.4",
|
"@vitest/coverage-v8": "3.2.4",
|
||||||
"bun-types": "^1.2.2",
|
"bun-types": "^1.2.19",
|
||||||
"jsdom": "^26.0.0",
|
"jsdom": "^26.1.0",
|
||||||
"solid-devtools": "^0.34.0",
|
"solid-devtools": "^0.34.3",
|
||||||
"vite-plugin-solid": "^2.11.2",
|
"vite-plugin-solid": "^2.11.7",
|
||||||
"vite-plugin-solid-svg": "^0.8.1",
|
"vite-plugin-solid-svg": "^0.8.1",
|
||||||
"vitest": "^3.0.6",
|
"vitest": "^3.2.4",
|
||||||
"workbox-window": "^7.3.0"
|
"workbox-window": "^7.3.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
@ -12,7 +12,7 @@ interface Contents extends Map<string, Map<string, string>> { }
|
||||||
|
|
||||||
export const read = (file: File): Promise<Map<string, string> | undefined> => {
|
export const read = (file: File): Promise<Map<string, string> | undefined> => {
|
||||||
switch (file.type) {
|
switch (file.type) {
|
||||||
case 'application/json': return json.load(file.stream());
|
case 'application/json': return json.load(file.text());
|
||||||
|
|
||||||
default: return Promise.resolve(undefined);
|
default: return Promise.resolve(undefined);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,200 +1,20 @@
|
||||||
import { decode } from "~/utilities";
|
import { decode } from "~/utilities";
|
||||||
|
|
||||||
export async function load(stream: ReadableStream<Uint8Array>): Promise<Map<string, string>> {
|
export async function load(text: Promise<string>): Promise<Map<string, string>> {
|
||||||
return new Map(await Array.fromAsync(parse(stream), ({ key, value }) => [key, value]));
|
const source = JSON.parse(await text);
|
||||||
}
|
const result = new Map();
|
||||||
|
const candidates = Object.entries(source);
|
||||||
|
|
||||||
interface Entry {
|
while (candidates.length !== 0) {
|
||||||
key: string;
|
const [ key, value ] = candidates.shift()!;
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface State {
|
if (typeof value !== 'object' || value === null || value === undefined) {
|
||||||
(token: Token): State;
|
result.set(key, decode(value as string));
|
||||||
entry?: Entry
|
|
||||||
}
|
|
||||||
|
|
||||||
const states = {
|
|
||||||
none(): State {
|
|
||||||
return (token: Token) => {
|
|
||||||
if (token.kind === 'braceOpen') {
|
|
||||||
return states.object();
|
|
||||||
}
|
|
||||||
|
|
||||||
return states.none;
|
|
||||||
};
|
|
||||||
},
|
|
||||||
object({ path = [], expect = 'key' }: Partial<{ path: string[], expect: 'key' | 'colon' | 'value' }> = {}): State {
|
|
||||||
return (token: Token) => {
|
|
||||||
switch (expect) {
|
|
||||||
case 'key': {
|
|
||||||
if (token.kind === 'braceClose') {
|
|
||||||
return states.object({
|
|
||||||
path: path.slice(0, -1),
|
|
||||||
expect: 'key',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (token.kind === 'string') {
|
|
||||||
return states.object({
|
|
||||||
path: [...path, token.value],
|
|
||||||
expect: 'colon'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return states.error(`Expected a key, got ${token.kind} instead`);
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'colon': {
|
|
||||||
if (token.kind !== 'colon') {
|
|
||||||
return states.error(`Expected a ':', got ${token.kind} instead`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return states.object({
|
|
||||||
path,
|
|
||||||
expect: 'value'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'value': {
|
|
||||||
if (token.kind === 'braceOpen') {
|
|
||||||
return states.object({
|
|
||||||
path,
|
|
||||||
expect: 'key',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (token.kind === 'string') {
|
|
||||||
const next = states.object({
|
|
||||||
path: path.slice(0, -1),
|
|
||||||
expect: 'key',
|
|
||||||
});
|
|
||||||
|
|
||||||
next.entry = { key: path.join('.'), value: decode(token.value) };
|
|
||||||
|
|
||||||
return next
|
|
||||||
}
|
|
||||||
|
|
||||||
return states.error(`Invalid value type found '${token.kind}'`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return states.none();
|
|
||||||
}
|
}
|
||||||
},
|
else {
|
||||||
error(message: string): State {
|
candidates.unshift(...Object.entries(value).map<[string, any]>(([ k, v ]) => [`${key}.${k}`, v]));
|
||||||
throw new Error(message);
|
|
||||||
|
|
||||||
return states.none();
|
|
||||||
},
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
async function* parse(stream: ReadableStream<Uint8Array>): AsyncGenerator<any, void, unknown> {
|
|
||||||
let state = states.none();
|
|
||||||
|
|
||||||
for await (const token of tokenize(read(toGenerator(stream)))) {
|
|
||||||
try {
|
|
||||||
state = state(token);
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state.entry) {
|
|
||||||
yield state.entry;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
async function* take<T>(iterable: AsyncIterable<T>, numberToTake: number): AsyncGenerator<T, void, unknown> {
|
return result;
|
||||||
let i = 0;
|
|
||||||
for await (const entry of iterable) {
|
|
||||||
yield entry;
|
|
||||||
|
|
||||||
i++;
|
|
||||||
|
|
||||||
if (i === numberToTake) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Token = { start: number, length: number } & (
|
|
||||||
| { kind: 'braceOpen' }
|
|
||||||
| { kind: 'braceClose' }
|
|
||||||
| { kind: 'colon' }
|
|
||||||
| { kind: 'string', value: string }
|
|
||||||
);
|
|
||||||
|
|
||||||
async function* tokenize(characters: AsyncIterable<number>): AsyncGenerator<Token, void, unknown> {
|
|
||||||
let buffer: string = '';
|
|
||||||
let clearBuffer = false;
|
|
||||||
let start = 0;
|
|
||||||
let i = 0;
|
|
||||||
|
|
||||||
for await (const character of characters) {
|
|
||||||
if (buffer.length === 0) {
|
|
||||||
start = i;
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer += String.fromCharCode(character);
|
|
||||||
const length = buffer.length;
|
|
||||||
|
|
||||||
if (buffer === '{') {
|
|
||||||
yield { kind: 'braceOpen', start, length };
|
|
||||||
clearBuffer = true;
|
|
||||||
}
|
|
||||||
else if (buffer === '}') {
|
|
||||||
yield { kind: 'braceClose', start, length };
|
|
||||||
clearBuffer = true;
|
|
||||||
}
|
|
||||||
else if (buffer === ':') {
|
|
||||||
yield { kind: 'colon', start, length };
|
|
||||||
clearBuffer = true;
|
|
||||||
}
|
|
||||||
else if (buffer.length > 1 && buffer.startsWith('"') && buffer.endsWith('"') && buffer.at(-2) !== '\\') {
|
|
||||||
yield { kind: 'string', start, length, value: buffer.slice(1, buffer.length - 1) };
|
|
||||||
clearBuffer = true;
|
|
||||||
}
|
|
||||||
else if (buffer === ',') {
|
|
||||||
clearBuffer = true;
|
|
||||||
}
|
|
||||||
else if (buffer.trim() === '') {
|
|
||||||
clearBuffer = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clearBuffer) {
|
|
||||||
buffer = '';
|
|
||||||
clearBuffer = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function* read(chunks: AsyncIterable<Uint8Array>): AsyncGenerator<number, void, unknown> {
|
|
||||||
for await (const chunk of chunks) {
|
|
||||||
for (const character of chunk) {
|
|
||||||
yield character;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function* toGenerator<T>(stream: ReadableStream<T>): AsyncGenerator<T, void, unknown> {
|
|
||||||
const reader = stream.getReader();
|
|
||||||
|
|
||||||
try {
|
|
||||||
while (true) {
|
|
||||||
const { done, value } = await reader.read();
|
|
||||||
|
|
||||||
if (done) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
yield value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
reader.releaseLock();
|
|
||||||
}
|
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue