@sequenceholdings/artifact-studio 0.4.0 → 0.6.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/README.md +35 -1
- package/VERSION-PIN.md +29 -0
- package/dist/active-deploy-policy.d.ts +9 -3
- package/dist/active-deploy-policy.js +20 -8
- package/dist/artifact-env-bindings.d.ts +9 -0
- package/dist/artifact-env-bindings.js +32 -0
- package/dist/build.js +6 -0
- package/dist/cli.js +62 -13
- package/dist/deployment-validation.d.ts +2 -0
- package/dist/deployment-validation.js +2 -0
- package/dist/dev-link.js +28 -2
- package/dist/env-binding-scan.d.ts +10 -0
- package/dist/env-binding-scan.js +179 -0
- package/dist/env-bindings.d.ts +52 -0
- package/dist/env-bindings.js +180 -0
- package/dist/gateway/control.d.ts +33 -0
- package/dist/gateway/control.js +29 -0
- package/dist/gateway/hosted-proxy.d.ts +5 -0
- package/dist/gateway/hosted-proxy.js +4 -1
- package/dist/gateway/lattice-invoke.d.ts +48 -0
- package/dist/gateway/lattice-invoke.js +245 -0
- package/dist/gateway/lattice-upstream.d.ts +38 -0
- package/dist/gateway/lattice-upstream.js +136 -0
- package/dist/gateway/server.js +2 -3
- package/dist/gateway/session.d.ts +3 -1
- package/dist/gateway/session.js +10 -3
- package/dist/manifest-next.d.ts +55 -0
- package/dist/manifest-next.js +35 -2
- package/dist/manifest-static.d.ts +35 -0
- package/dist/manifest-static.js +12 -0
- package/dist/manifest.d.ts +48 -0
- package/dist/next-build.d.ts +2 -1
- package/dist/next-build.js +12 -7
- package/dist/offline/cache.d.ts +4 -0
- package/dist/offline/cache.js +7 -0
- package/dist/offline/connector.d.ts +28 -0
- package/dist/offline/connector.js +122 -0
- package/dist/offline/crypto.d.ts +5 -0
- package/dist/offline/crypto.js +24 -0
- package/dist/offline/index.d.ts +8 -0
- package/dist/offline/index.js +7 -0
- package/dist/offline/indexed-db.d.ts +10 -0
- package/dist/offline/indexed-db.js +104 -0
- package/dist/offline/management-types.d.ts +25 -0
- package/dist/offline/management-types.js +1 -0
- package/dist/offline/outbox.d.ts +7 -0
- package/dist/offline/outbox.js +46 -0
- package/dist/offline/replay.d.ts +25 -0
- package/dist/offline/replay.js +192 -0
- package/dist/offline/secure-store.d.ts +6 -0
- package/dist/offline/secure-store.js +51 -0
- package/dist/offline/types.d.ts +162 -0
- package/dist/offline/types.js +1 -0
- package/dist/orm-json-scalars.d.ts +21 -0
- package/dist/orm-json-scalars.js +21 -0
- package/dist/runtime-contracts.d.ts +7 -7
- package/dist/runtime-contracts.js +1 -1
- package/dist/sdk-orm.d.ts +7 -1
- package/dist/sdk-orm.js +12 -5
- package/dist/sdk.d.ts +134 -1
- package/dist/server/api-client.d.ts +26 -0
- package/dist/server/api-client.js +38 -0
- package/dist/server/broker-contract.d.ts +191 -5
- package/dist/server/broker-contract.js +142 -1
- package/dist/server/client.d.ts +82 -47
- package/dist/server/client.js +138 -59
- package/dist/server/connection-retry.d.ts +12 -0
- package/dist/server/connection-retry.js +40 -0
- package/dist/server/errors.d.ts +61 -0
- package/dist/server/errors.js +89 -0
- package/dist/server/external-context.d.ts +21 -0
- package/dist/server/external-context.js +19 -0
- package/dist/server/internal/signed-token.d.ts +15 -0
- package/dist/server/internal/signed-token.js +67 -0
- package/dist/server/internal/v1-context.d.ts +18 -0
- package/dist/server/internal/v1-context.js +26 -0
- package/dist/server/internal/verify-any.d.ts +22 -0
- package/dist/server/internal/verify-any.js +53 -0
- package/dist/server/logger.d.ts +1 -1
- package/dist/server/managed-server-path.d.ts +6 -0
- package/dist/server/managed-server-path.js +14 -0
- package/dist/server/principal.d.ts +48 -0
- package/dist/server/principal.js +39 -0
- package/dist/server/processes.d.ts +290 -0
- package/dist/server/processes.js +56 -0
- package/dist/server/request-context.d.ts +121 -3
- package/dist/server/request-context.js +111 -33
- package/dist/server/testing.d.ts +40 -0
- package/dist/server/testing.js +86 -0
- package/dist/server.d.ts +13 -4
- package/dist/server.js +6 -2
- package/dist/source-resolver.d.ts +9 -2
- package/dist/source-resolver.js +55 -8
- package/dist/templates/nextjs/AGENTS.md +10 -0
- package/dist/templates/nextjs/CLAUDE.md +1 -0
- package/dist/templates/nextjs/README.md +88 -0
- package/dist/templates/nextjs/package.json +3 -1
- package/dist/templates/nextjs/tests/server.test.mjs +25 -0
- package/dist/templates/react-vite/AGENTS.md +8 -88
- package/dist/templates/react-vite/CLAUDE.md +0 -6
- package/dist/templates/react-vite/package.json +1 -1
- package/dist/templates/react-vite/src/App.tsx +37 -14
- package/dist/templates/react-vite/src/styles.css +10 -0
- package/package.json +15 -3
- package/templates/nextjs/AGENTS.md +10 -0
- package/templates/nextjs/CLAUDE.md +1 -0
- package/templates/nextjs/README.md +88 -0
- package/templates/nextjs/package.json +3 -1
- package/templates/nextjs/tests/server.test.mjs +25 -0
- package/templates/react-vite/AGENTS.md +8 -88
- package/templates/react-vite/CLAUDE.md +0 -6
- package/templates/react-vite/package.json +1 -1
- package/templates/react-vite/src/App.tsx +37 -14
- package/templates/react-vite/src/styles.css +10 -0
package/README.md
CHANGED
|
@@ -36,15 +36,49 @@ contract for external clients.
|
|
|
36
36
|
The adapter validates the namespace and manifest, rejects unknown operations,
|
|
37
37
|
and returns the operation's `data`. Errors retain `OrmOperationError.status`,
|
|
38
38
|
`code`, and `errors`; missing host context or malformed success responses fail
|
|
39
|
-
instead of reporting saved data.
|
|
39
|
+
instead of reporting saved data. The adapter never retries a call automatically. For
|
|
40
40
|
several operations in one traced UI action, pass `withTraceAction`'s client as
|
|
41
41
|
the optional `sdk` argument.
|
|
42
42
|
|
|
43
|
+
### Exact numbers
|
|
44
|
+
|
|
45
|
+
By default `numeric` and `bigint` values come back as JSON numbers, which
|
|
46
|
+
JavaScript rounds past 15-17 significant digits; a form that saves what it read
|
|
47
|
+
would write the rounded value back. Ask for strings instead:
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
const orm = createArtifactOrmExecutor({ namespace: 'lending', manifest, jsonScalars: 'lossless-v1' })
|
|
51
|
+
// A Next.js server component, route handler or Server Action:
|
|
52
|
+
const db = client.orm({ namespace: 'lending', manifest, jsonScalars: 'lossless-v1' })
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Results then carry these columns as strings (`"250000.00"`,
|
|
56
|
+
`"9007199254740993"`). The executor refuses any successful result the platform
|
|
57
|
+
does not acknowledge in that form, with `OrmOperationError` code
|
|
58
|
+
`LOSSLESS_TRANSPORT_UNAVAILABLE`, so an older Atlas can never hand back rounded
|
|
59
|
+
money as exact. Send `numeric` and `bigint` variables as decimal strings in
|
|
60
|
+
either mode, and avoid `Number`/`parseFloat` on exact values.
|
|
61
|
+
|
|
43
62
|
Artifact Studio is a workflow-agnostic platform primitive. The host, SDK, and
|
|
44
63
|
deployer provide generic application capabilities; each deployed artifact owns
|
|
45
64
|
its product behavior. Primitive code must not branch on an artifact ID or import
|
|
46
65
|
an application's services. See [Platform primitive boundaries](../../../docs/platform/platform-primitives.md).
|
|
47
66
|
|
|
67
|
+
## Lost connections in a Next.js server
|
|
68
|
+
|
|
69
|
+
The server client (`@sequenceholdings/artifact-studio/server`) sends a read up
|
|
70
|
+
to three times when its connection to Atlas fails before Atlas answers: the
|
|
71
|
+
connection was refused or reset, the host or network was unreachable, or a DNS
|
|
72
|
+
lookup failed transiently. It pauses up to 100 ms, then up to 200 ms, and logs a
|
|
73
|
+
`broker.reconnect` warning for each resend. Reads are `client.orm` operations
|
|
74
|
+
the manifest lists with `kind: 'query'`, `client.api` GET and HEAD requests,
|
|
75
|
+
Storage GET and HEAD requests, and flag evaluations. ORM mutations, other API
|
|
76
|
+
methods, Storage writes, function invokes and process starts are sent once,
|
|
77
|
+
because a reset can come after Atlas acted on them, and reject with fetch's
|
|
78
|
+
`TypeError: fetch failed` as before. (Audit delivery keeps its own single
|
|
79
|
+
resend under the same event id.) A response with any status is Atlas's answer
|
|
80
|
+
and is never resent.
|
|
81
|
+
|
|
48
82
|
## Grouping calls into a trace
|
|
49
83
|
|
|
50
84
|
The Artifact host supplies tracing context automatically for supported platform
|
package/VERSION-PIN.md
CHANGED
|
@@ -21,6 +21,35 @@ Artifact Studio 0.3.1 (Studio CLI 0.2.2) verifies the per-request `jti` that the
|
|
|
21
21
|
gateway now signs into every request context. The 0.3.0 context schema predates
|
|
22
22
|
that claim and rejects those contexts, so Next.js applications need 0.3.1 or newer.
|
|
23
23
|
|
|
24
|
+
Artifact Studio 0.6.0 (Studio CLI 0.4.2) adds external request contexts (v2)
|
|
25
|
+
for callers that are not Atlas users, admitted through a manifest-declared
|
|
26
|
+
ingress. 0.5.0 was never published; 0.6.0 also ships its server `flags`,
|
|
27
|
+
`audit` and `jsonScalars` surface and the Lattice `processes.read` reads
|
|
28
|
+
(`listRuns`, `getRun`, `describe`, `listTasks`) and `processes.act` writes
|
|
29
|
+
(`advance`, `assign`, run viewers, `searchPrincipals`). The public verifiers
|
|
30
|
+
(`verifyArtifactRequestContext`, `getArtifactRequestContext`) stay v1-only and
|
|
31
|
+
refuse a v2 context, or any token signed with an `external-` kid, with reason
|
|
32
|
+
`external_context_not_accepted`. A handler accepts external callers only
|
|
33
|
+
through `verifyArtifactExternalContext` or `createArtifactServerClient({ accept })`,
|
|
34
|
+
and an external principal reaches platform services only through `asService()`.
|
|
35
|
+
`@sequenceholdings/artifact-studio/server/testing` signs test contexts.
|
|
36
|
+
0.6.0 also carries `error.atlas` (`{ code, detail }`) on a refused broker call
|
|
37
|
+
(#13398); a step-up 401 still surfaces as `insufficient_user_authentication`.
|
|
38
|
+
|
|
39
|
+
Context version policy: a context verifier rejects any unknown top-level or
|
|
40
|
+
`principal` key unless it sits under `ext` or is named in `crit` (and `crit`
|
|
41
|
+
names only extensions the verifier understands; today none, so it is empty).
|
|
42
|
+
Security-relevant additions bump `v`; they are never added as optional keys.
|
|
43
|
+
|
|
44
|
+
The Artifact mobile-pattern source adds new atlas-ui exports. Roll that surface
|
|
45
|
+
out in dependency order: publish atlas-ui from its changeset, update the exact
|
|
46
|
+
`@sequenceholdings/atlas-ui` dependency in `artifact-studio/package.json`,
|
|
47
|
+
publish Artifact Studio and Studio CLI, then migrate and redeploy artifact
|
|
48
|
+
consumers. A source-linked candidate preview may exercise the new exports
|
|
49
|
+
before publication; an ordinary released CLI intentionally cannot.
|
|
50
|
+
The React/Vite starter therefore uses released primitives and responsive CSS
|
|
51
|
+
until that pin advances. Its build test exercises the ordinary CLI toolchain.
|
|
52
|
+
|
|
24
53
|
## What this means
|
|
25
54
|
|
|
26
55
|
`shared/services/artifact-studio/src/build.ts` force-aliases platform peers via
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Official (active-channel) Artifact Studio deploys must come from `main
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Official (active-channel) Artifact Studio deploys must come from `main`, or
|
|
3
|
+
* from `staging` when the target is a staging environment, with a resolved
|
|
4
|
+
* commit and a clean artifact source directory — whether the source is a
|
|
5
|
+
* platform git-service repo (`--repo`) or a monorepo / local checkout.
|
|
5
6
|
* Preview deploys may use feature branches, dirty trees, or other refs.
|
|
6
7
|
*
|
|
7
8
|
* This checks reported provenance (branch name + explicit clean flag + commit).
|
|
@@ -19,9 +20,12 @@ export interface ActiveDeployProvenance {
|
|
|
19
20
|
gitCommit?: string | null;
|
|
20
21
|
gitBranch?: string | null;
|
|
21
22
|
gitDirty?: boolean | null;
|
|
23
|
+
environment?: string | null;
|
|
22
24
|
}
|
|
23
25
|
/** Canonical default branch name for official artifact deploys. */
|
|
24
26
|
export declare const ACTIVE_DEPLOY_DEFAULT_BRANCH = "main";
|
|
27
|
+
/** Branch allowed for official artifact deploys to staging environments. */
|
|
28
|
+
export declare const ACTIVE_DEPLOY_STAGING_BRANCH = "staging";
|
|
25
29
|
export declare class ActiveDeployPolicyError extends Error {
|
|
26
30
|
readonly code: "ACTIVE_DEPLOY_POLICY";
|
|
27
31
|
constructor(message: string);
|
|
@@ -29,6 +33,8 @@ export declare class ActiveDeployPolicyError extends Error {
|
|
|
29
33
|
/** Strip `refs/heads/` so `refs/heads/main` and `main` compare equal. */
|
|
30
34
|
export declare function normalizeGitBranchName(branch: string | null | undefined): string | null;
|
|
31
35
|
export declare function isActiveDeployDefaultBranch(branch: string | null | undefined): boolean;
|
|
36
|
+
/** Recognize first-party and provisioned staging environment names. */
|
|
37
|
+
export declare function isStagingEnvironment(environment: string | null | undefined): boolean;
|
|
32
38
|
/**
|
|
33
39
|
* Throw if provenance is not eligible for an official (active) deploy.
|
|
34
40
|
* Preview callers must not invoke this.
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Official (active-channel) Artifact Studio deploys must come from `main
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* Official (active-channel) Artifact Studio deploys must come from `main`, or
|
|
3
|
+
* from `staging` when the target is a staging environment, with a resolved
|
|
4
|
+
* commit and a clean artifact source directory — whether the source is a
|
|
5
|
+
* platform git-service repo (`--repo`) or a monorepo / local checkout.
|
|
5
6
|
* Preview deploys may use feature branches, dirty trees, or other refs.
|
|
6
7
|
*
|
|
7
8
|
* This checks reported provenance (branch name + explicit clean flag + commit).
|
|
@@ -17,6 +18,8 @@
|
|
|
17
18
|
*/
|
|
18
19
|
/** Canonical default branch name for official artifact deploys. */
|
|
19
20
|
export const ACTIVE_DEPLOY_DEFAULT_BRANCH = 'main';
|
|
21
|
+
/** Branch allowed for official artifact deploys to staging environments. */
|
|
22
|
+
export const ACTIVE_DEPLOY_STAGING_BRANCH = 'staging';
|
|
20
23
|
export class ActiveDeployPolicyError extends Error {
|
|
21
24
|
code = 'ACTIVE_DEPLOY_POLICY';
|
|
22
25
|
constructor(message) {
|
|
@@ -36,6 +39,11 @@ export function normalizeGitBranchName(branch) {
|
|
|
36
39
|
export function isActiveDeployDefaultBranch(branch) {
|
|
37
40
|
return normalizeGitBranchName(branch) === ACTIVE_DEPLOY_DEFAULT_BRANCH;
|
|
38
41
|
}
|
|
42
|
+
/** Recognize first-party and provisioned staging environment names. */
|
|
43
|
+
export function isStagingEnvironment(environment) {
|
|
44
|
+
const normalized = environment?.trim().toLowerCase();
|
|
45
|
+
return normalized === 'staging' || normalized?.endsWith('_staging') === true || normalized?.endsWith('-staging') === true;
|
|
46
|
+
}
|
|
39
47
|
/**
|
|
40
48
|
* Throw if provenance is not eligible for an official (active) deploy.
|
|
41
49
|
* Preview callers must not invoke this.
|
|
@@ -52,18 +60,22 @@ export function assertActiveDeployProvenance(provenance) {
|
|
|
52
60
|
}
|
|
53
61
|
const branch = normalizeGitBranchName(provenance.gitBranch);
|
|
54
62
|
if (!branch) {
|
|
55
|
-
throw new ActiveDeployPolicyError('Active (official) artifact deploys require git provenance on `main
|
|
56
|
-
'
|
|
63
|
+
throw new ActiveDeployPolicyError('Active (official) artifact deploys require git provenance on `main` ' +
|
|
64
|
+
'or `staging` for a staging target. ' +
|
|
65
|
+
'Check out `main` (or `staging` for staging) in a monorepo or local clone, or deploy with ' +
|
|
57
66
|
'`seq-studio artifact deploy --repo <ns>/<slug>` (defaults to the repo default branch). ' +
|
|
58
67
|
'Feature branches and detached HEAD (unless at the tip of `main`) are preview-only.');
|
|
59
68
|
}
|
|
60
|
-
|
|
69
|
+
const stagingBranchAllowed = branch === ACTIVE_DEPLOY_STAGING_BRANCH && isStagingEnvironment(provenance.environment);
|
|
70
|
+
if (branch !== ACTIVE_DEPLOY_DEFAULT_BRANCH && !stagingBranchAllowed) {
|
|
61
71
|
throw new ActiveDeployPolicyError(`Active (official) artifact deploys must use \`${ACTIVE_DEPLOY_DEFAULT_BRANCH}\` ` +
|
|
72
|
+
`or \`${ACTIVE_DEPLOY_STAGING_BRANCH}\` for staging targets ` +
|
|
62
73
|
`(got branch/ref \`${branch}\`). Merge via a Pull, then redeploy from \`main\`; ` +
|
|
63
74
|
'use a preview channel for feature-branch or dirty local work.');
|
|
64
75
|
}
|
|
65
76
|
if (!provenance.gitCommit?.trim()) {
|
|
66
|
-
throw new ActiveDeployPolicyError('Active (official) artifact deploys require a resolved git commit on `main
|
|
67
|
-
'Deploy from a git checkout or `--repo`
|
|
77
|
+
throw new ActiveDeployPolicyError('Active (official) artifact deploys require a resolved git commit on `main` ' +
|
|
78
|
+
'or `staging` for a staging target. Deploy from a git checkout or `--repo` ' +
|
|
79
|
+
'so provenance is recorded.');
|
|
68
80
|
}
|
|
69
81
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Fail an artifact deploy whose source still points at another environment. */
|
|
2
|
+
export declare function assertArtifactEnvironmentBindings({ directory, environment, token, }: {
|
|
3
|
+
directory: string;
|
|
4
|
+
environment: {
|
|
5
|
+
name: string;
|
|
6
|
+
url: string;
|
|
7
|
+
};
|
|
8
|
+
token: string;
|
|
9
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { getJsonOr404 } from './api.js';
|
|
2
|
+
import { knownEnvUrls } from './config.js';
|
|
3
|
+
import { assertNoCrossEnvironmentBindings, canonicalDeploymentOrigin } from './env-bindings.js';
|
|
4
|
+
import { readArtifactStudioSource } from './project.js';
|
|
5
|
+
/** Fail an artifact deploy whose source still points at another environment. */
|
|
6
|
+
export async function assertArtifactEnvironmentBindings({ directory, environment, token, }) {
|
|
7
|
+
const source = await readArtifactStudioSource(directory);
|
|
8
|
+
const deployOrigin = canonicalDeploymentOrigin(environment.url);
|
|
9
|
+
if (!deployOrigin)
|
|
10
|
+
throw new Error(`Invalid deployment URL '${environment.url}'.`);
|
|
11
|
+
const origins = Object.entries(knownEnvUrls()).flatMap(([name, url]) => {
|
|
12
|
+
const origin = canonicalDeploymentOrigin(url);
|
|
13
|
+
return origin ? [{ name, origin }] : [];
|
|
14
|
+
});
|
|
15
|
+
await assertNoCrossEnvironmentBindings({
|
|
16
|
+
deployEnvironment: environment.name,
|
|
17
|
+
deployOrigin,
|
|
18
|
+
origins,
|
|
19
|
+
workspaces: [],
|
|
20
|
+
files: source.files,
|
|
21
|
+
workspaceExists: async (workspaceId) => {
|
|
22
|
+
const workspace = await getJsonOr404({
|
|
23
|
+
baseUrl: environment.url,
|
|
24
|
+
token,
|
|
25
|
+
path: `/api/storage/workspaces/${workspaceId}`,
|
|
26
|
+
});
|
|
27
|
+
// The selected API already scopes this read to its storage environment,
|
|
28
|
+
// including local `local:<project>:<branch>` values.
|
|
29
|
+
return workspace !== null;
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
}
|
package/dist/build.js
CHANGED
|
@@ -2,6 +2,7 @@ import { existsSync } from 'node:fs';
|
|
|
2
2
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
4
|
import { dirname, relative, resolve } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
5
6
|
import { build as viteBuild } from 'vite';
|
|
6
7
|
import { atlasUiCssResolverPlugin, collectAtlasUiResolvedTokenSelections, hasUnifiedAtlasUiTokens } from './atlas-ui-css-plugin.js';
|
|
7
8
|
import { validateAtlasUiTokenStylesheetContract } from './atlas-ui-package-contract.js';
|
|
@@ -51,6 +52,10 @@ const atlasUiTokens = resolveAtlasUiTokensPath(atlasUiDir);
|
|
|
51
52
|
// `seq-studio dev --link`) work.
|
|
52
53
|
const formRendererDir = dirname(cliRequire.resolve('@sequenceholdings/lattice-form-renderer/package.json'));
|
|
53
54
|
const formRendererDist = resolve(formRendererDir, 'dist');
|
|
55
|
+
const artifactStudioModuleDir = dirname(fileURLToPath(import.meta.url));
|
|
56
|
+
const offlineRuntimeEntry = existsSync(resolve(artifactStudioModuleDir, 'offline/index.js'))
|
|
57
|
+
? resolve(artifactStudioModuleDir, 'offline/index.js')
|
|
58
|
+
: resolve(artifactStudioModuleDir, 'offline/index.ts');
|
|
54
59
|
/**
|
|
55
60
|
* Import aliases for the Vite build. Artifacts are built against their own
|
|
56
61
|
* source tree, which usually has no installed node_modules, so every bare
|
|
@@ -79,6 +84,7 @@ function createPeerResolveAlias({ atlasUiPackageDir = atlasUiDir } = {}) {
|
|
|
79
84
|
sonner: sonnerDir,
|
|
80
85
|
'@sequenceholdings/storage': cliRequire.resolve('@sequenceholdings/storage'),
|
|
81
86
|
'@sequenceholdings/orm/client': cliRequire.resolve('@sequenceholdings/orm/client'),
|
|
87
|
+
'@sequenceholdings/artifact-studio/offline': offlineRuntimeEntry,
|
|
82
88
|
// Candidate Atlas UI exposes canonical tokens at the package root; released
|
|
83
89
|
// 2.0.1 keeps them in dist. Preserve explicit alias resolution so legacy
|
|
84
90
|
// source reaches the normalizer instead of falling through to the bare alias.
|
package/dist/cli.js
CHANGED
|
@@ -16,6 +16,7 @@ import { runWatchLoop } from './watch-loop.js';
|
|
|
16
16
|
import { hashLinkedSources, hasLinkedAtlasUiToolchain, parseLinks, rebuildLinkedDep, resolveLinkedDeps, syncLinkedDist, } from './dev-link.js';
|
|
17
17
|
import { lintArtifactSandbox, reportSandboxWarnings } from './sandbox-lint.js';
|
|
18
18
|
import { assertActiveDeployProvenance } from './active-deploy-policy.js';
|
|
19
|
+
import { assertArtifactEnvironmentBindings } from './artifact-env-bindings.js';
|
|
19
20
|
import { normalizeArtifactPath } from './paths.js';
|
|
20
21
|
import yaml from 'js-yaml';
|
|
21
22
|
import { warnOnRoleDefinitions } from './role-definitions-notice.js';
|
|
@@ -29,23 +30,26 @@ const USAGE = `usage:
|
|
|
29
30
|
seq-studio artifact link --env <env> [--project <id>]
|
|
30
31
|
seq-studio artifact env list|use <env>
|
|
31
32
|
seq-studio artifact status [--env <env>]
|
|
32
|
-
seq-studio artifact list --env <env>
|
|
33
|
-
seq-studio artifact show <slug-or-id> --env <env>
|
|
33
|
+
seq-studio artifact list --env <env> [--json]
|
|
34
|
+
seq-studio artifact show <slug-or-id> --env <env> [--json]
|
|
34
35
|
seq-studio artifact validate [dir]
|
|
35
|
-
seq-studio artifact build [dir | --repo <ns>/<name> | --git-url <url>] [--ref <ref>] [--out dist/artifact-bundle.json]
|
|
36
|
-
seq-studio artifact plan [dir | --repo <ns>/<name> | --git-url <url>] [--ref <ref>] --env <env>
|
|
37
|
-
seq-studio artifact deploy [dir | --repo <ns>/<name> | --git-url <url>] [--ref <ref>] --env <env> [--skip-unchanged] [--no-create] [--project <id>]
|
|
36
|
+
seq-studio artifact build [dir | --repo <ns>/<name> | --git-url <url>] [--ref <ref>] [--path <dir>] [--out dist/artifact-bundle.json]
|
|
37
|
+
seq-studio artifact plan [dir | --repo <ns>/<name> | --git-url <url>] [--ref <ref>] [--path <dir>] --env <env>
|
|
38
|
+
seq-studio artifact deploy [dir | --repo <ns>/<name> | --git-url <url>] [--ref <ref>] [--path <dir>] --env <env> [--skip-unchanged] [--no-create] [--project <id>]
|
|
38
39
|
seq-studio artifact dev [dir] --env <env> [--once] [--key default] [--link <pkgDir>[,<pkgDir>]] [--port <port>]
|
|
39
40
|
seq-studio artifact promote <deployment-id> --env <env>
|
|
40
41
|
seq-studio artifact pull <project-id> --env <env> --out <dir>
|
|
41
42
|
seq-studio artifact rollback <deployment-id> --env <env>
|
|
42
43
|
|
|
43
44
|
Add --help or -h to any artifact command to print this usage without running it.
|
|
45
|
+
init includes AGENTS.md pointing to your environment's live docs, with a
|
|
46
|
+
CLAUDE.md import, for both templates.
|
|
44
47
|
|
|
45
48
|
Source: a local [dir] (default), a platform git-service repo (--repo <ns>/<name>),
|
|
46
49
|
or a public HTTPS git URL (--git-url <url>). --ref picks a branch/tag/commit (default: the
|
|
47
|
-
repo's default branch).
|
|
48
|
-
|
|
50
|
+
repo's default branch). --path <dir> selects the application directory inside a
|
|
51
|
+
--repo / --git-url repository (default: its root). The target project comes from
|
|
52
|
+
the source's artifact.bundle.yml project_id.
|
|
49
53
|
|
|
50
54
|
Official (active) deploys to deployed environments require \`main\` with a
|
|
51
55
|
resolved git commit and a clean artifact source directory. Dirty files
|
|
@@ -83,6 +87,11 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
83
87
|
console.error(parsed.error);
|
|
84
88
|
return 1;
|
|
85
89
|
}
|
|
90
|
+
const optionError = validateKnownOptions(parsed);
|
|
91
|
+
if (optionError) {
|
|
92
|
+
console.error(optionError);
|
|
93
|
+
return 1;
|
|
94
|
+
}
|
|
86
95
|
switch (parsed.command) {
|
|
87
96
|
case 'init': return initCommand(parsed);
|
|
88
97
|
case 'whoami': return whoamiCommand(parsed);
|
|
@@ -101,6 +110,34 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
101
110
|
case 'rollback': return rollbackCommand(parsed);
|
|
102
111
|
}
|
|
103
112
|
}
|
|
113
|
+
function validateKnownOptions(args) {
|
|
114
|
+
const options = {
|
|
115
|
+
init: { values: ['template'], booleans: [] }, whoami: { values: ['env'], booleans: [] },
|
|
116
|
+
link: { values: ['env', 'project'], booleans: [] }, env: { values: [], booleans: [] },
|
|
117
|
+
status: { values: ['env'], booleans: [] }, list: { values: ['env'], booleans: ['json'] },
|
|
118
|
+
show: { values: ['env'], booleans: ['json'] }, validate: { values: [], booleans: [] },
|
|
119
|
+
build: { values: ['env', 'repo', 'git-url', 'ref', 'path', 'out'], booleans: [] },
|
|
120
|
+
plan: { values: ['env', 'repo', 'git-url', 'ref', 'path'], booleans: [] },
|
|
121
|
+
deploy: { values: ['env', 'repo', 'git-url', 'ref', 'path', 'project', 'out'], booleans: ['skip-unchanged', 'no-create'] },
|
|
122
|
+
dev: { values: ['env', 'key', 'link', 'port'], booleans: ['once'] },
|
|
123
|
+
promote: { values: ['env'], booleans: [] }, pull: { values: ['env', 'out'], booleans: [] },
|
|
124
|
+
rollback: { values: ['env'], booleans: [] },
|
|
125
|
+
};
|
|
126
|
+
const { values, booleans } = options[args.command];
|
|
127
|
+
const allowed = new Set([...values, ...booleans]);
|
|
128
|
+
for (const key of Object.keys(args.flags)) {
|
|
129
|
+
if (!allowed.has(key)) {
|
|
130
|
+
return `Unknown option --${key} for artifact ${args.command}. Run seq-studio artifact --help.`;
|
|
131
|
+
}
|
|
132
|
+
if (values.includes(key) && (args.flags[key] === true || args.flags[key]?.trim() === '')) {
|
|
133
|
+
return `Option --${key} requires a value for artifact ${args.command}.`;
|
|
134
|
+
}
|
|
135
|
+
if (booleans.includes(key) && args.flags[key] !== true) {
|
|
136
|
+
return `Option --${key} does not take a value for artifact ${args.command}.`;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return undefined;
|
|
140
|
+
}
|
|
104
141
|
function parseArgs(argv) {
|
|
105
142
|
if (argv[0] === '--')
|
|
106
143
|
argv = argv.slice(1);
|
|
@@ -158,8 +195,9 @@ async function initCommand(args) {
|
|
|
158
195
|
}
|
|
159
196
|
}
|
|
160
197
|
console.log(`[seq-studio] initialized ${dir}`);
|
|
198
|
+
console.log('AGENTS.md points to your environment\'s live docs; CLAUDE.md imports it.');
|
|
161
199
|
console.log(template === 'nextjs'
|
|
162
|
-
? 'Next: cd into the app
|
|
200
|
+
? 'Next: use Node.js 24, cd into the app and run pnpm install, then pnpm run lint && pnpm run type-check && pnpm test. Commit pnpm-lock.yaml before deploying. seq-studio login && seq-studio artifact dev --env <env> starts development; repository CI and hosted builds use Buildkite. No cloud credentials are required.'
|
|
163
201
|
: 'Next: seq-studio login && seq-studio artifact dev --env <env> (see: seq-studio envs list)');
|
|
164
202
|
return 0;
|
|
165
203
|
}
|
|
@@ -273,6 +311,10 @@ async function listCommand(args) {
|
|
|
273
311
|
token,
|
|
274
312
|
path: '/api/artifact-studio/projects',
|
|
275
313
|
});
|
|
314
|
+
if (args.flags.json === true) {
|
|
315
|
+
console.log(JSON.stringify(data, null, 2));
|
|
316
|
+
return 0;
|
|
317
|
+
}
|
|
276
318
|
if (data.projects.length === 0) {
|
|
277
319
|
console.log(`[seq-studio] no artifact projects visible on ${env.name}`);
|
|
278
320
|
return 0;
|
|
@@ -326,6 +368,10 @@ async function showCommand(args) {
|
|
|
326
368
|
console.error(`[seq-studio] project ${projectId} not found on ${env.name}`);
|
|
327
369
|
return 1;
|
|
328
370
|
}
|
|
371
|
+
if (args.flags.json === true) {
|
|
372
|
+
console.log(JSON.stringify(data, null, 2));
|
|
373
|
+
return 0;
|
|
374
|
+
}
|
|
329
375
|
const project = data.project;
|
|
330
376
|
console.log(`[seq-studio] project on ${env.name}:`);
|
|
331
377
|
console.log(` id: ${project.id}`);
|
|
@@ -504,11 +550,13 @@ async function deployCommand(args) {
|
|
|
504
550
|
// Fail closed before build / project create for deployed environments.
|
|
505
551
|
// Local Atlas is a developer's own target, so it intentionally accepts
|
|
506
552
|
// the current branch and dirty working tree.
|
|
507
|
-
if (env.name !== 'local')
|
|
508
|
-
assertActiveDeployProvenance(source.provenance);
|
|
553
|
+
if (env.name !== 'local') {
|
|
554
|
+
assertActiveDeployProvenance({ ...source.provenance, environment: env.name });
|
|
555
|
+
}
|
|
509
556
|
const manifest = await readArtifactStudioManifest(source.dir);
|
|
510
557
|
// After source resolution, so --repo / --git-url deploys check the fetched manifest.
|
|
511
558
|
warnOnRoleDefinitions(manifest);
|
|
559
|
+
await assertArtifactEnvironmentBindings({ directory: source.dir, environment: env, token });
|
|
512
560
|
if (isNextArtifactManifest(manifest)) {
|
|
513
561
|
return await buildNextCommand({ args, source, active: true });
|
|
514
562
|
}
|
|
@@ -569,8 +617,9 @@ async function buildNextCommand({ args, source, active }) {
|
|
|
569
617
|
const { nextSourceRepository, resolveNextRepositoryId, submitNextBuild } = await import('./next-build.js');
|
|
570
618
|
const spec = parseSourceSpec(args);
|
|
571
619
|
const repository = nextSourceRepository({ spec, root: source.dir });
|
|
572
|
-
if (active)
|
|
573
|
-
assertActiveDeployProvenance(source.provenance);
|
|
620
|
+
if (active) {
|
|
621
|
+
assertActiveDeployProvenance({ ...source.provenance, environment: env.name });
|
|
622
|
+
}
|
|
574
623
|
const repositoryId = active || spec.kind === 'git-service'
|
|
575
624
|
? await resolveNextRepositoryId({ spec, repository, baseUrl: env.url, token })
|
|
576
625
|
: undefined;
|
|
@@ -583,7 +632,7 @@ async function buildNextCommand({ args, source, active }) {
|
|
|
583
632
|
console.log('[seq-studio] Next.js submits source to Atlas for build verification; local static-bundle skip checks do not apply.');
|
|
584
633
|
}
|
|
585
634
|
return submitNextBuild({
|
|
586
|
-
root: source.dir, manifest, projectId: project.id, baseUrl: env.url,
|
|
635
|
+
root: source.dir, sourceRoot: source.sourceRoot, manifest, projectId: project.id, baseUrl: env.url,
|
|
587
636
|
getToken: () => getRequiredToken(args), provenance: source.provenance, repository, repositoryId,
|
|
588
637
|
channel: active ? 'active' : 'preview', out: typeof args.flags.out === 'string' ? resolve(args.flags.out) : undefined,
|
|
589
638
|
});
|
|
@@ -7,3 +7,5 @@ export declare function authenticatedRequestUrl({ baseUrl, path, }: {
|
|
|
7
7
|
baseUrl: string;
|
|
8
8
|
path: string;
|
|
9
9
|
}): string;
|
|
10
|
+
export { assertNoCrossEnvironmentBindings, canonicalDeploymentOrigin, findCrossEnvironmentBindings, type BindingSourceFile, type CrossEnvironmentBinding, type NamedOrigin, type NamedWorkspace, } from './env-bindings.js';
|
|
11
|
+
export { readArtifactStudioSource } from './project.js';
|
|
@@ -68,3 +68,5 @@ export function authenticatedRequestUrl({ baseUrl, path, }) {
|
|
|
68
68
|
}
|
|
69
69
|
return request.href;
|
|
70
70
|
}
|
|
71
|
+
export { assertNoCrossEnvironmentBindings, canonicalDeploymentOrigin, findCrossEnvironmentBindings, } from './env-bindings.js';
|
|
72
|
+
export { readArtifactStudioSource } from './project.js';
|
package/dist/dev-link.js
CHANGED
|
@@ -3,7 +3,7 @@ import { createHash } from 'node:crypto';
|
|
|
3
3
|
import { existsSync, readFileSync, realpathSync } from 'node:fs';
|
|
4
4
|
import { cp, mkdir, readFile, readdir, rm } from 'node:fs/promises';
|
|
5
5
|
import { createRequire } from 'node:module';
|
|
6
|
-
import { dirname, join, relative, resolve } from 'node:path';
|
|
6
|
+
import { basename, dirname, join, relative, resolve } from 'node:path';
|
|
7
7
|
/** True only when this dev session explicitly selected a local Atlas UI package. */
|
|
8
8
|
export function hasLinkedAtlasUiToolchain({ deps }) {
|
|
9
9
|
return deps.some((dep) => dep.name === '@sequenceholdings/atlas-ui');
|
|
@@ -168,6 +168,16 @@ export function rebuildLinkedDep(dep, signal, env) {
|
|
|
168
168
|
});
|
|
169
169
|
});
|
|
170
170
|
}
|
|
171
|
+
function exportsReferencePath(value, path) {
|
|
172
|
+
if (typeof value === 'string') {
|
|
173
|
+
const pattern = value.split('*').map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('.*');
|
|
174
|
+
return new RegExp('^' + pattern + '$').test(path);
|
|
175
|
+
}
|
|
176
|
+
if (value && typeof value === 'object') {
|
|
177
|
+
return Object.values(value).some((entry) => exportsReferencePath(entry, path));
|
|
178
|
+
}
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
171
181
|
/**
|
|
172
182
|
* Copy a linked package's freshly built `dist` and root CSS entries into the
|
|
173
183
|
* location the artifact build resolves them from. A no-op when the artifact
|
|
@@ -181,10 +191,26 @@ export async function syncLinkedDist(dep) {
|
|
|
181
191
|
throw new Error(`${dep.name} build produced no dist at ${dep.distDir}`);
|
|
182
192
|
}
|
|
183
193
|
const targetPkgDir = dirname(dep.targetDistDir);
|
|
194
|
+
const targetManifest = join(targetPkgDir, 'package.json');
|
|
195
|
+
const targetExports = existsSync(targetManifest)
|
|
196
|
+
? JSON.parse(await readFile(targetManifest, 'utf8')).exports
|
|
197
|
+
: undefined;
|
|
184
198
|
await mkdir(targetPkgDir, { recursive: true });
|
|
185
199
|
await rm(dep.targetDistDir, { recursive: true, force: true });
|
|
186
200
|
await cp(dep.distDir, dep.targetDistDir, { recursive: true });
|
|
187
201
|
for (const file of await packageRootCssFiles(dep.dir)) {
|
|
188
|
-
|
|
202
|
+
// Published packages can export a compiled sheet from dist while the
|
|
203
|
+
// linked source package also keeps its generated source sheet at root.
|
|
204
|
+
// Copying both creates two candidates for the same subpath and violates
|
|
205
|
+
// the package contract. Only remove it when the installed exports map
|
|
206
|
+
// explicitly owns the dist path and no condition still needs the root.
|
|
207
|
+
const targetRootFile = join(targetPkgDir, relative(dep.dir, file));
|
|
208
|
+
if (existsSync(join(dep.distDir, basename(file))) &&
|
|
209
|
+
exportsReferencePath(targetExports, './dist/' + basename(file)) &&
|
|
210
|
+
!exportsReferencePath(targetExports, './' + basename(file))) {
|
|
211
|
+
await rm(targetRootFile, { force: true });
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
await cp(file, targetRootFile);
|
|
189
215
|
}
|
|
190
216
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { BindingSourceFile, NamedOrigin } from './env-bindings.js';
|
|
2
|
+
export declare function originsDeclaredInFiles(files: readonly BindingSourceFile[]): NamedOrigin[];
|
|
3
|
+
export declare function declaredTargets(content: string): Record<string, string>;
|
|
4
|
+
export declare function contentContainsOrigin(content: string, origin: string, skippedLines: ReadonlySet<number>): boolean;
|
|
5
|
+
/** Manifest text with the root `targets` map removed, so its URLs are declarations. */
|
|
6
|
+
export declare function contentOutsideTargets(content: string): string;
|
|
7
|
+
export declare function collectWorkspaceSlotIds(files: readonly BindingSourceFile[]): string[];
|
|
8
|
+
export declare function workspaceSlotIds(file: BindingSourceFile): string[];
|
|
9
|
+
export declare function scannable(file: BindingSourceFile): boolean;
|
|
10
|
+
export declare function basename(path: string): string;
|