@_linked/cli 1.11.0 → 1.12.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/CHANGELOG.md +69 -0
- package/README.md +48 -0
- package/defaults/app-react-native/README.md +56 -0
- package/defaults/app-react-native/apps/mobile/App.tsx +26 -0
- package/defaults/app-react-native/apps/mobile/__tests__/linkedDefaults.test.tsx +65 -0
- package/defaults/app-react-native/apps/mobile/__tests__/shapes.test.ts +27 -0
- package/defaults/app-react-native/apps/mobile/app.json +14 -0
- package/defaults/app-react-native/apps/mobile/gitignore.template +41 -0
- package/defaults/app-react-native/apps/mobile/index.ts +8 -0
- package/defaults/app-react-native/apps/mobile/metro.config.js +17 -0
- package/defaults/app-react-native/apps/mobile/package.json +41 -0
- package/defaults/app-react-native/apps/mobile/src/shell/linkedDefaults.tsx +10 -0
- package/defaults/app-react-native/apps/mobile/tsconfig.json +8 -0
- package/defaults/app-react-native/gitignore.template +6 -0
- package/defaults/app-react-native/package.json +14 -0
- package/defaults/app-react-native/packages/app-shapes/package.json +10 -0
- package/defaults/app-react-native/packages/app-shapes/src/index.ts +2 -0
- package/defaults/app-react-native/packages/app-shapes/src/package.ts +10 -0
- package/defaults/app-react-native/packages/app-shapes/src/shapes/Example.ts +17 -0
- package/defaults/app-react-native/packages/app-shapes/tsconfig.json +12 -0
- package/defaults/app-react-native/services/api/README.md +4 -0
- package/defaults/app-react-native/services/api/package.json +5 -0
- package/defaults/package/npmignore.template +6 -0
- package/defaults/package/src/backend.ts +5 -0
- package/defaults/package/src/data/example-ontology.json +10 -0
- package/defaults/package/src/data/example-ontology.json.d.ts +1 -0
- package/defaults/package/src/index.ts +8 -0
- package/defaults/package/src/ontologies/example-ontology.ts +42 -0
- package/defaults/package/src/package.ts +16 -0
- package/defaults/package/src/shapes/index.ts +5 -0
- package/defaults/package/src/types.ts +4 -0
- package/defaults/package/tsconfig.json +17 -0
- package/defaults/setup-publish/dual-branch/github/workflows/publish.yml +29 -15
- package/lib/esm/cli-methods.d.ts +34 -0
- package/lib/esm/cli-methods.d.ts.map +1 -1
- package/lib/esm/cli-methods.js +286 -11
- package/lib/esm/cli-methods.js.map +1 -1
- package/lib/esm/cli.d.ts.map +1 -1
- package/lib/esm/cli.js +9 -1
- package/lib/esm/cli.js.map +1 -1
- package/lib/esm/commands/setup-publish.js +23 -2
- package/lib/esm/commands/setup-publish.js.map +1 -1
- package/lib/esm/utils/packageSetup.d.ts +19 -0
- package/lib/esm/utils/packageSetup.d.ts.map +1 -0
- package/lib/esm/utils/packageSetup.js +23 -0
- package/lib/esm/utils/packageSetup.js.map +1 -0
- package/lib/esm/utils/shippedDotfiles.d.ts +2 -0
- package/lib/esm/utils/shippedDotfiles.d.ts.map +1 -0
- package/lib/esm/utils/shippedDotfiles.js +21 -0
- package/lib/esm/utils/shippedDotfiles.js.map +1 -0
- package/lib/esm/vite-config.d.ts +14 -0
- package/lib/esm/vite-config.d.ts.map +1 -1
- package/lib/esm/vite-config.js +7 -3
- package/lib/esm/vite-config.js.map +1 -1
- package/package.json +9 -1
- package/.changeset/README.md +0 -20
- package/.changeset/config.json +0 -14
- package/.github/workflows/changeset-check.yml +0 -44
- package/.github/workflows/ci.yml +0 -51
- package/.github/workflows/publish.yml +0 -77
- package/.prettierignore +0 -4
- package/.prettierrc.json +0 -24
- package/babel.config.cjs +0 -11
- package/docs/architecture/vite-runtime-config.md +0 -57
- package/jest.config.cjs +0 -33
- package/lib-template/cjs/package.json +0 -3
- package/lib-template/esm/package.json +0 -3
- package/test-results/.last-run.json +0 -6
- package/test-results/create-app-Person-fixture-add-/342/206/222-edit-/342/206/222-delete-round-trip/error-context.md +0 -16
- package/test-results/create-app-Person-fixture-add-/342/206/222-edit-/342/206/222-delete-round-trip/trace.zip +0 -0
- package/tests/e2e/create-app.spec.ts +0 -157
- package/tests/unit/safeYarn.test.ts +0 -55
- package/tsconfig-cjs.json +0 -10
- package/tsconfig-esm.json +0 -10
package/jest.config.cjs
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
// Jest config for @_linked/cli unit tests.
|
|
2
|
-
//
|
|
3
|
-
// The cli is now ESM-only (no lib/cjs). Rather than configure jest's ESM mode
|
|
4
|
-
// (notoriously fiddly), tests import raw `src/` .ts and let babel-jest
|
|
5
|
-
// transpile ESM→CJS. The `moduleNameMapper` below strips the `.js` suffix from
|
|
6
|
-
// relative specifiers (src uses the published-output `./foo.js` convention) so
|
|
7
|
-
// they resolve back to the `.ts` source.
|
|
8
|
-
//
|
|
9
|
-
// E2E specs live in tests/e2e/ and run under Playwright (see
|
|
10
|
-
// playwright.config.ts), not Jest.
|
|
11
|
-
module.exports = {
|
|
12
|
-
testEnvironment: 'node',
|
|
13
|
-
rootDir: '.',
|
|
14
|
-
roots: ['<rootDir>/tests/unit'],
|
|
15
|
-
testMatch: ['**/*.test.{ts,js}'],
|
|
16
|
-
transform: {
|
|
17
|
-
'^.+\\.tsx?$': [
|
|
18
|
-
'babel-jest',
|
|
19
|
-
{
|
|
20
|
-
configFile: false,
|
|
21
|
-
babelrc: false,
|
|
22
|
-
presets: [
|
|
23
|
-
['@babel/preset-env', { targets: { node: 'current' } }],
|
|
24
|
-
'@babel/preset-typescript',
|
|
25
|
-
],
|
|
26
|
-
},
|
|
27
|
-
],
|
|
28
|
-
},
|
|
29
|
-
moduleNameMapper: {
|
|
30
|
-
'^(\\.{1,2}/.*)\\.js$': '$1',
|
|
31
|
-
},
|
|
32
|
-
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'],
|
|
33
|
-
};
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# Instructions
|
|
2
|
-
|
|
3
|
-
- Following Playwright test failed.
|
|
4
|
-
- Explain why, be concise, respect Playwright best practices.
|
|
5
|
-
- Provide a snippet of code with the fix, if possible.
|
|
6
|
-
|
|
7
|
-
# Test info
|
|
8
|
-
|
|
9
|
-
- Name: create-app.spec.ts >> Person fixture: add → edit → delete round-trip
|
|
10
|
-
- Location: tests/e2e/create-app.spec.ts:113:1
|
|
11
|
-
|
|
12
|
-
# Error details
|
|
13
|
-
|
|
14
|
-
```
|
|
15
|
-
Error: Log message "/Started.*Server/" not received after 60000ms
|
|
16
|
-
```
|
|
Binary file
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
// End-to-end: scaffold a Linked app via `linked create-app`, install, start it
|
|
2
|
-
// against a fresh Fuseki container, then drive the Person CRUD flow.
|
|
3
|
-
//
|
|
4
|
-
// Self-contained: scaffolds into an OS tmpdir, installs deps from npm,
|
|
5
|
-
// starts a Fuseki testcontainer, exercises the Person fixture. No
|
|
6
|
-
// dependency on any parent monorepo. Companion `@_linked/*` packages
|
|
7
|
-
// must be published to npm at compatible versions; if they aren't yet,
|
|
8
|
-
// set SKIP_E2E=1 to skip this test.
|
|
9
|
-
import {test, expect} from '@playwright/test';
|
|
10
|
-
import {GenericContainer, Wait, StartedTestContainer} from 'testcontainers';
|
|
11
|
-
import {spawn, ChildProcess, execSync} from 'node:child_process';
|
|
12
|
-
import {rmSync, existsSync, mkdtempSync} from 'node:fs';
|
|
13
|
-
import {tmpdir} from 'node:os';
|
|
14
|
-
import {dirname, join, resolve} from 'node:path';
|
|
15
|
-
import {fileURLToPath} from 'node:url';
|
|
16
|
-
|
|
17
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
|
-
const CLI_LAUNCH = resolve(__dirname, '../../lib/esm/launch.js');
|
|
19
|
-
|
|
20
|
-
// Deterministic-enough app name; salted so concurrent runs don't collide.
|
|
21
|
-
const APP_NAME = `e2e-test-app-${process.pid}`;
|
|
22
|
-
const SCAFFOLD_ROOT = mkdtempSync(join(tmpdir(), 'linked-cli-e2e-'));
|
|
23
|
-
const APP_DIR = join(SCAFFOLD_ROOT, APP_NAME);
|
|
24
|
-
const DATASET = `${APP_NAME}-main`;
|
|
25
|
-
const APP_PORT = 4400 + (process.pid % 100); // avoid collision with a CN backend on 4040
|
|
26
|
-
|
|
27
|
-
let fuseki: StartedTestContainer;
|
|
28
|
-
let appProc: ChildProcess | null = null;
|
|
29
|
-
let fusekiUrl: string;
|
|
30
|
-
|
|
31
|
-
test.beforeAll(async () => {
|
|
32
|
-
if (process.env.SKIP_E2E === '1') test.skip();
|
|
33
|
-
|
|
34
|
-
// 1. Start a fresh Fuseki container with a random host port.
|
|
35
|
-
fuseki = await new GenericContainer('stain/jena-fuseki')
|
|
36
|
-
.withExposedPorts(3030)
|
|
37
|
-
.withEnvironment({ADMIN_PASSWORD: 'admin'})
|
|
38
|
-
.withWaitStrategy(Wait.forLogMessage(/Started.*Server/))
|
|
39
|
-
.start();
|
|
40
|
-
fusekiUrl = `http://${fuseki.getHost()}:${fuseki.getMappedPort(3030)}`;
|
|
41
|
-
|
|
42
|
-
// 2. Scaffold the app via the local CLI build.
|
|
43
|
-
if (existsSync(APP_DIR)) rmSync(APP_DIR, {recursive: true, force: true});
|
|
44
|
-
execSync(
|
|
45
|
-
`node ${CLI_LAUNCH} create-app ${APP_NAME} --app-name "E2E" --app-prefix e2e --app-domain e2e.local --skip-install`,
|
|
46
|
-
{cwd: SCAFFOLD_ROOT, stdio: 'inherit'},
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
// 3. Create the matching dataset in Fuseki. Template default is
|
|
50
|
-
// `${hyphen_name}-main` → matches DATASET above.
|
|
51
|
-
const adminAuth = Buffer.from('admin:admin').toString('base64');
|
|
52
|
-
const resp = await fetch(`${fusekiUrl}/$/datasets`, {
|
|
53
|
-
method: 'POST',
|
|
54
|
-
headers: {
|
|
55
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
56
|
-
Authorization: `Basic ${adminAuth}`,
|
|
57
|
-
},
|
|
58
|
-
body: `dbName=${DATASET}&dbType=mem`,
|
|
59
|
-
});
|
|
60
|
-
if (!resp.ok && resp.status !== 409) {
|
|
61
|
-
throw new Error(`Failed to create dataset: ${resp.status} ${await resp.text()}`);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// 4. Install scaffolded app deps from npm. The scaffolded package.json
|
|
65
|
-
// pins @_linked/* versions; install resolves them straight from the
|
|
66
|
-
// registry (no workspace symlinks involved).
|
|
67
|
-
execSync('yarn install', {cwd: APP_DIR, stdio: 'inherit'});
|
|
68
|
-
|
|
69
|
-
// 5. Boot the app with env overrides pointing at the testcontainer Fuseki.
|
|
70
|
-
appProc = spawn('yarn', ['start'], {
|
|
71
|
-
cwd: APP_DIR,
|
|
72
|
-
env: {
|
|
73
|
-
...process.env,
|
|
74
|
-
FUSEKI_BASE_URL: fusekiUrl,
|
|
75
|
-
FUSEKI_DATASET: DATASET,
|
|
76
|
-
PORT: String(APP_PORT),
|
|
77
|
-
},
|
|
78
|
-
stdio: 'inherit',
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
// 6. Wait for HTTP 200 on /.
|
|
82
|
-
await waitForServer(`http://localhost:${APP_PORT}/`, 90_000);
|
|
83
|
-
}, /* timeout */ 4 * 60 * 1000);
|
|
84
|
-
|
|
85
|
-
test.afterAll(async () => {
|
|
86
|
-
// Kill the app server.
|
|
87
|
-
if (appProc) {
|
|
88
|
-
appProc.kill('SIGTERM');
|
|
89
|
-
await new Promise((r) => setTimeout(r, 1000));
|
|
90
|
-
if (!appProc.killed) appProc.kill('SIGKILL');
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Drop the Fuseki dataset (idempotent).
|
|
94
|
-
try {
|
|
95
|
-
const adminAuth = Buffer.from('admin:admin').toString('base64');
|
|
96
|
-
await fetch(`${fusekiUrl}/$/datasets/${DATASET}`, {
|
|
97
|
-
method: 'DELETE',
|
|
98
|
-
headers: {Authorization: `Basic ${adminAuth}`},
|
|
99
|
-
});
|
|
100
|
-
} catch {
|
|
101
|
-
// Container may be gone already; ignore.
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// Stop testcontainer.
|
|
105
|
-
await fuseki?.stop().catch(() => {});
|
|
106
|
-
|
|
107
|
-
// Remove scaffolded app folder + its parent tmpdir.
|
|
108
|
-
if (existsSync(SCAFFOLD_ROOT)) {
|
|
109
|
-
rmSync(SCAFFOLD_ROOT, {recursive: true, force: true});
|
|
110
|
-
}
|
|
111
|
-
}, /* timeout */ 2 * 60 * 1000);
|
|
112
|
-
|
|
113
|
-
test('Person fixture: add → edit → delete round-trip', async ({page}) => {
|
|
114
|
-
await page.goto(`http://localhost:${APP_PORT}/`);
|
|
115
|
-
await expect(page.getByTestId('person-overview')).toBeVisible();
|
|
116
|
-
|
|
117
|
-
// Add Alice
|
|
118
|
-
await page.getByPlaceholder('First name').fill('Alice');
|
|
119
|
-
await page.getByPlaceholder('Last name').fill('Anderson');
|
|
120
|
-
await page.getByRole('button', {name: 'Add'}).click();
|
|
121
|
-
await expect(page.getByTestId('person-name').filter({hasText: 'Alice Anderson'})).toBeVisible();
|
|
122
|
-
|
|
123
|
-
// Edit to Alicia
|
|
124
|
-
await page.getByRole('button', {name: 'Edit'}).first().click();
|
|
125
|
-
const editRow = page.getByTestId('person-edit').first();
|
|
126
|
-
await editRow.getByLabel('First name').fill('Alicia');
|
|
127
|
-
await editRow.getByRole('button', {name: 'Save'}).click();
|
|
128
|
-
await expect(
|
|
129
|
-
page.getByTestId('person-name').filter({hasText: 'Alicia Anderson'}),
|
|
130
|
-
).toBeVisible();
|
|
131
|
-
|
|
132
|
-
// Reload — change must persist
|
|
133
|
-
await page.reload();
|
|
134
|
-
await expect(
|
|
135
|
-
page.getByTestId('person-name').filter({hasText: 'Alicia Anderson'}),
|
|
136
|
-
).toBeVisible();
|
|
137
|
-
|
|
138
|
-
// Delete — row disappears
|
|
139
|
-
await page.getByRole('button', {name: 'Delete'}).first().click();
|
|
140
|
-
await expect(
|
|
141
|
-
page.getByTestId('person-name').filter({hasText: 'Alicia Anderson'}),
|
|
142
|
-
).toBeHidden();
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
async function waitForServer(url: string, timeoutMs: number): Promise<void> {
|
|
146
|
-
const deadline = Date.now() + timeoutMs;
|
|
147
|
-
while (Date.now() < deadline) {
|
|
148
|
-
try {
|
|
149
|
-
const r = await fetch(url, {method: 'GET'});
|
|
150
|
-
if (r.ok) return;
|
|
151
|
-
} catch {
|
|
152
|
-
/* not ready yet */
|
|
153
|
-
}
|
|
154
|
-
await new Promise((res) => setTimeout(res, 1000));
|
|
155
|
-
}
|
|
156
|
-
throw new Error(`Server at ${url} did not respond within ${timeoutMs}ms`);
|
|
157
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
// Verifies that safeYarn honours LINKED_YARN_DRY_RUN=1 — it logs the would-be
|
|
2
|
-
// command and the parsed args, then returns without executing yarn or
|
|
3
|
-
// touching mrgit-related lockfiles. Covers a deferred gap from
|
|
4
|
-
// docs/backlog/010-cli-test-harness.md.
|
|
5
|
-
import {safeYarn} from '../../src/commands/safe-yarn.js';
|
|
6
|
-
|
|
7
|
-
describe('safeYarn (dry-run)', () => {
|
|
8
|
-
const originalEnv = process.env.LINKED_YARN_DRY_RUN;
|
|
9
|
-
let logSpy: jest.SpyInstance;
|
|
10
|
-
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
process.env.LINKED_YARN_DRY_RUN = '1';
|
|
13
|
-
logSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
afterEach(() => {
|
|
17
|
-
if (originalEnv === undefined) {
|
|
18
|
-
delete process.env.LINKED_YARN_DRY_RUN;
|
|
19
|
-
} else {
|
|
20
|
-
process.env.LINKED_YARN_DRY_RUN = originalEnv;
|
|
21
|
-
}
|
|
22
|
-
logSpy.mockRestore();
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
test('passes a plain install through', async () => {
|
|
26
|
-
await safeYarn(['install']);
|
|
27
|
-
const joined = logSpy.mock.calls.map((c) => c.join(' ')).join('\n');
|
|
28
|
-
expect(joined).toMatch(/yarn install/);
|
|
29
|
-
expect(joined).toMatch(/\["install"\]/);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
test('preserves multi-word args', async () => {
|
|
33
|
-
await safeYarn(['workspace', '@_linked/core', 'build']);
|
|
34
|
-
const joined = logSpy.mock.calls.map((c) => c.join(' ')).join('\n');
|
|
35
|
-
expect(joined).toMatch(/yarn workspace @_linked\/core build/);
|
|
36
|
-
expect(joined).toMatch(/\["workspace","@_linked\/core","build"\]/);
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
test('preserves --flag style args', async () => {
|
|
40
|
-
await safeYarn(['install', '--immutable']);
|
|
41
|
-
const joined = logSpy.mock.calls.map((c) => c.join(' ')).join('\n');
|
|
42
|
-
expect(joined).toMatch(/yarn install --immutable/);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
test('preserves quoted-style empty args list', async () => {
|
|
46
|
-
await safeYarn([]);
|
|
47
|
-
const joined = logSpy.mock.calls.map((c) => c.join(' ')).join('\n');
|
|
48
|
-
expect(joined).toMatch(/would execute: yarn /);
|
|
49
|
-
expect(joined).toMatch(/\[\]/);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
test('returns synchronously without throwing on minimal args', async () => {
|
|
53
|
-
await expect(safeYarn(['--version'])).resolves.toBeUndefined();
|
|
54
|
-
});
|
|
55
|
-
});
|
package/tsconfig-cjs.json
DELETED